From d78191c5ce5f9c0e5a2329c7b113fc25b27535b9 Mon Sep 17 00:00:00 2001 From: fi3ework Date: Sat, 29 May 2021 11:09:38 +0800 Subject: [PATCH] feat(plugins/worker): support SharedWorker (resolve #2093) (#2505) --- docs/guide/assets.md | 2 +- docs/guide/features.md | 2 +- .../worker/__tests__/worker.spec.ts | 30 +++++++++++++++++-- packages/playground/worker/index.html | 18 +++++++++++ .../playground/worker/my-shared-worker.ts | 16 ++++++++++ packages/vite/src/node/constants.ts | 2 +- packages/vite/src/node/plugins/worker.ts | 23 +++++++++++--- 7 files changed, 84 insertions(+), 9 deletions(-) create mode 100644 packages/playground/worker/my-shared-worker.ts diff --git a/docs/guide/assets.md b/docs/guide/assets.md index f99d74224d3c1f..77988ff2092a08 100644 --- a/docs/guide/assets.md +++ b/docs/guide/assets.md @@ -45,7 +45,7 @@ import shaderString from './shader.glsl?raw' ### Importing Script as a Worker -Scripts can be imported as web workers with the `?worker` suffix. +Scripts can be imported as web workers with the `?worker` or `?sharedworker` suffix. ```js // Separate chunk in the production build diff --git a/docs/guide/features.md b/docs/guide/features.md index 383735ba0f8b4e..040376642ac719 100644 --- a/docs/guide/features.md +++ b/docs/guide/features.md @@ -290,7 +290,7 @@ In the production build, `.wasm` files smaller than `assetInlineLimit` will be i ## Web Workers -A web worker script can be directly imported by appending `?worker` to the import request. The default export will be a custom worker constructor: +A web worker script can be directly imported by appending `?worker` or `?sharedworker` to the import request. The default export will be a custom worker constructor: ```js import MyWorker from './worker?worker' diff --git a/packages/playground/worker/__tests__/worker.spec.ts b/packages/playground/worker/__tests__/worker.spec.ts index a096472547df46..9e19a9f60a8abf 100644 --- a/packages/playground/worker/__tests__/worker.spec.ts +++ b/packages/playground/worker/__tests__/worker.spec.ts @@ -1,6 +1,7 @@ import fs from 'fs' import path from 'path' import { untilUpdated, isBuild, testDir } from '../../testUtils' +import { Page } from 'playwright-chromium' test('normal', async () => { await page.click('.ping') @@ -16,17 +17,42 @@ test('inlined', async () => { 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++ + + 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) +}) + 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(`(window.URL||window.webkitURL).createObjectURL`) }) 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 e11c76a44d3b20..4352869419d897 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/ /** * Prefix for resolved fs paths, since windows paths may not be valid as URLs. diff --git a/packages/vite/src/node/plugins/worker.ts b/packages/vite/src/node/plugins/worker.ts index c061bc38ec224d..2c2e4501b79ac1 100644 --- a/packages/vite/src/node/plugins/worker.ts +++ b/packages/vite/src/node/plugins/worker.ts @@ -24,8 +24,14 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin { name: 'vite:worker', load(id) { - if (isBuild && parseWorkerRequest(id)?.worker != null) { - return '' + if (isBuild) { + const parsedQuery = parseWorkerRequest(id) + if ( + parsedQuery && + (parsedQuery.worker ?? parsedQuery.sharedworker) != null + ) { + return '' + } } }, @@ -36,7 +42,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 } @@ -80,8 +89,14 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin { url = injectQuery(url, WorkerFileId) } + const workerConstructor = + query.sharedworker != null ? 'SharedWorker' : 'Worker' + const workerOptions = { 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)}) }` } }