diff --git a/packages/vite/src/node/plugins/asset.ts b/packages/vite/src/node/plugins/asset.ts index 0d67af4fa0e05b..aa7832b14aa8e7 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,8 @@ export function renderAssetUrlInJS( while ((match = assetUrlRE.exec(code))) { s ||= new MagicString(code) - const [full, hash, 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 [full, referenceId, postfix = ''] = match + const file = ctx.getFileName(referenceId) chunk.viteMetadata.importedAssets.add(cleanUrl(file)) const filename = file + postfix const replacement = toOutputFilePathInJS( @@ -127,9 +119,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 +126,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 +241,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 @@ -458,47 +439,20 @@ 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 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 7b44559009742a..fb23eb0fd6953e 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -56,7 +56,6 @@ import { assetUrlRE, checkPublicFile, fileToUrl, - getAssetFilename, publicAssetUrlCache, publicAssetUrlRE, publicFileToBuiltUrl, @@ -475,7 +474,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 === '' @@ -494,7 +496,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++ 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 ) })