diff --git a/docs/config/worker-options.md b/docs/config/worker-options.md index 840caa60915ba2..99500ae259ccfe 100644 --- a/docs/config/worker-options.md +++ b/docs/config/worker-options.md @@ -5,7 +5,7 @@ Options related to Web Workers. ## worker.format - **Type:** `'es' | 'iife'` -- **Default:** `iife` +- **Default:** `'iife'` Output format for worker bundle. diff --git a/packages/vite/src/node/plugins/worker.ts b/packages/vite/src/node/plugins/worker.ts index aae1a4a1423cda..197b3feeabe0bc 100644 --- a/packages/vite/src/node/plugins/worker.ts +++ b/packages/vite/src/node/plugins/worker.ts @@ -287,25 +287,40 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin { : 'classic' : 'module' const workerOptions = workerType === 'classic' ? '' : ',{type: "module"}' + if (isBuild) { getDepsOptimizer(config, ssr)?.registerWorkersSource(id) if (query.inline != null) { const chunk = await bundleWorkerEntry(config, id, query) - // inline as blob data url - return { - code: `const encodedJs = "${Buffer.from(chunk.code).toString( - 'base64', - )}"; - const blob = typeof window !== "undefined" && window.Blob && new Blob([atob(encodedJs)], { type: "text/javascript;charset=utf-8" }); - export default function WorkerWrapper() { - const objURL = blob && (window.URL || window.webkitURL).createObjectURL(blob); - try { - return objURL ? new ${workerConstructor}(objURL) : new ${workerConstructor}("data:application/javascript;base64," + encodedJs${workerOptions}); - } finally { - objURL && (window.URL || window.webkitURL).revokeObjectURL(objURL); - } - }`, + const encodedJs = `const encodedJs = "${Buffer.from( + chunk.code, + ).toString('base64')}";` + + const code = + // Using blob URL for SharedWorker results in multiple instances of a same worker + workerConstructor === 'Worker' + ? `${encodedJs} + const blob = typeof window !== "undefined" && window.Blob && new Blob([atob(encodedJs)], { type: "text/javascript;charset=utf-8" }); + export default function WorkerWrapper() { + let objURL; + try { + objURL = blob && (window.URL || window.webkitURL).createObjectURL(blob); + if (!objURL) throw '' + return new ${workerConstructor}(objURL) + } catch(e) { + return new ${workerConstructor}("data:application/javascript;base64," + encodedJs${workerOptions}); + } finally { + objURL && (window.URL || window.webkitURL).revokeObjectURL(objURL); + } + }` + : `${encodedJs} + export default function WorkerWrapper() { + return new ${workerConstructor}("data:application/javascript;base64," + encodedJs${workerOptions}); + } + ` + return { + code, // Empty sourcemap to suppress Rollup warning map: { mappings: '' }, } diff --git a/playground/worker/__tests__/es/es-worker.spec.ts b/playground/worker/__tests__/es/es-worker.spec.ts index 817198c23a07a4..dfa2462f61a551 100644 --- a/playground/worker/__tests__/es/es-worker.spec.ts +++ b/playground/worker/__tests__/es/es-worker.spec.ts @@ -34,6 +34,10 @@ test('shared worker', async () => { await untilUpdated(() => page.textContent('.tick-count'), 'pong', true) }) +test('inline shared worker', async () => { + await untilUpdated(() => page.textContent('.pong-shared-inline'), 'pong') +}) + test('worker emitted and import.meta.url in nested worker (serve)', async () => { await untilUpdated( () => page.textContent('.nested-worker'), @@ -72,9 +76,16 @@ describe.runIf(isBuild)('build', () => { // chunk expect(content).toMatch(`new Worker("/es/assets`) expect(content).toMatch(`new SharedWorker("/es/assets`) - // inlined + // inlined worker expect(content).toMatch(`(window.URL||window.webkitURL).createObjectURL`) expect(content).toMatch(`window.Blob`) + expect(content).toMatch( + /try\{if\(\w+=\w+&&\(window\.URL\|\|window\.webkitURL\)\.createObjectURL\(\w+\),!\w+\)throw""/, + ) + // inlined shared worker + expect(content).toMatch( + `return new SharedWorker("data:application/javascript;base64,"+`, + ) }) test('worker emitted and import.meta.url in nested worker (build)', async () => { diff --git a/playground/worker/__tests__/iife/iife-worker.spec.ts b/playground/worker/__tests__/iife/iife-worker.spec.ts index 5e81c84a777985..f37c70643f5fc4 100644 --- a/playground/worker/__tests__/iife/iife-worker.spec.ts +++ b/playground/worker/__tests__/iife/iife-worker.spec.ts @@ -29,6 +29,10 @@ test('shared worker', async () => { await untilUpdated(() => page.textContent('.tick-count'), 'pong') }) +test('inline shared worker', async () => { + await untilUpdated(() => page.textContent('.pong-shared-inline'), 'pong') +}) + test('worker emitted and import.meta.url in nested worker (serve)', async () => { await untilUpdated(() => page.textContent('.nested-worker'), '/worker-nested') await untilUpdated( diff --git a/playground/worker/__tests__/relative-base/relative-base-worker.spec.ts b/playground/worker/__tests__/relative-base/relative-base-worker.spec.ts index db8284a46b2863..87bb5e4ab338d9 100644 --- a/playground/worker/__tests__/relative-base/relative-base-worker.spec.ts +++ b/playground/worker/__tests__/relative-base/relative-base-worker.spec.ts @@ -35,6 +35,10 @@ test('shared worker', async () => { await untilUpdated(() => page.textContent('.tick-count'), 'pong', true) }) +test('inline shared worker', async () => { + await untilUpdated(() => page.textContent('.pong-shared-inline'), 'pong') +}) + test('worker emitted and import.meta.url in nested worker (serve)', async () => { await untilUpdated( () => page.textContent('.nested-worker'), diff --git a/playground/worker/__tests__/sourcemap-hidden/sourcemap-hidden-worker.spec.ts b/playground/worker/__tests__/sourcemap-hidden/sourcemap-hidden-worker.spec.ts index afcb51af117729..2565e941962f63 100644 --- a/playground/worker/__tests__/sourcemap-hidden/sourcemap-hidden-worker.spec.ts +++ b/playground/worker/__tests__/sourcemap-hidden/sourcemap-hidden-worker.spec.ts @@ -10,7 +10,7 @@ describe.runIf(isBuild)('build', () => { const files = fs.readdirSync(assetsDir) // should have 2 worker chunk - expect(files.length).toBe(31) + expect(files.length).toBe(32) const index = files.find((f) => f.includes('main-module')) const content = fs.readFileSync(path.resolve(assetsDir, index), 'utf-8') const indexSourcemap = getSourceMapUrl(content) diff --git a/playground/worker/__tests__/sourcemap/sourcemap-worker.spec.ts b/playground/worker/__tests__/sourcemap/sourcemap-worker.spec.ts index 04f272b1487a8c..837c118a6ebaf3 100644 --- a/playground/worker/__tests__/sourcemap/sourcemap-worker.spec.ts +++ b/playground/worker/__tests__/sourcemap/sourcemap-worker.spec.ts @@ -9,7 +9,7 @@ describe.runIf(isBuild)('build', () => { const assetsDir = path.resolve(testDir, 'dist/iife-sourcemap/assets') const files = fs.readdirSync(assetsDir) // should have 2 worker chunk - expect(files.length).toBe(31) + expect(files.length).toBe(32) const index = files.find((f) => f.includes('main-module')) const content = fs.readFileSync(path.resolve(assetsDir, index), 'utf-8') const indexSourcemap = getSourceMapUrl(content) diff --git a/playground/worker/index.html b/playground/worker/index.html index 5c9c85b4799fd5..ff26fbcbba3206 100644 --- a/playground/worker/index.html +++ b/playground/worker/index.html @@ -38,6 +38,12 @@

