Skip to content

Commit a9fe3e7

Browse files
committed
feat: web worker
1 parent e23e21f commit a9fe3e7

File tree

13 files changed

+190
-20
lines changed

13 files changed

+190
-20
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import fs from 'fs'
2+
import path from 'path'
3+
import { untilUpdated, isBuild, testDir } from '../../testUtils'
4+
5+
test('normal', async () => {
6+
await page.click('.ping')
7+
await untilUpdated(() => page.textContent('.pong'), 'pong')
8+
})
9+
10+
test('inlined', async () => {
11+
await page.click('.ping-inline')
12+
await untilUpdated(() => page.textContent('.pong-inline'), 'pong')
13+
})
14+
15+
if (isBuild) {
16+
// assert correct files
17+
test('inlined code generation', async () => {
18+
const assetsDir = path.resolve(testDir, 'dist/assets')
19+
const files = fs.readdirSync(assetsDir)
20+
// should have only 1 worker chunk
21+
expect(files.length).toBe(2)
22+
const index = files.find((f) => f.includes('index'))
23+
const content = fs.readFileSync(path.resolve(assetsDir, index), 'utf-8')
24+
// chunk
25+
expect(content).toMatch(`new Worker("/assets`)
26+
// inlined
27+
expect(content).toMatch(`new Worker("data:application/javascript`)
28+
})
29+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<button class="ping">Ping</button>
2+
<div>Response from worker: <span class="pong"></span></div>
3+
4+
<button class="ping-inline">Ping Inline Worker</button>
5+
<div>Response from inline worker: <span class="pong-inline"></span></div>
6+
7+
<script type="module">
8+
import Worker from './my-worker?worker'
9+
import InlineWorker from './my-worker?worker&inline'
10+
11+
const worker = new Worker()
12+
worker.addEventListener('message', (e) => {
13+
document.querySelector('.pong').textContent = e.data
14+
})
15+
16+
document.querySelector('.ping').addEventListener('click', () => {
17+
worker.postMessage('ping')
18+
})
19+
20+
const inlineWorker = new InlineWorker()
21+
inlineWorker.addEventListener('message', (e) => {
22+
document.querySelector('.pong-inline').textContent = e.data
23+
})
24+
25+
document.querySelector('.ping-inline').addEventListener('click', () => {
26+
inlineWorker.postMessage('ping')
27+
})
28+
</script>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { msg } from './workerImport'
2+
3+
self.onmessage = (e) => {
4+
if (e.data === 'ping') {
5+
self.postMessage(msg)
6+
}
7+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "test-worker",
3+
"private": true,
4+
"version": "0.0.0",
5+
"scripts": {
6+
"dev": "vite",
7+
"build": "vite build",
8+
"debug": "node --inspect-brk ../../vite/bin/vite"
9+
}
10+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const msg = 'pong'

packages/vite/src/node/build/index.ts

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import fs from 'fs'
22
import path from 'path'
3-
import { resolveConfig, UserConfig } from '../config'
3+
import { resolveConfig, UserConfig, ResolvedConfig } from '../config'
44
import Rollup, { Plugin, RollupBuild, RollupOptions } from 'rollup'
55
import { buildReporterPlugin } from '../plugins/reporter'
66
import { buildDefinePlugin } from '../plugins/define'
@@ -149,21 +149,9 @@ export async function build(
149149
}
150150
}
151151

152-
async function doBuild(
153-
inlineConfig: UserConfig & { mode?: string } = {},
154-
configPath?: string | false
155-
) {
156-
const mode = inlineConfig.mode || 'production'
157-
const config = await resolveConfig(inlineConfig, 'build', mode, configPath)
152+
export function resolveBuildPlugins(config: ResolvedConfig): Plugin[] {
158153
const options = config.build
159-
160-
const resolve = (p: string) => path.resolve(config.root, p)
161-
162-
const input = options.rollupOptions?.input || resolve('index.html')
163-
const outDir = resolve(options.outDir)
164-
const publicDir = resolve('public')
165-
166-
const plugins = [
154+
return [
167155
...(config.plugins as Plugin[]),
168156
...(options.rollupOptions.plugins || []),
169157
buildHtmlPlugin(config),
@@ -179,6 +167,22 @@ async function doBuild(
179167
...(options.manifest ? [manifestPlugin()] : []),
180168
buildReporterPlugin(config)
181169
]
170+
}
171+
172+
async function doBuild(
173+
inlineConfig: UserConfig & { mode?: string } = {},
174+
configPath?: string | false
175+
) {
176+
const mode = inlineConfig.mode || 'production'
177+
const config = await resolveConfig(inlineConfig, 'build', mode, configPath)
178+
const options = config.build
179+
180+
const resolve = (p: string) => path.resolve(config.root, p)
181+
182+
const input = options.rollupOptions?.input || resolve('index.html')
183+
const outDir = resolve(options.outDir)
184+
const publicDir = resolve('public')
185+
const plugins = resolveBuildPlugins(config)
182186

183187
const rollup = require('rollup') as typeof Rollup
184188

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { assetPlugin } from './asset'
1010
import { clientInjectionsPlugin } from './clientInjections'
1111
import { htmlPlugin } from './html'
1212
import { wasmPlugin } from './wasm'
13+
import { webWorkerPlugin } from './worker'
1314

1415
export function resolvePlugins(
1516
config: ResolvedConfig,
@@ -29,6 +30,7 @@ export function resolvePlugins(
2930
namedExports: true
3031
}),
3132
wasmPlugin(config),
33+
webWorkerPlugin(config),
3234
assetPlugin(config),
3335
...normalPlugins,
3436
...postPlugins,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export function buildReporterPlugin(config: ResolvedConfig): Plugin {
4646

4747
return {
4848
name: 'vite:size',
49-
generateBundle(_, output) {
49+
writeBundle(_, output) {
5050
for (const file in output) {
5151
const chunk = output[file]
5252
if (chunk.type === 'chunk') {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
normalizePath
1414
} from '../utils'
1515

16-
const supportedExts = ['.mjs', '.js', '.ts', '.jsx', '.tsx']
16+
export const supportedExts = ['.mjs', '.js', '.ts', '.jsx', '.tsx']
1717
const mainFields = ['module', 'jsnext', 'jsnext:main', 'browser', 'main']
1818

1919
const isDebug = process.env.DEBUG
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { ResolvedConfig } from '../config'
2+
import { Plugin } from '../plugin'
3+
import { parse as parseUrl } from 'url'
4+
import qs, { ParsedUrlQuery } from 'querystring'
5+
import { fileToUrl } from './asset'
6+
import { cleanUrl } from '../utils'
7+
import Rollup from 'rollup'
8+
import { resolveBuildPlugins } from '..'
9+
10+
function isWorkerRequest(id: string): ParsedUrlQuery | false {
11+
const { search } = parseUrl(id)
12+
if (!search) {
13+
return false
14+
}
15+
const query = qs.parse(search.slice(1))
16+
return query.worker != null ? query : false
17+
}
18+
19+
export function webWorkerPlugin(config: ResolvedConfig): Plugin {
20+
const isBuild = config.command === 'build'
21+
22+
return {
23+
name: 'vite:worker',
24+
25+
load(id) {
26+
if (isBuild && isWorkerRequest(id)) {
27+
return ''
28+
}
29+
},
30+
31+
async transform(_, id) {
32+
const query = isWorkerRequest(id)
33+
if (!query) {
34+
return
35+
}
36+
37+
let url: string
38+
if (config.command === 'serve') {
39+
url = await fileToUrl(cleanUrl(id), config, this)
40+
} else {
41+
if (query.inline != null) {
42+
// bundle the file as entry to support imports and inline as base64
43+
// data url
44+
const rollup = require('rollup') as typeof Rollup
45+
const bundle = await rollup.rollup({
46+
input: cleanUrl(id),
47+
plugins: resolveBuildPlugins(config)
48+
})
49+
try {
50+
const { output } = await bundle.generate({
51+
format: 'es',
52+
sourcemap: config.build.sourcemap
53+
})
54+
url = `data:application/javascript;base64,${Buffer.from(
55+
output[0].code
56+
).toString('base64')}`
57+
} finally {
58+
bundle.close()
59+
}
60+
} else {
61+
// emit as separate chunk
62+
url = `__VITE_ASSET__${this.emitFile({
63+
type: 'chunk',
64+
id: cleanUrl(id)
65+
})}`
66+
}
67+
}
68+
69+
return `export default function WorkerWrapper() {
70+
return new Worker(${JSON.stringify(url)}, { type: 'module' })
71+
}`
72+
}
73+
}
74+
}

0 commit comments

Comments
 (0)