Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(worker): using data URLs for inline shared worker #12014

Merged
merged 10 commits into from
Mar 18, 2023
Merged
2 changes: 1 addition & 1 deletion docs/config/worker-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Options related to Web Workers.
## worker.format

- **Type:** `'es' | 'iife'`
- **Default:** `iife`
- **Default:** `'iife'`

Output format for worker bundle.

Expand Down
43 changes: 29 additions & 14 deletions packages/vite/src/node/plugins/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: '' },
}
Expand Down
13 changes: 12 additions & 1 deletion playground/worker/__tests__/es/es-worker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down Expand Up @@ -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 () => {
Expand Down
4 changes: 4 additions & 0 deletions playground/worker/__tests__/iife/iife-worker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions playground/worker/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ <h2 class="format-iife">format iife:</h2>
</p>
<code class="tick-count"></code>

<p>
import InlineSharedWorker from '../my-shared-worker?sharedworker&inline'
<span class="classname">.pong-shared-inline</span>
</p>
<code class="pong-shared-inline"></code>

<p>
new Worker(new URL('./url-worker.js', import.meta.url), { type: 'module' })
<span class="classname">.worker-import-meta-url</span>
Expand Down
13 changes: 13 additions & 0 deletions playground/worker/my-inline-shared-worker.ts
Original file line number Diff line number Diff line change
@@ -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')
11 changes: 4 additions & 7 deletions playground/worker/my-shared-worker.ts
Original file line number Diff line number Diff line change
@@ -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')
}
}

Expand Down
26 changes: 21 additions & 5 deletions playground/worker/worker/main-module.js
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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')
Expand Down