@@ -5,7 +5,7 @@ import type {
5
5
ImportSpecifier ,
6
6
} from 'es-module-lexer'
7
7
import { init , parse as parseImports } from 'es-module-lexer'
8
- import type { SourceMap } from 'rollup'
8
+ import type { OutputChunk , SourceMap } from 'rollup'
9
9
import type { RawSourceMap } from '@ampproject/remapping'
10
10
import convertSourceMap from 'convert-source-map'
11
11
import {
@@ -22,18 +22,18 @@ import type { Environment } from '../environment'
22
22
import { removedPureCssFilesCache } from './css'
23
23
import { createParseErrorInfo } from './importAnalysis'
24
24
25
- type FileDep = {
26
- url : string
27
- runtime : boolean
28
- }
25
+ const symbolString = ( name : string ) =>
26
+ `__viteSymbol_${ name } _${ Math . random ( ) . toString ( 36 ) . slice ( 2 ) } __`
29
27
30
28
type VitePreloadErrorEvent = Event & { payload : Error }
31
29
32
30
// Placeholder symbols for injecting helpers
33
31
export const isEsmFlag = `__VITE_IS_MODERN__`
32
+ const isEsmFlagPattern = new RegExp ( '\\b' + isEsmFlag + '\\b' , 'g' )
33
+
34
34
export const preloadMethod = `__vitePreload`
35
35
const preloadMarker = `__VITE_PRELOAD__`
36
- const viteMapDeps = '__vite__mapDeps'
36
+ const chunkRegistryPlaceholder = symbolString ( 'chunkRegistryPlaceholder' )
37
37
38
38
export const preloadHelperId = '\0vite/preload-helper.js'
39
39
const preloadMarkerRE = new RegExp ( '\\b' + preloadMarker + '\\b' , 'g' )
@@ -93,6 +93,9 @@ function preload(
93
93
94
94
promise = Promise . allSettled (
95
95
deps . map ( ( dep ) => {
96
+ // @ts -expect-error chunkRegistry is declared before preload.toString()
97
+ dep = chunkRegistry [ dep ]
98
+
96
99
// @ts -expect-error assetsURL is declared before preload.toString()
97
100
dep = assetsURL ( dep , importerUrl )
98
101
if ( dep in seen ) return
@@ -211,6 +214,7 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
211
214
// is appended inside __vitePreload too.
212
215
`(dep) => ${ JSON . stringify ( config . base ) } +dep`
213
216
const code = [
217
+ `const chunkRegistry = ${ chunkRegistryPlaceholder } ` ,
214
218
`const scriptRel = ${ scriptRel } ` ,
215
219
`const assetsURL = ${ assetsURL } ` ,
216
220
`const seen = {}` ,
@@ -381,25 +385,31 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
381
385
} ,
382
386
383
387
renderChunk ( code , _ , { format } ) {
388
+ const s = new MagicString ( code )
389
+
384
390
// make sure we only perform the preload logic in modern builds.
385
- if ( ! code . includes ( isEsmFlag ) ) {
386
- return
391
+ if ( code . includes ( isEsmFlag ) ) {
392
+ const isEsm = String ( format === 'es' )
393
+ let match : RegExpExecArray | null
394
+ while ( ( match = isEsmFlagPattern . exec ( code ) ) ) {
395
+ s . update ( match . index , match . index + isEsmFlag . length , isEsm )
396
+ }
387
397
}
388
398
389
- const re = new RegExp ( isEsmFlag , 'g' )
390
- const isEsm = String ( format === 'es' )
391
- if ( ! this . environment . config . build . sourcemap ) {
392
- return code . replace ( re , isEsm )
399
+ if ( format !== 'es' && code . includes ( chunkRegistryPlaceholder ) ) {
400
+ s . overwrite (
401
+ code . indexOf ( chunkRegistryPlaceholder ) ,
402
+ code . indexOf ( chunkRegistryPlaceholder ) +
403
+ chunkRegistryPlaceholder . length ,
404
+ '""' ,
405
+ )
393
406
}
394
407
395
- const s = new MagicString ( code )
396
- let match : RegExpExecArray | null
397
- while ( ( match = re . exec ( code ) ) ) {
398
- s . update ( match . index , match . index + isEsmFlag . length , isEsm )
399
- }
400
408
return {
401
409
code : s . toString ( ) ,
402
- map : s . generateMap ( { hires : 'boundary' } ) ,
410
+ map : this . environment . config . build . sourcemap
411
+ ? s . generateMap ( { hires : 'boundary' } )
412
+ : null ,
403
413
}
404
414
} ,
405
415
@@ -469,6 +479,15 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
469
479
const buildSourcemap = this . environment . config . build . sourcemap
470
480
const { modulePreload } = this . environment . config . build
471
481
482
+ const chunkRegistry : string [ ] = [ ]
483
+ const getChunkId = ( url : string , runtime : boolean = false ) => {
484
+ if ( ! runtime ) {
485
+ url = JSON . stringify ( url )
486
+ }
487
+ const index = chunkRegistry . indexOf ( url )
488
+ return index > - 1 ? index : chunkRegistry . push ( url ) - 1
489
+ }
490
+
472
491
for ( const chunkName in bundle ) {
473
492
const chunk = bundle [ chunkName ]
474
493
if ( chunk . type !== 'chunk' ) {
@@ -485,7 +504,7 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
485
504
486
505
let dynamicImports ! : ImportSpecifier [ ]
487
506
try {
488
- dynamicImports = parseImports ( code ) [ 0 ] . filter ( ( i ) => i . d > - 1 )
507
+ dynamicImports = parseImports ( code ) [ 0 ] . filter ( ( i ) => i . d !== - 1 )
489
508
} catch ( e : any ) {
490
509
const loc = numberToPos ( code , e . idx )
491
510
this . error ( {
@@ -505,15 +524,6 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
505
524
const s = new MagicString ( code )
506
525
const rewroteMarkerStartPos = new Set ( ) // position of the leading double quote
507
526
508
- const chunkRegistry : FileDep [ ] = [ ]
509
- const getChunkId = ( url : string , runtime : boolean = false ) => {
510
- const index = chunkRegistry . findIndex ( ( dep ) => dep . url === url )
511
- if ( index === - 1 ) {
512
- return chunkRegistry . push ( { url, runtime } ) - 1
513
- }
514
- return index
515
- }
516
-
517
527
for ( const dynamicImport of dynamicImports ) {
518
528
// To handle escape sequences in specifier strings, the .n field will be provided where possible.
519
529
const { s : start , e : end , ss : expStart , se : expEnd } = dynamicImport
@@ -642,31 +652,12 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
642
652
s . update (
643
653
markerStartPos ,
644
654
markerStartPos + preloadMarker . length ,
645
- chunkDependencies . length > 0
646
- ? `${ viteMapDeps } ([${ chunkDependencies . join ( ',' ) } ])`
647
- : `[]` ,
655
+ `[${ chunkDependencies . join ( ',' ) } ]` ,
648
656
)
649
657
rewroteMarkerStartPos . add ( markerStartPos )
650
658
}
651
659
}
652
660
653
- if ( chunkRegistry . length > 0 ) {
654
- const chunkRegistryCode = `[${ chunkRegistry
655
- . map ( ( fileDep ) =>
656
- fileDep . runtime ? fileDep . url : JSON . stringify ( fileDep . url ) ,
657
- )
658
- . join ( ',' ) } ]`
659
-
660
- const mapDepsCode = `const ${ viteMapDeps } =(i,m=${ viteMapDeps } ,d=(m.f||(m.f=${ chunkRegistryCode } )))=>i.map(i=>d[i]);\n`
661
-
662
- // inject extra code at the top or next line of hashbang
663
- if ( code . startsWith ( '#!' ) ) {
664
- s . prependLeft ( code . indexOf ( '\n' ) + 1 , mapDepsCode )
665
- } else {
666
- s . prepend ( mapDepsCode )
667
- }
668
- }
669
-
670
661
// there may still be markers due to inlined dynamic imports, remove
671
662
// all the markers regardless
672
663
let markerStartPos = indexOfRegexp ( code , preloadMarkerRE )
@@ -685,27 +676,45 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
685
676
)
686
677
}
687
678
688
- if ( ! s . hasChanged ( ) ) {
689
- continue
679
+ if ( s . hasChanged ( ) ) {
680
+ patchChunkWithMagicString ( chunk , s )
690
681
}
682
+ }
683
+
684
+ const chunkToPatchWithRegistry = Object . values ( bundle ) . find (
685
+ ( chunk ) =>
686
+ chunk . type === 'chunk' &&
687
+ chunk . code . includes ( chunkRegistryPlaceholder ) ,
688
+ ) as OutputChunk | undefined
689
+ if ( chunkToPatchWithRegistry ) {
690
+ const chunkRegistryCode = `[${ chunkRegistry . join ( ',' ) } ]`
691
+ const s = new MagicString ( chunkToPatchWithRegistry . code )
692
+ s . overwrite (
693
+ chunkToPatchWithRegistry . code . indexOf ( chunkRegistryPlaceholder ) ,
694
+ chunkToPatchWithRegistry . code . indexOf ( chunkRegistryPlaceholder ) +
695
+ chunkRegistryPlaceholder . length ,
696
+ chunkRegistryCode ,
697
+ )
698
+
699
+ patchChunkWithMagicString ( chunkToPatchWithRegistry , s )
700
+ }
691
701
702
+ function patchChunkWithMagicString ( chunk : OutputChunk , s : MagicString ) {
692
703
chunk . code = s . toString ( )
693
704
694
705
if ( ! buildSourcemap || ! chunk . map ) {
695
- continue
706
+ return
696
707
}
697
708
698
- const nextMap = s . generateMap ( {
699
- source : chunk . fileName ,
700
- hires : 'boundary' ,
701
- } )
709
+ const { debugId } = chunk . map
702
710
const map = combineSourcemaps ( chunk . fileName , [
703
- nextMap as RawSourceMap ,
711
+ s . generateMap ( {
712
+ source : chunk . fileName ,
713
+ hires : 'boundary' ,
714
+ } ) as RawSourceMap ,
704
715
chunk . map as RawSourceMap ,
705
716
] ) as SourceMap
706
717
map . toUrl = ( ) => genSourceMapUrl ( map )
707
-
708
- const originalDebugId = chunk . map . debugId
709
718
chunk . map = map
710
719
711
720
if ( buildSourcemap === 'inline' ) {
@@ -715,8 +724,8 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
715
724
)
716
725
chunk . code += `\n//# sourceMappingURL=${ genSourceMapUrl ( map ) } `
717
726
} else {
718
- if ( originalDebugId ) {
719
- map . debugId = originalDebugId
727
+ if ( debugId ) {
728
+ map . debugId = debugId
720
729
}
721
730
const mapAsset = bundle [ chunk . fileName + '.map' ]
722
731
if ( mapAsset && mapAsset . type === 'asset' ) {
0 commit comments