@@ -4,6 +4,7 @@ import { createControlledPromise, isPromise } from './utils'
4
4
import { isNotFound } from './not-found'
5
5
import { rootRouteId } from './root'
6
6
import { isRedirect } from './redirect'
7
+ import type { Awaitable } from './utils'
7
8
import type { NotFoundError } from './not-found'
8
9
import type { ParsedLocation } from './location'
9
10
import type {
@@ -34,7 +35,7 @@ type InnerLoadContext = {
34
35
onReady ?: ( ) => Promise < void >
35
36
sync ?: boolean
36
37
/** mutable state, scoped to a `loadMatches` call */
37
- matchPromises : Array < Promise < AnyRouteMatch > >
38
+ matchPromises : Array < Awaitable < AnyRouteMatch > >
38
39
}
39
40
40
41
const triggerOnReady = ( inner : InnerLoadContext ) : void | Promise < void > => {
@@ -703,10 +704,10 @@ const runLoader = async (
703
704
}
704
705
}
705
706
706
- const loadRouteMatch = async (
707
+ const loadRouteMatch = (
707
708
inner : InnerLoadContext ,
708
709
index : number ,
709
- ) : Promise < AnyRouteMatch > => {
710
+ ) : Awaitable < AnyRouteMatch > => {
710
711
const { id : matchId , routeId } = inner . matches [ index ] !
711
712
let loaderShouldRunAsync = false
712
713
let loaderIsRunningAsync = false
@@ -716,121 +717,140 @@ const loadRouteMatch = async (
716
717
if ( inner . router . isServer ) {
717
718
const headResult = executeHead ( inner , matchId , route )
718
719
if ( headResult ) {
719
- const head = await headResult
720
- inner . updateMatch ( matchId , ( prev ) => ( {
721
- ...prev ,
722
- ...head ,
723
- } ) )
720
+ return headResult . then ( ( head ) => {
721
+ inner . updateMatch ( matchId , ( prev ) => ( {
722
+ ...prev ,
723
+ ...head ,
724
+ } ) )
725
+ return inner . router . getMatch ( matchId ) !
726
+ } )
724
727
}
725
728
return inner . router . getMatch ( matchId ) !
726
729
}
727
- } else {
728
- const prevMatch = inner . router . getMatch ( matchId ) !
729
- // there is a loaderPromise, so we are in the middle of a load
730
- if ( prevMatch . _nonReactive . loaderPromise ) {
731
- // do not block if we already have stale data we can show
732
- // but only if the ongoing load is not a preload since error handling is different for preloads
733
- // and we don't want to swallow errors
734
- if ( prevMatch . status === 'success' && ! inner . sync && ! prevMatch . preload ) {
735
- return prevMatch
736
- }
737
- await prevMatch . _nonReactive . loaderPromise
730
+ return settleLoadRouteMatch ( )
731
+ }
732
+
733
+ const prevMatch = inner . router . getMatch ( matchId ) !
734
+
735
+ // there is a loaderPromise, so we are in the middle of a load
736
+ if ( prevMatch . _nonReactive . loaderPromise ) {
737
+ // do not block if we already have stale data we can show
738
+ // but only if the ongoing load is not a preload since error handling is different for preloads
739
+ // and we don't want to swallow errors
740
+ if ( prevMatch . status === 'success' && ! inner . sync && ! prevMatch . preload ) {
741
+ return prevMatch
742
+ }
743
+ return prevMatch . _nonReactive . loaderPromise . then ( ( ) => {
738
744
const match = inner . router . getMatch ( matchId ) !
739
745
if ( match . error ) {
740
746
handleRedirectAndNotFound ( inner , match , match . error )
741
747
}
742
- } else {
743
- // This is where all of the stale-while-revalidate magic happens
744
- const age = Date . now ( ) - prevMatch . updatedAt
745
-
746
- const preload = resolvePreload ( inner , matchId )
747
-
748
- const staleAge = preload
749
- ? ( route . options . preloadStaleTime ??
750
- inner . router . options . defaultPreloadStaleTime ??
751
- 30_000 ) // 30 seconds for preloads by default
752
- : ( route . options . staleTime ??
753
- inner . router . options . defaultStaleTime ??
754
- 0 )
755
-
756
- const shouldReloadOption = route . options . shouldReload
757
-
758
- // Default to reloading the route all the time
759
- // Allow shouldReload to get the last say,
760
- // if provided.
761
- const shouldReload =
762
- typeof shouldReloadOption === 'function'
763
- ? shouldReloadOption ( getLoaderContext ( inner , matchId , index , route ) )
764
- : shouldReloadOption
765
-
766
- const nextPreload =
767
- ! ! preload && ! inner . router . state . matches . some ( ( d ) => d . id === matchId )
768
- const match = inner . router . getMatch ( matchId ) !
769
- match . _nonReactive . loaderPromise = createControlledPromise < void > ( )
770
- if ( nextPreload !== match . preload ) {
771
- inner . updateMatch ( matchId , ( prev ) => ( {
772
- ...prev ,
773
- preload : nextPreload ,
774
- } ) )
775
- }
748
+ return settleLoadRouteMatch ( )
749
+ } )
750
+ }
751
+
752
+ // This is where all of the stale-while-revalidate magic happens
753
+ const age = Date . now ( ) - prevMatch . updatedAt
754
+
755
+ const preload = resolvePreload ( inner , matchId )
756
+
757
+ const staleAge = preload
758
+ ? ( route . options . preloadStaleTime ??
759
+ inner . router . options . defaultPreloadStaleTime ??
760
+ 30_000 ) // 30 seconds for preloads by default
761
+ : ( route . options . staleTime ?? inner . router . options . defaultStaleTime ?? 0 )
762
+
763
+ const shouldReloadOption = route . options . shouldReload
764
+
765
+ // Default to reloading the route all the time
766
+ // Allow shouldReload to get the last say,
767
+ // if provided.
768
+ const shouldReload =
769
+ typeof shouldReloadOption === 'function'
770
+ ? shouldReloadOption ( getLoaderContext ( inner , matchId , index , route ) )
771
+ : shouldReloadOption
772
+
773
+ const nextPreload =
774
+ ! ! preload && ! inner . router . state . matches . some ( ( d ) => d . id === matchId )
775
+ const match = inner . router . getMatch ( matchId ) !
776
+ match . _nonReactive . loaderPromise = createControlledPromise < void > ( )
777
+ if ( nextPreload !== match . preload ) {
778
+ inner . updateMatch ( matchId , ( prev ) => ( {
779
+ ...prev ,
780
+ preload : nextPreload ,
781
+ } ) )
782
+ }
776
783
777
- // If the route is successful and still fresh, just resolve
778
- const { status, invalid } = match
779
- loaderShouldRunAsync =
780
- status === 'success' && ( invalid || ( shouldReload ?? age > staleAge ) )
781
- if ( preload && route . options . preload === false ) {
782
- // Do nothing
783
- } else if ( loaderShouldRunAsync && ! inner . sync ) {
784
- loaderIsRunningAsync = true
785
- ; ( async ( ) => {
786
- try {
787
- await runLoader ( inner , matchId , index , route )
788
- const match = inner . router . getMatch ( matchId ) !
789
- match . _nonReactive . loaderPromise ?. resolve ( )
790
- match . _nonReactive . loadPromise ?. resolve ( )
791
- match . _nonReactive . loaderPromise = undefined
792
- } catch ( err ) {
793
- if ( isRedirect ( err ) ) {
794
- await inner . router . navigate ( err . options )
795
- }
796
- }
797
- } ) ( )
798
- } else if ( status !== 'success' || ( loaderShouldRunAsync && inner . sync ) ) {
784
+ if ( preload && route . options . preload === false ) {
785
+ // Do nothing
786
+ return settleLoadRouteMatch ( )
787
+ }
788
+
789
+ // If the route is successful and still fresh, just resolve
790
+ const { status, invalid } = match
791
+ loaderShouldRunAsync =
792
+ status === 'success' && ( invalid || ( shouldReload ?? age > staleAge ) )
793
+ if ( loaderShouldRunAsync && ! inner . sync ) {
794
+ loaderIsRunningAsync = true
795
+ ; ( async ( ) => {
796
+ try {
799
797
await runLoader ( inner , matchId , index , route )
800
- } else {
801
- // if the loader did not run, still update head.
802
- // reason: parent's beforeLoad may have changed the route context
803
- // and only now do we know the route context (and that the loader would not run)
804
- const headResult = executeHead ( inner , matchId , route )
805
- if ( headResult ) {
806
- const head = await headResult
807
- inner . updateMatch ( matchId , ( prev ) => ( {
808
- ...prev ,
809
- ...head ,
810
- } ) )
798
+ const match = inner . router . getMatch ( matchId ) !
799
+ match . _nonReactive . loaderPromise ?. resolve ( )
800
+ match . _nonReactive . loadPromise ?. resolve ( )
801
+ match . _nonReactive . loaderPromise = undefined
802
+ } catch ( err ) {
803
+ if ( isRedirect ( err ) ) {
804
+ await inner . router . navigate ( err . options )
811
805
}
812
806
}
813
- }
807
+ } ) ( )
808
+ return settleLoadRouteMatch ( )
814
809
}
815
- const match = inner . router . getMatch ( matchId ) !
816
- if ( ! loaderIsRunningAsync ) {
817
- match . _nonReactive . loaderPromise ?. resolve ( )
818
- match . _nonReactive . loadPromise ?. resolve ( )
810
+
811
+ if ( status !== 'success' || ( loaderShouldRunAsync && inner . sync ) ) {
812
+ return runLoader ( inner , matchId , index , route ) . then ( settleLoadRouteMatch )
819
813
}
820
814
821
- clearTimeout ( match . _nonReactive . pendingTimeout )
822
- match . _nonReactive . pendingTimeout = undefined
823
- if ( ! loaderIsRunningAsync ) match . _nonReactive . loaderPromise = undefined
824
- match . _nonReactive . dehydrated = undefined
825
- const nextIsFetching = loaderIsRunningAsync ? match . isFetching : false
826
- if ( nextIsFetching !== match . isFetching || match . invalid !== false ) {
827
- inner . updateMatch ( matchId , ( prev ) => ( {
828
- ...prev ,
829
- isFetching : nextIsFetching ,
830
- invalid : false ,
831
- } ) )
832
- return inner . router . getMatch ( matchId ) !
833
- } else {
815
+ // if the loader did not run, still update head.
816
+ // reason: parent's beforeLoad may have changed the route context
817
+ // and only now do we know the route context (and that the loader would not run)
818
+ const headResult = executeHead ( inner , matchId , route )
819
+ if ( headResult ) {
820
+ return headResult . then ( ( head ) => {
821
+ inner . updateMatch ( matchId , ( prev ) => ( {
822
+ ...prev ,
823
+ ...head ,
824
+ } ) )
825
+ return settleLoadRouteMatch ( )
826
+ } )
827
+ }
828
+
829
+ return settleLoadRouteMatch ( )
830
+
831
+ function settleLoadRouteMatch ( ) {
832
+ const match = inner . router . getMatch ( matchId ) !
833
+
834
+ if ( ! loaderIsRunningAsync ) {
835
+ match . _nonReactive . loaderPromise ?. resolve ( )
836
+ match . _nonReactive . loadPromise ?. resolve ( )
837
+ match . _nonReactive . loaderPromise = undefined
838
+ }
839
+
840
+ clearTimeout ( match . _nonReactive . pendingTimeout )
841
+ match . _nonReactive . pendingTimeout = undefined
842
+ match . _nonReactive . dehydrated = undefined
843
+
844
+ const nextIsFetching = loaderIsRunningAsync ? match . isFetching : false
845
+ if ( nextIsFetching !== match . isFetching || match . invalid !== false ) {
846
+ inner . updateMatch ( matchId , ( prev ) => ( {
847
+ ...prev ,
848
+ isFetching : nextIsFetching ,
849
+ invalid : false ,
850
+ } ) )
851
+ return inner . router . getMatch ( matchId ) !
852
+ }
853
+
834
854
return match
835
855
}
836
856
}
@@ -866,10 +886,13 @@ export async function loadMatches(arg: {
866
886
867
887
// Execute all loaders in parallel
868
888
const max = inner . firstBadMatchIndex ?? inner . matches . length
889
+ let hasPromises = false
869
890
for ( let i = 0 ; i < max ; i ++ ) {
870
- inner . matchPromises . push ( loadRouteMatch ( inner , i ) )
891
+ const result = loadRouteMatch ( inner , i )
892
+ inner . matchPromises . push ( result )
893
+ if ( ! hasPromises && isPromise ( result ) ) hasPromises = true
871
894
}
872
- await Promise . all ( inner . matchPromises )
895
+ if ( hasPromises ) await Promise . all ( inner . matchPromises )
873
896
874
897
const readyPromise = triggerOnReady ( inner )
875
898
if ( isPromise ( readyPromise ) ) await readyPromise
0 commit comments