diff --git a/packages/playground/worker/__tests__/worker.spec.ts b/packages/playground/worker/__tests__/worker.spec.ts index eb0d457754209d..6514b5357cd088 100644 --- a/packages/playground/worker/__tests__/worker.spec.ts +++ b/packages/playground/worker/__tests__/worker.spec.ts @@ -16,17 +16,43 @@ test('inlined', async () => { await untilUpdated(() => page.textContent('.pong-inline'), 'pong') }) +let resolvedSharedWorkerCount = 0 + +async function waitSharedWorkerTick(page) { + await untilUpdated(async () => { + const count = await page.textContent('.tick-count') + // ignore the initial 0 + return count === '1' ? '1' : '' + }, '1') + resolvedSharedWorkerCount++ + // test.concurrent sequential is not guaranteed + // force page to wait to ensure two pages overlap in time + await untilUpdated(() => { + return resolvedSharedWorkerCount === 2 ? '2' : '' + }, '2') +} + +test.concurrent('shared worker 1', async () => { + await waitSharedWorkerTick(page) +}) + +test.concurrent('shared worker 2', async () => { + await page.click('.tick-shared') + await waitSharedWorkerTick(page) +}) + if (isBuild) { // assert correct files test('inlined code generation', async () => { const assetsDir = path.resolve(testDir, 'dist/assets') const files = fs.readdirSync(assetsDir) - // should have only 1 worker chunk - expect(files.length).toBe(2) + // should have 2 worker chunk + expect(files.length).toBe(3) const index = files.find((f) => f.includes('index')) const content = fs.readFileSync(path.resolve(assetsDir, index), 'utf-8') // chunk expect(content).toMatch(`new Worker("/assets`) + expect(content).toMatch(`new SharedWorker("/assets`) // inlined expect(content).toMatch(`new Worker("data:application/javascript`) }) diff --git a/packages/playground/worker/index.html b/packages/playground/worker/index.html index e7bd7c78c1adb2..3203fb0a98874e 100644 --- a/packages/playground/worker/index.html +++ b/packages/playground/worker/index.html @@ -6,9 +6,16 @@
Response from inline worker:
+ +
+ Tick from shared worker, it syncs between pages: + 0 +
+ diff --git a/packages/playground/worker/my-shared-worker.ts b/packages/playground/worker/my-shared-worker.ts new file mode 100644 index 00000000000000..cd5b24f265b955 --- /dev/null +++ b/packages/playground/worker/my-shared-worker.ts @@ -0,0 +1,16 @@ +let count = 0 +const ports = new Set() + +onconnect = (event) => { + const port = event.ports[0] + ports.add(port) + port.postMessage(count) + port.onmessage = (message) => { + if (message.data === 'tick') { + count++ + ports.forEach((p) => { + p.postMessage(count) + }) + } + } +} diff --git a/packages/vite/src/node/constants.ts b/packages/vite/src/node/constants.ts index 8c9192929b04dc..aafe292f3e7a1d 100644 --- a/packages/vite/src/node/constants.ts +++ b/packages/vite/src/node/constants.ts @@ -19,7 +19,7 @@ export const JS_TYPES_RE = /\.(?:j|t)sx?$|\.mjs$/ export const OPTIMIZABLE_ENTRY_RE = /\.(?:m?js|ts)$/ -export const SPECIAL_QUERY_RE = /[\?&](?:worker|raw|url)\b/ +export const SPECIAL_QUERY_RE = /[\?&](?:worker|sharedworker|raw|url)\b/ export const DEP_CACHE_DIR = `.vite` diff --git a/packages/vite/src/node/plugins/worker.ts b/packages/vite/src/node/plugins/worker.ts index 2eb6e32ac83d79..9a1f5639f8d4e3 100644 --- a/packages/vite/src/node/plugins/worker.ts +++ b/packages/vite/src/node/plugins/worker.ts @@ -24,7 +24,11 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin { name: 'vite:worker', load(id) { - if (isBuild && parseWorkerRequest(id)?.worker != null) { + if ( + isBuild && + (parseWorkerRequest(id)?.worker ?? + parseWorkerRequest(id)?.sharedworker) != null + ) { return '' } }, @@ -36,7 +40,10 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin { code: `import '${ENV_PUBLIC_PATH}'\n` + _ } } - if (query == null || (query && query.worker == null)) { + if ( + query == null || + (query && (query.worker ?? query.sharedworker) == null) + ) { return } @@ -73,8 +80,15 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin { } } + const workerConstructor = + query.sharedworker != null ? 'SharedWorker' : 'Worker' + const { worker, inline, sharedworker, ...optionsFromQuery } = query + const workerOptions = { ...optionsFromQuery, type: 'module' } + return `export default function WorkerWrapper() { - return new Worker(${JSON.stringify(url)}, { type: 'module' }) + return new ${workerConstructor}(${JSON.stringify( + url + )}, ${JSON.stringify(workerOptions, null, 2)}) }` } }