@tanstack/router-core 1.166.2 → 1.166.6
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 +57 -23
- package/dist/cjs/load-matches.cjs.map +1 -1
- package/dist/cjs/load-matches.d.cts +4 -2
- package/dist/cjs/router.cjs +2 -0
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/esm/load-matches.d.ts +4 -2
- package/dist/esm/load-matches.js +57 -23
- package/dist/esm/load-matches.js.map +1 -1
- package/dist/esm/router.js +2 -0
- package/dist/esm/router.js.map +1 -1
- package/package.json +1 -1
- package/src/load-matches.ts +109 -34
- package/src/router.ts +2 -0
package/package.json
CHANGED
package/src/load-matches.ts
CHANGED
|
@@ -33,6 +33,7 @@ type InnerLoadContext = {
|
|
|
33
33
|
updateMatch: UpdateMatchFn
|
|
34
34
|
matches: Array<AnyRouteMatch>
|
|
35
35
|
preload?: boolean
|
|
36
|
+
forceStaleReload?: boolean
|
|
36
37
|
onReady?: () => Promise<void>
|
|
37
38
|
sync?: boolean
|
|
38
39
|
}
|
|
@@ -166,7 +167,10 @@ const shouldSkipLoader = (
|
|
|
166
167
|
inner: InnerLoadContext,
|
|
167
168
|
matchId: string,
|
|
168
169
|
): boolean => {
|
|
169
|
-
const match = inner.router.getMatch(matchId)
|
|
170
|
+
const match = inner.router.getMatch(matchId)
|
|
171
|
+
if (!match) {
|
|
172
|
+
return true
|
|
173
|
+
}
|
|
170
174
|
// upon hydration, we skip the loader if the match has been dehydrated on the server
|
|
171
175
|
if (!(isServer ?? inner.router.isServer) && match._nonReactive.dehydrated) {
|
|
172
176
|
return true
|
|
@@ -179,6 +183,21 @@ const shouldSkipLoader = (
|
|
|
179
183
|
return false
|
|
180
184
|
}
|
|
181
185
|
|
|
186
|
+
const syncMatchContext = (
|
|
187
|
+
inner: InnerLoadContext,
|
|
188
|
+
matchId: string,
|
|
189
|
+
index: number,
|
|
190
|
+
): void => {
|
|
191
|
+
const nextContext = buildMatchContext(inner, index)
|
|
192
|
+
|
|
193
|
+
inner.updateMatch(matchId, (prev) => {
|
|
194
|
+
return {
|
|
195
|
+
...prev,
|
|
196
|
+
context: nextContext,
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
|
|
182
201
|
const handleSerialError = (
|
|
183
202
|
inner: InnerLoadContext,
|
|
184
203
|
index: number,
|
|
@@ -479,8 +498,6 @@ const executeBeforeLoad = (
|
|
|
479
498
|
|
|
480
499
|
batch(() => {
|
|
481
500
|
pending()
|
|
482
|
-
// Only store __beforeLoadContext here, don't update context yet
|
|
483
|
-
// Context will be updated in loadRouteMatch after loader completes
|
|
484
501
|
inner.updateMatch(matchId, (prev) => ({
|
|
485
502
|
...prev,
|
|
486
503
|
__beforeLoadContext: beforeLoadContext,
|
|
@@ -736,6 +753,10 @@ const runLoader = async (
|
|
|
736
753
|
onErrorError,
|
|
737
754
|
)
|
|
738
755
|
}
|
|
756
|
+
if (!isRedirect(error) && !isNotFound(error)) {
|
|
757
|
+
await loadRouteChunk(route, ['errorComponent'])
|
|
758
|
+
}
|
|
759
|
+
|
|
739
760
|
inner.updateMatch(matchId, (prev) => ({
|
|
740
761
|
...prev,
|
|
741
762
|
error,
|
|
@@ -762,6 +783,7 @@ const loadRouteMatch = async (
|
|
|
762
783
|
async function handleLoader(
|
|
763
784
|
preload: boolean,
|
|
764
785
|
prevMatch: AnyRouteMatch,
|
|
786
|
+
previousRouteMatchId: string | undefined,
|
|
765
787
|
match: AnyRouteMatch,
|
|
766
788
|
route: AnyRoute,
|
|
767
789
|
) {
|
|
@@ -787,8 +809,15 @@ const loadRouteMatch = async (
|
|
|
787
809
|
|
|
788
810
|
// If the route is successful and still fresh, just resolve
|
|
789
811
|
const { status, invalid } = match
|
|
812
|
+
const staleMatchShouldReload =
|
|
813
|
+
age > staleAge &&
|
|
814
|
+
(!!inner.forceStaleReload ||
|
|
815
|
+
match.cause === 'enter' ||
|
|
816
|
+
(previousRouteMatchId !== undefined &&
|
|
817
|
+
previousRouteMatchId !== match.id))
|
|
790
818
|
loaderShouldRunAsync =
|
|
791
|
-
status === 'success' &&
|
|
819
|
+
status === 'success' &&
|
|
820
|
+
(invalid || (shouldReload ?? staleMatchShouldReload))
|
|
792
821
|
if (preload && route.options.preload === false) {
|
|
793
822
|
// Do nothing
|
|
794
823
|
} else if (loaderShouldRunAsync && !inner.sync) {
|
|
@@ -808,6 +837,8 @@ const loadRouteMatch = async (
|
|
|
808
837
|
})()
|
|
809
838
|
} else if (status !== 'success' || (loaderShouldRunAsync && inner.sync)) {
|
|
810
839
|
await runLoader(inner, matchPromises, matchId, index, route)
|
|
840
|
+
} else {
|
|
841
|
+
syncMatchContext(inner, matchId, index)
|
|
811
842
|
}
|
|
812
843
|
}
|
|
813
844
|
|
|
@@ -817,11 +848,22 @@ const loadRouteMatch = async (
|
|
|
817
848
|
const route = inner.router.looseRoutesById[routeId]!
|
|
818
849
|
|
|
819
850
|
if (shouldSkipLoader(inner, matchId)) {
|
|
851
|
+
const match = inner.router.getMatch(matchId)
|
|
852
|
+
if (!match) {
|
|
853
|
+
return inner.matches[index]!
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
syncMatchContext(inner, matchId, index)
|
|
857
|
+
|
|
820
858
|
if (isServer ?? inner.router.isServer) {
|
|
821
859
|
return inner.router.getMatch(matchId)!
|
|
822
860
|
}
|
|
823
861
|
} else {
|
|
824
862
|
const prevMatch = inner.router.getMatch(matchId)! // This is where all of the stale-while-revalidate magic happens
|
|
863
|
+
const previousRouteMatchId =
|
|
864
|
+
inner.router.state.matches[index]?.routeId === routeId
|
|
865
|
+
? inner.router.state.matches[index]!.id
|
|
866
|
+
: inner.router.state.matches.find((d) => d.routeId === routeId)?.id
|
|
825
867
|
const preload = resolvePreload(inner, matchId)
|
|
826
868
|
|
|
827
869
|
// there is a loaderPromise, so we are in the middle of a load
|
|
@@ -840,7 +882,13 @@ const loadRouteMatch = async (
|
|
|
840
882
|
}
|
|
841
883
|
|
|
842
884
|
if (match.status === 'pending') {
|
|
843
|
-
await handleLoader(
|
|
885
|
+
await handleLoader(
|
|
886
|
+
preload,
|
|
887
|
+
prevMatch,
|
|
888
|
+
previousRouteMatchId,
|
|
889
|
+
match,
|
|
890
|
+
route,
|
|
891
|
+
)
|
|
844
892
|
}
|
|
845
893
|
} else {
|
|
846
894
|
const nextPreload =
|
|
@@ -854,7 +902,7 @@ const loadRouteMatch = async (
|
|
|
854
902
|
}))
|
|
855
903
|
}
|
|
856
904
|
|
|
857
|
-
await handleLoader(preload, prevMatch, match, route)
|
|
905
|
+
await handleLoader(preload, prevMatch, previousRouteMatchId, match, route)
|
|
858
906
|
}
|
|
859
907
|
}
|
|
860
908
|
const match = inner.router.getMatch(matchId)!
|
|
@@ -886,6 +934,7 @@ export async function loadMatches(arg: {
|
|
|
886
934
|
location: ParsedLocation
|
|
887
935
|
matches: Array<AnyRouteMatch>
|
|
888
936
|
preload?: boolean
|
|
937
|
+
forceStaleReload?: boolean
|
|
889
938
|
onReady?: () => Promise<void>
|
|
890
939
|
updateMatch: UpdateMatchFn
|
|
891
940
|
sync?: boolean
|
|
@@ -1031,7 +1080,7 @@ export async function loadMatches(arg: {
|
|
|
1031
1080
|
|
|
1032
1081
|
// Ensure the rendering boundary route chunk (and its lazy components, including
|
|
1033
1082
|
// lazy notFoundComponent) is loaded before we continue to head execution/render.
|
|
1034
|
-
await loadRouteChunk(boundaryRoute)
|
|
1083
|
+
await loadRouteChunk(boundaryRoute, ['notFoundComponent'])
|
|
1035
1084
|
} else if (!inner.preload) {
|
|
1036
1085
|
// Clear stale root global-not-found state on normal navigations that do not
|
|
1037
1086
|
// throw notFound. This must live here (instead of only in runLoader success)
|
|
@@ -1061,7 +1110,7 @@ export async function loadMatches(arg: {
|
|
|
1061
1110
|
inner.router.looseRoutesById[
|
|
1062
1111
|
inner.matches[inner.firstBadMatchIndex]!.routeId
|
|
1063
1112
|
]!
|
|
1064
|
-
await loadRouteChunk(errorRoute)
|
|
1113
|
+
await loadRouteChunk(errorRoute, ['errorComponent'])
|
|
1065
1114
|
}
|
|
1066
1115
|
|
|
1067
1116
|
// serially execute heads once after loaders/notFound handling, ensuring
|
|
@@ -1100,7 +1149,29 @@ export async function loadMatches(arg: {
|
|
|
1100
1149
|
return inner.matches
|
|
1101
1150
|
}
|
|
1102
1151
|
|
|
1103
|
-
export
|
|
1152
|
+
export type RouteComponentType =
|
|
1153
|
+
| 'component'
|
|
1154
|
+
| 'errorComponent'
|
|
1155
|
+
| 'pendingComponent'
|
|
1156
|
+
| 'notFoundComponent'
|
|
1157
|
+
|
|
1158
|
+
function preloadRouteComponents(
|
|
1159
|
+
route: AnyRoute,
|
|
1160
|
+
componentTypesToLoad: Array<RouteComponentType>,
|
|
1161
|
+
): Promise<void> | undefined {
|
|
1162
|
+
const preloads = componentTypesToLoad
|
|
1163
|
+
.map((type) => (route.options[type] as any)?.preload?.())
|
|
1164
|
+
.filter(Boolean)
|
|
1165
|
+
|
|
1166
|
+
if (preloads.length === 0) return undefined
|
|
1167
|
+
|
|
1168
|
+
return Promise.all(preloads) as any as Promise<void>
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
export function loadRouteChunk(
|
|
1172
|
+
route: AnyRoute,
|
|
1173
|
+
componentTypesToLoad: Array<RouteComponentType> = componentTypes,
|
|
1174
|
+
) {
|
|
1104
1175
|
if (!route._lazyLoaded && route._lazyPromise === undefined) {
|
|
1105
1176
|
if (route.lazyFn) {
|
|
1106
1177
|
route._lazyPromise = route.lazyFn().then((lazyRoute) => {
|
|
@@ -1115,30 +1186,34 @@ export async function loadRouteChunk(route: AnyRoute) {
|
|
|
1115
1186
|
}
|
|
1116
1187
|
}
|
|
1117
1188
|
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1189
|
+
const runAfterLazy = () =>
|
|
1190
|
+
route._componentsLoaded
|
|
1191
|
+
? undefined
|
|
1192
|
+
: componentTypesToLoad === componentTypes
|
|
1193
|
+
? (() => {
|
|
1194
|
+
if (route._componentsPromise === undefined) {
|
|
1195
|
+
const componentsPromise = preloadRouteComponents(
|
|
1196
|
+
route,
|
|
1197
|
+
componentTypes,
|
|
1198
|
+
)
|
|
1199
|
+
|
|
1200
|
+
if (componentsPromise) {
|
|
1201
|
+
route._componentsPromise = componentsPromise.then(() => {
|
|
1202
|
+
route._componentsLoaded = true
|
|
1203
|
+
route._componentsPromise = undefined // gc promise, we won't need it anymore
|
|
1204
|
+
})
|
|
1205
|
+
} else {
|
|
1206
|
+
route._componentsLoaded = true
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
return route._componentsPromise
|
|
1211
|
+
})()
|
|
1212
|
+
: preloadRouteComponents(route, componentTypesToLoad)
|
|
1213
|
+
|
|
1214
|
+
return route._lazyPromise
|
|
1215
|
+
? route._lazyPromise.then(runAfterLazy)
|
|
1216
|
+
: runAfterLazy()
|
|
1142
1217
|
}
|
|
1143
1218
|
|
|
1144
1219
|
function makeMaybe<TValue, TError>(
|
|
@@ -1160,7 +1235,7 @@ export function routeNeedsPreload(route: AnyRoute) {
|
|
|
1160
1235
|
return false
|
|
1161
1236
|
}
|
|
1162
1237
|
|
|
1163
|
-
export const componentTypes = [
|
|
1238
|
+
export const componentTypes: Array<RouteComponentType> = [
|
|
1164
1239
|
'component',
|
|
1165
1240
|
'errorComponent',
|
|
1166
1241
|
'pendingComponent',
|
package/src/router.ts
CHANGED
|
@@ -2364,6 +2364,7 @@ export class RouterCore<
|
|
|
2364
2364
|
let redirect: AnyRedirect | undefined
|
|
2365
2365
|
let notFound: NotFoundError | undefined
|
|
2366
2366
|
let loadPromise: Promise<void>
|
|
2367
|
+
const previousLocation = this.state.resolvedLocation ?? this.state.location
|
|
2367
2368
|
|
|
2368
2369
|
// eslint-disable-next-line prefer-const
|
|
2369
2370
|
loadPromise = new Promise<void>((resolve) => {
|
|
@@ -2394,6 +2395,7 @@ export class RouterCore<
|
|
|
2394
2395
|
await loadMatches({
|
|
2395
2396
|
router: this,
|
|
2396
2397
|
sync: opts?.sync,
|
|
2398
|
+
forceStaleReload: previousLocation.href === next.href,
|
|
2397
2399
|
matches: this.state.pendingMatches as Array<AnyRouteMatch>,
|
|
2398
2400
|
location: next,
|
|
2399
2401
|
updateMatch: this.updateMatch,
|