@@ -80,6 +80,12 @@ const hasViteIgnoreRE = /\/\*\s*@vite-ignore\s*\*\//
80
80
const cleanUpRawUrlRE = / \/ \* [ \s \S ] * ?\* \/ | ( [ ^ \\ : ] | ^ ) \/ \/ .* $ / gm
81
81
const urlIsStringRE = / ^ (?: ' .* ' | " .* " | ` .* ` ) $ /
82
82
83
+ interface UrlPosition {
84
+ url : string
85
+ start : number
86
+ end : number
87
+ }
88
+
83
89
export function isExplicitImportRequired ( url : string ) : boolean {
84
90
return ! isJSRequest ( cleanUrl ( url ) ) && ! isCSSRequest ( url )
85
91
}
@@ -271,13 +277,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
271
277
let s : MagicString | undefined
272
278
const str = ( ) => s || ( s = new MagicString ( source ) )
273
279
const importedUrls = new Set < string > ( )
274
- const acceptedUrls = new Set < {
275
- url : string
276
- start : number
277
- end : number
278
- } > ( )
279
280
let isPartiallySelfAccepting = false
280
- const acceptedExports = new Set < string > ( )
281
281
const importedBindings = enablePartialAccept
282
282
? new Map < string , Set < string > > ( )
283
283
: null
@@ -409,268 +409,288 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
409
409
return [ url , resolved . id ]
410
410
}
411
411
412
- for ( let index = 0 ; index < imports . length ; index ++ ) {
413
- const {
414
- s : start ,
415
- e : end ,
416
- ss : expStart ,
417
- se : expEnd ,
418
- d : dynamicIndex ,
419
- // #2083 User may use escape path,
420
- // so use imports[index].n to get the unescaped string
421
- n : specifier ,
422
- a : assertIndex ,
423
- } = imports [ index ]
424
-
425
- const rawUrl = source . slice ( start , end )
426
-
427
- // check import.meta usage
428
- if ( rawUrl === 'import.meta' ) {
429
- const prop = source . slice ( end , end + 4 )
430
- if ( prop === '.hot' ) {
431
- hasHMR = true
432
- const endHot = end + 4 + ( source [ end + 4 ] === '?' ? 1 : 0 )
433
- if ( source . slice ( endHot , endHot + 7 ) === '.accept' ) {
434
- // further analyze accepted modules
435
- if ( source . slice ( endHot , endHot + 14 ) === '.acceptExports' ) {
436
- lexAcceptedHmrExports (
437
- source ,
438
- source . indexOf ( '(' , endHot + 14 ) + 1 ,
439
- acceptedExports ,
440
- )
441
- isPartiallySelfAccepting = true
442
- } else if (
443
- lexAcceptedHmrDeps (
444
- source ,
445
- source . indexOf ( '(' , endHot + 7 ) + 1 ,
446
- acceptedUrls ,
447
- )
448
- ) {
449
- isSelfAccepting = true
412
+ const orderedAcceptedUrls = new Array < Set < UrlPosition > | undefined > (
413
+ imports . length ,
414
+ )
415
+ const orderedAcceptedExports = new Array < Set < string > | undefined > (
416
+ imports . length ,
417
+ )
418
+
419
+ await Promise . all (
420
+ imports . map ( async ( importSpecifier , index ) => {
421
+ const {
422
+ s : start ,
423
+ e : end ,
424
+ ss : expStart ,
425
+ se : expEnd ,
426
+ d : dynamicIndex ,
427
+ // #2083 User may use escape path,
428
+ // so use imports[index].n to get the unescaped string
429
+ n : specifier ,
430
+ a : assertIndex ,
431
+ } = importSpecifier
432
+
433
+ const rawUrl = source . slice ( start , end )
434
+
435
+ // check import.meta usage
436
+ if ( rawUrl === 'import.meta' ) {
437
+ const prop = source . slice ( end , end + 4 )
438
+ if ( prop === '.hot' ) {
439
+ hasHMR = true
440
+ const endHot = end + 4 + ( source [ end + 4 ] === '?' ? 1 : 0 )
441
+ if ( source . slice ( endHot , endHot + 7 ) === '.accept' ) {
442
+ // further analyze accepted modules
443
+ if ( source . slice ( endHot , endHot + 14 ) === '.acceptExports' ) {
444
+ const importAcceptedExports = ( orderedAcceptedExports [ index ] =
445
+ new Set < string > ( ) )
446
+ lexAcceptedHmrExports (
447
+ source ,
448
+ source . indexOf ( '(' , endHot + 14 ) + 1 ,
449
+ importAcceptedExports ,
450
+ )
451
+ isPartiallySelfAccepting = true
452
+ } else {
453
+ const importAcceptedUrls = ( orderedAcceptedUrls [ index ] =
454
+ new Set < UrlPosition > ( ) )
455
+ if (
456
+ lexAcceptedHmrDeps (
457
+ source ,
458
+ source . indexOf ( '(' , endHot + 7 ) + 1 ,
459
+ importAcceptedUrls ,
460
+ )
461
+ ) {
462
+ isSelfAccepting = true
463
+ }
464
+ }
450
465
}
466
+ } else if ( prop === '.env' ) {
467
+ hasEnv = true
451
468
}
452
- } else if ( prop === '.env' ) {
453
- hasEnv = true
469
+ return
454
470
}
455
- continue
456
- }
457
471
458
- const isDynamicImport = dynamicIndex > - 1
472
+ const isDynamicImport = dynamicIndex > - 1
459
473
460
- // strip import assertions as we can process them ourselves
461
- if ( ! isDynamicImport && assertIndex > - 1 ) {
462
- str ( ) . remove ( end + 1 , expEnd )
463
- }
464
-
465
- // static import or valid string in dynamic import
466
- // If resolvable, let's resolve it
467
- if ( specifier ) {
468
- // skip external / data uri
469
- if ( isExternalUrl ( specifier ) || isDataUrl ( specifier ) ) {
470
- continue
474
+ // strip import assertions as we can process them ourselves
475
+ if ( ! isDynamicImport && assertIndex > - 1 ) {
476
+ str ( ) . remove ( end + 1 , expEnd )
471
477
}
472
- // skip ssr external
473
- if ( ssr ) {
474
- if ( config . legacy ?. buildSsrCjsExternalHeuristics ) {
475
- if ( cjsShouldExternalizeForSSR ( specifier , server . _ssrExternals ) ) {
476
- continue
478
+
479
+ // static import or valid string in dynamic import
480
+ // If resolvable, let's resolve it
481
+ if ( specifier ) {
482
+ // skip external / data uri
483
+ if ( isExternalUrl ( specifier ) || isDataUrl ( specifier ) ) {
484
+ return
485
+ }
486
+ // skip ssr external
487
+ if ( ssr ) {
488
+ if ( config . legacy ?. buildSsrCjsExternalHeuristics ) {
489
+ if (
490
+ cjsShouldExternalizeForSSR ( specifier , server . _ssrExternals )
491
+ ) {
492
+ return
493
+ }
494
+ } else if ( shouldExternalizeForSSR ( specifier , config ) ) {
495
+ return
496
+ }
497
+ if ( isBuiltin ( specifier ) ) {
498
+ return
477
499
}
478
- } else if ( shouldExternalizeForSSR ( specifier , config ) ) {
479
- continue
480
500
}
481
- if ( isBuiltin ( specifier ) ) {
482
- continue
501
+ // skip client
502
+ if ( specifier === clientPublicPath ) {
503
+ return
483
504
}
484
- }
485
- // skip client
486
- if ( specifier === clientPublicPath ) {
487
- continue
488
- }
489
505
490
- // warn imports to non-asset /public files
491
- if (
492
- specifier [ 0 ] === '/' &&
493
- ! config . assetsInclude ( cleanUrl ( specifier ) ) &&
494
- ! specifier . endsWith ( '.json' ) &&
495
- checkPublicFile ( specifier , config )
496
- ) {
497
- throw new Error (
498
- `Cannot import non-asset file ${ specifier } which is inside /public.` +
499
- `JS/CSS files inside /public are copied as-is on build and ` +
500
- `can only be referenced via <script src> or <link href> in html.` ,
501
- )
502
- }
503
-
504
- // normalize
505
- const [ url , resolvedId ] = await normalizeUrl ( specifier , start )
506
-
507
- if (
508
- ! isDynamicImport &&
509
- specifier &&
510
- ! specifier . includes ( '?' ) && // ignore custom queries
511
- isCSSRequest ( resolvedId ) &&
512
- ! isModuleCSSRequest ( resolvedId )
513
- ) {
514
- const sourceExp = source . slice ( expStart , start )
506
+ // warn imports to non-asset /public files
515
507
if (
516
- sourceExp . includes ( 'from' ) && // check default and named imports
517
- ! sourceExp . includes ( '__vite_glob_' ) // glob handles deprecation message itself
508
+ specifier [ 0 ] === '/' &&
509
+ ! config . assetsInclude ( cleanUrl ( specifier ) ) &&
510
+ ! specifier . endsWith ( '.json' ) &&
511
+ checkPublicFile ( specifier , config )
518
512
) {
519
- const newImport =
520
- sourceExp + specifier + `?inline` + source . slice ( end , expEnd )
521
- this . warn (
522
- `\n` +
523
- colors . cyan ( importerModule . file ) +
524
- `\n` +
525
- colors . reset ( generateCodeFrame ( source , start ) ) +
526
- `\n` +
527
- colors . yellow (
528
- `Default and named imports from CSS files are deprecated. ` +
529
- `Use the ?inline query instead. ` +
530
- `For example: ${ newImport } ` ,
531
- ) ,
513
+ throw new Error (
514
+ `Cannot import non-asset file ${ specifier } which is inside /public.` +
515
+ `JS/CSS files inside /public are copied as-is on build and ` +
516
+ `can only be referenced via <script src> or <link href> in html.` ,
532
517
)
533
518
}
534
- }
535
519
536
- // record as safe modules
537
- server ?. moduleGraph . safeModulesPath . add ( fsPathFromUrl ( url ) )
520
+ // normalize
521
+ const [ url , resolvedId ] = await normalizeUrl ( specifier , start )
538
522
539
- if ( url !== specifier ) {
540
- let rewriteDone = false
541
523
if (
542
- depsOptimizer ?. isOptimizedDepFile ( resolvedId ) &&
543
- ! resolvedId . match ( optimizedDepChunkRE )
524
+ ! isDynamicImport &&
525
+ specifier &&
526
+ ! specifier . includes ( '?' ) && // ignore custom queries
527
+ isCSSRequest ( resolvedId ) &&
528
+ ! isModuleCSSRequest ( resolvedId )
544
529
) {
545
- // for optimized cjs deps, support named imports by rewriting named imports to const assignments.
546
- // internal optimized chunks don't need es interop and are excluded
547
-
548
- // The browserHash in resolvedId could be stale in which case there will be a full
549
- // page reload. We could return a 404 in that case but it is safe to return the request
550
- const file = cleanUrl ( resolvedId ) // Remove ?v={hash}
551
-
552
- const needsInterop = await optimizedDepNeedsInterop (
553
- depsOptimizer . metadata ,
554
- file ,
555
- config ,
556
- ssr ,
557
- )
558
-
559
- if ( needsInterop === undefined ) {
560
- // Non-entry dynamic imports from dependencies will reach here as there isn't
561
- // optimize info for them, but they don't need es interop. If the request isn't
562
- // a dynamic import, then it is an internal Vite error
563
- if ( ! file . match ( optimizedDepDynamicRE ) ) {
564
- config . logger . error (
565
- colors . red (
566
- `Vite Error, ${ url } optimized info should be defined` ,
530
+ const sourceExp = source . slice ( expStart , start )
531
+ if (
532
+ sourceExp . includes ( 'from' ) && // check default and named imports
533
+ ! sourceExp . includes ( '__vite_glob_' ) // glob handles deprecation message itself
534
+ ) {
535
+ const newImport =
536
+ sourceExp + specifier + `?inline` + source . slice ( end , expEnd )
537
+ this . warn (
538
+ `\n` +
539
+ colors . cyan ( importerModule . file ) +
540
+ `\n` +
541
+ colors . reset ( generateCodeFrame ( source , start ) ) +
542
+ `\n` +
543
+ colors . yellow (
544
+ `Default and named imports from CSS files are deprecated. ` +
545
+ `Use the ?inline query instead. ` +
546
+ `For example: ${ newImport } ` ,
567
547
) ,
568
- )
569
- }
570
- } else if ( needsInterop ) {
571
- debug ?.( `${ url } needs interop` )
572
- interopNamedImports ( str ( ) , imports [ index ] , url , index )
573
- rewriteDone = true
548
+ )
574
549
}
575
550
}
576
- // If source code imports builtin modules via named imports, the stub proxy export
577
- // would fail as it's `export default` only. Apply interop for builtin modules to
578
- // correctly throw the error message.
579
- else if (
580
- url . includes ( browserExternalId ) &&
581
- source . slice ( expStart , start ) . includes ( '{' )
582
- ) {
583
- interopNamedImports ( str ( ) , imports [ index ] , url , index )
584
- rewriteDone = true
585
- }
586
- if ( ! rewriteDone ) {
587
- const rewrittenUrl = JSON . stringify ( url )
588
- const s = isDynamicImport ? start : start - 1
589
- const e = isDynamicImport ? end : end + 1
590
- str ( ) . overwrite ( s , e , rewrittenUrl , {
591
- contentOnly : true ,
592
- } )
593
- }
594
- }
595
551
596
- // record for HMR import chain analysis
597
- // make sure to unwrap and normalize away base
598
- const hmrUrl = unwrapId ( stripBase ( url , base ) )
599
- const isLocalImport = ! isExternalUrl ( hmrUrl ) && ! isDataUrl ( hmrUrl )
600
- if ( isLocalImport ) {
601
- importedUrls . add ( hmrUrl )
602
- }
552
+ // record as safe modules
553
+ server ?. moduleGraph . safeModulesPath . add ( fsPathFromUrl ( url ) )
603
554
604
- if ( enablePartialAccept && importedBindings ) {
605
- extractImportedBindings (
606
- resolvedId ,
607
- source ,
608
- imports [ index ] ,
609
- importedBindings ,
610
- )
611
- }
555
+ if ( url !== specifier ) {
556
+ let rewriteDone = false
557
+ if (
558
+ depsOptimizer ?. isOptimizedDepFile ( resolvedId ) &&
559
+ ! resolvedId . match ( optimizedDepChunkRE )
560
+ ) {
561
+ // for optimized cjs deps, support named imports by rewriting named imports to const assignments.
562
+ // internal optimized chunks don't need es interop and are excluded
563
+
564
+ // The browserHash in resolvedId could be stale in which case there will be a full
565
+ // page reload. We could return a 404 in that case but it is safe to return the request
566
+ const file = cleanUrl ( resolvedId ) // Remove ?v={hash}
567
+
568
+ const needsInterop = await optimizedDepNeedsInterop (
569
+ depsOptimizer . metadata ,
570
+ file ,
571
+ config ,
572
+ ssr ,
573
+ )
612
574
613
- if (
614
- ! isDynamicImport &&
615
- isLocalImport &&
616
- config . server . preTransformRequests
617
- ) {
618
- // pre-transform known direct imports
619
- // These requests will also be registered in transformRequest to be awaited
620
- // by the deps optimizer
621
- const url = removeImportQuery ( hmrUrl )
622
- server . transformRequest ( url , { ssr } ) . catch ( ( e ) => {
623
- if ( e ?. code === ERR_OUTDATED_OPTIMIZED_DEP ) {
624
- // This are expected errors
625
- return
575
+ if ( needsInterop === undefined ) {
576
+ // Non-entry dynamic imports from dependencies will reach here as there isn't
577
+ // optimize info for them, but they don't need es interop. If the request isn't
578
+ // a dynamic import, then it is an internal Vite error
579
+ if ( ! file . match ( optimizedDepDynamicRE ) ) {
580
+ config . logger . error (
581
+ colors . red (
582
+ `Vite Error, ${ url } optimized info should be defined` ,
583
+ ) ,
584
+ )
585
+ }
586
+ } else if ( needsInterop ) {
587
+ debug ?.( `${ url } needs interop` )
588
+ interopNamedImports ( str ( ) , importSpecifier , url , index )
589
+ rewriteDone = true
590
+ }
626
591
}
627
- // Unexpected error, log the issue but avoid an unhandled exception
628
- config . logger . error ( e . message )
629
- } )
630
- }
631
- } else if ( ! importer . startsWith ( clientDir ) ) {
632
- if ( ! isInNodeModules ( importer ) ) {
633
- // check @vite -ignore which suppresses dynamic import warning
634
- const hasViteIgnore = hasViteIgnoreRE . test (
635
- // complete expression inside parens
636
- source . slice ( dynamicIndex + 1 , end ) ,
637
- )
638
- if ( ! hasViteIgnore ) {
639
- this . warn (
640
- `\n` +
641
- colors . cyan ( importerModule . file ) +
642
- `\n` +
643
- colors . reset ( generateCodeFrame ( source , start ) ) +
644
- colors . yellow (
645
- `\nThe above dynamic import cannot be analyzed by Vite.\n` +
646
- `See ${ colors . blue (
647
- `https://github.com/rollup/plugins/tree/master/packages/dynamic-import-vars#limitations` ,
648
- ) } ` +
649
- `for supported dynamic import formats. ` +
650
- `If this is intended to be left as-is, you can use the ` +
651
- `/* @vite-ignore */ comment inside the import() call to suppress this warning.\n` ,
652
- ) ,
592
+ // If source code imports builtin modules via named imports, the stub proxy export
593
+ // would fail as it's `export default` only. Apply interop for builtin modules to
594
+ // correctly throw the error message.
595
+ else if (
596
+ url . includes ( browserExternalId ) &&
597
+ source . slice ( expStart , start ) . includes ( '{' )
598
+ ) {
599
+ interopNamedImports ( str ( ) , importSpecifier , url , index )
600
+ rewriteDone = true
601
+ }
602
+ if ( ! rewriteDone ) {
603
+ const rewrittenUrl = JSON . stringify ( url )
604
+ const s = isDynamicImport ? start : start - 1
605
+ const e = isDynamicImport ? end : end + 1
606
+ str ( ) . overwrite ( s , e , rewrittenUrl , {
607
+ contentOnly : true ,
608
+ } )
609
+ }
610
+ }
611
+
612
+ // record for HMR import chain analysis
613
+ // make sure to unwrap and normalize away base
614
+ const hmrUrl = unwrapId ( stripBase ( url , base ) )
615
+ const isLocalImport = ! isExternalUrl ( hmrUrl ) && ! isDataUrl ( hmrUrl )
616
+ if ( isLocalImport ) {
617
+ importedUrls . add ( hmrUrl )
618
+ }
619
+
620
+ if ( enablePartialAccept && importedBindings ) {
621
+ extractImportedBindings (
622
+ resolvedId ,
623
+ source ,
624
+ importSpecifier ,
625
+ importedBindings ,
653
626
)
654
627
}
655
- }
656
628
657
- if ( ! ssr ) {
658
- const url = rawUrl . replace ( cleanUpRawUrlRE , '' ) . trim ( )
659
629
if (
660
- ! urlIsStringRE . test ( url ) ||
661
- isExplicitImportRequired ( url . slice ( 1 , - 1 ) )
630
+ ! isDynamicImport &&
631
+ isLocalImport &&
632
+ config . server . preTransformRequests
662
633
) {
663
- needQueryInjectHelper = true
664
- str ( ) . overwrite (
665
- start ,
666
- end ,
667
- `__vite__injectQuery(${ url } , 'import')` ,
668
- { contentOnly : true } ,
634
+ // pre-transform known direct imports
635
+ // These requests will also be registered in transformRequest to be awaited
636
+ // by the deps optimizer
637
+ const url = removeImportQuery ( hmrUrl )
638
+ server . transformRequest ( url , { ssr } ) . catch ( ( e ) => {
639
+ if ( e ?. code === ERR_OUTDATED_OPTIMIZED_DEP ) {
640
+ // This are expected errors
641
+ return
642
+ }
643
+ // Unexpected error, log the issue but avoid an unhandled exception
644
+ config . logger . error ( e . message )
645
+ } )
646
+ }
647
+ } else if ( ! importer . startsWith ( clientDir ) ) {
648
+ if ( ! isInNodeModules ( importer ) ) {
649
+ // check @vite -ignore which suppresses dynamic import warning
650
+ const hasViteIgnore = hasViteIgnoreRE . test (
651
+ // complete expression inside parens
652
+ source . slice ( dynamicIndex + 1 , end ) ,
669
653
)
654
+ if ( ! hasViteIgnore ) {
655
+ this . warn (
656
+ `\n` +
657
+ colors . cyan ( importerModule . file ) +
658
+ `\n` +
659
+ colors . reset ( generateCodeFrame ( source , start ) ) +
660
+ colors . yellow (
661
+ `\nThe above dynamic import cannot be analyzed by Vite.\n` +
662
+ `See ${ colors . blue (
663
+ `https://github.com/rollup/plugins/tree/master/packages/dynamic-import-vars#limitations` ,
664
+ ) } ` +
665
+ `for supported dynamic import formats. ` +
666
+ `If this is intended to be left as-is, you can use the ` +
667
+ `/* @vite-ignore */ comment inside the import() call to suppress this warning.\n` ,
668
+ ) ,
669
+ )
670
+ }
671
+ }
672
+
673
+ if ( ! ssr ) {
674
+ const url = rawUrl . replace ( cleanUpRawUrlRE , '' ) . trim ( )
675
+ if (
676
+ ! urlIsStringRE . test ( url ) ||
677
+ isExplicitImportRequired ( url . slice ( 1 , - 1 ) )
678
+ ) {
679
+ needQueryInjectHelper = true
680
+ str ( ) . overwrite (
681
+ start ,
682
+ end ,
683
+ `__vite__injectQuery(${ url } , 'import')` ,
684
+ { contentOnly : true } ,
685
+ )
686
+ }
670
687
}
671
688
}
672
- }
673
- }
689
+ } ) ,
690
+ )
691
+
692
+ const acceptedUrls = mergeAcceptedUrls ( orderedAcceptedUrls )
693
+ const acceptedExports = mergeAcceptedUrls ( orderedAcceptedExports )
674
694
675
695
if ( hasEnv ) {
676
696
// inject import.meta.env
@@ -777,6 +797,15 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
777
797
}
778
798
}
779
799
800
+ function mergeAcceptedUrls < T > ( orderedUrls : Array < Set < T > | undefined > ) {
801
+ const acceptedUrls = new Set < T > ( )
802
+ for ( const urls of orderedUrls ) {
803
+ if ( ! urls ) continue
804
+ for ( const url of urls ) acceptedUrls . add ( url )
805
+ }
806
+ return acceptedUrls
807
+ }
808
+
780
809
export function interopNamedImports (
781
810
str : MagicString ,
782
811
importSpecifier : ImportSpecifier ,
0 commit comments