format iife:

+

+ import InlineSharedWorker from '../my-shared-worker?sharedworker&inline' + .pong-shared-inline +

+ +

new Worker(new URL('./url-worker.js', import.meta.url), { type: 'module' }) .worker-import-meta-url diff --git a/playground/worker/my-inline-shared-worker.ts b/playground/worker/my-inline-shared-worker.ts new file mode 100644 index 00000000000000..580c6cf4fdb9b6 --- /dev/null +++ b/playground/worker/my-inline-shared-worker.ts @@ -0,0 +1,13 @@ +let inlineSharedWorkerCount = 0 + +// @ts-expect-error onconnect exists in worker +self.onconnect = (event) => { + inlineSharedWorkerCount++ + const port = event.ports[0] + if (inlineSharedWorkerCount >= 2) { + port.postMessage('pong') + } +} + +// for sourcemap +console.log('my-inline-shared-worker.js') diff --git a/playground/worker/my-shared-worker.ts b/playground/worker/my-shared-worker.ts index 552c3e20f57853..d9728304191085 100644 --- a/playground/worker/my-shared-worker.ts +++ b/playground/worker/my-shared-worker.ts @@ -1,14 +1,11 @@ -const ports = new Set() +let sharedWorkerCount = 0 // @ts-expect-error onconnect exists in worker self.onconnect = (event) => { + sharedWorkerCount++ const port = event.ports[0] - ports.add(port) - port.postMessage('pong') - port.onmessage = () => { - ports.forEach((p: any) => { - p.postMessage('pong') - }) + if (sharedWorkerCount >= 2) { + port.postMessage('pong') } } diff --git a/playground/worker/worker/main-module.js b/playground/worker/worker/main-module.js index 1e5242935cdbb8..e19f8f4ec3af3a 100644 --- a/playground/worker/worker/main-module.js +++ b/playground/worker/worker/main-module.js @@ -1,5 +1,6 @@ import myWorker from '../my-worker.ts?worker' import InlineWorker from '../my-worker.ts?worker&inline' +import InlineSharedWorker from '../my-inline-shared-worker?sharedworker&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' @@ -26,11 +27,26 @@ inlineWorker.addEventListener('message', (e) => { text('.pong-inline', e.data.msg) }) -const sharedWorker = new mySharedWorker() -sharedWorker.port.addEventListener('message', (event) => { - text('.tick-count', event.data) -}) -sharedWorker.port.start() +const startSharedWorker = () => { + const sharedWorker = new mySharedWorker() + sharedWorker.port.addEventListener('message', (event) => { + text('.tick-count', event.data) + }) + sharedWorker.port.start() +} +startSharedWorker() +startSharedWorker() + +const startInlineSharedWorker = () => { + const inlineSharedWorker = new InlineSharedWorker() + inlineSharedWorker.port.addEventListener('message', (event) => { + text('.pong-shared-inline', event.data) + }) + inlineSharedWorker.port.start() +} + +startInlineSharedWorker() +startInlineSharedWorker() const tsOutputWorker = new TSOutputWorker() tsOutputWorker.postMessage('ping')