Skip to content

Commit 79a5007

Browse files
fi3eworksapphi-red
andauthored
fix(worker): using data URLs for inline shared worker (#12014)
Co-authored-by: 翠 / green <green@sapphi.red>
1 parent 1a8af8d commit 79a5007

File tree

11 files changed

+96
-30
lines changed

11 files changed

+96
-30
lines changed

docs/config/worker-options.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Options related to Web Workers.
55
## worker.format
66

77
- **Type:** `'es' | 'iife'`
8-
- **Default:** `iife`
8+
- **Default:** `'iife'`
99

1010
Output format for worker bundle.
1111

packages/vite/src/node/plugins/worker.ts

+29-14
Original file line numberDiff line numberDiff line change
@@ -287,25 +287,40 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin {
287287
: 'classic'
288288
: 'module'
289289
const workerOptions = workerType === 'classic' ? '' : ',{type: "module"}'
290+
290291
if (isBuild) {
291292
getDepsOptimizer(config, ssr)?.registerWorkersSource(id)
292293
if (query.inline != null) {
293294
const chunk = await bundleWorkerEntry(config, id, query)
294-
// inline as blob data url
295-
return {
296-
code: `const encodedJs = "${Buffer.from(chunk.code).toString(
297-
'base64',
298-
)}";
299-
const blob = typeof window !== "undefined" && window.Blob && new Blob([atob(encodedJs)], { type: "text/javascript;charset=utf-8" });
300-
export default function WorkerWrapper() {
301-
const objURL = blob && (window.URL || window.webkitURL).createObjectURL(blob);
302-
try {
303-
return objURL ? new ${workerConstructor}(objURL) : new ${workerConstructor}("data:application/javascript;base64," + encodedJs${workerOptions});
304-
} finally {
305-
objURL && (window.URL || window.webkitURL).revokeObjectURL(objURL);
306-
}
307-
}`,
295+
const encodedJs = `const encodedJs = "${Buffer.from(
296+
chunk.code,
297+
).toString('base64')}";`
298+
299+
const code =
300+
// Using blob URL for SharedWorker results in multiple instances of a same worker
301+
workerConstructor === 'Worker'
302+
? `${encodedJs}
303+
const blob = typeof window !== "undefined" && window.Blob && new Blob([atob(encodedJs)], { type: "text/javascript;charset=utf-8" });
304+
export default function WorkerWrapper() {
305+
let objURL;
306+
try {
307+
objURL = blob && (window.URL || window.webkitURL).createObjectURL(blob);
308+
if (!objURL) throw ''
309+
return new ${workerConstructor}(objURL)
310+
} catch(e) {
311+
return new ${workerConstructor}("data:application/javascript;base64," + encodedJs${workerOptions});
312+
} finally {
313+
objURL && (window.URL || window.webkitURL).revokeObjectURL(objURL);
314+
}
315+
}`
316+
: `${encodedJs}
317+
export default function WorkerWrapper() {
318+
return new ${workerConstructor}("data:application/javascript;base64," + encodedJs${workerOptions});
319+
}
320+
`
308321

322+
return {
323+
code,
309324
// Empty sourcemap to suppress Rollup warning
310325
map: { mappings: '' },
311326
}

playground/worker/__tests__/es/es-worker.spec.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ test('shared worker', async () => {
3434
await untilUpdated(() => page.textContent('.tick-count'), 'pong', true)
3535
})
3636

37+
test('inline shared worker', async () => {
38+
await untilUpdated(() => page.textContent('.pong-shared-inline'), 'pong')
39+
})
40+
3741
test('worker emitted and import.meta.url in nested worker (serve)', async () => {
3842
await untilUpdated(
3943
() => page.textContent('.nested-worker'),
@@ -72,9 +76,16 @@ describe.runIf(isBuild)('build', () => {
7276
// chunk
7377
expect(content).toMatch(`new Worker("/es/assets`)
7478
expect(content).toMatch(`new SharedWorker("/es/assets`)
75-
// inlined
79+
// inlined worker
7680
expect(content).toMatch(`(window.URL||window.webkitURL).createObjectURL`)
7781
expect(content).toMatch(`window.Blob`)
82+
expect(content).toMatch(
83+
/try\{if\(\w+=\w+&&\(window\.URL\|\|window\.webkitURL\)\.createObjectURL\(\w+\),!\w+\)throw""/,
84+
)
85+
// inlined shared worker
86+
expect(content).toMatch(
87+
`return new SharedWorker("data:application/javascript;base64,"+`,
88+
)
7889
})
7990

8091
test('worker emitted and import.meta.url in nested worker (build)', async () => {

playground/worker/__tests__/iife/iife-worker.spec.ts

+4
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ test('shared worker', async () => {
2929
await untilUpdated(() => page.textContent('.tick-count'), 'pong')
3030
})
3131

32+
test('inline shared worker', async () => {
33+
await untilUpdated(() => page.textContent('.pong-shared-inline'), 'pong')
34+
})
35+
3236
test('worker emitted and import.meta.url in nested worker (serve)', async () => {
3337
await untilUpdated(() => page.textContent('.nested-worker'), '/worker-nested')
3438
await untilUpdated(

playground/worker/__tests__/relative-base/relative-base-worker.spec.ts

+4
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ test('shared worker', async () => {
3535
await untilUpdated(() => page.textContent('.tick-count'), 'pong', true)
3636
})
3737

38+
test('inline shared worker', async () => {
39+
await untilUpdated(() => page.textContent('.pong-shared-inline'), 'pong')
40+
})
41+
3842
test('worker emitted and import.meta.url in nested worker (serve)', async () => {
3943
await untilUpdated(
4044
() => page.textContent('.nested-worker'),

playground/worker/__tests__/sourcemap-hidden/sourcemap-hidden-worker.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ describe.runIf(isBuild)('build', () => {
1010

1111
const files = fs.readdirSync(assetsDir)
1212
// should have 2 worker chunk
13-
expect(files.length).toBe(31)
13+
expect(files.length).toBe(32)
1414
const index = files.find((f) => f.includes('main-module'))
1515
const content = fs.readFileSync(path.resolve(assetsDir, index), 'utf-8')
1616
const indexSourcemap = getSourceMapUrl(content)

playground/worker/__tests__/sourcemap/sourcemap-worker.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ describe.runIf(isBuild)('build', () => {
99
const assetsDir = path.resolve(testDir, 'dist/iife-sourcemap/assets')
1010
const files = fs.readdirSync(assetsDir)
1111
// should have 2 worker chunk
12-
expect(files.length).toBe(31)
12+
expect(files.length).toBe(32)
1313
const index = files.find((f) => f.includes('main-module'))
1414
const content = fs.readFileSync(path.resolve(assetsDir, index), 'utf-8')
1515
const indexSourcemap = getSourceMapUrl(content)

playground/worker/index.html

+6
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ <h2 class="format-iife">format iife:</h2>
3838
</p>
3939
<code class="tick-count"></code>
4040

41+
<p>
42+
import InlineSharedWorker from '../my-shared-worker?sharedworker&inline'
43+
<span class="classname">.pong-shared-inline</span>
44+
</p>
45+
<code class="pong-shared-inline"></code>
46+
4147
<p>
4248
new Worker(new URL('./url-worker.js', import.meta.url), { type: 'module' })
4349
<span class="classname">.worker-import-meta-url</span>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
let inlineSharedWorkerCount = 0
2+
3+
// @ts-expect-error onconnect exists in worker
4+
self.onconnect = (event) => {
5+
inlineSharedWorkerCount++
6+
const port = event.ports[0]
7+
if (inlineSharedWorkerCount >= 2) {
8+
port.postMessage('pong')
9+
}
10+
}
11+
12+
// for sourcemap
13+
console.log('my-inline-shared-worker.js')

playground/worker/my-shared-worker.ts

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
const ports = new Set()
1+
let sharedWorkerCount = 0
22

33
// @ts-expect-error onconnect exists in worker
44
self.onconnect = (event) => {
5+
sharedWorkerCount++
56
const port = event.ports[0]
6-
ports.add(port)
7-
port.postMessage('pong')
8-
port.onmessage = () => {
9-
ports.forEach((p: any) => {
10-
p.postMessage('pong')
11-
})
7+
if (sharedWorkerCount >= 2) {
8+
port.postMessage('pong')
129
}
1310
}
1411

playground/worker/worker/main-module.js

+21-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import myWorker from '../my-worker.ts?worker'
22
import InlineWorker from '../my-worker.ts?worker&inline'
3+
import InlineSharedWorker from '../my-inline-shared-worker?sharedworker&inline'
34
import mySharedWorker from '../my-shared-worker?sharedworker&name=shared'
45
import TSOutputWorker from '../possible-ts-output-worker?worker'
56
import NestedWorker from '../worker-nested-worker?worker'
@@ -26,11 +27,26 @@ inlineWorker.addEventListener('message', (e) => {
2627
text('.pong-inline', e.data.msg)
2728
})
2829

29-
const sharedWorker = new mySharedWorker()
30-
sharedWorker.port.addEventListener('message', (event) => {
31-
text('.tick-count', event.data)
32-
})
33-
sharedWorker.port.start()
30+
const startSharedWorker = () => {
31+
const sharedWorker = new mySharedWorker()
32+
sharedWorker.port.addEventListener('message', (event) => {
33+
text('.tick-count', event.data)
34+
})
35+
sharedWorker.port.start()
36+
}
37+
startSharedWorker()
38+
startSharedWorker()
39+
40+
const startInlineSharedWorker = () => {
41+
const inlineSharedWorker = new InlineSharedWorker()
42+
inlineSharedWorker.port.addEventListener('message', (event) => {
43+
text('.pong-shared-inline', event.data)
44+
})
45+
inlineSharedWorker.port.start()
46+
}
47+
48+
startInlineSharedWorker()
49+
startInlineSharedWorker()
3450

3551
const tsOutputWorker = new TSOutputWorker()
3652
tsOutputWorker.postMessage('ping')

0 commit comments

Comments
 (0)