@tanstack/router-core 1.131.21 → 1.131.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/load-matches.cjs +70 -74
- package/dist/cjs/load-matches.cjs.map +1 -1
- package/dist/esm/load-matches.js +70 -74
- package/dist/esm/load-matches.js.map +1 -1
- package/package.json +1 -1
- package/src/load-matches.ts +102 -112
package/src/load-matches.ts
CHANGED
|
@@ -5,7 +5,6 @@ import { isNotFound } from './not-found'
|
|
|
5
5
|
import { rootRouteId } from './root'
|
|
6
6
|
import { isRedirect } from './redirect'
|
|
7
7
|
import type { NotFoundError } from './not-found'
|
|
8
|
-
import type { ControlledPromise } from './utils'
|
|
9
8
|
import type { ParsedLocation } from './location'
|
|
10
9
|
import type {
|
|
11
10
|
AnyRoute,
|
|
@@ -56,12 +55,6 @@ const _handleNotFound = (inner: InnerLoadContext, err: NotFoundError) => {
|
|
|
56
55
|
// First check if a specific route is requested to show the error
|
|
57
56
|
const routeCursor =
|
|
58
57
|
inner.router.routesById[err.routeId ?? ''] ?? inner.router.routeTree
|
|
59
|
-
const matchesByRouteId: Record<string, AnyRouteMatch> = {}
|
|
60
|
-
|
|
61
|
-
// Setup routesByRouteId object for quick access
|
|
62
|
-
for (const match of inner.matches) {
|
|
63
|
-
matchesByRouteId[match.routeId] = match
|
|
64
|
-
}
|
|
65
58
|
|
|
66
59
|
// Ensure a NotFoundComponent exists on the route
|
|
67
60
|
if (
|
|
@@ -80,7 +73,7 @@ const _handleNotFound = (inner: InnerLoadContext, err: NotFoundError) => {
|
|
|
80
73
|
)
|
|
81
74
|
|
|
82
75
|
// Find the match for this route
|
|
83
|
-
const matchForRoute =
|
|
76
|
+
const matchForRoute = inner.matches.find((m) => m.routeId === routeCursor.id)
|
|
84
77
|
|
|
85
78
|
invariant(matchForRoute, 'Could not find match for route: ' + routeCursor.id)
|
|
86
79
|
|
|
@@ -246,7 +239,7 @@ const isBeforeLoadSsr = (
|
|
|
246
239
|
existingMatch.ssr = parentOverride(route.options.ssr)
|
|
247
240
|
return
|
|
248
241
|
}
|
|
249
|
-
const { search, params } =
|
|
242
|
+
const { search, params } = existingMatch
|
|
250
243
|
|
|
251
244
|
const ssrFnContext: SsrContextOptions<any, any, any> = {
|
|
252
245
|
search: makeMaybe(search, existingMatch.searchError),
|
|
@@ -280,8 +273,8 @@ const setupPendingTimeout = (
|
|
|
280
273
|
inner: InnerLoadContext,
|
|
281
274
|
matchId: string,
|
|
282
275
|
route: AnyRoute,
|
|
276
|
+
match: AnyRouteMatch,
|
|
283
277
|
): void => {
|
|
284
|
-
const match = inner.router.getMatch(matchId)!
|
|
285
278
|
if (match._nonReactive.pendingTimeout !== undefined) return
|
|
286
279
|
|
|
287
280
|
const pendingMs =
|
|
@@ -324,20 +317,20 @@ const shouldExecuteBeforeLoad = (
|
|
|
324
317
|
)
|
|
325
318
|
return true
|
|
326
319
|
|
|
327
|
-
setupPendingTimeout(inner, matchId, route)
|
|
320
|
+
setupPendingTimeout(inner, matchId, route, existingMatch)
|
|
328
321
|
|
|
329
322
|
const then = () => {
|
|
330
|
-
let
|
|
323
|
+
let result = true
|
|
331
324
|
const match = inner.router.getMatch(matchId)!
|
|
332
325
|
if (match.status === 'error') {
|
|
333
|
-
|
|
326
|
+
result = true
|
|
334
327
|
} else if (
|
|
335
328
|
match.preload &&
|
|
336
329
|
(match.status === 'redirected' || match.status === 'notFound')
|
|
337
330
|
) {
|
|
338
331
|
handleRedirectAndNotFound(inner, match, match.error)
|
|
339
332
|
}
|
|
340
|
-
return
|
|
333
|
+
return result
|
|
341
334
|
}
|
|
342
335
|
|
|
343
336
|
// Wait for the beforeLoad to resolve before we continue
|
|
@@ -354,7 +347,6 @@ const executeBeforeLoad = (
|
|
|
354
347
|
): void | Promise<void> => {
|
|
355
348
|
const match = inner.router.getMatch(matchId)!
|
|
356
349
|
|
|
357
|
-
match._nonReactive.beforeLoadPromise = createControlledPromise<void>()
|
|
358
350
|
// explicitly capture the previous loadPromise
|
|
359
351
|
const prevLoadPromise = match._nonReactive.loadPromise
|
|
360
352
|
match._nonReactive.loadPromise = createControlledPromise<void>(() => {
|
|
@@ -371,7 +363,7 @@ const executeBeforeLoad = (
|
|
|
371
363
|
handleSerialError(inner, index, searchError, 'VALIDATE_SEARCH')
|
|
372
364
|
}
|
|
373
365
|
|
|
374
|
-
setupPendingTimeout(inner, matchId, route)
|
|
366
|
+
setupPendingTimeout(inner, matchId, route, match)
|
|
375
367
|
|
|
376
368
|
const abortController = new AbortController()
|
|
377
369
|
|
|
@@ -415,6 +407,8 @@ const executeBeforeLoad = (
|
|
|
415
407
|
return
|
|
416
408
|
}
|
|
417
409
|
|
|
410
|
+
match._nonReactive.beforeLoadPromise = createControlledPromise<void>()
|
|
411
|
+
|
|
418
412
|
const { search, params, cause } = match
|
|
419
413
|
const preload = resolvePreload(inner, matchId)
|
|
420
414
|
const beforeLoadFnContext: BeforeLoadContextOptions<any, any, any, any, any> =
|
|
@@ -510,8 +504,8 @@ const handleBeforeLoad = (
|
|
|
510
504
|
: execute(shouldExecuteBeforeLoadResult)
|
|
511
505
|
}
|
|
512
506
|
|
|
513
|
-
const execute = (
|
|
514
|
-
if (
|
|
507
|
+
const execute = (shouldExec: boolean) => {
|
|
508
|
+
if (shouldExec) {
|
|
515
509
|
// If we are not in the middle of a load OR the previous load failed, start it
|
|
516
510
|
return executeBeforeLoad(inner, matchId, index, route)
|
|
517
511
|
}
|
|
@@ -567,14 +561,6 @@ const executeHead = (
|
|
|
567
561
|
})
|
|
568
562
|
}
|
|
569
563
|
|
|
570
|
-
const potentialPendingMinPromise = (
|
|
571
|
-
inner: InnerLoadContext,
|
|
572
|
-
matchId: string,
|
|
573
|
-
): void | ControlledPromise<void> => {
|
|
574
|
-
const latestMatch = inner.router.getMatch(matchId)!
|
|
575
|
-
return latestMatch._nonReactive.minPendingPromise
|
|
576
|
-
}
|
|
577
|
-
|
|
578
564
|
const getLoaderContext = (
|
|
579
565
|
inner: InnerLoadContext,
|
|
580
566
|
matchId: string,
|
|
@@ -592,7 +578,7 @@ const getLoaderContext = (
|
|
|
592
578
|
deps: loaderDeps,
|
|
593
579
|
preload: !!preload,
|
|
594
580
|
parentMatchPromise,
|
|
595
|
-
abortController
|
|
581
|
+
abortController,
|
|
596
582
|
context,
|
|
597
583
|
location: inner.location,
|
|
598
584
|
navigate: (opts) =>
|
|
@@ -618,12 +604,11 @@ const runLoader = async (
|
|
|
618
604
|
// before committing to the match and resolving
|
|
619
605
|
// the loadPromise
|
|
620
606
|
|
|
607
|
+
const match = inner.router.getMatch(matchId)!
|
|
608
|
+
|
|
621
609
|
// Actually run the loader and handle the result
|
|
622
610
|
try {
|
|
623
|
-
if (
|
|
624
|
-
!inner.router.isServer ||
|
|
625
|
-
inner.router.getMatch(matchId)!.ssr === true
|
|
626
|
-
) {
|
|
611
|
+
if (!inner.router.isServer || match.ssr === true) {
|
|
627
612
|
loadRouteChunk(route)
|
|
628
613
|
}
|
|
629
614
|
|
|
@@ -641,7 +626,7 @@ const runLoader = async (
|
|
|
641
626
|
route.options.head ||
|
|
642
627
|
route.options.scripts ||
|
|
643
628
|
route.options.headers ||
|
|
644
|
-
|
|
629
|
+
match._nonReactive.minPendingPromise
|
|
645
630
|
)
|
|
646
631
|
|
|
647
632
|
if (willLoadSomething) {
|
|
@@ -675,7 +660,7 @@ const runLoader = async (
|
|
|
675
660
|
if (route._lazyPromise) await route._lazyPromise
|
|
676
661
|
const headResult = executeHead(inner, matchId, route)
|
|
677
662
|
const head = headResult ? await headResult : undefined
|
|
678
|
-
const pendingPromise =
|
|
663
|
+
const pendingPromise = match._nonReactive.minPendingPromise
|
|
679
664
|
if (pendingPromise) await pendingPromise
|
|
680
665
|
|
|
681
666
|
// Last but not least, wait for the the components
|
|
@@ -692,7 +677,7 @@ const runLoader = async (
|
|
|
692
677
|
} catch (e) {
|
|
693
678
|
let error = e
|
|
694
679
|
|
|
695
|
-
const pendingPromise =
|
|
680
|
+
const pendingPromise = match._nonReactive.minPendingPromise
|
|
696
681
|
if (pendingPromise) await pendingPromise
|
|
697
682
|
|
|
698
683
|
handleRedirectAndNotFound(inner, inner.router.getMatch(matchId), e)
|
|
@@ -744,7 +729,6 @@ const loadRouteMatch = async (
|
|
|
744
729
|
let loaderIsRunningAsync = false
|
|
745
730
|
const route = inner.router.looseRoutesById[routeId]!
|
|
746
731
|
|
|
747
|
-
const prevMatch = inner.router.getMatch(matchId)!
|
|
748
732
|
if (shouldSkipLoader(inner, matchId)) {
|
|
749
733
|
if (inner.router.isServer) {
|
|
750
734
|
const headResult = executeHead(inner, matchId, route)
|
|
@@ -757,88 +741,92 @@ const loadRouteMatch = async (
|
|
|
757
741
|
}
|
|
758
742
|
return inner.router.getMatch(matchId)!
|
|
759
743
|
}
|
|
760
|
-
}
|
|
761
|
-
// there is a loaderPromise, so we are in the middle of a load
|
|
762
|
-
else if (prevMatch._nonReactive.loaderPromise) {
|
|
763
|
-
// do not block if we already have stale data we can show
|
|
764
|
-
// but only if the ongoing load is not a preload since error handling is different for preloads
|
|
765
|
-
// and we don't want to swallow errors
|
|
766
|
-
if (prevMatch.status === 'success' && !inner.sync && !prevMatch.preload) {
|
|
767
|
-
return inner.router.getMatch(matchId)!
|
|
768
|
-
}
|
|
769
|
-
await prevMatch._nonReactive.loaderPromise
|
|
770
|
-
const match = inner.router.getMatch(matchId)!
|
|
771
|
-
if (match.error) {
|
|
772
|
-
handleRedirectAndNotFound(inner, match, match.error)
|
|
773
|
-
}
|
|
774
744
|
} else {
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
// Allow shouldReload to get the last say,
|
|
790
|
-
// if provided.
|
|
791
|
-
const shouldReload =
|
|
792
|
-
typeof shouldReloadOption === 'function'
|
|
793
|
-
? shouldReloadOption(getLoaderContext(inner, matchId, index, route))
|
|
794
|
-
: shouldReloadOption
|
|
795
|
-
|
|
796
|
-
const nextPreload =
|
|
797
|
-
!!preload && !inner.router.state.matches.some((d) => d.id === matchId)
|
|
798
|
-
const match = inner.router.getMatch(matchId)!
|
|
799
|
-
match._nonReactive.loaderPromise = createControlledPromise<void>()
|
|
800
|
-
if (nextPreload !== match.preload) {
|
|
801
|
-
inner.updateMatch(matchId, (prev) => ({
|
|
802
|
-
...prev,
|
|
803
|
-
preload: nextPreload,
|
|
804
|
-
}))
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
// If the route is successful and still fresh, just resolve
|
|
808
|
-
const { status, invalid } = inner.router.getMatch(matchId)!
|
|
809
|
-
loaderShouldRunAsync =
|
|
810
|
-
status === 'success' && (invalid || (shouldReload ?? age > staleAge))
|
|
811
|
-
if (preload && route.options.preload === false) {
|
|
812
|
-
// Do nothing
|
|
813
|
-
} else if (loaderShouldRunAsync && !inner.sync) {
|
|
814
|
-
loaderIsRunningAsync = true
|
|
815
|
-
;(async () => {
|
|
816
|
-
try {
|
|
817
|
-
await runLoader(inner, matchId, index, route)
|
|
818
|
-
const match = inner.router.getMatch(matchId)!
|
|
819
|
-
match._nonReactive.loaderPromise?.resolve()
|
|
820
|
-
match._nonReactive.loadPromise?.resolve()
|
|
821
|
-
match._nonReactive.loaderPromise = undefined
|
|
822
|
-
} catch (err) {
|
|
823
|
-
if (isRedirect(err)) {
|
|
824
|
-
await inner.router.navigate(err.options)
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
})()
|
|
828
|
-
} else if (status !== 'success' || (loaderShouldRunAsync && inner.sync)) {
|
|
829
|
-
await runLoader(inner, matchId, index, route)
|
|
745
|
+
const prevMatch = inner.router.getMatch(matchId)!
|
|
746
|
+
// there is a loaderPromise, so we are in the middle of a load
|
|
747
|
+
if (prevMatch._nonReactive.loaderPromise) {
|
|
748
|
+
// do not block if we already have stale data we can show
|
|
749
|
+
// but only if the ongoing load is not a preload since error handling is different for preloads
|
|
750
|
+
// and we don't want to swallow errors
|
|
751
|
+
if (prevMatch.status === 'success' && !inner.sync && !prevMatch.preload) {
|
|
752
|
+
return prevMatch
|
|
753
|
+
}
|
|
754
|
+
await prevMatch._nonReactive.loaderPromise
|
|
755
|
+
const match = inner.router.getMatch(matchId)!
|
|
756
|
+
if (match.error) {
|
|
757
|
+
handleRedirectAndNotFound(inner, match, match.error)
|
|
758
|
+
}
|
|
830
759
|
} else {
|
|
831
|
-
//
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
const
|
|
835
|
-
|
|
836
|
-
|
|
760
|
+
// This is where all of the stale-while-revalidate magic happens
|
|
761
|
+
const age = Date.now() - prevMatch.updatedAt
|
|
762
|
+
|
|
763
|
+
const preload = resolvePreload(inner, matchId)
|
|
764
|
+
|
|
765
|
+
const staleAge = preload
|
|
766
|
+
? (route.options.preloadStaleTime ??
|
|
767
|
+
inner.router.options.defaultPreloadStaleTime ??
|
|
768
|
+
30_000) // 30 seconds for preloads by default
|
|
769
|
+
: (route.options.staleTime ??
|
|
770
|
+
inner.router.options.defaultStaleTime ??
|
|
771
|
+
0)
|
|
772
|
+
|
|
773
|
+
const shouldReloadOption = route.options.shouldReload
|
|
774
|
+
|
|
775
|
+
// Default to reloading the route all the time
|
|
776
|
+
// Allow shouldReload to get the last say,
|
|
777
|
+
// if provided.
|
|
778
|
+
const shouldReload =
|
|
779
|
+
typeof shouldReloadOption === 'function'
|
|
780
|
+
? shouldReloadOption(getLoaderContext(inner, matchId, index, route))
|
|
781
|
+
: shouldReloadOption
|
|
782
|
+
|
|
783
|
+
const nextPreload =
|
|
784
|
+
!!preload && !inner.router.state.matches.some((d) => d.id === matchId)
|
|
785
|
+
const match = inner.router.getMatch(matchId)!
|
|
786
|
+
match._nonReactive.loaderPromise = createControlledPromise<void>()
|
|
787
|
+
if (nextPreload !== match.preload) {
|
|
837
788
|
inner.updateMatch(matchId, (prev) => ({
|
|
838
789
|
...prev,
|
|
839
|
-
|
|
790
|
+
preload: nextPreload,
|
|
840
791
|
}))
|
|
841
792
|
}
|
|
793
|
+
|
|
794
|
+
// If the route is successful and still fresh, just resolve
|
|
795
|
+
const { status, invalid } = match
|
|
796
|
+
loaderShouldRunAsync =
|
|
797
|
+
status === 'success' && (invalid || (shouldReload ?? age > staleAge))
|
|
798
|
+
if (preload && route.options.preload === false) {
|
|
799
|
+
// Do nothing
|
|
800
|
+
} else if (loaderShouldRunAsync && !inner.sync) {
|
|
801
|
+
loaderIsRunningAsync = true
|
|
802
|
+
;(async () => {
|
|
803
|
+
try {
|
|
804
|
+
await runLoader(inner, matchId, index, route)
|
|
805
|
+
const match = inner.router.getMatch(matchId)!
|
|
806
|
+
match._nonReactive.loaderPromise?.resolve()
|
|
807
|
+
match._nonReactive.loadPromise?.resolve()
|
|
808
|
+
match._nonReactive.loaderPromise = undefined
|
|
809
|
+
} catch (err) {
|
|
810
|
+
if (isRedirect(err)) {
|
|
811
|
+
await inner.router.navigate(err.options)
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
})()
|
|
815
|
+
} else if (status !== 'success' || (loaderShouldRunAsync && inner.sync)) {
|
|
816
|
+
await runLoader(inner, matchId, index, route)
|
|
817
|
+
} else {
|
|
818
|
+
// if the loader did not run, still update head.
|
|
819
|
+
// reason: parent's beforeLoad may have changed the route context
|
|
820
|
+
// and only now do we know the route context (and that the loader would not run)
|
|
821
|
+
const headResult = executeHead(inner, matchId, route)
|
|
822
|
+
if (headResult) {
|
|
823
|
+
const head = await headResult
|
|
824
|
+
inner.updateMatch(matchId, (prev) => ({
|
|
825
|
+
...prev,
|
|
826
|
+
...head,
|
|
827
|
+
}))
|
|
828
|
+
}
|
|
829
|
+
}
|
|
842
830
|
}
|
|
843
831
|
}
|
|
844
832
|
const match = inner.router.getMatch(matchId)!
|
|
@@ -858,8 +846,10 @@ const loadRouteMatch = async (
|
|
|
858
846
|
isFetching: nextIsFetching,
|
|
859
847
|
invalid: false,
|
|
860
848
|
}))
|
|
849
|
+
return inner.router.getMatch(matchId)!
|
|
850
|
+
} else {
|
|
851
|
+
return match
|
|
861
852
|
}
|
|
862
|
-
return inner.router.getMatch(matchId)!
|
|
863
853
|
}
|
|
864
854
|
|
|
865
855
|
export async function loadMatches(arg: {
|