@@ -5,7 +5,7 @@ import type {
55 ImportSpecifier ,
66} from 'es-module-lexer'
77import { init , parse as parseImports } from 'es-module-lexer'
8- import type { SourceMap } from 'rollup'
8+ import type { OutputChunk , SourceMap } from 'rollup'
99import type { RawSourceMap } from '@ampproject/remapping'
1010import convertSourceMap from 'convert-source-map'
1111import {
@@ -22,18 +22,18 @@ import type { Environment } from '../environment'
2222import { removedPureCssFilesCache } from './css'
2323import { createParseErrorInfo } from './importAnalysis'
2424
25- type FileDep = {
26- url : string
27- runtime : boolean
28- }
25+ const symbolString = ( name : string ) =>
26+ `__viteSymbol_${ name } _${ Math . random ( ) . toString ( 36 ) . slice ( 2 ) } __`
2927
3028type VitePreloadErrorEvent = Event & { payload : Error }
3129
3230// Placeholder symbols for injecting helpers
3331export const isEsmFlag = `__VITE_IS_MODERN__`
32+ const isEsmFlagPattern = new RegExp ( '\\b' + isEsmFlag + '\\b' , 'g' )
33+
3434export const preloadMethod = `__vitePreload`
3535const preloadMarker = `__VITE_PRELOAD__`
36- const viteMapDeps = '__vite__mapDeps'
36+ const chunkRegistryPlaceholder = symbolString ( 'chunkRegistryPlaceholder' )
3737
3838export const preloadHelperId = '\0vite/preload-helper.js'
3939const preloadMarkerRE = new RegExp ( '\\b' + preloadMarker + '\\b' , 'g' )
@@ -93,6 +93,9 @@ function preload(
9393
9494 promise = Promise . allSettled (
9595 deps . map ( ( dep ) => {
96+ // @ts -expect-error chunkRegistry is declared before preload.toString()
97+ dep = chunkRegistry [ dep ]
98+
9699 // @ts -expect-error assetsURL is declared before preload.toString()
97100 dep = assetsURL ( dep , importerUrl )
98101 if ( dep in seen ) return
@@ -211,6 +214,7 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
211214 // is appended inside __vitePreload too.
212215 `(dep) => ${ JSON . stringify ( config . base ) } +dep`
213216 const code = [
217+ `const chunkRegistry = ${ chunkRegistryPlaceholder } ` ,
214218 `const scriptRel = ${ scriptRel } ` ,
215219 `const assetsURL = ${ assetsURL } ` ,
216220 `const seen = {}` ,
@@ -381,25 +385,31 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
381385 } ,
382386
383387 renderChunk ( code , _ , { format } ) {
388+ const s = new MagicString ( code )
389+
384390 // 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+ }
387397 }
388398
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+ )
393406 }
394407
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- }
400408 return {
401409 code : s . toString ( ) ,
402- map : s . generateMap ( { hires : 'boundary' } ) ,
410+ map : this . environment . config . build . sourcemap
411+ ? s . generateMap ( { hires : 'boundary' } )
412+ : null ,
403413 }
404414 } ,
405415
@@ -469,6 +479,15 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
469479 const buildSourcemap = this . environment . config . build . sourcemap
470480 const { modulePreload } = this . environment . config . build
471481
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+
472491 for ( const chunkName in bundle ) {
473492 const chunk = bundle [ chunkName ]
474493 if ( chunk . type !== 'chunk' ) {
@@ -485,7 +504,7 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
485504
486505 let dynamicImports ! : ImportSpecifier [ ]
487506 try {
488- dynamicImports = parseImports ( code ) [ 0 ] . filter ( ( i ) => i . d > - 1 )
507+ dynamicImports = parseImports ( code ) [ 0 ] . filter ( ( i ) => i . d !== - 1 )
489508 } catch ( e : any ) {
490509 const loc = numberToPos ( code , e . idx )
491510 this . error ( {
@@ -505,15 +524,6 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
505524 const s = new MagicString ( code )
506525 const rewroteMarkerStartPos = new Set ( ) // position of the leading double quote
507526
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-
517527 for ( const dynamicImport of dynamicImports ) {
518528 // To handle escape sequences in specifier strings, the .n field will be provided where possible.
519529 const { s : start , e : end , ss : expStart , se : expEnd } = dynamicImport
@@ -642,31 +652,12 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
642652 s . update (
643653 markerStartPos ,
644654 markerStartPos + preloadMarker . length ,
645- chunkDependencies . length > 0
646- ? `${ viteMapDeps } ([${ chunkDependencies . join ( ',' ) } ])`
647- : `[]` ,
655+ `[${ chunkDependencies . join ( ',' ) } ]` ,
648656 )
649657 rewroteMarkerStartPos . add ( markerStartPos )
650658 }
651659 }
652660
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-
670661 // there may still be markers due to inlined dynamic imports, remove
671662 // all the markers regardless
672663 let markerStartPos = indexOfRegexp ( code , preloadMarkerRE )
@@ -685,27 +676,45 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
685676 )
686677 }
687678
688- if ( ! s . hasChanged ( ) ) {
689- continue
679+ if ( s . hasChanged ( ) ) {
680+ patchChunkWithMagicString ( chunk , s )
690681 }
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+ }
691701
702+ function patchChunkWithMagicString ( chunk : OutputChunk , s : MagicString ) {
692703 chunk . code = s . toString ( )
693704
694705 if ( ! buildSourcemap || ! chunk . map ) {
695- continue
706+ return
696707 }
697708
698- const nextMap = s . generateMap ( {
699- source : chunk . fileName ,
700- hires : 'boundary' ,
701- } )
709+ const { debugId } = chunk . map
702710 const map = combineSourcemaps ( chunk . fileName , [
703- nextMap as RawSourceMap ,
711+ s . generateMap ( {
712+ source : chunk . fileName ,
713+ hires : 'boundary' ,
714+ } ) as RawSourceMap ,
704715 chunk . map as RawSourceMap ,
705716 ] ) as SourceMap
706717 map . toUrl = ( ) => genSourceMapUrl ( map )
707-
708- const originalDebugId = chunk . map . debugId
709718 chunk . map = map
710719
711720 if ( buildSourcemap === 'inline' ) {
@@ -715,8 +724,8 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
715724 )
716725 chunk . code += `\n//# sourceMappingURL=${ genSourceMapUrl ( map ) } `
717726 } else {
718- if ( originalDebugId ) {
719- map . debugId = originalDebugId
727+ if ( debugId ) {
728+ map . debugId = debugId
720729 }
721730 const mapAsset = bundle [ chunk . fileName + '.map' ]
722731 if ( mapAsset && mapAsset . type === 'asset' ) {
0 commit comments