From 90790d0e276c968a8156dd55c773c1846d0236e1 Mon Sep 17 00:00:00 2001 From: patak Date: Fri, 11 Nov 2022 12:06:05 +0100 Subject: [PATCH 1/5] refactor: use rollup hashing when emitting assets --- packages/vite/src/node/plugins/asset.ts | 74 ++++++---------------- packages/vite/src/node/plugins/css.ts | 8 ++- packages/vite/src/node/plugins/html.ts | 5 +- packages/vite/src/node/plugins/manifest.ts | 32 ++++++++-- 4 files changed, 49 insertions(+), 70 deletions(-) diff --git a/packages/vite/src/node/plugins/asset.ts b/packages/vite/src/node/plugins/asset.ts index 9bb5d7f7a42d84..1ca4656001a404 100644 --- a/packages/vite/src/node/plugins/asset.ts +++ b/packages/vite/src/node/plugins/asset.ts @@ -5,7 +5,6 @@ import { Buffer } from 'node:buffer' import * as mrmime from 'mrmime' import type { NormalizedOutputOptions, - OutputAsset, OutputOptions, PluginContext, PreRenderedAsset, @@ -22,24 +21,19 @@ import type { ResolvedConfig } from '../config' import { cleanUrl, getHash, joinUrlSegments, normalizePath } from '../utils' import { FS_PREFIX } from '../constants' -export const assetUrlRE = /__VITE_ASSET__([a-z\d]{8})__(?:\$_(.*?)__)?/g - -export const duplicateAssets = new WeakMap< - ResolvedConfig, - Map ->() +export const assetUrlRE = /__VITE_ASSET__([a-z\d]+)__(?:\$_(.*?)__)?/g const rawRE = /(\?|&)raw(?:&|$)/ const urlRE = /(\?|&)url(?:&|$)/ const assetCache = new WeakMap>() -const assetHashToFilenameMap = new WeakMap< +// chunk.name is the basename for the asset ignoring the directory structure +// For the manifest, we need to preserve the original file path +export const generatedAssets = new WeakMap< ResolvedConfig, - Map + Map >() -// save hashes of the files that has been emitted in build watch -const emittedHashMap = new WeakMap>() // add own dictionary entry by directly assigning mrmime export function registerCustomMime(): void { @@ -77,10 +71,10 @@ export function renderAssetUrlInJS( while ((match = assetUrlRE.exec(code))) { s ||= new MagicString(code) - const [full, hash, postfix = ''] = match + const [full, referenceId, postfix = ''] = match // some internal plugins may still need to emit chunks (e.g. worker) so // fallback to this.getFileName for that. TODO: remove, not needed - const file = getAssetFilename(hash, config) || ctx.getFileName(hash) + const file = ctx.getFileName(referenceId) // getAssetFilename(hash, config) || ctx.getFileName(hash) chunk.viteMetadata.importedAssets.add(cleanUrl(file)) const filename = file + postfix const replacement = toOutputFilePathInJS( @@ -127,9 +121,6 @@ export function renderAssetUrlInJS( * Also supports loading plain strings with import text from './foo.txt?raw' */ export function assetPlugin(config: ResolvedConfig): Plugin { - // assetHashToFilenameMap initialization in buildStart causes getAssetFilename to return undefined - assetHashToFilenameMap.set(config, new Map()) - registerCustomMime() return { @@ -137,8 +128,7 @@ export function assetPlugin(config: ResolvedConfig): Plugin { buildStart() { assetCache.set(config, new Map()) - emittedHashMap.set(config, new Set()) - duplicateAssets.set(config, new Map()) + generatedAssets.set(config, new Map()) }, resolveId(id) { @@ -253,13 +243,6 @@ function fileToDevUrl(id: string, config: ResolvedConfig) { return joinUrlSegments(base, rtn.replace(/^\//, '')) } -export function getAssetFilename( - hash: string, - config: ResolvedConfig -): string | undefined { - return assetHashToFilenameMap.get(config)?.get(hash) -} - export function getPublicAssetFilename( hash: string, config: ResolvedConfig @@ -464,41 +447,20 @@ async function fileToBuiltUrl( // into the chunk's hash, so we have to do our own content hashing here. // https://bundlers.tooling.report/hashing/asset-cascade/ // https://github.com/rollup/rollup/issues/3415 - const map = assetHashToFilenameMap.get(config)! - const contentHash = getHash(content) const { search, hash } = parseUrl(id) const postfix = (search || '') + (hash || '') - const fileName = assetFileNamesToFileName( - resolveAssetFileNames(config), - file, - contentHash, - content - ) - if (!map.has(contentHash)) { - map.set(contentHash, fileName) - } - const emittedSet = emittedHashMap.get(config)! - const duplicates = duplicateAssets.get(config)! - const name = normalizePath(path.relative(config.root, file)) - if (!emittedSet.has(contentHash)) { - pluginContext.emitFile({ - name, - fileName, - type: 'asset', - source: content - }) - emittedSet.add(contentHash) - } else { - duplicates.set(name, { - name, - fileName: map.get(contentHash)!, - type: 'asset', - source: content - }) - } + const referenceId = pluginContext.emitFile({ + // Ignore directory structure for asset file names + name: path.basename(file), + type: 'asset', + source: content + }) + + const originalName = normalizePath(path.relative(config.root, file)) + generatedAssets.get(config)!.set(referenceId, { originalName }) - url = `__VITE_ASSET__${contentHash}__${postfix ? `$_${postfix}__` : ``}` // TODO_BASE + url = `__VITE_ASSET__${referenceId}__${postfix ? `$_${postfix}__` : ``}` // TODO_BASE } cache.set(id, url) diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts index c14269ccf99734..b52f568f6c760f 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -55,7 +55,6 @@ import { assetUrlRE, checkPublicFile, fileToUrl, - getAssetFilename, publicAssetUrlCache, publicAssetUrlRE, publicFileToBuiltUrl, @@ -478,7 +477,10 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { const publicAssetUrlMap = publicAssetUrlCache.get(config)! // resolve asset URL placeholders to their built file URLs - function resolveAssetUrlsInCss(chunkCSS: string, cssAssetName: string) { + const resolveAssetUrlsInCss = ( + chunkCSS: string, + cssAssetName: string + ) => { const encodedPublicUrls = encodePublicUrlsInCSS(config) const relative = config.base === './' || config.base === '' @@ -497,7 +499,7 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { // replace asset url references with resolved url. chunkCSS = chunkCSS.replace(assetUrlRE, (_, fileHash, postfix = '') => { - const filename = getAssetFilename(fileHash, config) + postfix + const filename = this.getFileName(fileHash) + postfix chunk.viteMetadata.importedAssets.add(cleanUrl(filename)) return toOutputFilePathInCss( filename, diff --git a/packages/vite/src/node/plugins/html.ts b/packages/vite/src/node/plugins/html.ts index c13a334ba7b7da..5a9ac5b7b805bb 100644 --- a/packages/vite/src/node/plugins/html.ts +++ b/packages/vite/src/node/plugins/html.ts @@ -26,7 +26,6 @@ import { toOutputFilePathInHtml } from '../build' import { assetUrlRE, checkPublicFile, - getAssetFilename, getPublicAssetFilename, publicAssetUrlRE, urlToBuiltUrl @@ -794,9 +793,7 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin { }) // resolve asset url references result = result.replace(assetUrlRE, (_, fileHash, postfix = '') => { - return ( - toOutputAssetFilePath(getAssetFilename(fileHash, config)!) + postfix - ) + return toOutputAssetFilePath(this.getFileName(fileHash)) + postfix }) result = result.replace(publicAssetUrlRE, (_, fileHash) => { diff --git a/packages/vite/src/node/plugins/manifest.ts b/packages/vite/src/node/plugins/manifest.ts index a584daadc940e0..45b9f703ece106 100644 --- a/packages/vite/src/node/plugins/manifest.ts +++ b/packages/vite/src/node/plugins/manifest.ts @@ -4,7 +4,7 @@ import type { ResolvedConfig } from '..' import type { Plugin } from '../plugin' import { normalizePath } from '../utils' import { cssEntryFilesCache } from './css' -import { duplicateAssets } from './asset' +import { generatedAssets } from './asset' export type Manifest = Record @@ -101,10 +101,10 @@ export function manifestPlugin(config: ResolvedConfig): Plugin { return manifestChunk } - function createAsset(chunk: OutputAsset): ManifestChunk { + function createAsset(chunk: OutputAsset, src: string): ManifestChunk { const manifestChunk: ManifestChunk = { file: chunk.fileName, - src: chunk.name + src } if (cssEntryFiles.has(chunk.name!)) manifestChunk.isEntry = true @@ -114,18 +114,36 @@ export function manifestPlugin(config: ResolvedConfig): Plugin { const cssEntryFiles = cssEntryFilesCache.get(config)! + const fileNameToAssetMeta = new Map() + generatedAssets.get(config)!.forEach((asset, referenceId) => { + const fileName = this.getFileName(referenceId) + fileNameToAssetMeta.set(fileName, asset) + }) + + const fileNameToAsset = new Map() + for (const file in bundle) { const chunk = bundle[file] if (chunk.type === 'chunk') { manifest[getChunkName(chunk)] = createChunk(chunk) } else if (chunk.type === 'asset' && typeof chunk.name === 'string') { - manifest[chunk.name] = createAsset(chunk) + // Add every unique asset to the manifest, keyed by its original name + const src = + fileNameToAssetMeta.get(chunk.fileName)?.originalName ?? chunk.name + const asset = createAsset(chunk, src) + manifest[src] = asset + fileNameToAsset.set(chunk.fileName, asset) } } - duplicateAssets.get(config)!.forEach((asset) => { - const chunk = createAsset(asset) - manifest[asset.name!] = chunk + // Add duplicate assets to the manifest + fileNameToAssetMeta.forEach(({ originalName }, fileName) => { + if (!manifest[originalName]) { + const asset = fileNameToAsset.get(fileName) + if (asset) { + manifest[originalName] = asset + } + } }) outputCount++ From 6e20afa332f7ccd3192b53a0ba8885fae6109499 Mon Sep 17 00:00:00 2001 From: patak Date: Fri, 11 Nov 2022 12:06:35 +0100 Subject: [PATCH 2/5] test: fix faulty worker asset tests --- playground/worker/__tests__/es/es-worker.spec.ts | 2 +- playground/worker/__tests__/iife/iife-worker.spec.ts | 2 +- .../worker/__tests__/relative-base/relative-base-worker.spec.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/playground/worker/__tests__/es/es-worker.spec.ts b/playground/worker/__tests__/es/es-worker.spec.ts index b3c49375cc1e47..66e11f5949dae0 100644 --- a/playground/worker/__tests__/es/es-worker.spec.ts +++ b/playground/worker/__tests__/es/es-worker.spec.ts @@ -17,7 +17,7 @@ test('normal', async () => { ) await untilUpdated( () => page.textContent('.asset-url'), - isBuild ? '/es/assets/vite.svg' : '/es/vite.svg', + isBuild ? '/es/assets/worker_asset.vite.svg' : '/es/vite.svg', true ) }) diff --git a/playground/worker/__tests__/iife/iife-worker.spec.ts b/playground/worker/__tests__/iife/iife-worker.spec.ts index d6a4e784de8380..fec23c2d34da8b 100644 --- a/playground/worker/__tests__/iife/iife-worker.spec.ts +++ b/playground/worker/__tests__/iife/iife-worker.spec.ts @@ -12,7 +12,7 @@ test('normal', async () => { ) await untilUpdated( () => page.textContent('.asset-url'), - isBuild ? '/iife/assets/vite.svg' : '/iife/vite.svg', + isBuild ? '/iife/assets/worker_asset.vite.svg' : '/iife/vite.svg', true ) }) diff --git a/playground/worker/__tests__/relative-base/relative-base-worker.spec.ts b/playground/worker/__tests__/relative-base/relative-base-worker.spec.ts index 431b0bb54f96f8..dd7837414d4bf4 100644 --- a/playground/worker/__tests__/relative-base/relative-base-worker.spec.ts +++ b/playground/worker/__tests__/relative-base/relative-base-worker.spec.ts @@ -17,7 +17,7 @@ test('normal', async () => { ) await untilUpdated( () => page.textContent('.asset-url'), - isBuild ? '/other-assets/vite' : '/vite.svg', + isBuild ? '/worker-assets/worker_asset.vite' : '/vite.svg', true ) }) From ce1118c907dfeaa6f7ac6a9f2abbedf3faef5b8c Mon Sep 17 00:00:00 2001 From: patak Date: Fri, 11 Nov 2022 13:09:46 +0100 Subject: [PATCH 3/5] chore: remove outdated comment --- packages/vite/src/node/plugins/asset.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/vite/src/node/plugins/asset.ts b/packages/vite/src/node/plugins/asset.ts index 1ca4656001a404..876843b6e3c762 100644 --- a/packages/vite/src/node/plugins/asset.ts +++ b/packages/vite/src/node/plugins/asset.ts @@ -441,12 +441,6 @@ async function fileToBuiltUrl( url = `data:${mimeType};base64,${content.toString('base64')}` } else { // emit as asset - // rollup supports `import.meta.ROLLUP_FILE_URL_*`, but it generates code - // that uses runtime url sniffing and it can be verbose when targeting - // non-module format. It also fails to cascade the asset content change - // into the chunk's hash, so we have to do our own content hashing here. - // https://bundlers.tooling.report/hashing/asset-cascade/ - // https://github.com/rollup/rollup/issues/3415 const { search, hash } = parseUrl(id) const postfix = (search || '') + (hash || '') From 3551d276a2dc4a673b97bda70207326db8b69439 Mon Sep 17 00:00:00 2001 From: patak Date: Fri, 11 Nov 2022 19:53:45 +0100 Subject: [PATCH 4/5] chore: remove comment --- packages/vite/src/node/plugins/asset.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite/src/node/plugins/asset.ts b/packages/vite/src/node/plugins/asset.ts index 876843b6e3c762..01d008efb32386 100644 --- a/packages/vite/src/node/plugins/asset.ts +++ b/packages/vite/src/node/plugins/asset.ts @@ -74,7 +74,7 @@ export function renderAssetUrlInJS( const [full, referenceId, postfix = ''] = match // some internal plugins may still need to emit chunks (e.g. worker) so // fallback to this.getFileName for that. TODO: remove, not needed - const file = ctx.getFileName(referenceId) // getAssetFilename(hash, config) || ctx.getFileName(hash) + const file = ctx.getFileName(referenceId) chunk.viteMetadata.importedAssets.add(cleanUrl(file)) const filename = file + postfix const replacement = toOutputFilePathInJS( From 0a4a2bd8af7025e193babdbf97572cf6a8615436 Mon Sep 17 00:00:00 2001 From: patak Date: Sun, 13 Nov 2022 09:20:41 +0100 Subject: [PATCH 5/5] chore: remove outdated comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 翠 / green --- packages/vite/src/node/plugins/asset.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/vite/src/node/plugins/asset.ts b/packages/vite/src/node/plugins/asset.ts index 01d008efb32386..a5789d181ee5ce 100644 --- a/packages/vite/src/node/plugins/asset.ts +++ b/packages/vite/src/node/plugins/asset.ts @@ -72,8 +72,6 @@ export function renderAssetUrlInJS( while ((match = assetUrlRE.exec(code))) { s ||= new MagicString(code) const [full, referenceId, postfix = ''] = match - // some internal plugins may still need to emit chunks (e.g. worker) so - // fallback to this.getFileName for that. TODO: remove, not needed const file = ctx.getFileName(referenceId) chunk.viteMetadata.importedAssets.add(cleanUrl(file)) const filename = file + postfix