Skip to content

Commit

Permalink
feat: worker emit fileName with config (#7804)
Browse files Browse the repository at this point in the history
  • Loading branch information
poyoho committed May 12, 2022
1 parent d0deac0 commit 04c2edd
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 116 deletions.
9 changes: 8 additions & 1 deletion packages/vite/src/node/config.ts
Expand Up @@ -252,6 +252,8 @@ export type ResolvedConfig = Readonly<
command: 'build' | 'serve'
mode: string
isWorker: boolean
/** @internal */
mainConfig: ResolvedConfig | null
isProduction: boolean
env: Record<string, any>
resolve: ResolveOptions & {
Expand Down Expand Up @@ -482,6 +484,7 @@ export async function resolveConfig(
command,
mode,
isWorker: false,
mainConfig: null,
isProduction,
plugins: userPlugins,
server,
Expand Down Expand Up @@ -513,7 +516,11 @@ export async function resolveConfig(
// flat config.worker.plugin
const [workerPrePlugins, workerNormalPlugins, workerPostPlugins] =
sortUserPlugins(config.worker?.plugins as Plugin[])
const workerResolved: ResolvedConfig = { ...resolved, isWorker: true }
const workerResolved: ResolvedConfig = {
...resolved,
isWorker: true,
mainConfig: resolved
}
resolved.worker.plugins = await resolvePlugins(
workerResolved,
workerPrePlugins,
Expand Down
189 changes: 79 additions & 110 deletions packages/vite/src/node/plugins/worker.ts
@@ -1,85 +1,40 @@
import path from 'path'
import type Rollup from 'rollup'
import type { EmittedFile, TransformPluginContext } from 'rollup'
import type { EmittedAsset, OutputChunk, TransformPluginContext } from 'rollup'
import type { ResolvedConfig } from '../config'
import type { Plugin } from '../plugin'
import { cleanUrl, getHash, injectQuery, parseRequest } from '../utils'
import { cleanUrl, injectQuery, parseRequest } from '../utils'
import { ENV_PUBLIC_PATH } from '../constants'
import { onRollupWarning } from '../build'
import { fileToUrl } from './asset'

interface WorkerCache {
// save worker bundle emitted files avoid overwrites the same file.
// <chunk_filename, hash>
assets: Map<string, string>
chunks: Map<string, string>
// save worker all emit chunk avoid rollup make the same asset unique.
assets: Map<string, EmittedAsset>

// worker bundle don't deps on any more worker runtime info an id only had an result.
// save worker bundled file id to avoid repeated execution of bundles
// <filename, hash>
// <input_filename, hash>
bundle: Map<string, string>
// nested worker bundle context don't had file what emitted by outside bundle
// save the hash to id to rewrite truth id.
// <hash, id>
emitted: Map<string, string>
}

const WorkerFileId = 'worker_file'
const workerCache = new WeakMap<ResolvedConfig, WorkerCache>()

function emitWorkerFile(
ctx: Rollup.TransformPluginContext,
function saveEmitWorkerAsset(
config: ResolvedConfig,
asset: EmittedFile,
type: 'assets' | 'chunks'
): string {
asset: EmittedAsset
): void {
const fileName = asset.fileName!
const workerMap = workerCache.get(config)!

if (workerMap[type].has(fileName)) {
return workerMap[type].get(fileName)!
}
const hash = ctx.emitFile(asset)
workerMap[type].set(fileName, hash)
workerMap.emitted.set(hash, fileName)
return hash
}

function emitWorkerAssets(
ctx: Rollup.TransformPluginContext,
config: ResolvedConfig,
asset: EmittedFile
) {
const { format } = config.worker
return emitWorkerFile(
ctx,
config,
asset,
format === 'es' ? 'chunks' : 'assets'
)
}

function emitWorkerSourcemap(
ctx: Rollup.TransformPluginContext,
config: ResolvedConfig,
asset: EmittedFile
) {
return emitWorkerFile(ctx, config, asset, 'assets')
}

function emitWorkerChunks(
ctx: Rollup.TransformPluginContext,
config: ResolvedConfig,
asset: EmittedFile
) {
return emitWorkerFile(ctx, config, asset, 'chunks')
const workerMap = workerCache.get(config.mainConfig || config)!
workerMap.assets.set(fileName, asset)
}

export async function bundleWorkerEntry(
ctx: Rollup.TransformPluginContext,
ctx: TransformPluginContext,
config: ResolvedConfig,
id: string,
query: Record<string, string> | null
): Promise<Buffer> {
): Promise<OutputChunk> {
// bundle the file as entry to support imports
const { rollup } = await import('rollup')
const { plugins, rollupOptions, format } = config.worker
Expand All @@ -92,24 +47,40 @@ export async function bundleWorkerEntry(
},
preserveEntrySignatures: false
})
let chunk: Rollup.OutputChunk
let chunk: OutputChunk
try {
const workerOutputConfig = config.worker.rollupOptions.output
const workerConfig = workerOutputConfig
? Array.isArray(workerOutputConfig)
? workerOutputConfig[0] || {}
: workerOutputConfig
: {}
const {
output: [outputChunk, ...outputChunks]
} = await bundle.generate({
entryFileNames: path.posix.join(
config.build.assetsDir,
'[name].[hash].js'
),
chunkFileNames: path.posix.join(
config.build.assetsDir,
'[name].[hash].js'
),
assetFileNames: path.posix.join(
config.build.assetsDir,
'[name].[hash].[ext]'
),
...workerConfig,
format,
sourcemap: config.build.sourcemap
})
chunk = outputChunk
outputChunks.forEach((outputChunk) => {
if (outputChunk.type === 'asset') {
emitWorkerAssets(ctx, config, outputChunk)
saveEmitWorkerAsset(config, outputChunk)
} else if (outputChunk.type === 'chunk') {
emitWorkerChunks(ctx, config, {
fileName: path.posix.join(
config.build.assetsDir,
outputChunk.fileName
),
saveEmitWorkerAsset(config, {
fileName: outputChunk.fileName,
source: outputChunk.code,
type: 'asset'
})
Expand All @@ -118,36 +89,32 @@ export async function bundleWorkerEntry(
} finally {
await bundle.close()
}
return emitSourcemapForWorkerEntry(ctx, config, id, query, chunk)
return emitSourcemapForWorkerEntry(ctx, config, query, chunk)
}

function emitSourcemapForWorkerEntry(
context: TransformPluginContext,
ctx: TransformPluginContext,
config: ResolvedConfig,
id: string,
query: Record<string, string> | null,
chunk: Rollup.OutputChunk
): Buffer {
let { code, map: sourcemap } = chunk
chunk: OutputChunk
): OutputChunk {
const { map: sourcemap } = chunk

if (sourcemap) {
if (config.build.sourcemap === 'inline') {
// Manually add the sourcemap to the code if configured for inline sourcemaps.
// TODO: Remove when https://github.com/rollup/rollup/issues/3913 is resolved
// Currently seems that it won't be resolved until Rollup 3
const dataUrl = sourcemap.toUrl()
code += `//# sourceMappingURL=${dataUrl}`
chunk.code += `//# sourceMappingURL=${dataUrl}`
} else if (
config.build.sourcemap === 'hidden' ||
config.build.sourcemap === true
) {
const basename = path.parse(cleanUrl(id)).name
const data = sourcemap.toString()
const content = Buffer.from(data)
const contentHash = getHash(content)
const fileName = `${basename}.${contentHash}.js.map`
const filePath = path.posix.join(config.build.assetsDir, fileName)
emitWorkerSourcemap(context, config, {
fileName: filePath,
const mapFileName = chunk.fileName + '.map'
saveEmitWorkerAsset(config, {
fileName: mapFileName,
type: 'asset',
source: data
})
Expand All @@ -161,57 +128,50 @@ function emitSourcemapForWorkerEntry(
// non-inline web workers can use a relative path
const sourceMapUrl =
query?.inline != null
? path.posix.join(config.base, filePath)
: fileName
code += `//# sourceMappingURL=${sourceMapUrl}`
? mapFileName
: path.relative(config.build.assetsDir, mapFileName)
chunk.code += `//# sourceMappingURL=${sourceMapUrl}`
}
}
}

return Buffer.from(code)
return chunk
}

export async function workerFileToUrl(
ctx: Rollup.TransformPluginContext,
ctx: TransformPluginContext,
config: ResolvedConfig,
id: string,
query: Record<string, string> | null
): Promise<string> {
const workerMap = workerCache.get(config)!

let hash = workerMap.bundle.get(id)
if (hash) {
// rewrite truth id, no need to replace by asset plugin
return config.base + workerMap.emitted.get(hash)!
const workerMap = workerCache.get(config.mainConfig || config)!
let fileName = workerMap.bundle.get(id)
if (!fileName) {
const outputChunk = await bundleWorkerEntry(ctx, config, id, query)
fileName = outputChunk.fileName
saveEmitWorkerAsset(config, {
fileName,
source: outputChunk.code,
type: 'asset'
})
workerMap.bundle.set(id, fileName)
}
const code = await bundleWorkerEntry(ctx, config, id, query)
const basename = path.parse(cleanUrl(id)).name
const contentHash = getHash(code)
const fileName = path.posix.join(
config.build.assetsDir,
`${basename}.${contentHash}.js`
)
hash = emitWorkerAssets(ctx, config, {
fileName,
type: 'asset',
source: code
})
workerMap.bundle.set(id, hash)
return `__VITE_ASSET__${hash}__`
return config.base + fileName
}

export function webWorkerPlugin(config: ResolvedConfig): Plugin {
const isBuild = config.command === 'build'

const isWorker = config.isWorker
return {
name: 'vite:worker',

buildStart() {
if (isWorker) {
return
}
workerCache.set(config, {
assets: new Map(),
chunks: new Map(),
bundle: new Map(),
emitted: new Map()
bundle: new Map()
})
},

Expand Down Expand Up @@ -244,12 +204,14 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin {
let url: string
if (isBuild) {
if (query.inline != null) {
const code = await bundleWorkerEntry(this, config, id, query)
const chunk = await bundleWorkerEntry(this, config, id, query)
const { format } = config.worker
const workerOptions = format === 'es' ? '{type: "module"}' : '{}'
// inline as blob data url
return {
code: `const encodedJs = "${code.toString('base64')}";
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);
Expand Down Expand Up @@ -289,6 +251,13 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin {
if (config.isWorker && code.includes('import.meta.url')) {
return code.replace('import.meta.url', 'self.location.href')
}
if (!isWorker) {
const workerMap = workerCache.get(config)!
workerMap.assets.forEach((asset) => {
this.emitFile(asset)
workerMap.assets.delete(asset.fileName!)
})
}
}
}
}
8 changes: 5 additions & 3 deletions playground/worker/__tests__/es/es-worker.spec.ts
Expand Up @@ -52,8 +52,10 @@ test.each([[true], [false]])('shared worker', async (doTick) => {
})

test('worker emitted and import.meta.url in nested worker (serve)', async () => {
expect(await page.textContent('.nested-worker')).toMatch('/worker-nested')
expect(await page.textContent('.nested-worker-module')).toMatch('/sub-worker')
expect(await page.textContent('.nested-worker')).toMatch(
'worker-nested-worker'
)
expect(await page.textContent('.nested-worker-module')).toMatch('sub-worker')
expect(await page.textContent('.nested-worker-constructor')).toMatch(
'"type":"constructor"'
)
Expand All @@ -64,7 +66,7 @@ describe.runIf(isBuild)('build', () => {
test('inlined code generation', async () => {
const assetsDir = path.resolve(testDir, 'dist/es/assets')
const files = fs.readdirSync(assetsDir)
expect(files.length).toBe(26)
expect(files.length).toBe(25)
const index = files.find((f) => f.includes('main-module'))
const content = fs.readFileSync(path.resolve(assetsDir, index), 'utf-8')
const worker = files.find((f) => f.includes('my-worker'))
Expand Down
18 changes: 16 additions & 2 deletions playground/worker/vite.config-es.js
Expand Up @@ -7,10 +7,24 @@ module.exports = vite.defineConfig({
enforce: 'pre',
worker: {
format: 'es',
plugins: [vueJsx()]
plugins: [vueJsx()],
rollupOptions: {
output: {
assetFileNames: 'assets/worker_asset.[name].[ext]',
chunkFileNames: 'assets/worker_chunk.[name].js',
entryFileNames: 'assets/worker_entry.[name].js'
}
}
},
build: {
outDir: 'dist/es'
outDir: 'dist/es',
rollupOptions: {
output: {
assetFileNames: 'assets/[name].[ext]',
chunkFileNames: 'assets/[name].js',
entryFileNames: 'assets/[name].js'
}
}
},
plugins: [
{
Expand Down

0 comments on commit 04c2edd

Please sign in to comment.