Skip to content

Commit 78c77be

Browse files
authoredNov 13, 2022
refactor: use rollup hashing when emitting assets (#10878)
1 parent feb9b10 commit 78c77be

File tree

7 files changed

+52
-81
lines changed

7 files changed

+52
-81
lines changed
 

‎packages/vite/src/node/plugins/asset.ts

+18-64
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { Buffer } from 'node:buffer'
55
import * as mrmime from 'mrmime'
66
import type {
77
NormalizedOutputOptions,
8-
OutputAsset,
98
OutputOptions,
109
PluginContext,
1110
PreRenderedAsset,
@@ -22,24 +21,19 @@ import type { ResolvedConfig } from '../config'
2221
import { cleanUrl, getHash, joinUrlSegments, normalizePath } from '../utils'
2322
import { FS_PREFIX } from '../constants'
2423

25-
export const assetUrlRE = /__VITE_ASSET__([a-z\d]{8})__(?:\$_(.*?)__)?/g
26-
27-
export const duplicateAssets = new WeakMap<
28-
ResolvedConfig,
29-
Map<string, OutputAsset>
30-
>()
24+
export const assetUrlRE = /__VITE_ASSET__([a-z\d]+)__(?:\$_(.*?)__)?/g
3125

3226
const rawRE = /(\?|&)raw(?:&|$)/
3327
const urlRE = /(\?|&)url(?:&|$)/
3428

3529
const assetCache = new WeakMap<ResolvedConfig, Map<string, string>>()
3630

37-
const assetHashToFilenameMap = new WeakMap<
31+
// chunk.name is the basename for the asset ignoring the directory structure
32+
// For the manifest, we need to preserve the original file path
33+
export const generatedAssets = new WeakMap<
3834
ResolvedConfig,
39-
Map<string, string>
35+
Map<string, { originalName: string }>
4036
>()
41-
// save hashes of the files that has been emitted in build watch
42-
const emittedHashMap = new WeakMap<ResolvedConfig, Set<string>>()
4337

4438
// add own dictionary entry by directly assigning mrmime
4539
export function registerCustomMime(): void {
@@ -77,10 +71,8 @@ export function renderAssetUrlInJS(
7771

7872
while ((match = assetUrlRE.exec(code))) {
7973
s ||= new MagicString(code)
80-
const [full, hash, postfix = ''] = match
81-
// some internal plugins may still need to emit chunks (e.g. worker) so
82-
// fallback to this.getFileName for that. TODO: remove, not needed
83-
const file = getAssetFilename(hash, config) || ctx.getFileName(hash)
74+
const [full, referenceId, postfix = ''] = match
75+
const file = ctx.getFileName(referenceId)
8476
chunk.viteMetadata.importedAssets.add(cleanUrl(file))
8577
const filename = file + postfix
8678
const replacement = toOutputFilePathInJS(
@@ -127,18 +119,14 @@ export function renderAssetUrlInJS(
127119
* Also supports loading plain strings with import text from './foo.txt?raw'
128120
*/
129121
export function assetPlugin(config: ResolvedConfig): Plugin {
130-
// assetHashToFilenameMap initialization in buildStart causes getAssetFilename to return undefined
131-
assetHashToFilenameMap.set(config, new Map())
132-
133122
registerCustomMime()
134123

135124
return {
136125
name: 'vite:asset',
137126

138127
buildStart() {
139128
assetCache.set(config, new Map())
140-
emittedHashMap.set(config, new Set())
141-
duplicateAssets.set(config, new Map())
129+
generatedAssets.set(config, new Map())
142130
},
143131

144132
resolveId(id) {
@@ -253,13 +241,6 @@ function fileToDevUrl(id: string, config: ResolvedConfig) {
253241
return joinUrlSegments(base, rtn.replace(/^\//, ''))
254242
}
255243

256-
export function getAssetFilename(
257-
hash: string,
258-
config: ResolvedConfig
259-
): string | undefined {
260-
return assetHashToFilenameMap.get(config)?.get(hash)
261-
}
262-
263244
export function getPublicAssetFilename(
264245
hash: string,
265246
config: ResolvedConfig
@@ -458,47 +439,20 @@ async function fileToBuiltUrl(
458439
url = `data:${mimeType};base64,${content.toString('base64')}`
459440
} else {
460441
// emit as asset
461-
// rollup supports `import.meta.ROLLUP_FILE_URL_*`, but it generates code
462-
// that uses runtime url sniffing and it can be verbose when targeting
463-
// non-module format. It also fails to cascade the asset content change
464-
// into the chunk's hash, so we have to do our own content hashing here.
465-
// https://bundlers.tooling.report/hashing/asset-cascade/
466-
// https://github.com/rollup/rollup/issues/3415
467-
const map = assetHashToFilenameMap.get(config)!
468-
const contentHash = getHash(content)
469442
const { search, hash } = parseUrl(id)
470443
const postfix = (search || '') + (hash || '')
471444

472-
const fileName = assetFileNamesToFileName(
473-
resolveAssetFileNames(config),
474-
file,
475-
contentHash,
476-
content
477-
)
478-
if (!map.has(contentHash)) {
479-
map.set(contentHash, fileName)
480-
}
481-
const emittedSet = emittedHashMap.get(config)!
482-
const duplicates = duplicateAssets.get(config)!
483-
const name = normalizePath(path.relative(config.root, file))
484-
if (!emittedSet.has(contentHash)) {
485-
pluginContext.emitFile({
486-
name,
487-
fileName,
488-
type: 'asset',
489-
source: content
490-
})
491-
emittedSet.add(contentHash)
492-
} else {
493-
duplicates.set(name, {
494-
name,
495-
fileName: map.get(contentHash)!,
496-
type: 'asset',
497-
source: content
498-
})
499-
}
445+
const referenceId = pluginContext.emitFile({
446+
// Ignore directory structure for asset file names
447+
name: path.basename(file),
Has a conversation. Original line has a conversation.
448+
type: 'asset',
449+
source: content
450+
})
451+
452+
const originalName = normalizePath(path.relative(config.root, file))
453+
generatedAssets.get(config)!.set(referenceId, { originalName })
500454

501-
url = `__VITE_ASSET__${contentHash}__${postfix ? `$_${postfix}__` : ``}` // TODO_BASE
455+
url = `__VITE_ASSET__${referenceId}__${postfix ? `$_${postfix}__` : ``}` // TODO_BASE
502456
}
503457

504458
cache.set(id, url)

‎packages/vite/src/node/plugins/css.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ import {
5656
assetUrlRE,
5757
checkPublicFile,
5858
fileToUrl,
59-
getAssetFilename,
6059
publicAssetUrlCache,
6160
publicAssetUrlRE,
6261
publicFileToBuiltUrl,
@@ -475,7 +474,10 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
475474
const publicAssetUrlMap = publicAssetUrlCache.get(config)!
476475

477476
// resolve asset URL placeholders to their built file URLs
478-
function resolveAssetUrlsInCss(chunkCSS: string, cssAssetName: string) {
477+
const resolveAssetUrlsInCss = (
478+
chunkCSS: string,
479+
cssAssetName: string
480+
) => {
479481
const encodedPublicUrls = encodePublicUrlsInCSS(config)
480482

481483
const relative = config.base === './' || config.base === ''
@@ -494,7 +496,7 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
494496

495497
// replace asset url references with resolved url.
496498
chunkCSS = chunkCSS.replace(assetUrlRE, (_, fileHash, postfix = '') => {
497-
const filename = getAssetFilename(fileHash, config) + postfix
499+
const filename = this.getFileName(fileHash) + postfix
498500
chunk.viteMetadata.importedAssets.add(cleanUrl(filename))
499501
return toOutputFilePathInCss(
500502
filename,

‎packages/vite/src/node/plugins/html.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import { toOutputFilePathInHtml } from '../build'
2626
import {
2727
assetUrlRE,
2828
checkPublicFile,
29-
getAssetFilename,
3029
getPublicAssetFilename,
3130
publicAssetUrlRE,
3231
urlToBuiltUrl
@@ -794,9 +793,7 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
794793
})
795794
// resolve asset url references
796795
result = result.replace(assetUrlRE, (_, fileHash, postfix = '') => {
797-
return (
798-
toOutputAssetFilePath(getAssetFilename(fileHash, config)!) + postfix
799-
)
796+
return toOutputAssetFilePath(this.getFileName(fileHash)) + postfix
800797
})
801798

802799
result = result.replace(publicAssetUrlRE, (_, fileHash) => {

‎packages/vite/src/node/plugins/manifest.ts

+25-7
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { ResolvedConfig } from '..'
44
import type { Plugin } from '../plugin'
55
import { normalizePath } from '../utils'
66
import { cssEntryFilesCache } from './css'
7-
import { duplicateAssets } from './asset'
7+
import { generatedAssets } from './asset'
88

99
export type Manifest = Record<string, ManifestChunk>
1010

@@ -101,10 +101,10 @@ export function manifestPlugin(config: ResolvedConfig): Plugin {
101101
return manifestChunk
102102
}
103103

104-
function createAsset(chunk: OutputAsset): ManifestChunk {
104+
function createAsset(chunk: OutputAsset, src: string): ManifestChunk {
105105
const manifestChunk: ManifestChunk = {
106106
file: chunk.fileName,
107-
src: chunk.name
107+
src
108108
}
109109

110110
if (cssEntryFiles.has(chunk.name!)) manifestChunk.isEntry = true
@@ -114,18 +114,36 @@ export function manifestPlugin(config: ResolvedConfig): Plugin {
114114

115115
const cssEntryFiles = cssEntryFilesCache.get(config)!
116116

117+
const fileNameToAssetMeta = new Map<string, { originalName: string }>()
118+
generatedAssets.get(config)!.forEach((asset, referenceId) => {
119+
const fileName = this.getFileName(referenceId)
120+
fileNameToAssetMeta.set(fileName, asset)
121+
})
122+
123+
const fileNameToAsset = new Map<string, ManifestChunk>()
124+
117125
for (const file in bundle) {
118126
const chunk = bundle[file]
119127
if (chunk.type === 'chunk') {
120128
manifest[getChunkName(chunk)] = createChunk(chunk)
121129
} else if (chunk.type === 'asset' && typeof chunk.name === 'string') {
122-
manifest[chunk.name] = createAsset(chunk)
130+
// Add every unique asset to the manifest, keyed by its original name
131+
const src =
132+
fileNameToAssetMeta.get(chunk.fileName)?.originalName ?? chunk.name
133+
const asset = createAsset(chunk, src)
134+
manifest[src] = asset
135+
fileNameToAsset.set(chunk.fileName, asset)
123136
}
124137
}
125138

126-
duplicateAssets.get(config)!.forEach((asset) => {
127-
const chunk = createAsset(asset)
128-
manifest[asset.name!] = chunk
139+
// Add duplicate assets to the manifest
140+
fileNameToAssetMeta.forEach(({ originalName }, fileName) => {
141+
if (!manifest[originalName]) {
142+
const asset = fileNameToAsset.get(fileName)
143+
if (asset) {
144+
manifest[originalName] = asset
145+
}
146+
}
129147
})
130148

131149
outputCount++

‎playground/worker/__tests__/es/es-worker.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ test('normal', async () => {
1717
)
1818
await untilUpdated(
1919
() => page.textContent('.asset-url'),
20-
isBuild ? '/es/assets/vite.svg' : '/es/vite.svg',
20+
isBuild ? '/es/assets/worker_asset.vite.svg' : '/es/vite.svg',
2121
true
2222
)
2323
})

‎playground/worker/__tests__/iife/iife-worker.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ test('normal', async () => {
1212
)
1313
await untilUpdated(
1414
() => page.textContent('.asset-url'),
15-
isBuild ? '/iife/assets/vite.svg' : '/iife/vite.svg',
15+
isBuild ? '/iife/assets/worker_asset.vite.svg' : '/iife/vite.svg',
1616
true
1717
)
1818
})

‎playground/worker/__tests__/relative-base/relative-base-worker.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ test('normal', async () => {
1717
)
1818
await untilUpdated(
1919
() => page.textContent('.asset-url'),
20-
isBuild ? '/other-assets/vite' : '/vite.svg',
20+
isBuild ? '/worker-assets/worker_asset.vite' : '/vite.svg',
2121
true
2222
)
2323
})

0 commit comments

Comments
 (0)
Please sign in to comment.