Skip to content

Commit

Permalink
feat(plugins/worker): support SharedWorker (resolve #2093) (#2505)
Browse files Browse the repository at this point in the history
  • Loading branch information
fi3ework committed May 29, 2021
1 parent 421c530 commit d78191c
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 9 deletions.
2 changes: 1 addition & 1 deletion docs/guide/assets.md
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/features.md
Expand Up @@ -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'
Expand Down
30 changes: 28 additions & 2 deletions 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')
Expand All @@ -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`)
})
Expand Down
18 changes: 18 additions & 0 deletions packages/playground/worker/index.html
Expand Up @@ -6,9 +6,16 @@
<button class="ping-inline">Ping Inline Worker</button>
<div>Response from inline worker: <span class="pong-inline"></span></div>

<button class="tick-shared">Tick Shared Worker</button>
<div>
Tick from shared worker, it syncs between pages:
<span class="tick-count">0</span>
</div>

<script type="module">
import Worker from './my-worker?worker'
import InlineWorker from './my-worker?worker&inline'
import SharedWorker from './my-shared-worker?sharedworker&name=shared'

const worker = new Worker()
worker.addEventListener('message', (e) => {
Expand All @@ -28,4 +35,15 @@
document.querySelector('.ping-inline').addEventListener('click', () => {
inlineWorker.postMessage('ping')
})

const sharedWorker = new SharedWorker()
document.querySelector('.tick-shared').addEventListener('click', () => {
sharedWorker.port.postMessage('tick')
})

sharedWorker.port.addEventListener('message', (event) => {
document.querySelector('.tick-count').textContent = event.data
})

sharedWorker.port.start()
</script>
16 changes: 16 additions & 0 deletions 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)
})
}
}
}
2 changes: 1 addition & 1 deletion packages/vite/src/node/constants.ts
Expand Up @@ -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.
Expand Down
23 changes: 19 additions & 4 deletions packages/vite/src/node/plugins/worker.ts
Expand Up @@ -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 ''
}
}
},

Expand All @@ -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
}

Expand Down Expand Up @@ -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)})
}`
}
}
Expand Down

0 comments on commit d78191c

Please sign in to comment.