@@ -8,6 +8,7 @@ import { cleanUrl } from '../utils'
8
8
import { FS_PREFIX } from '../constants'
9
9
import { PluginContext , RenderedChunk } from 'rollup'
10
10
import MagicString from 'magic-string'
11
+ import { createHash } from 'crypto'
11
12
12
13
export const assetUrlRE = / _ _ V I T E _ A S S E T _ _ ( [ a - z \d ] { 8 } ) _ _ (?: ( .* ?) _ _ ) ? / g
13
14
@@ -63,8 +64,10 @@ export function assetPlugin(config: ResolvedConfig): Plugin {
63
64
let s
64
65
while ( ( match = assetUrlQuotedRE . exec ( code ) ) ) {
65
66
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 )
68
71
registerAssetToChunk ( chunk , file )
69
72
const outputFilepath = config . base + file + postfix
70
73
s . overwrite (
@@ -155,6 +158,15 @@ export function fileToDevUrl(id: string, config: ResolvedConfig) {
155
158
156
159
const assetCache = new WeakMap < ResolvedConfig , Map < string , string > > ( )
157
160
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
+
158
170
/**
159
171
* Register an asset to be emitted as part of the bundle (if necessary)
160
172
* and returns the resolved public URL
@@ -196,20 +208,43 @@ async function fileToBuiltUrl(
196
208
// emit as asset
197
209
// rollup supports `import.meta.ROLLUP_FILE_URL_*`, but it generates code
198
210
// 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 } __` : `` } `
207
238
}
208
239
209
240
cache . set ( id , url )
210
241
return url
211
242
}
212
243
244
+ function getAssetHash ( content : Buffer ) {
245
+ return createHash ( 'sha256' ) . update ( content ) . digest ( 'hex' ) . slice ( 0 , 8 )
246
+ }
247
+
213
248
export async function urlToBuiltUrl (
214
249
url : string ,
215
250
importer : string ,
0 commit comments