diff --git a/packages/playground/worker/__tests__/es/es-worker.spec.ts b/packages/playground/worker/__tests__/es/es-worker.spec.ts new file mode 100644 index 00000000000000..51497a0f5ebadd --- /dev/null +++ b/packages/playground/worker/__tests__/es/es-worker.spec.ts @@ -0,0 +1,102 @@ +import fs from 'fs' +import path from 'path' +import { untilUpdated, isBuild, testDir } from '../../../testUtils' +import type { Page } from 'playwright-chromium' + +test('normal', async () => { + await page.click('.ping') + await untilUpdated(() => page.textContent('.pong'), 'pong') + await untilUpdated( + () => page.textContent('.mode'), + isBuild ? 'production' : 'development' + ) + await untilUpdated( + () => page.textContent('.bundle-with-plugin'), + 'worker bundle with plugin success!' + ) +}) + +test('TS output', async () => { + await page.click('.ping-ts-output') + await untilUpdated(() => page.textContent('.pong-ts-output'), 'pong') +}) + +test('inlined', async () => { + await page.click('.ping-inline') + await untilUpdated(() => page.textContent('.pong-inline'), 'pong') +}) + +const waitSharedWorkerTick = ( + (resolvedSharedWorkerCount: number) => async (page: Page) => { + await untilUpdated(async () => { + const count = await page.textContent('.tick-count') + // ignore the initial 0 + return count === '1' ? 'page loaded' : '' + }, 'page loaded') + // test.concurrent sequential is not guaranteed + // force page to wait to ensure two pages overlap in time + resolvedSharedWorkerCount++ + if (resolvedSharedWorkerCount < 2) return + + await untilUpdated(() => { + return resolvedSharedWorkerCount === 2 ? 'all pages loaded' : '' + }, 'all pages loaded') + } +)(0) + +test.concurrent.each([[true], [false]])('shared worker', async (doTick) => { + if (doTick) { + await page.click('.tick-shared') + } + await waitSharedWorkerTick(page) +}) + +test('worker emitted', async () => { + await untilUpdated(() => page.textContent('.nested-worker'), 'pong') +}) + +if (isBuild) { + const assetsDir = path.resolve(testDir, 'dist/es/assets') + // assert correct files + test('inlined code generation', async () => { + const files = fs.readdirSync(assetsDir) + expect(files.length).toBe(20) + const index = files.find((f) => f.includes('main-module')) + const content = fs.readFileSync(path.resolve(assetsDir, index), 'utf-8') + const worker = files.find((f) => f.includes('my-worker')) + const workerContent = fs.readFileSync( + path.resolve(assetsDir, worker), + 'utf-8' + ) + + // worker should have all imports resolved and no exports + expect(workerContent).not.toMatch(`import`) + expect(workerContent).not.toMatch(`export`) + // chunk + expect(content).toMatch(`new Worker("/es/assets`) + expect(content).toMatch(`new SharedWorker("/es/assets`) + // inlined + expect(content).toMatch(`(window.URL||window.webkitURL).createObjectURL`) + expect(content).toMatch(`window.Blob`) + }) +} + +test('module worker', async () => { + expect(await page.textContent('.shared-worker-import-meta-url')).toMatch( + 'A string' + ) +}) + +test('classic worker', async () => { + expect(await page.textContent('.classic-worker')).toMatch('A classic') + expect(await page.textContent('.classic-shared-worker')).toMatch('A classic') +}) + +test('emit chunk', async () => { + expect(await page.textContent('.emti-chunk-worker')).toMatch( + '{"msg1":"module1","msg2":"module2","msg3":"module3"}' + ) + expect(await page.textContent('.emti-chunk-dynamic-import-worker')).toMatch( + '"A string/es/"' + ) +}) diff --git a/packages/playground/worker/__tests__/es/vite.config.js b/packages/playground/worker/__tests__/es/vite.config.js new file mode 100644 index 00000000000000..931d457792c4f9 --- /dev/null +++ b/packages/playground/worker/__tests__/es/vite.config.js @@ -0,0 +1 @@ +module.exports = require('../../vite.config-es') diff --git a/packages/playground/worker/__tests__/worker.spec.ts b/packages/playground/worker/__tests__/worker.spec.ts index 6d93e810c0c510..fc381467f6a4d2 100644 --- a/packages/playground/worker/__tests__/worker.spec.ts +++ b/packages/playground/worker/__tests__/worker.spec.ts @@ -53,19 +53,15 @@ test.concurrent.each([[true], [false]])('shared worker', async (doTick) => { test('worker emitted', async () => { await untilUpdated(() => page.textContent('.nested-worker'), 'pong') - await untilUpdated( - () => page.textContent('.nested-worker-dynamic-import'), - '"msg":"pong"' - ) }) if (isBuild) { - const assetsDir = path.resolve(testDir, 'dist/assets') + const assetsDir = path.resolve(testDir, 'dist/iife/assets') // assert correct files test('inlined code generation', async () => { const files = fs.readdirSync(assetsDir) - expect(files.length).toBe(11) - const index = files.find((f) => f.includes('index')) + expect(files.length).toBe(13) + const index = files.find((f) => f.includes('main-module')) const content = fs.readFileSync(path.resolve(assetsDir, index), 'utf-8') const worker = files.find((f) => f.includes('my-worker')) const workerContent = fs.readFileSync( @@ -77,15 +73,21 @@ if (isBuild) { expect(workerContent).not.toMatch(`import`) expect(workerContent).not.toMatch(`export`) // chunk - expect(content).toMatch(`new Worker("/assets`) - expect(content).toMatch(`new SharedWorker("/assets`) + expect(content).toMatch(`new Worker("/iife/assets`) + expect(content).toMatch(`new SharedWorker("/iife/assets`) // inlined expect(content).toMatch(`(window.URL||window.webkitURL).createObjectURL`) expect(content).toMatch(`window.Blob`) }) } -test('classic worker is run', async () => { +test('module worker', async () => { + expect(await page.textContent('.shared-worker-import-meta-url')).toMatch( + 'A string' + ) +}) + +test('classic worker', async () => { expect(await page.textContent('.classic-worker')).toMatch('A classic') expect(await page.textContent('.classic-shared-worker')).toMatch('A classic') }) diff --git a/packages/playground/worker/newUrl/classic-shared-worker.js b/packages/playground/worker/classic-shared-worker.js similarity index 58% rename from packages/playground/worker/newUrl/classic-shared-worker.js rename to packages/playground/worker/classic-shared-worker.js index 462e49dfa8847f..8bd39e194f0618 100644 --- a/packages/playground/worker/newUrl/classic-shared-worker.js +++ b/packages/playground/worker/classic-shared-worker.js @@ -1,4 +1,4 @@ -importScripts('/classic.js') +importScripts(`/${self.location.pathname.split('/')[1]}/classic.js`) self.onconnect = (event) => { const port = event.ports[0] diff --git a/packages/playground/worker/classic-worker.js b/packages/playground/worker/classic-worker.js index bb6f9c3f49fc84..0700428ee0c80b 100644 --- a/packages/playground/worker/classic-worker.js +++ b/packages/playground/worker/classic-worker.js @@ -1,29 +1,5 @@ -// prettier-ignore -function text(el, text) { - document.querySelector(el).textContent = text -} +importScripts(`/${self.location.pathname.split("/")[1]}/classic.js`) -const classicWorker = new Worker( - new URL('./newUrl/classic-worker.js', import.meta.url) /* , */ , - // test comment - -) - -classicWorker.addEventListener('message', ({ data }) => { - text('.classic-worker', data) -}) -classicWorker.postMessage('ping') - -const classicSharedWorker = new SharedWorker( - new URL('./newUrl/classic-shared-worker.js', import.meta.url), - { - type: 'classic' - } -) -classicSharedWorker.port.addEventListener('message', (ev) => { - text( - '.classic-shared-worker', - ev.data - ) +self.addEventListener('message', () => { + self.postMessage(self.constant) }) -classicSharedWorker.port.start() diff --git a/packages/playground/worker/emit-chunk-dynamic-import-worker.js b/packages/playground/worker/emit-chunk-dynamic-import-worker.js new file mode 100644 index 00000000000000..f96e0b15d26497 --- /dev/null +++ b/packages/playground/worker/emit-chunk-dynamic-import-worker.js @@ -0,0 +1,3 @@ +import('./modules/module').then((module) => { + self.postMessage(module.default + import.meta.env.BASE_URL) +}) diff --git a/packages/playground/worker/emit-chunk-nested-worker.js b/packages/playground/worker/emit-chunk-nested-worker.js new file mode 100644 index 00000000000000..dff0f5bc64c5ad --- /dev/null +++ b/packages/playground/worker/emit-chunk-nested-worker.js @@ -0,0 +1,7 @@ +import SubWorker from './emit-chunk-sub-worker?worker' + +const subWorker = new SubWorker() + +subWorker.onmessage = (event) => { + self.postMessage(event.data) +} diff --git a/packages/playground/worker/emit-chunk-sub-worker.js b/packages/playground/worker/emit-chunk-sub-worker.js new file mode 100644 index 00000000000000..bd6b1f6e4f7419 --- /dev/null +++ b/packages/playground/worker/emit-chunk-sub-worker.js @@ -0,0 +1,6 @@ +Promise.all([import('./modules/module2'), import('./modules/module3')]).then( + (data) => { + const _data = { ...data[0], ...data[1] } + self.postMessage(_data) + } +) diff --git a/packages/playground/worker/index.html b/packages/playground/worker/index.html index b3525da299ff5a..60289ff84d6a06 100644 --- a/packages/playground/worker/index.html +++ b/packages/playground/worker/index.html @@ -1,3 +1,4 @@ +

format iife:

Expected values:
@@ -20,113 +21,59 @@ 0
-

new Worker(new Url('path', import.meta.url), { type: 'module' })

-
- -

new SharedWorker(new Url('path', import.meta.url), { type: 'module' })

-
- -

nested worker

-
-
-

new Worker(new Url('path', import.meta.url))

-
- -

new Worker(new Url('path', import.meta.url), { type: 'classic' })

-
- - + .classname { + color: green; + } + + diff --git a/packages/playground/worker/newUrl/module.js b/packages/playground/worker/modules/module.js similarity index 100% rename from packages/playground/worker/newUrl/module.js rename to packages/playground/worker/modules/module.js diff --git a/packages/playground/worker/modules/module1.js b/packages/playground/worker/modules/module1.js new file mode 100644 index 00000000000000..191db09d29c44f --- /dev/null +++ b/packages/playground/worker/modules/module1.js @@ -0,0 +1 @@ +export const msg1 = 'module1' diff --git a/packages/playground/worker/modules/module2.js b/packages/playground/worker/modules/module2.js new file mode 100644 index 00000000000000..60447933b8b16e --- /dev/null +++ b/packages/playground/worker/modules/module2.js @@ -0,0 +1,3 @@ +export * from './module' +export * from './module1' +export const msg2 = 'module2' diff --git a/packages/playground/worker/modules/module3.js b/packages/playground/worker/modules/module3.js new file mode 100644 index 00000000000000..33355423bc030e --- /dev/null +++ b/packages/playground/worker/modules/module3.js @@ -0,0 +1,2 @@ +export * from './module' +export const msg3 = 'module3' diff --git a/packages/playground/worker/test-plugin.tsx b/packages/playground/worker/modules/test-plugin.tsx similarity index 100% rename from packages/playground/worker/test-plugin.tsx rename to packages/playground/worker/modules/test-plugin.tsx diff --git a/packages/playground/worker/workerImport.js b/packages/playground/worker/modules/workerImport.js similarity index 100% rename from packages/playground/worker/workerImport.js rename to packages/playground/worker/modules/workerImport.js diff --git a/packages/playground/worker/my-worker.ts b/packages/playground/worker/my-worker.ts index 550382be72c331..dd6061885128c7 100644 --- a/packages/playground/worker/my-worker.ts +++ b/packages/playground/worker/my-worker.ts @@ -1,5 +1,5 @@ -import { msg, mode } from './workerImport' -import { bundleWithPlugin } from './test-plugin' +import { msg, mode } from './modules/workerImport' +import { bundleWithPlugin } from './modules/test-plugin' self.onmessage = (e) => { if (e.data === 'ping') { diff --git a/packages/playground/worker/newUrl/classic-worker.js b/packages/playground/worker/newUrl/classic-worker.js deleted file mode 100644 index 865810c76fbf85..00000000000000 --- a/packages/playground/worker/newUrl/classic-worker.js +++ /dev/null @@ -1,5 +0,0 @@ -importScripts('/classic.js') - -self.addEventListener('message', () => { - self.postMessage(self.constant) -}) diff --git a/packages/playground/worker/newUrl/url-worker.js b/packages/playground/worker/newUrl/url-worker.js deleted file mode 100644 index afd91bfe613dc2..00000000000000 --- a/packages/playground/worker/newUrl/url-worker.js +++ /dev/null @@ -1,3 +0,0 @@ -import constant from './module' - -self.postMessage(constant) diff --git a/packages/playground/worker/package.json b/packages/playground/worker/package.json index 131df8c4cbf336..54b737ca2d9cc4 100644 --- a/packages/playground/worker/package.json +++ b/packages/playground/worker/package.json @@ -5,8 +5,11 @@ "scripts": { "dev": "vite", "build": "vite build", - "debug": "node --inspect-brk ../../vite/bin/vite", - "preview": "vite preview" + "preview": "vite preview", + "dev:es": "vite --config ./vite.config-es.js dev", + "build:es": "vite --config ./vite.config-es.js build", + "preview:es": "vite --config ./vite.config-es.js preview", + "debug": "node --inspect-brk ../../vite/bin/vite" }, "devDependencies": { "@vitejs/plugin-vue-jsx": "workspace:*" diff --git a/packages/playground/worker/possible-ts-output-worker.mjs b/packages/playground/worker/possible-ts-output-worker.mjs index 2bcce3faa8a50e..25f1a447617cd9 100644 --- a/packages/playground/worker/possible-ts-output-worker.mjs +++ b/packages/playground/worker/possible-ts-output-worker.mjs @@ -1,4 +1,4 @@ -import { msg, mode } from './workerImport' +import { msg, mode } from './modules/workerImport' self.onmessage = (e) => { if (e.data === 'ping') { diff --git a/packages/playground/worker/sub-worker.js b/packages/playground/worker/sub-worker.js index ab64b3667099bb..eec65b86a0382a 100644 --- a/packages/playground/worker/sub-worker.js +++ b/packages/playground/worker/sub-worker.js @@ -3,11 +3,3 @@ self.onmessage = (event) => { self.postMessage('pong') } } -const data = import('./workerImport') -data.then((data) => { - const { mode, msg } = data - self.postMessage({ - mode, - msg - }) -}) diff --git a/packages/playground/worker/newUrl/url-shared-worker.js b/packages/playground/worker/url-shared-worker.js similarity index 69% rename from packages/playground/worker/newUrl/url-shared-worker.js rename to packages/playground/worker/url-shared-worker.js index f52de169243056..3535d5c277ec84 100644 --- a/packages/playground/worker/newUrl/url-shared-worker.js +++ b/packages/playground/worker/url-shared-worker.js @@ -1,4 +1,4 @@ -import constant from './module' +import constant from './modules/module' self.onconnect = (event) => { const port = event.ports[0] diff --git a/packages/playground/worker/url-worker.js b/packages/playground/worker/url-worker.js new file mode 100644 index 00000000000000..79d05da0f06082 --- /dev/null +++ b/packages/playground/worker/url-worker.js @@ -0,0 +1 @@ +self.postMessage('A string' + import.meta.env.BASE_URL) diff --git a/packages/playground/worker/vite.config-es.js b/packages/playground/worker/vite.config-es.js new file mode 100644 index 00000000000000..a65dece2d0db21 --- /dev/null +++ b/packages/playground/worker/vite.config-es.js @@ -0,0 +1,29 @@ +const vueJsx = require('@vitejs/plugin-vue-jsx') +const vite = require('vite') +const path = require('path') + +module.exports = vite.defineConfig({ + base: '/es/', + enforce: 'pre', + worker: { + format: 'es', + plugins: [vueJsx()] + }, + build: { + outDir: 'dist/es' + }, + plugins: [ + { + name: 'resolve-format-es', + + transform(code, id) { + if (id.includes('main.js')) { + return code.replace( + `/* flag: will replace in vite config import("./format-es.js") */`, + `import("./main-format-es")` + ) + } + } + } + ] +}) diff --git a/packages/playground/worker/vite.config.js b/packages/playground/worker/vite.config.js new file mode 100644 index 00000000000000..b7760bc4d7a240 --- /dev/null +++ b/packages/playground/worker/vite.config.js @@ -0,0 +1,13 @@ +const vueJsx = require('@vitejs/plugin-vue-jsx') +const vite = require('vite') + +module.exports = vite.defineConfig({ + base: '/iife/', + worker: { + format: 'iife', + plugins: [vueJsx()] + }, + build: { + outDir: 'dist/iife' + } +}) diff --git a/packages/playground/worker/vite.config.ts b/packages/playground/worker/vite.config.ts deleted file mode 100644 index 6cef7d9cea0bed..00000000000000 --- a/packages/playground/worker/vite.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import vueJsx from '@vitejs/plugin-vue-jsx' -import { defineConfig } from 'vite' - -export default defineConfig({ - worker: { - format: 'es', - plugins: [vueJsx()] - } -}) diff --git a/packages/playground/worker/worker/main-classic.js b/packages/playground/worker/worker/main-classic.js new file mode 100644 index 00000000000000..4ef7776a56876e --- /dev/null +++ b/packages/playground/worker/worker/main-classic.js @@ -0,0 +1,28 @@ +// prettier-ignore +function text(el, text) { + document.querySelector(el).textContent = text +} + +let classicWorker = new Worker( + new URL('../classic-worker.js', import.meta.url) /* , */ + // test comment +) + +// just test for case: ') ... ,' mean no worker options parmas +classicWorker = new Worker(new URL('../classic-worker.js', import.meta.url)) + +classicWorker.addEventListener('message', ({ data }) => { + text('.classic-worker', JSON.stringify(data)) +}) +classicWorker.postMessage('ping') + +const classicSharedWorker = new SharedWorker( + new URL('../classic-shared-worker.js', import.meta.url), + { + type: 'classic' + } +) +classicSharedWorker.port.addEventListener('message', (ev) => { + text('.classic-shared-worker', JSON.stringify(ev.data)) +}) +classicSharedWorker.port.start() diff --git a/packages/playground/worker/worker/main-format-es.js b/packages/playground/worker/worker/main-format-es.js new file mode 100644 index 00000000000000..aa16636c34c73a --- /dev/null +++ b/packages/playground/worker/worker/main-format-es.js @@ -0,0 +1,23 @@ +// run when format es +import NestedWorker from '../emit-chunk-nested-worker?worker' + +function text(el, text) { + document.querySelector(el).textContent = text +} + +text('.format-es', 'format es:') + +const nestedWorker = new NestedWorker() +nestedWorker.addEventListener('message', (ev) => { + text('.emti-chunk-worker', JSON.stringify(ev.data)) +}) + +const dynamicImportWorker = new Worker( + new URL('../emit-chunk-dynamic-import-worker.js', import.meta.url), + { + type: 'module' + } +) +dynamicImportWorker.addEventListener('message', (ev) => { + text('.emti-chunk-dynamic-import-worker', JSON.stringify(ev.data)) +}) diff --git a/packages/playground/worker/worker/main-module.js b/packages/playground/worker/worker/main-module.js new file mode 100644 index 00000000000000..b66c7299cdf690 --- /dev/null +++ b/packages/playground/worker/worker/main-module.js @@ -0,0 +1,84 @@ +import myWorker from '../my-worker?worker' +import InlineWorker from '../my-worker?worker&inline' +import mySharedWorker from '../my-shared-worker?sharedworker&name=shared' +import TSOutputWorker from '../possible-ts-output-worker?worker' +import NestedWorker from '../worker-nested-worker?worker' +import { mode } from '../modules/workerImport' + +function text(el, text) { + document.querySelector(el).textContent = text +} + +document.querySelector('.mode-true').textContent = mode + +const worker = new myWorker() +worker.addEventListener('message', (e) => { + text('.pong', e.data.msg) + text('.mode', e.data.mode) + text('.bundle-with-plugin', e.data.bundleWithPlugin) +}) + +document.querySelector('.ping').addEventListener('click', () => { + worker.postMessage('ping') +}) + +const inlineWorker = new InlineWorker() +inlineWorker.addEventListener('message', (e) => { + text('.pong-inline', e.data.msg) +}) + +document.querySelector('.ping-inline').addEventListener('click', () => { + inlineWorker.postMessage('ping') +}) + +const sharedWorker = new mySharedWorker() +document.querySelector('.tick-shared').addEventListener('click', () => { + sharedWorker.port.postMessage('tick') +}) + +sharedWorker.port.addEventListener('message', (event) => { + text('.tick-count', event.data) +}) + +sharedWorker.port.start() + +const tsOutputWorker = new TSOutputWorker() +tsOutputWorker.addEventListener('message', (e) => { + text('.pong-ts-output', e.data.msg) +}) + +document.querySelector('.ping-ts-output').addEventListener('click', () => { + tsOutputWorker.postMessage('ping') +}) + +const nestedWorker = new NestedWorker() +nestedWorker.addEventListener('message', (ev) => { + if (typeof ev.data === 'string') { + text('.nested-worker', JSON.stringify(ev.data)) + } +}) +nestedWorker.postMessage('ping') + +const workerOptions = { type: 'module' } +// url import worker +const w = new Worker( + new URL('../url-worker.js', import.meta.url), + /* @vite-ignore */ workerOptions +) +w.addEventListener('message', (ev) => + text('.worker-import-meta-url', JSON.stringify(ev.data)) +) + +const genWorkerName = () => 'module' +const w2 = new SharedWorker( + new URL('../url-shared-worker.js', import.meta.url), + { + /* @vite-ignore */ + name: genWorkerName(), + type: 'module' + } +) +w2.port.addEventListener('message', (ev) => { + text('.shared-worker-import-meta-url', JSON.stringify(ev.data)) +}) +w2.port.start() diff --git a/packages/playground/worker/worker/main.js b/packages/playground/worker/worker/main.js new file mode 100644 index 00000000000000..953b5ef1bf3b53 --- /dev/null +++ b/packages/playground/worker/worker/main.js @@ -0,0 +1,3 @@ +/* flag: will replace in vite config import("./format-es.js") */ +import('./main-module') +import('./main-classic') diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 7f0b014659b221..6b4d38836b6c51 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -306,6 +306,7 @@ export function resolveBuildPlugins(config: ResolvedConfig): { post: Plugin[] } { const options = config.build + return { pre: [ watchPackageDataPlugin(config), diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 68fe8abe85fc6d..9910cbb3a8b004 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -248,6 +248,7 @@ export type ResolvedConfig = Readonly< cacheDir: string command: 'build' | 'serve' mode: string + isWorker: boolean isProduction: boolean env: Record resolve: ResolveOptions & { @@ -476,6 +477,7 @@ export async function resolveConfig( cacheDir, command, mode, + isWorker: false, isProduction, plugins: userPlugins, server, @@ -508,7 +510,7 @@ export async function resolveConfig( // flat config.worker.plugin const [workerPrePlugins, workerNormalPlugins, workerPostPlugins] = sortUserPlugins(config.worker?.plugins as Plugin[]) - const workerResolved = { ...resolved } + const workerResolved: ResolvedConfig = { ...resolved, isWorker: true } resolved.worker.plugins = await resolvePlugins( workerResolved, workerPrePlugins, diff --git a/packages/vite/src/node/plugins/importAnalysisBuild.ts b/packages/vite/src/node/plugins/importAnalysisBuild.ts index c8bef0231af757..91ce663b9f8111 100644 --- a/packages/vite/src/node/plugins/importAnalysisBuild.ts +++ b/packages/vite/src/node/plugins/importAnalysisBuild.ts @@ -86,6 +86,7 @@ function preload(baseModule: () => Promise<{}>, deps?: string[]) { export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin { const ssr = !!config.build.ssr const insertPreload = !(ssr || !!config.build.lib) + const isWorker = config.isWorker const scriptRel = config.build.polyfillModulePreload ? `'modulepreload'` @@ -120,6 +121,11 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin { return } + if (isWorker) { + // preload method use `document` and can't run in the worker + return + } + await init let imports: readonly ImportSpecifier[] = [] @@ -132,7 +138,6 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin { if (!imports.length) { return null } - let s: MagicString | undefined const str = () => s || (s = new MagicString(source)) let needPreloadHelper = false @@ -241,7 +246,7 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin { }, generateBundle({ format }, bundle) { - if (format !== 'es' || ssr) { + if (format !== 'es' || ssr || isWorker) { return } diff --git a/packages/vite/src/node/plugins/workerImportMetaUrl.ts b/packages/vite/src/node/plugins/workerImportMetaUrl.ts index f3ed3cf1b8cbe0..233d83d066bcb7 100644 --- a/packages/vite/src/node/plugins/workerImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/workerImportMetaUrl.ts @@ -15,6 +15,7 @@ import { parseRequest } from '../utils' import { ENV_ENTRY, ENV_PUBLIC_PATH } from '../constants' import MagicString from 'magic-string' import type { ViteDevServer } from '..' +import type { RollupError } from 'rollup' type WorkerType = 'classic' | 'module' | 'ignore' @@ -25,14 +26,26 @@ function getWorkerType( noCommentsCode: string, i: number ): WorkerType { + function err(e: string, pos: number) { + const error = new Error(e) as RollupError + error.pos = pos + throw error + } + const commaIndex = noCommentsCode.indexOf(',', i) if (commaIndex === -1) { return 'classic' } const endIndex = noCommentsCode.indexOf(')', i) + // case: ') ... ,' mean no worker options params + if (commaIndex > endIndex) { + return 'classic' + } + // need to find in comment code let workerOptsString = code.substring(commaIndex + 1, endIndex) + const hasViteIgnore = /\/\*\s*@vite-ignore\s*\*\//.test(workerOptsString) if (hasViteIgnore) { return 'ignore' @@ -49,9 +62,10 @@ function getWorkerType( workerOpts = JSON5.parse(workerOptsString) } catch (e) { // can't parse by JSON5, so the worker options had unexpect char. - throw new Error( + err( 'Vite is unable to parse the worker options as the value is not static.' + - 'To ignore this error, please use /* @vite-ignore */ in the worker options.' + 'To ignore this error, please use /* @vite-ignore */ in the worker options.', + commaIndex + 1 ) } diff --git a/scripts/jestPerTestSetup.ts b/scripts/jestPerTestSetup.ts index 312d254a3b11d2..150c02eed5b76c 100644 --- a/scripts/jestPerTestSetup.ts +++ b/scripts/jestPerTestSetup.ts @@ -4,12 +4,12 @@ import { resolve, dirname } from 'path' import sirv from 'sirv' import type { ViteDevServer, - UserConfig, + InlineConfig, PluginOption, ResolvedConfig, Logger } from 'vite' -import { createServer, build } from 'vite' +import { createServer, build, mergeConfig } from 'vite' import type { Page, ConsoleMessage } from 'playwright-chromium' import type { RollupError, RollupWatcher, RollupWatcherEvent } from 'rollup' @@ -90,9 +90,16 @@ beforeAll(async () => { } } + const testCustomConfig = resolve(dirname(testPath), 'vite.config.js') + let config: InlineConfig | undefined + if (fs.existsSync(testCustomConfig)) { + // test has custom server configuration. + config = require(testCustomConfig) + } + const serverLogs: string[] = [] - const options: UserConfig = { + const options: InlineConfig = { root: rootDir, logLevel: 'silent', server: { @@ -120,7 +127,9 @@ beforeAll(async () => { if (!isBuildTest) { process.env.VITE_INLINE = 'inline-serve' - server = await (await createServer(options)).listen() + server = await ( + await createServer(mergeConfig(options, config || {})) + ).listen() // use resolved port/base from server const base = server.config.base === '/' ? '' : server.config.base const url = @@ -137,14 +146,14 @@ beforeAll(async () => { } }) options.plugins = [resolvedPlugin()] - const rollupOutput = await build(options) + const rollupOutput = await build(mergeConfig(options, config || {})) const isWatch = !!resolvedConfig!.build.watch // in build watch,call startStaticServer after the build is complete if (isWatch) { global.watcher = rollupOutput as RollupWatcher await notifyRebuildComplete(global.watcher) } - const url = (global.viteTestUrl = await startStaticServer()) + const url = (global.viteTestUrl = await startStaticServer(config)) await page.goto(url) } } @@ -172,13 +181,15 @@ afterAll(async () => { } }) -function startStaticServer(): Promise { - // check if the test project has base config - const configFile = resolve(rootDir, 'vite.config.js') - let config: UserConfig | undefined - try { - config = require(configFile) - } catch (e) {} +function startStaticServer(config?: InlineConfig): Promise { + if (!config) { + // check if the test project has base config + const configFile = resolve(rootDir, 'vite.config.js') + try { + config = require(configFile) + } catch (e) {} + } + // fallback internal base to '' const base = (config?.base ?? '/') === '/' ? '' : config?.base ?? ''