From 04c2edd80f654a9e3c23e59d8b9060caa28e459e Mon Sep 17 00:00:00 2001 From: yoho Date: Fri, 13 May 2022 03:23:47 +0800 Subject: [PATCH] feat: worker emit fileName with config (#7804) --- packages/vite/src/node/config.ts | 9 +- packages/vite/src/node/plugins/worker.ts | 189 ++++++++---------- .../worker/__tests__/es/es-worker.spec.ts | 8 +- playground/worker/vite.config-es.js | 18 +- 4 files changed, 108 insertions(+), 116 deletions(-) diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 21fe3b72915bd1..18c12568a260ca 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -252,6 +252,8 @@ export type ResolvedConfig = Readonly< command: 'build' | 'serve' mode: string isWorker: boolean + /** @internal */ + mainConfig: ResolvedConfig | null isProduction: boolean env: Record resolve: ResolveOptions & { @@ -482,6 +484,7 @@ export async function resolveConfig( command, mode, isWorker: false, + mainConfig: null, isProduction, plugins: userPlugins, server, @@ -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, diff --git a/packages/vite/src/node/plugins/worker.ts b/packages/vite/src/node/plugins/worker.ts index 3e2bc5f1bb1088..72f492ecfb9d19 100644 --- a/packages/vite/src/node/plugins/worker.ts +++ b/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. - // - assets: Map - chunks: Map + // save worker all emit chunk avoid rollup make the same asset unique. + assets: Map + // 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 - // + // bundle: Map - // nested worker bundle context don't had file what emitted by outside bundle - // save the hash to id to rewrite truth id. - // - emitted: Map } const WorkerFileId = 'worker_file' const workerCache = new WeakMap() -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 | null -): Promise { +): Promise { // bundle the file as entry to support imports const { rollup } = await import('rollup') const { plugins, rollupOptions, format } = config.worker @@ -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' }) @@ -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 | 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 }) @@ -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 | null ): Promise { - 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() }) }, @@ -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); @@ -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!) + }) + } } } } diff --git a/playground/worker/__tests__/es/es-worker.spec.ts b/playground/worker/__tests__/es/es-worker.spec.ts index 3ca94faac2e7d0..767fd386606afc 100644 --- a/playground/worker/__tests__/es/es-worker.spec.ts +++ b/playground/worker/__tests__/es/es-worker.spec.ts @@ -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"' ) @@ -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')) diff --git a/playground/worker/vite.config-es.js b/playground/worker/vite.config-es.js index a65dece2d0db21..899ce6d0825a8b 100644 --- a/playground/worker/vite.config-es.js +++ b/playground/worker/vite.config-es.js @@ -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: [ {