@@ -26,6 +26,11 @@ import { getDepsOptimizer, optimizedDepNeedsInterop } from '../optimizer'
2626import { removedPureCssFilesCache } from './css'
2727import { interopNamedImports } from './importAnalysis'
2828
29+ type FileDep = {
30+ url : string
31+ runtime : boolean
32+ }
33+
2934/**
3035 * A flag for injected helpers. This flag will be set to `false` if the output
3136 * target is not native es - so that injected helper logic can be conditionally
@@ -450,6 +455,26 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
450455 const s = new MagicString ( code )
451456 const rewroteMarkerStartPos = new Set ( ) // position of the leading double quote
452457
458+ const fileDeps : FileDep [ ] = [ ]
459+ const addFileDep = (
460+ url : string ,
461+ runtime : boolean = false ,
462+ ) : number => {
463+ const index = fileDeps . findIndex ( ( dep ) => dep . url === url )
464+ if ( index === - 1 ) {
465+ return fileDeps . push ( { url, runtime } ) - 1
466+ } else {
467+ return index
468+ }
469+ }
470+ const getFileDep = ( index : number ) : FileDep => {
471+ const fileDep = fileDeps [ index ]
472+ if ( ! fileDep ) {
473+ throw new Error ( `Cannot find file dep at index ${ index } ` )
474+ }
475+ return fileDep
476+ }
477+
453478 if ( imports . length ) {
454479 for ( let index = 0 ; index < imports . length ; index ++ ) {
455480 // To handle escape sequences in specifier strings, the .n field will be provided where possible.
@@ -467,7 +492,7 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
467492 if ( rawUrl [ 0 ] === `"` && rawUrl [ rawUrl . length - 1 ] === `"` )
468493 url = rawUrl . slice ( 1 , - 1 )
469494 }
470- const deps : Set < string > = new Set ( )
495+ const deps : Set < number > = new Set ( )
471496 let hasRemovedPureCssChunk = false
472497
473498 let normalizedFile : string | undefined = undefined
@@ -487,12 +512,12 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
487512 analyzed . add ( filename )
488513 const chunk = bundle [ filename ] as OutputChunk | undefined
489514 if ( chunk ) {
490- deps . add ( chunk . fileName )
515+ deps . add ( addFileDep ( chunk . fileName ) )
491516 chunk . imports . forEach ( addDeps )
492517 // Ensure that the css imported by current chunk is loaded after the dependencies.
493518 // So the style of current chunk won't be overwritten unexpectedly.
494519 chunk . viteMetadata ! . importedCss . forEach ( ( file ) => {
495- deps . add ( file )
520+ deps . add ( addFileDep ( file ) )
496521 } )
497522 } else {
498523 const removedPureCssFiles =
@@ -501,7 +526,7 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
501526 if ( chunk ) {
502527 if ( chunk . viteMetadata ! . importedCss . size ) {
503528 chunk . viteMetadata ! . importedCss . forEach ( ( file ) => {
504- deps . add ( file )
529+ deps . add ( addFileDep ( file ) )
505530 } )
506531 hasRemovedPureCssChunk = true
507532 }
@@ -535,74 +560,96 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
535560 ? modulePreload === false
536561 ? // CSS deps use the same mechanism as module preloads, so even if disabled,
537562 // we still need to pass these deps to the preload helper in dynamic imports.
538- [ ...deps ] . filter ( ( d ) => d . endsWith ( '.css' ) )
563+ [ ...deps ] . filter ( ( d ) =>
564+ getFileDep ( d ) . url . endsWith ( '.css' ) ,
565+ )
539566 : [ ...deps ]
540567 : [ ]
541568
542- let renderedDeps : string [ ]
569+ let renderedDeps : number [ ]
543570 if ( normalizedFile && customModulePreloadPaths ) {
544571 const { modulePreload } = config . build
545572 const resolveDependencies = modulePreload
546573 ? modulePreload . resolveDependencies
547574 : undefined
548- let resolvedDeps : string [ ]
575+ let resolvedDeps : number [ ]
549576 if ( resolveDependencies ) {
550577 // We can't let the user remove css deps as these aren't really preloads, they are just using
551578 // the same mechanism as module preloads for this chunk
552- const cssDeps : string [ ] = [ ]
553- const otherDeps : string [ ] = [ ]
579+ const cssDeps : number [ ] = [ ]
580+ const otherDeps : number [ ] = [ ]
554581 for ( const dep of depsArray ) {
555- ; ( dep . endsWith ( '.css' ) ? cssDeps : otherDeps ) . push ( dep )
582+ if ( getFileDep ( dep ) . url . endsWith ( '.css' ) ) {
583+ cssDeps . push ( dep )
584+ } else {
585+ otherDeps . push ( dep )
586+ }
556587 }
557588 resolvedDeps = [
558- ...resolveDependencies ( normalizedFile , otherDeps , {
559- hostId : file ,
560- hostType : 'js' ,
561- } ) ,
589+ ...resolveDependencies (
590+ normalizedFile ,
591+ otherDeps . map ( ( otherDep ) => getFileDep ( otherDep ) . url ) ,
592+ {
593+ hostId : file ,
594+ hostType : 'js' ,
595+ } ,
596+ ) . map ( ( otherDep ) => addFileDep ( otherDep ) ) ,
562597 ...cssDeps ,
563598 ]
564599 } else {
565600 resolvedDeps = depsArray
566601 }
567602
568- renderedDeps = resolvedDeps . map ( ( dep : string ) => {
603+ renderedDeps = resolvedDeps . map ( ( dep : number ) => {
569604 const replacement = toOutputFilePathInJS (
570- dep ,
605+ getFileDep ( dep ) . url ,
571606 'asset' ,
572607 chunk . fileName ,
573608 'js' ,
574609 config ,
575610 toRelativePath ,
576611 )
577- const replacementString =
578- typeof replacement === 'string'
579- ? JSON . stringify ( replacement )
580- : replacement . runtime
581612
582- return replacementString
613+ if ( typeof replacement === 'string' ) {
614+ return addFileDep ( replacement )
615+ }
616+
617+ return addFileDep ( replacement . runtime , true )
583618 } )
584619 } else {
585620 renderedDeps = depsArray . map ( ( d ) =>
586621 // Don't include the assets dir if the default asset file names
587622 // are used, the path will be reconstructed by the import preload helper
588- JSON . stringify (
589- optimizeModulePreloadRelativePaths
590- ? toRelativePath ( d , file )
591- : d ,
592- ) ,
623+ optimizeModulePreloadRelativePaths
624+ ? addFileDep ( toRelativePath ( getFileDep ( d ) . url , file ) )
625+ : d ,
593626 )
594627 }
595628
596629 s . update (
597630 markerStartPos ,
598631 markerStartPos + preloadMarker . length + 2 ,
599- `[${ renderedDeps . join ( ',' ) } ]` ,
632+ `__vite__mapDeps( [${ renderedDeps . join ( ',' ) } ]) ` ,
600633 )
601634 rewroteMarkerStartPos . add ( markerStartPos )
602635 }
603636 }
604637 }
605638
639+ const fileDepsCode = `[${ fileDeps
640+ . map ( ( fileDep ) =>
641+ fileDep . runtime ? fileDep . url : JSON . stringify ( fileDep . url ) ,
642+ )
643+ . join ( ',' ) } ]`
644+
645+ s . append ( `\
646+ function __vite__mapDeps(indexes) {
647+ if (!__vite__mapDeps.viteFileDeps) {
648+ __vite__mapDeps.viteFileDeps = ${ fileDepsCode }
649+ }
650+ return indexes.map((i) => __vite__mapDeps.viteFileDeps[i])
651+ }` )
652+
606653 // there may still be markers due to inlined dynamic imports, remove
607654 // all the markers regardless
608655 let markerStartPos = indexOfMatchInSlice ( code , preloadMarkerWithQuote )
0 commit comments