Skip to content

Commit

Permalink
fix(build): support for firefox/safari workers
Browse files Browse the repository at this point in the history
  • Loading branch information
NotWoods committed Jun 25, 2021
1 parent fd3a85b commit 7c8f609
Show file tree
Hide file tree
Showing 9 changed files with 184 additions and 42 deletions.
49 changes: 49 additions & 0 deletions packages/playground/worker-code-split/__tests__/worker.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import fs from 'fs'
import path from 'path'
import { untilUpdated, isBuild, testDir } from '../../testUtils'

test('normal', async () => {
await page.click('.ping')
await untilUpdated(() => page.textContent('.pong'), 'pong')
await untilUpdated(
() => page.textContent('.mode'),
isBuild ? 'production' : 'development'
)
})

test('legacy', async () => {
await page.click('.ping-inline')
await untilUpdated(() => page.textContent('.pong-inline'), 'pong')
})

if (isBuild) {
// assert correct files
test('inlined code generation', async () => {
const assetsDir = path.resolve(testDir, 'dist/assets')
const files = fs.readdirSync(assetsDir)
// should have 1 worker chunk for module and legacy
expect(files.length).toBe(6)
expect(files[5]).toMatch('workerImport.')

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`)
// not inlined
expect(content).not.toMatch(`new Worker("data:application/javascript`)

const workers = files.filter((f) => f.includes('my-worker-1'))
expect(workers.length).toBe(2)
const moduleContent = fs.readFileSync(
path.resolve(assetsDir, workers[0]),
'utf-8'
)
const scriptContent = fs.readFileSync(
path.resolve(assetsDir, workers[1]),
'utf-8'
)
// module worker
expect(moduleContent).toMatch(/import{.+}from".\/workerImport/)
expect(scriptContent).not.toMatch('import')
})
}
31 changes: 31 additions & 0 deletions packages/playground/worker-code-split/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<button class="ping">Ping</button>
<div>
Response from worker: <span class="pong"></span><span class="mode"></span>
</div>

<button class="ping-inline">Ping Inline Worker</button>
<div>Response from inline worker: <span class="pong-inline"></span></div>

<script type="module">
import Worker1 from './my-worker-1?worker'
import Worker2 from './my-worker-2?worker'

const worker1 = new Worker1()
worker1.addEventListener('message', (e) => {
document.querySelector('.pong').textContent = e.data.msg
document.querySelector('.mode').textContent = e.data.mode
})

document.querySelector('.ping').addEventListener('click', () => {
worker1.postMessage('ping')
})

const worker2 = new Worker2()
worker2.addEventListener('message', (e) => {
document.querySelector('.pong-inline').textContent = e.data.msg
})

document.querySelector('.ping-inline').addEventListener('click', () => {
worker2.postMessage('ping')
})
</script>
7 changes: 7 additions & 0 deletions packages/playground/worker-code-split/my-worker-1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { msg, mode } from './workerImport'

self.onmessage = (e) => {
if (e.data === 'ping') {
self.postMessage({ msg, mode })
}
}
7 changes: 7 additions & 0 deletions packages/playground/worker-code-split/my-worker-2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { msg, mode } from './workerImport'

self.onmessage = (e) => {
if (e.data === 'ping') {
self.postMessage({ msg, mode })
}
}
11 changes: 11 additions & 0 deletions packages/playground/worker-code-split/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "test-worker-code-split",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"debug": "node --inspect-brk ../../vite/bin/vite",
"serve": "vite preview"
}
}
2 changes: 2 additions & 0 deletions packages/playground/worker-code-split/workerImport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const msg = 'pong'
export const mode = process.env.NODE_ENV
2 changes: 1 addition & 1 deletion packages/playground/worker/__tests__/worker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ if (isBuild) {
test('inlined code generation', async () => {
const assetsDir = path.resolve(testDir, 'dist/assets')
const files = fs.readdirSync(assetsDir)
// should have 2 worker chunk
// should have worker chunk for module and legacy
expect(files.length).toBe(3)
const index = files.find((f) => f.includes('index'))
const content = fs.readFileSync(path.resolve(assetsDir, index), 'utf-8')
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/src/node/plugins/asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ async function fileToBuiltUrl(
return url
}

function getAssetHash(content: Buffer) {
export function getAssetHash(content: Buffer): string {
return createHash('sha256').update(content).digest('hex').slice(0, 8)
}

Expand Down
115 changes: 75 additions & 40 deletions packages/vite/src/node/plugins/worker.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { ResolvedConfig } from '../config'
import { Plugin } from '../plugin'
import { resolvePlugins } from '../plugins'
import { parse as parseUrl } from 'url'
import path from 'path'
import qs, { ParsedUrlQuery } from 'querystring'
import { fileToUrl } from './asset'
import { fileToUrl, getAssetHash } from './asset'
import { cleanUrl, injectQuery } from '../utils'
import Rollup from 'rollup'
import { ENV_PUBLIC_PATH } from '../constants'
Expand Down Expand Up @@ -39,13 +41,9 @@ function buildWorkerConstructor(query: ParsedUrlQuery | null) {
return null
}

return (urlVariable: string, options?: object) => {
if (options) {
return `new ${workerConstructor}(${urlVariable}, ${JSON.stringify(
options,
null,
2
)})`
return (urlVariable: string, optionsVariable?: string) => {
if (optionsVariable) {
return `new ${workerConstructor}(${urlVariable}, ${optionsVariable})`
} else {
return `new ${workerConstructor}(${urlVariable})`
}
Expand Down Expand Up @@ -82,51 +80,88 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin {

let url: string
if (isBuild) {
if (query.inline != null) {
// bundle the file as entry to support imports and inline as blob
// data url
const rollup = require('rollup') as typeof Rollup
const bundle = await rollup.rollup({
input: cleanUrl(id),
plugins: config.plugins as Plugin[]
// bundle an inline script version of the worker
const { rollup } = require('rollup') as typeof Rollup
const bundle = await rollup({
input: cleanUrl(id),
plugins: await resolvePlugins({ ...config }, [], [], [])
})

let code: string
try {
const { output } = await bundle.generate({
format: 'iife',
sourcemap: config.build.sourcemap
})
try {
const { output } = await bundle.generate({
format: 'es',
sourcemap: config.build.sourcemap
})

return `const blob = new Blob([atob(\"${Buffer.from(
output[0].code
).toString(
'base64'
)}\")], { type: 'text/javascript;charset=utf-8' });
const URL = window.URL || window.webkitURL;
export default function WorkerWrapper() {
const objURL = URL.createObjectURL(blob);
try {
return ${workerConstructor('objUrl')};
} finally {
URL.revokeObjectURL(objURL);
}
}`
} finally {
await bundle.close()
}

code = output[0].code
} finally {
await bundle.close()
}
const content = Buffer.from(code)

if (query.inline != null) {
// inline as blob data url
return `const blob = new Blob([atob(\"${content.toString(
'base64'
)}\")], { type: 'text/javascript;charset=utf-8' });
const URL = window.URL || window.webkitURL;
export default function WorkerWrapper() {
const objURL = URL.createObjectURL(blob);
try {
return ${workerConstructor('objUrl')};
} finally {
URL.revokeObjectURL(objURL);
}
}`
} else {
// emit as separate chunk
// emit as separate chunk (type module)
url = `__VITE_ASSET__${this.emitFile({
type: 'chunk',
id: cleanUrl(id)
})}__`

// emit separate bundled chunk (type script)
const { name } = path.parse(cleanUrl(id))
const contentHash = getAssetHash(content)
const fileName = path.posix.join(
config.build.assetsDir,
`${name}.${contentHash}.js`
)
const legacyUrl = `__VITE_ASSET__${this.emitFile({
fileName: fileName,
type: 'asset',
source: code
})}__`

// create wrapper that tries to build a worker module,
// and falls back to legacy script module
return `export default function WorkerWrapper() {
let supportsModuleWorker = false;
const options = {
get type() {
supportsModuleWorker = true;
return 'module';
}
};
const modWorker = ${workerConstructor(
JSON.stringify(url),
'options'
)};
if (supportsModuleWorker) {
return modWorker;
} else {
return ${workerConstructor(JSON.stringify(legacyUrl))};
}
}`
}
} else {
url = await fileToUrl(cleanUrl(id), config, this)
url = injectQuery(url, WorkerFileId)
}

const workerUrl = JSON.stringify(url)
const workerOptions = { type: 'module' }
const workerOptions = JSON.stringify({ type: 'module' }, null, 2)

return `export default function WorkerWrapper() {
return ${workerConstructor(workerUrl, workerOptions)};
Expand Down

0 comments on commit 7c8f609

Please sign in to comment.