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..b184d335c5cdcc 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
}
@@ -73,8 +82,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)})
}`
}
}