@@ -99,7 +99,7 @@ async function createDepsOptimizer(
99
99
100
100
const cachedMetadata = await loadCachedDepOptimizationMetadata ( config , ssr )
101
101
102
- let handle : NodeJS . Timeout | undefined
102
+ let debounceProcessingHandle : NodeJS . Timeout | undefined
103
103
104
104
let closed = false
105
105
@@ -155,9 +155,20 @@ async function createDepsOptimizer(
155
155
let enqueuedRerun : ( ( ) => void ) | undefined
156
156
let currentlyProcessing = false
157
157
158
- // If there wasn't a cache or it is outdated, we need to prepare a first run
159
158
let firstRunCalled = ! ! cachedMetadata
160
159
160
+ // During build, we wait for every module to be scanned before resolving
161
+ // optimized deps loading for rollup on each rebuild. It will be recreated
162
+ // after each buildStart.
163
+ // During dev, if this is a cold run, we wait for static imports discovered
164
+ // from the first request before resolving to minimize full page reloads.
165
+ // On warm start or after the first optimization is run, we use a simpler
166
+ // debounce strategy each time a new dep is discovered.
167
+ let crawlEndFinder : CrawlEndFinder | undefined
168
+ if ( isBuild || ! cachedMetadata ) {
169
+ crawlEndFinder = setupOnCrawlEnd ( onCrawlEnd )
170
+ }
171
+
161
172
let optimizationResult :
162
173
| {
163
174
cancel : ( ) => Promise < void >
@@ -174,6 +185,7 @@ async function createDepsOptimizer(
174
185
175
186
async function close ( ) {
176
187
closed = true
188
+ crawlEndFinder ?. cancel ( )
177
189
await Promise . allSettled ( [
178
190
discover ?. cancel ( ) ,
179
191
depsOptimizer . scanProcessing ,
@@ -290,7 +302,7 @@ async function createDepsOptimizer(
290
302
enqueuedRerun = undefined
291
303
292
304
// Ensure that a rerun will not be issued for current discovered deps
293
- if ( handle ) clearTimeout ( handle )
305
+ if ( debounceProcessingHandle ) clearTimeout ( debounceProcessingHandle )
294
306
295
307
if ( closed || Object . keys ( metadata . discovered ) . length === 0 ) {
296
308
currentlyProcessing = false
@@ -529,7 +541,12 @@ async function createDepsOptimizer(
529
541
// we can get a list of every missing dependency before giving to the
530
542
// browser a dependency that may be outdated, thus avoiding full page reloads
531
543
532
- if ( firstRunCalled ) {
544
+ if ( ! crawlEndFinder ) {
545
+ if ( isBuild ) {
546
+ logger . error (
547
+ 'Vite Internal Error: Missing dependency found after crawling ended' ,
548
+ )
549
+ }
533
550
// Debounced rerun, let other missing dependencies be discovered before
534
551
// the running next optimizeDeps
535
552
debouncedProcessing ( )
@@ -570,26 +587,34 @@ async function createDepsOptimizer(
570
587
// Debounced rerun, let other missing dependencies be discovered before
571
588
// the running next optimizeDeps
572
589
enqueuedRerun = undefined
573
- if ( handle ) clearTimeout ( handle )
590
+ if ( debounceProcessingHandle ) clearTimeout ( debounceProcessingHandle )
574
591
if ( newDepsToLogHandle ) clearTimeout ( newDepsToLogHandle )
575
592
newDepsToLogHandle = undefined
576
- handle = setTimeout ( ( ) => {
577
- handle = undefined
593
+ debounceProcessingHandle = setTimeout ( ( ) => {
594
+ debounceProcessingHandle = undefined
578
595
enqueuedRerun = rerun
579
596
if ( ! currentlyProcessing ) {
580
597
enqueuedRerun ( )
581
598
}
582
599
} , timeout )
583
600
}
584
601
602
+ // During dev, onCrawlEnd is called once when the server starts and all static
603
+ // imports after the first request have been crawled (dynamic imports may also
604
+ // be crawled if the browser requests them right away).
605
+ // During build, onCrawlEnd will be called once after each buildStart (so in
606
+ // watch mode it will be called after each rebuild has processed every module).
607
+ // All modules are transformed first in this case (both static and dynamic).
585
608
async function onCrawlEnd ( ) {
609
+ // On build time, a missing dep appearing after onCrawlEnd is an internal error
610
+ // On dev, switch after this point to a simple debounce strategy
611
+ crawlEndFinder = undefined
612
+
586
613
debug ?.( colors . green ( `✨ static imports crawl ended` ) )
587
- if ( firstRunCalled ) {
614
+ if ( closed ) {
588
615
return
589
616
}
590
617
591
- currentlyProcessing = false
592
-
593
618
const crawlDeps = Object . keys ( metadata . discovered )
594
619
595
620
// Await for the scan+optimize step running in the background
@@ -599,6 +624,7 @@ async function createDepsOptimizer(
599
624
if ( ! isBuild && optimizationResult ) {
600
625
const result = await optimizationResult . result
601
626
optimizationResult = undefined
627
+ currentlyProcessing = false
602
628
603
629
const scanDeps = Object . keys ( result . metadata . optimized )
604
630
@@ -650,6 +676,8 @@ async function createDepsOptimizer(
650
676
runOptimizer ( result )
651
677
}
652
678
} else {
679
+ currentlyProcessing = false
680
+
653
681
if ( crawlDeps . length === 0 ) {
654
682
debug ?.(
655
683
colors . green (
@@ -664,30 +692,62 @@ async function createDepsOptimizer(
664
692
}
665
693
}
666
694
667
- const runOptimizerIfIdleAfterMs = 50
695
+ // Called during buildStart at build time, when build --watch is used.
696
+ function resetRegisteredIds ( ) {
697
+ crawlEndFinder ?. cancel ( )
698
+ crawlEndFinder = setupOnCrawlEnd ( onCrawlEnd )
699
+ }
700
+
701
+ function registerWorkersSource ( id : string ) {
702
+ crawlEndFinder ?. registerWorkersSource ( id )
703
+ }
704
+ function delayDepsOptimizerUntil ( id : string , done : ( ) => Promise < any > ) {
705
+ if ( crawlEndFinder && ! depsOptimizer . isOptimizedDepFile ( id ) ) {
706
+ crawlEndFinder . delayDepsOptimizerUntil ( id , done )
707
+ }
708
+ }
709
+ function ensureFirstRun ( ) {
710
+ crawlEndFinder ?. ensureFirstRun ( )
711
+ }
712
+ }
668
713
714
+ const runOptimizerIfIdleAfterMs = 50
715
+
716
+ interface CrawlEndFinder {
717
+ ensureFirstRun : ( ) => void
718
+ registerWorkersSource : ( id : string ) => void
719
+ delayDepsOptimizerUntil : ( id : string , done : ( ) => Promise < any > ) => void
720
+ cancel : ( ) => void
721
+ }
722
+
723
+ function setupOnCrawlEnd ( onCrawlEnd : ( ) => void ) : CrawlEndFinder {
669
724
let registeredIds : { id : string ; done : ( ) => Promise < any > } [ ] = [ ]
670
- let seenIds = new Set < string > ( )
671
- let workersSources = new Set < string > ( )
672
- const waitingOn = new Set < string > ( )
725
+ const seenIds = new Set < string > ( )
726
+ const workersSources = new Set < string > ( )
727
+ const waitingOn = new Map < string , ( ) => void > ( )
673
728
let firstRunEnsured = false
729
+ let crawlEndCalled = false
674
730
675
- function resetRegisteredIds ( ) {
676
- registeredIds = [ ]
677
- seenIds = new Set < string > ( )
678
- workersSources = new Set < string > ( )
679
- waitingOn . clear ( )
680
- firstRunEnsured = false
731
+ let cancelled = false
732
+ function cancel ( ) {
733
+ cancelled = true
734
+ }
735
+
736
+ function callOnCrawlEnd ( ) {
737
+ if ( ! cancelled && ! crawlEndCalled ) {
738
+ crawlEndCalled = true
739
+ onCrawlEnd ( )
740
+ }
681
741
}
682
742
683
743
// If all the inputs are dependencies, we aren't going to get any
684
744
// delayDepsOptimizerUntil(id) calls. We need to guard against this
685
745
// by forcing a rerun if no deps have been registered
686
746
function ensureFirstRun ( ) {
687
- if ( ! firstRunEnsured && ! firstRunCalled && registeredIds . length === 0 ) {
747
+ if ( ! firstRunEnsured && seenIds . size === 0 ) {
688
748
setTimeout ( ( ) => {
689
- if ( ! closed && registeredIds . length === 0 ) {
690
- onCrawlEnd ( )
749
+ if ( seenIds . size === 0 ) {
750
+ callOnCrawlEnd ( )
691
751
}
692
752
} , runOptimizerIfIdleAfterMs )
693
753
}
@@ -699,37 +759,46 @@ async function createDepsOptimizer(
699
759
// Avoid waiting for this id, as it may be blocked by the rollup
700
760
// bundling process of the worker that also depends on the optimizer
701
761
registeredIds = registeredIds . filter ( ( registered ) => registered . id !== id )
702
- if ( waitingOn . has ( id ) ) {
703
- waitingOn . delete ( id )
704
- runOptimizerWhenIdle ( )
705
- }
762
+
763
+ const resolve = waitingOn . get ( id )
764
+ // Forced resolve to avoid waiting for the bundling of the worker to finish
765
+ resolve ?. ( )
706
766
}
707
767
708
768
function delayDepsOptimizerUntil ( id : string , done : ( ) => Promise < any > ) : void {
709
- if ( ! depsOptimizer . isOptimizedDepFile ( id ) && ! seenIds . has ( id ) ) {
769
+ if ( ! seenIds . has ( id ) ) {
710
770
seenIds . add ( id )
711
771
registeredIds . push ( { id, done } )
712
- runOptimizerWhenIdle ( )
772
+ callOnCrawlEndWhenIdle ( )
713
773
}
714
774
}
715
775
716
- async function runOptimizerWhenIdle ( ) {
717
- if ( waitingOn . size > 0 ) return
776
+ async function callOnCrawlEndWhenIdle ( ) {
777
+ if ( cancelled || waitingOn . size > 0 ) return
718
778
719
779
const processingRegisteredIds = registeredIds
720
780
registeredIds = [ ]
721
781
722
782
const donePromises = processingRegisteredIds . map ( async ( registeredId ) => {
723
- waitingOn . add ( registeredId . id )
724
- try {
725
- await registeredId . done ( )
726
- } finally {
727
- waitingOn . delete ( registeredId . id )
728
- }
783
+ // During build, we need to cancel workers
784
+ let resolve : ( ) => void
785
+ const waitUntilDone = new Promise < void > ( ( _resolve ) => {
786
+ resolve = _resolve
787
+ registeredId
788
+ . done ( )
789
+ . catch ( ( ) => {
790
+ // Ignore errors
791
+ } )
792
+ . finally ( ( ) => resolve ( ) )
793
+ } )
794
+ waitingOn . set ( registeredId . id , ( ) => resolve ( ) )
795
+
796
+ await waitUntilDone
797
+ waitingOn . delete ( registeredId . id )
729
798
} )
730
799
731
800
const afterLoad = ( ) => {
732
- if ( closed ) return
801
+ if ( cancelled ) return
733
802
if (
734
803
registeredIds . length > 0 &&
735
804
registeredIds . every ( ( registeredId ) =>
@@ -740,22 +809,26 @@ async function createDepsOptimizer(
740
809
}
741
810
742
811
if ( registeredIds . length > 0 ) {
743
- runOptimizerWhenIdle ( )
812
+ callOnCrawlEndWhenIdle ( )
744
813
} else {
745
- onCrawlEnd ( )
814
+ callOnCrawlEnd ( )
746
815
}
747
816
}
748
817
749
- const results = await Promise . allSettled ( donePromises )
750
- if (
751
- registeredIds . length > 0 ||
752
- results . some ( ( result ) => result . status === 'rejected' )
753
- ) {
818
+ await Promise . allSettled ( donePromises )
819
+ if ( registeredIds . length > 0 ) {
754
820
afterLoad ( )
755
821
} else {
756
822
setTimeout ( afterLoad , runOptimizerIfIdleAfterMs )
757
823
}
758
824
}
825
+
826
+ return {
827
+ ensureFirstRun,
828
+ registerWorkersSource,
829
+ delayDepsOptimizerUntil,
830
+ cancel,
831
+ }
759
832
}
760
833
761
834
async function createDevSsrDepsOptimizer (
0 commit comments