Skip to content

Commit f8e4eeb

Browse files
committed
fix: properly cascade asset hash change
workaround rollup/rollup#3415
1 parent ae50e38 commit f8e4eeb

File tree

3 files changed

+56
-15
lines changed

3 files changed

+56
-15
lines changed

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

+45-10
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { cleanUrl } from '../utils'
88
import { FS_PREFIX } from '../constants'
99
import { PluginContext, RenderedChunk } from 'rollup'
1010
import MagicString from 'magic-string'
11+
import { createHash } from 'crypto'
1112

1213
export const assetUrlRE = /__VITE_ASSET__([a-z\d]{8})__(?:(.*?)__)?/g
1314

@@ -63,8 +64,10 @@ export function assetPlugin(config: ResolvedConfig): Plugin {
6364
let s
6465
while ((match = assetUrlQuotedRE.exec(code))) {
6566
s = s || (s = new MagicString(code))
66-
const [full, fileHandle, postfix = ''] = match
67-
const file = this.getFileName(fileHandle)
67+
const [full, hash, postfix = ''] = match
68+
// some internal plugins may still need to emit chunks (e.g. worker) so
69+
// fallback to this.getFileName for that.
70+
const file = getAssetFilename(hash, config) || this.getFileName(hash)
6871
registerAssetToChunk(chunk, file)
6972
const outputFilepath = config.base + file + postfix
7073
s.overwrite(
@@ -155,6 +158,15 @@ export function fileToDevUrl(id: string, config: ResolvedConfig) {
155158

156159
const assetCache = new WeakMap<ResolvedConfig, Map<string, string>>()
157160

161+
const assetHashToFilenameMap = new WeakMap<
162+
ResolvedConfig,
163+
Map<string, string>
164+
>()
165+
166+
export function getAssetFilename(hash: string, config: ResolvedConfig) {
167+
return assetHashToFilenameMap.get(config)?.get(hash)
168+
}
169+
158170
/**
159171
* Register an asset to be emitted as part of the bundle (if necessary)
160172
* and returns the resolved public URL
@@ -196,20 +208,43 @@ async function fileToBuiltUrl(
196208
// emit as asset
197209
// rollup supports `import.meta.ROLLUP_FILE_URL_*`, but it generates code
198210
// that uses runtime url sniffing and it can be verbose when targeting
199-
// non-module format. For consistency, generate a marker here and replace
200-
// with resolved url strings in renderChunk.
201-
const fileId = pluginContext.emitFile({
202-
name: path.basename(file),
203-
type: 'asset',
204-
source: content
205-
})
206-
url = `__VITE_ASSET__${fileId}__${postfix ? `${postfix}__` : ``}`
211+
// non-module format. It also fails to cascade the asset content change
212+
// into the chunk's hash, so we have to do our own content hashing here.
213+
// https://bundlers.tooling.report/hashing/asset-cascade/
214+
// https://github.com/rollup/rollup/issues/3415
215+
let map = assetHashToFilenameMap.get(config)
216+
if (!map) {
217+
map = new Map()
218+
assetHashToFilenameMap.set(config, map)
219+
}
220+
221+
const contentHash = getAssetHash(content)
222+
if (!map.has(contentHash)) {
223+
const basename = path.basename(file)
224+
const ext = path.extname(basename)
225+
const fileName = path.posix.join(
226+
config.build.assetsDir,
227+
`${basename.slice(0, -ext.length)}.${contentHash}${ext}`
228+
)
229+
map.set(contentHash, fileName)
230+
pluginContext.emitFile({
231+
fileName,
232+
type: 'asset',
233+
source: content
234+
})
235+
}
236+
237+
url = `__VITE_ASSET__${contentHash}__${postfix ? `${postfix}__` : ``}`
207238
}
208239

209240
cache.set(id, url)
210241
return url
211242
}
212243

244+
function getAssetHash(content: Buffer) {
245+
return createHash('sha256').update(content).digest('hex').slice(0, 8)
246+
}
247+
213248
export async function urlToBuiltUrl(
214249
url: string,
215250
importer: string,

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
} from 'postcss'
3232
import { ResolveFn, ViteDevServer } from '../'
3333
import {
34+
getAssetFilename,
3435
assetUrlRE,
3536
fileToDevUrl,
3637
registerAssetToChunk,
@@ -273,8 +274,8 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
273274
) => {
274275
// replace asset url references with resolved url.
275276
const isRelativeBase = config.base === '' || config.base.startsWith('.')
276-
css = css.replace(assetUrlRE, (_, fileId, postfix = '') => {
277-
const filename = this.getFileName(fileId) + postfix
277+
css = css.replace(assetUrlRE, (_, fileHash, postfix = '') => {
278+
const filename = getAssetFilename(fileHash, config) + postfix
278279
registerAssetToChunk(chunk, filename)
279280
if (!isRelativeBase || inlined) {
280281
// absoulte base or relative base but inlined (injected as style tag into

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

+8-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ import { cleanUrl, isExternalUrl, isDataUrl, generateCodeFrame } from '../utils'
77
import { ResolvedConfig } from '../config'
88
import slash from 'slash'
99
import MagicString from 'magic-string'
10-
import { checkPublicFile, assetUrlRE, urlToBuiltUrl } from './asset'
10+
import {
11+
checkPublicFile,
12+
assetUrlRE,
13+
urlToBuiltUrl,
14+
getAssetFilename
15+
} from './asset'
1116
import { isCSSRequest, chunkToEmittedCssFileMap } from './css'
1217
import { polyfillId } from './dynamicImportPolyfill'
1318
import {
@@ -280,8 +285,8 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
280285

281286
for (const [id, html] of processedHtml) {
282287
// resolve asset url references
283-
let result = html.replace(assetUrlRE, (_, fileId, postfix = '') => {
284-
return config.base + this.getFileName(fileId) + postfix
288+
let result = html.replace(assetUrlRE, (_, fileHash, postfix = '') => {
289+
return config.base + getAssetFilename(fileHash, config) + postfix
285290
})
286291

287292
// find corresponding entry chunk

0 commit comments

Comments
 (0)