@tanstack/router-core 1.132.0-alpha.1 → 1.132.0-alpha.3
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/Matches.cjs.map +1 -1
- package/dist/cjs/Matches.d.cts +7 -9
- package/dist/cjs/index.cjs +8 -2
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +6 -2
- package/dist/cjs/load-matches.cjs +636 -0
- package/dist/cjs/load-matches.cjs.map +1 -0
- package/dist/cjs/load-matches.d.cts +16 -0
- package/dist/cjs/qss.cjs +19 -19
- package/dist/cjs/qss.cjs.map +1 -1
- package/dist/cjs/qss.d.cts +6 -4
- package/dist/cjs/redirect.cjs +3 -3
- package/dist/cjs/redirect.cjs.map +1 -1
- package/dist/cjs/route.cjs.map +1 -1
- package/dist/cjs/route.d.cts +0 -4
- package/dist/cjs/router.cjs +64 -632
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +14 -26
- package/dist/cjs/scroll-restoration.cjs +20 -25
- package/dist/cjs/scroll-restoration.cjs.map +1 -1
- package/dist/cjs/scroll-restoration.d.cts +0 -9
- package/dist/cjs/searchParams.cjs +7 -15
- package/dist/cjs/searchParams.cjs.map +1 -1
- package/dist/cjs/ssr/constants.cjs +5 -0
- package/dist/cjs/ssr/constants.cjs.map +1 -0
- package/dist/cjs/ssr/constants.d.cts +1 -0
- package/dist/cjs/ssr/{seroval-plugins.cjs → serializer/ShallowErrorPlugin.cjs} +2 -2
- package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs.map +1 -0
- package/dist/cjs/ssr/{seroval-plugins.d.cts → serializer/ShallowErrorPlugin.d.cts} +1 -2
- package/dist/cjs/ssr/serializer/seroval-plugins.cjs +11 -0
- package/dist/cjs/ssr/serializer/seroval-plugins.cjs.map +1 -0
- package/dist/cjs/ssr/serializer/seroval-plugins.d.cts +2 -0
- package/dist/cjs/ssr/serializer/transformer.cjs +50 -0
- package/dist/cjs/ssr/serializer/transformer.cjs.map +1 -0
- package/dist/cjs/ssr/serializer/transformer.d.cts +18 -0
- package/dist/cjs/ssr/ssr-client.cjs +53 -40
- package/dist/cjs/ssr/ssr-client.cjs.map +1 -1
- package/dist/cjs/ssr/ssr-client.d.cts +5 -1
- package/dist/cjs/ssr/ssr-server.cjs +12 -10
- package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
- package/dist/cjs/ssr/ssr-server.d.cts +0 -1
- package/dist/cjs/ssr/tsrScript.cjs +1 -1
- package/dist/cjs/ssr/tsrScript.cjs.map +1 -1
- package/dist/cjs/typePrimitives.d.cts +6 -6
- package/dist/cjs/utils.cjs +14 -7
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +2 -1
- package/dist/esm/Matches.d.ts +7 -9
- package/dist/esm/Matches.js.map +1 -1
- package/dist/esm/index.d.ts +6 -2
- package/dist/esm/index.js +9 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/load-matches.d.ts +16 -0
- package/dist/esm/load-matches.js +636 -0
- package/dist/esm/load-matches.js.map +1 -0
- package/dist/esm/qss.d.ts +6 -4
- package/dist/esm/qss.js +19 -19
- package/dist/esm/qss.js.map +1 -1
- package/dist/esm/redirect.js +3 -3
- package/dist/esm/redirect.js.map +1 -1
- package/dist/esm/route.d.ts +0 -4
- package/dist/esm/route.js.map +1 -1
- package/dist/esm/router.d.ts +14 -26
- package/dist/esm/router.js +64 -632
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/scroll-restoration.d.ts +0 -9
- package/dist/esm/scroll-restoration.js +20 -25
- package/dist/esm/scroll-restoration.js.map +1 -1
- package/dist/esm/searchParams.js +7 -15
- package/dist/esm/searchParams.js.map +1 -1
- package/dist/esm/ssr/constants.d.ts +1 -0
- package/dist/esm/ssr/constants.js +5 -0
- package/dist/esm/ssr/constants.js.map +1 -0
- package/dist/esm/ssr/{seroval-plugins.d.ts → serializer/ShallowErrorPlugin.d.ts} +1 -2
- package/dist/esm/ssr/{seroval-plugins.js → serializer/ShallowErrorPlugin.js} +2 -2
- package/dist/esm/ssr/serializer/ShallowErrorPlugin.js.map +1 -0
- package/dist/esm/ssr/serializer/seroval-plugins.d.ts +2 -0
- package/dist/esm/ssr/serializer/seroval-plugins.js +11 -0
- package/dist/esm/ssr/serializer/seroval-plugins.js.map +1 -0
- package/dist/esm/ssr/serializer/transformer.d.ts +18 -0
- package/dist/esm/ssr/serializer/transformer.js +50 -0
- package/dist/esm/ssr/serializer/transformer.js.map +1 -0
- package/dist/esm/ssr/ssr-client.d.ts +5 -1
- package/dist/esm/ssr/ssr-client.js +53 -40
- package/dist/esm/ssr/ssr-client.js.map +1 -1
- package/dist/esm/ssr/ssr-server.d.ts +0 -1
- package/dist/esm/ssr/ssr-server.js +12 -10
- package/dist/esm/ssr/ssr-server.js.map +1 -1
- package/dist/esm/ssr/tsrScript.js +1 -1
- package/dist/esm/ssr/tsrScript.js.map +1 -1
- package/dist/esm/typePrimitives.d.ts +6 -6
- package/dist/esm/utils.d.ts +2 -1
- package/dist/esm/utils.js +14 -7
- package/dist/esm/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/Matches.ts +16 -8
- package/src/index.ts +12 -2
- package/src/load-matches.ts +955 -0
- package/src/qss.ts +27 -24
- package/src/redirect.ts +3 -3
- package/src/route.ts +10 -2
- package/src/router.ts +99 -893
- package/src/scroll-restoration.ts +25 -32
- package/src/searchParams.ts +8 -19
- package/src/ssr/constants.ts +1 -0
- package/src/ssr/{seroval-plugins.ts → serializer/ShallowErrorPlugin.ts} +2 -2
- package/src/ssr/serializer/seroval-plugins.ts +9 -0
- package/src/ssr/serializer/transformer.ts +78 -0
- package/src/ssr/ssr-client.ts +72 -44
- package/src/ssr/ssr-server.ts +18 -10
- package/src/ssr/tsrScript.ts +5 -1
- package/src/typePrimitives.ts +6 -6
- package/src/utils.ts +21 -10
- package/dist/cjs/ssr/seroval-plugins.cjs.map +0 -1
- package/dist/esm/ssr/seroval-plugins.js.map +0 -1
package/src/router.ts
CHANGED
|
@@ -8,9 +8,9 @@ import invariant from 'tiny-invariant'
|
|
|
8
8
|
import {
|
|
9
9
|
createControlledPromise,
|
|
10
10
|
deepEqual,
|
|
11
|
+
findLast,
|
|
11
12
|
functionalUpdate,
|
|
12
13
|
last,
|
|
13
|
-
pick,
|
|
14
14
|
replaceEqualDeep,
|
|
15
15
|
} from './utils'
|
|
16
16
|
import {
|
|
@@ -34,6 +34,7 @@ import { defaultParseSearch, defaultStringifySearch } from './searchParams'
|
|
|
34
34
|
import { rootRouteId } from './root'
|
|
35
35
|
import { isRedirect, redirect } from './redirect'
|
|
36
36
|
import { createLRUCache } from './lru-cache'
|
|
37
|
+
import { loadMatches, loadRouteChunk, routeNeedsPreload } from './load-matches'
|
|
37
38
|
import type { ParsePathnameCache, Segment } from './path'
|
|
38
39
|
import type { SearchParser, SearchSerializer } from './searchParams'
|
|
39
40
|
import type { AnyRedirect, ResolvedRedirect } from './redirect'
|
|
@@ -56,13 +57,10 @@ import type {
|
|
|
56
57
|
AnyContext,
|
|
57
58
|
AnyRoute,
|
|
58
59
|
AnyRouteWithContext,
|
|
59
|
-
BeforeLoadContextOptions,
|
|
60
|
-
LoaderFnContext,
|
|
61
60
|
MakeRemountDepsOptionsUnion,
|
|
62
61
|
RouteContextOptions,
|
|
63
62
|
RouteMask,
|
|
64
63
|
SearchMiddleware,
|
|
65
|
-
SsrContextOptions,
|
|
66
64
|
} from './route'
|
|
67
65
|
import type {
|
|
68
66
|
FullSearchSchema,
|
|
@@ -119,6 +117,7 @@ export interface RouterOptions<
|
|
|
119
117
|
TDefaultStructuralSharingOption extends boolean = false,
|
|
120
118
|
TRouterHistory extends RouterHistory = RouterHistory,
|
|
121
119
|
TDehydrated extends Record<string, any> = Record<string, any>,
|
|
120
|
+
TTransformerConfig = any,
|
|
122
121
|
> extends RouterOptionsExtensions {
|
|
123
122
|
/**
|
|
124
123
|
* The history object that will be used to manage the browser history.
|
|
@@ -423,6 +422,8 @@ export interface RouterOptions<
|
|
|
423
422
|
* @default false
|
|
424
423
|
*/
|
|
425
424
|
disableGlobalCatchBoundary?: boolean
|
|
425
|
+
|
|
426
|
+
serializationAdapters?: TTransformerConfig
|
|
426
427
|
}
|
|
427
428
|
|
|
428
429
|
export interface RouterState<
|
|
@@ -531,13 +532,15 @@ export type RouterConstructorOptions<
|
|
|
531
532
|
TDefaultStructuralSharingOption extends boolean,
|
|
532
533
|
TRouterHistory extends RouterHistory,
|
|
533
534
|
TDehydrated extends Record<string, any>,
|
|
535
|
+
TTransformerConfig,
|
|
534
536
|
> = Omit<
|
|
535
537
|
RouterOptions<
|
|
536
538
|
TRouteTree,
|
|
537
539
|
TTrailingSlashOption,
|
|
538
540
|
TDefaultStructuralSharingOption,
|
|
539
541
|
TRouterHistory,
|
|
540
|
-
TDehydrated
|
|
542
|
+
TDehydrated,
|
|
543
|
+
TTransformerConfig
|
|
541
544
|
>,
|
|
542
545
|
'context'
|
|
543
546
|
> &
|
|
@@ -597,13 +600,15 @@ export type UpdateFn<
|
|
|
597
600
|
TDefaultStructuralSharingOption extends boolean,
|
|
598
601
|
TRouterHistory extends RouterHistory,
|
|
599
602
|
TDehydrated extends Record<string, any>,
|
|
603
|
+
TTransformerConfig extends any,
|
|
600
604
|
> = (
|
|
601
605
|
newOptions: RouterConstructorOptions<
|
|
602
606
|
TRouteTree,
|
|
603
607
|
TTrailingSlashOption,
|
|
604
608
|
TDefaultStructuralSharingOption,
|
|
605
609
|
TRouterHistory,
|
|
606
|
-
TDehydrated
|
|
610
|
+
TDehydrated,
|
|
611
|
+
TTransformerConfig
|
|
607
612
|
>,
|
|
608
613
|
) => void
|
|
609
614
|
|
|
@@ -688,10 +693,11 @@ export type AnyRouterWithContext<TContext> = RouterCore<
|
|
|
688
693
|
any,
|
|
689
694
|
any,
|
|
690
695
|
any,
|
|
696
|
+
any,
|
|
691
697
|
any
|
|
692
698
|
>
|
|
693
699
|
|
|
694
|
-
export type AnyRouter = RouterCore<any, any, any, any, any>
|
|
700
|
+
export type AnyRouter = RouterCore<any, any, any, any, any, any>
|
|
695
701
|
|
|
696
702
|
export interface ViewTransitionOptions {
|
|
697
703
|
types:
|
|
@@ -705,6 +711,7 @@ export interface ViewTransitionOptions {
|
|
|
705
711
|
}) => Array<string>)
|
|
706
712
|
}
|
|
707
713
|
|
|
714
|
+
// TODO where is this used? can we remove this?
|
|
708
715
|
export function defaultSerializeError(err: unknown) {
|
|
709
716
|
if (err instanceof Error) {
|
|
710
717
|
const obj = {
|
|
@@ -744,6 +751,7 @@ export type CreateRouterFn = <
|
|
|
744
751
|
TDefaultStructuralSharingOption extends boolean = false,
|
|
745
752
|
TRouterHistory extends RouterHistory = RouterHistory,
|
|
746
753
|
TDehydrated extends Record<string, any> = Record<string, any>,
|
|
754
|
+
TTransformerConfig = any,
|
|
747
755
|
>(
|
|
748
756
|
options: undefined extends number
|
|
749
757
|
? 'strictNullChecks must be enabled in tsconfig.json'
|
|
@@ -752,14 +760,16 @@ export type CreateRouterFn = <
|
|
|
752
760
|
TTrailingSlashOption,
|
|
753
761
|
TDefaultStructuralSharingOption,
|
|
754
762
|
TRouterHistory,
|
|
755
|
-
TDehydrated
|
|
763
|
+
TDehydrated,
|
|
764
|
+
TTransformerConfig
|
|
756
765
|
>,
|
|
757
766
|
) => RouterCore<
|
|
758
767
|
TRouteTree,
|
|
759
768
|
TTrailingSlashOption,
|
|
760
769
|
TDefaultStructuralSharingOption,
|
|
761
770
|
TRouterHistory,
|
|
762
|
-
TDehydrated
|
|
771
|
+
TDehydrated,
|
|
772
|
+
TTransformerConfig
|
|
763
773
|
>
|
|
764
774
|
|
|
765
775
|
export class RouterCore<
|
|
@@ -768,6 +778,7 @@ export class RouterCore<
|
|
|
768
778
|
in out TDefaultStructuralSharingOption extends boolean,
|
|
769
779
|
in out TRouterHistory extends RouterHistory = RouterHistory,
|
|
770
780
|
in out TDehydrated extends Record<string, any> = Record<string, any>,
|
|
781
|
+
in out TTransformerConfig = any,
|
|
771
782
|
> {
|
|
772
783
|
// Option-independent properties
|
|
773
784
|
tempLocationKey: string | undefined = `${Math.round(
|
|
@@ -789,7 +800,8 @@ export class RouterCore<
|
|
|
789
800
|
TTrailingSlashOption,
|
|
790
801
|
TDefaultStructuralSharingOption,
|
|
791
802
|
TRouterHistory,
|
|
792
|
-
TDehydrated
|
|
803
|
+
TDehydrated,
|
|
804
|
+
TTransformerConfig
|
|
793
805
|
>,
|
|
794
806
|
'stringifySearch' | 'parseSearch' | 'context'
|
|
795
807
|
>
|
|
@@ -812,7 +824,8 @@ export class RouterCore<
|
|
|
812
824
|
TTrailingSlashOption,
|
|
813
825
|
TDefaultStructuralSharingOption,
|
|
814
826
|
TRouterHistory,
|
|
815
|
-
TDehydrated
|
|
827
|
+
TDehydrated,
|
|
828
|
+
TTransformerConfig
|
|
816
829
|
>,
|
|
817
830
|
) {
|
|
818
831
|
this.update({
|
|
@@ -850,7 +863,8 @@ export class RouterCore<
|
|
|
850
863
|
TTrailingSlashOption,
|
|
851
864
|
TDefaultStructuralSharingOption,
|
|
852
865
|
TRouterHistory,
|
|
853
|
-
TDehydrated
|
|
866
|
+
TDehydrated,
|
|
867
|
+
TTransformerConfig
|
|
854
868
|
> = (newOptions) => {
|
|
855
869
|
if (newOptions.notFoundRoute) {
|
|
856
870
|
console.warn(
|
|
@@ -1139,8 +1153,8 @@ export class RouterCore<
|
|
|
1139
1153
|
const parentMatchId = parentMatch?.id
|
|
1140
1154
|
|
|
1141
1155
|
const parentContext = !parentMatchId
|
|
1142
|
-
? ((this.options.context as any) ??
|
|
1143
|
-
: (parentMatch.context ?? this.options.context ??
|
|
1156
|
+
? ((this.options.context as any) ?? undefined)
|
|
1157
|
+
: (parentMatch.context ?? this.options.context ?? undefined)
|
|
1144
1158
|
|
|
1145
1159
|
return parentContext
|
|
1146
1160
|
}
|
|
@@ -1162,12 +1176,12 @@ export class RouterCore<
|
|
|
1162
1176
|
] = (() => {
|
|
1163
1177
|
// Validate the search params and stabilize them
|
|
1164
1178
|
const parentSearch = parentMatch?.search ?? next.search
|
|
1165
|
-
const parentStrictSearch = parentMatch?._strictSearch ??
|
|
1179
|
+
const parentStrictSearch = parentMatch?._strictSearch ?? undefined
|
|
1166
1180
|
|
|
1167
1181
|
try {
|
|
1168
1182
|
const strictSearch =
|
|
1169
1183
|
validateSearch(route.options.validateSearch, { ...parentSearch }) ??
|
|
1170
|
-
|
|
1184
|
+
undefined
|
|
1171
1185
|
|
|
1172
1186
|
return [
|
|
1173
1187
|
{
|
|
@@ -1277,7 +1291,10 @@ export class RouterCore<
|
|
|
1277
1291
|
isFetching: false,
|
|
1278
1292
|
error: undefined,
|
|
1279
1293
|
paramsError: parseErrors[index],
|
|
1280
|
-
__routeContext:
|
|
1294
|
+
__routeContext: undefined,
|
|
1295
|
+
_nonReactive: {
|
|
1296
|
+
loadPromise: createControlledPromise(),
|
|
1297
|
+
},
|
|
1281
1298
|
__beforeLoadContext: undefined,
|
|
1282
1299
|
context: {},
|
|
1283
1300
|
abortController: new AbortController(),
|
|
@@ -1293,7 +1310,6 @@ export class RouterCore<
|
|
|
1293
1310
|
headScripts: undefined,
|
|
1294
1311
|
meta: undefined,
|
|
1295
1312
|
staticData: route.options.staticData || {},
|
|
1296
|
-
loadPromise: createControlledPromise(),
|
|
1297
1313
|
fullPath: route.fullPath,
|
|
1298
1314
|
}
|
|
1299
1315
|
}
|
|
@@ -1328,22 +1344,25 @@ export class RouterCore<
|
|
|
1328
1344
|
const parentContext = getParentContext(parentMatch)
|
|
1329
1345
|
|
|
1330
1346
|
// Update the match's context
|
|
1331
|
-
const contextFnContext: RouteContextOptions<any, any, any, any> = {
|
|
1332
|
-
deps: match.loaderDeps,
|
|
1333
|
-
params: match.params,
|
|
1334
|
-
context: parentContext,
|
|
1335
|
-
location: next,
|
|
1336
|
-
navigate: (opts: any) =>
|
|
1337
|
-
this.navigate({ ...opts, _fromLocation: next }),
|
|
1338
|
-
buildLocation: this.buildLocation,
|
|
1339
|
-
cause: match.cause,
|
|
1340
|
-
abortController: match.abortController,
|
|
1341
|
-
preload: !!match.preload,
|
|
1342
|
-
matches,
|
|
1343
|
-
}
|
|
1344
1347
|
|
|
1345
|
-
|
|
1346
|
-
|
|
1348
|
+
if (route.options.context) {
|
|
1349
|
+
const contextFnContext: RouteContextOptions<any, any, any, any> = {
|
|
1350
|
+
deps: match.loaderDeps,
|
|
1351
|
+
params: match.params,
|
|
1352
|
+
context: parentContext ?? {},
|
|
1353
|
+
location: next,
|
|
1354
|
+
navigate: (opts: any) =>
|
|
1355
|
+
this.navigate({ ...opts, _fromLocation: next }),
|
|
1356
|
+
buildLocation: this.buildLocation,
|
|
1357
|
+
cause: match.cause,
|
|
1358
|
+
abortController: match.abortController,
|
|
1359
|
+
preload: !!match.preload,
|
|
1360
|
+
matches,
|
|
1361
|
+
}
|
|
1362
|
+
// Get the route context
|
|
1363
|
+
match.__routeContext =
|
|
1364
|
+
route.options.context(contextFnContext) ?? undefined
|
|
1365
|
+
}
|
|
1347
1366
|
|
|
1348
1367
|
match.context = {
|
|
1349
1368
|
...parentContext,
|
|
@@ -1381,13 +1400,8 @@ export class RouterCore<
|
|
|
1381
1400
|
if (!match) return
|
|
1382
1401
|
|
|
1383
1402
|
match.abortController.abort()
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
return {
|
|
1387
|
-
...prev,
|
|
1388
|
-
pendingTimeout: undefined,
|
|
1389
|
-
}
|
|
1390
|
-
})
|
|
1403
|
+
clearTimeout(match._nonReactive.pendingTimeout)
|
|
1404
|
+
match._nonReactive.pendingTimeout = undefined
|
|
1391
1405
|
}
|
|
1392
1406
|
|
|
1393
1407
|
cancelMatches = () => {
|
|
@@ -1413,7 +1427,7 @@ export class RouterCore<
|
|
|
1413
1427
|
|
|
1414
1428
|
// First let's find the starting pathname
|
|
1415
1429
|
// By default, start with the current location
|
|
1416
|
-
let fromPath = lastMatch.fullPath
|
|
1430
|
+
let fromPath = this.resolvePathWithBase(lastMatch.fullPath, '.')
|
|
1417
1431
|
const toPath = dest.to
|
|
1418
1432
|
? this.resolvePathWithBase(fromPath, `${dest.to}`)
|
|
1419
1433
|
: this.resolvePathWithBase(fromPath, '.')
|
|
@@ -1436,13 +1450,11 @@ export class RouterCore<
|
|
|
1436
1450
|
undefined,
|
|
1437
1451
|
).matchedRoutes
|
|
1438
1452
|
|
|
1439
|
-
const matchedFrom =
|
|
1440
|
-
.
|
|
1441
|
-
|
|
1442
|
-
return comparePaths(d.fullPath, fromPath)
|
|
1443
|
-
})
|
|
1453
|
+
const matchedFrom = findLast(allCurrentLocationMatches, (d) => {
|
|
1454
|
+
return comparePaths(d.fullPath, fromPath)
|
|
1455
|
+
})
|
|
1444
1456
|
|
|
1445
|
-
const matchedCurrent =
|
|
1457
|
+
const matchedCurrent = findLast(allFromMatches, (d) => {
|
|
1446
1458
|
return comparePaths(d.fullPath, currentLocation.pathname)
|
|
1447
1459
|
})
|
|
1448
1460
|
|
|
@@ -1454,6 +1466,8 @@ export class RouterCore<
|
|
|
1454
1466
|
}
|
|
1455
1467
|
}
|
|
1456
1468
|
|
|
1469
|
+
fromPath = this.resolvePathWithBase(fromPath, '.')
|
|
1470
|
+
|
|
1457
1471
|
// From search should always use the current location
|
|
1458
1472
|
const fromSearch = lastMatch.search
|
|
1459
1473
|
// Same with params. It can't hurt to provide as many as possible
|
|
@@ -1465,50 +1479,43 @@ export class RouterCore<
|
|
|
1465
1479
|
: this.resolvePathWithBase(fromPath, '.')
|
|
1466
1480
|
|
|
1467
1481
|
// Resolve the next params
|
|
1468
|
-
|
|
1482
|
+
const nextParams =
|
|
1469
1483
|
dest.params === false || dest.params === null
|
|
1470
1484
|
? {}
|
|
1471
1485
|
: (dest.params ?? true) === true
|
|
1472
1486
|
? fromParams
|
|
1473
|
-
:
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1487
|
+
: Object.assign(
|
|
1488
|
+
fromParams,
|
|
1489
|
+
functionalUpdate(dest.params as any, fromParams),
|
|
1490
|
+
)
|
|
1477
1491
|
|
|
1478
1492
|
// Interpolate the path first to get the actual resolved path, then match against that
|
|
1479
1493
|
const interpolatedNextTo = interpolatePath({
|
|
1480
1494
|
path: nextTo,
|
|
1481
|
-
params: nextParams
|
|
1495
|
+
params: nextParams,
|
|
1482
1496
|
parseCache: this.parsePathnameCache,
|
|
1483
1497
|
}).interpolatedPath
|
|
1484
1498
|
|
|
1485
|
-
const destRoutes = this.matchRoutes(
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
{
|
|
1489
|
-
_buildLocation: true,
|
|
1490
|
-
},
|
|
1491
|
-
).map((d) => this.looseRoutesById[d.routeId]!)
|
|
1499
|
+
const destRoutes = this.matchRoutes(interpolatedNextTo, undefined, {
|
|
1500
|
+
_buildLocation: true,
|
|
1501
|
+
}).map((d) => this.looseRoutesById[d.routeId]!)
|
|
1492
1502
|
|
|
1493
1503
|
// If there are any params, we need to stringify them
|
|
1494
1504
|
if (Object.keys(nextParams).length > 0) {
|
|
1495
|
-
destRoutes
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
)
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
|
-
.forEach((fn) => {
|
|
1503
|
-
nextParams = { ...nextParams!, ...fn!(nextParams) }
|
|
1504
|
-
})
|
|
1505
|
+
for (const route of destRoutes) {
|
|
1506
|
+
const fn =
|
|
1507
|
+
route.options.params?.stringify ?? route.options.stringifyParams
|
|
1508
|
+
if (fn) {
|
|
1509
|
+
Object.assign(nextParams, fn(nextParams))
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1505
1512
|
}
|
|
1506
1513
|
|
|
1507
1514
|
const nextPathname = interpolatePath({
|
|
1508
1515
|
// Use the original template path for interpolation
|
|
1509
1516
|
// This preserves the original parameter syntax including optional parameters
|
|
1510
1517
|
path: nextTo,
|
|
1511
|
-
params: nextParams
|
|
1518
|
+
params: nextParams,
|
|
1512
1519
|
leaveWildcards: false,
|
|
1513
1520
|
leaveParams: opts.leaveParams,
|
|
1514
1521
|
decodeCharMap: this.pathParamsDecodeCharMap,
|
|
@@ -1518,20 +1525,20 @@ export class RouterCore<
|
|
|
1518
1525
|
// Resolve the next search
|
|
1519
1526
|
let nextSearch = fromSearch
|
|
1520
1527
|
if (opts._includeValidateSearch && this.options.search?.strict) {
|
|
1521
|
-
|
|
1528
|
+
const validatedSearch = {}
|
|
1522
1529
|
destRoutes.forEach((route) => {
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1530
|
+
if (route.options.validateSearch) {
|
|
1531
|
+
try {
|
|
1532
|
+
Object.assign(
|
|
1533
|
+
validatedSearch,
|
|
1534
|
+
validateSearch(route.options.validateSearch, {
|
|
1528
1535
|
...validatedSearch,
|
|
1529
1536
|
...nextSearch,
|
|
1530
|
-
})
|
|
1531
|
-
|
|
1537
|
+
}),
|
|
1538
|
+
)
|
|
1539
|
+
} catch {
|
|
1540
|
+
// ignore errors here because they are already handled in matchRoutes
|
|
1532
1541
|
}
|
|
1533
|
-
} catch {
|
|
1534
|
-
// ignore errors here because they are already handled in matchRoutes
|
|
1535
1542
|
}
|
|
1536
1543
|
})
|
|
1537
1544
|
nextSearch = validatedSearch
|
|
@@ -1618,7 +1625,7 @@ export class RouterCore<
|
|
|
1618
1625
|
if (foundMask) {
|
|
1619
1626
|
const { from: _from, ...maskProps } = foundMask
|
|
1620
1627
|
maskedDest = {
|
|
1621
|
-
|
|
1628
|
+
from: opts.from,
|
|
1622
1629
|
...maskProps,
|
|
1623
1630
|
params,
|
|
1624
1631
|
}
|
|
@@ -1636,7 +1643,7 @@ export class RouterCore<
|
|
|
1636
1643
|
|
|
1637
1644
|
if (opts.mask) {
|
|
1638
1645
|
return buildWithMatches(opts, {
|
|
1639
|
-
|
|
1646
|
+
from: opts.from,
|
|
1640
1647
|
...opts.mask,
|
|
1641
1648
|
})
|
|
1642
1649
|
}
|
|
@@ -1884,10 +1891,12 @@ export class RouterCore<
|
|
|
1884
1891
|
}),
|
|
1885
1892
|
})
|
|
1886
1893
|
|
|
1887
|
-
await
|
|
1894
|
+
await loadMatches({
|
|
1895
|
+
router: this,
|
|
1888
1896
|
sync: opts?.sync,
|
|
1889
1897
|
matches: this.state.pendingMatches as Array<AnyRouteMatch>,
|
|
1890
1898
|
location: next,
|
|
1899
|
+
updateMatch: this.updateMatch,
|
|
1891
1900
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
1892
1901
|
onReady: async () => {
|
|
1893
1902
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
@@ -2077,704 +2086,6 @@ export class RouterCore<
|
|
|
2077
2086
|
)
|
|
2078
2087
|
}
|
|
2079
2088
|
|
|
2080
|
-
loadMatches = async ({
|
|
2081
|
-
location,
|
|
2082
|
-
matches,
|
|
2083
|
-
preload: allPreload,
|
|
2084
|
-
onReady,
|
|
2085
|
-
updateMatch = this.updateMatch,
|
|
2086
|
-
sync,
|
|
2087
|
-
}: {
|
|
2088
|
-
location: ParsedLocation
|
|
2089
|
-
matches: Array<AnyRouteMatch>
|
|
2090
|
-
preload?: boolean
|
|
2091
|
-
onReady?: () => Promise<void>
|
|
2092
|
-
updateMatch?: (
|
|
2093
|
-
id: string,
|
|
2094
|
-
updater: (match: AnyRouteMatch) => AnyRouteMatch,
|
|
2095
|
-
) => void
|
|
2096
|
-
getMatch?: (matchId: string) => AnyRouteMatch | undefined
|
|
2097
|
-
sync?: boolean
|
|
2098
|
-
}): Promise<Array<MakeRouteMatch>> => {
|
|
2099
|
-
let firstBadMatchIndex: number | undefined
|
|
2100
|
-
let rendered = false
|
|
2101
|
-
|
|
2102
|
-
const triggerOnReady = async () => {
|
|
2103
|
-
if (!rendered) {
|
|
2104
|
-
rendered = true
|
|
2105
|
-
await onReady?.()
|
|
2106
|
-
}
|
|
2107
|
-
}
|
|
2108
|
-
|
|
2109
|
-
const resolvePreload = (matchId: string) => {
|
|
2110
|
-
return !!(allPreload && !this.state.matches.some((d) => d.id === matchId))
|
|
2111
|
-
}
|
|
2112
|
-
|
|
2113
|
-
// make sure the pending component is immediately rendered when hydrating a match that is not SSRed
|
|
2114
|
-
// the pending component was already rendered on the server and we want to keep it shown on the client until minPendingMs is reached
|
|
2115
|
-
if (!this.isServer && this.state.matches.some((d) => d._forcePending)) {
|
|
2116
|
-
triggerOnReady()
|
|
2117
|
-
}
|
|
2118
|
-
|
|
2119
|
-
const handleRedirectAndNotFound = (match: AnyRouteMatch, err: any) => {
|
|
2120
|
-
if (isRedirect(err) || isNotFound(err)) {
|
|
2121
|
-
if (isRedirect(err)) {
|
|
2122
|
-
if (err.redirectHandled) {
|
|
2123
|
-
if (!err.options.reloadDocument) {
|
|
2124
|
-
throw err
|
|
2125
|
-
}
|
|
2126
|
-
}
|
|
2127
|
-
}
|
|
2128
|
-
|
|
2129
|
-
match.beforeLoadPromise?.resolve()
|
|
2130
|
-
match.loaderPromise?.resolve()
|
|
2131
|
-
|
|
2132
|
-
updateMatch(match.id, (prev) => ({
|
|
2133
|
-
...prev,
|
|
2134
|
-
status: isRedirect(err)
|
|
2135
|
-
? 'redirected'
|
|
2136
|
-
: isNotFound(err)
|
|
2137
|
-
? 'notFound'
|
|
2138
|
-
: 'error',
|
|
2139
|
-
isFetching: false,
|
|
2140
|
-
error: err,
|
|
2141
|
-
beforeLoadPromise: undefined,
|
|
2142
|
-
loaderPromise: undefined,
|
|
2143
|
-
}))
|
|
2144
|
-
|
|
2145
|
-
if (!(err as any).routeId) {
|
|
2146
|
-
;(err as any).routeId = match.routeId
|
|
2147
|
-
}
|
|
2148
|
-
|
|
2149
|
-
match.loadPromise?.resolve()
|
|
2150
|
-
|
|
2151
|
-
if (isRedirect(err)) {
|
|
2152
|
-
rendered = true
|
|
2153
|
-
err.options._fromLocation = location
|
|
2154
|
-
err.redirectHandled = true
|
|
2155
|
-
err = this.resolveRedirect(err)
|
|
2156
|
-
throw err
|
|
2157
|
-
} else if (isNotFound(err)) {
|
|
2158
|
-
this._handleNotFound(matches, err, {
|
|
2159
|
-
updateMatch,
|
|
2160
|
-
})
|
|
2161
|
-
throw err
|
|
2162
|
-
}
|
|
2163
|
-
}
|
|
2164
|
-
}
|
|
2165
|
-
|
|
2166
|
-
const shouldSkipLoader = (matchId: string) => {
|
|
2167
|
-
const match = this.getMatch(matchId)!
|
|
2168
|
-
// upon hydration, we skip the loader if the match has been dehydrated on the server
|
|
2169
|
-
if (!this.isServer && match._dehydrated) {
|
|
2170
|
-
return true
|
|
2171
|
-
}
|
|
2172
|
-
|
|
2173
|
-
if (this.isServer) {
|
|
2174
|
-
if (match.ssr === false) {
|
|
2175
|
-
return true
|
|
2176
|
-
}
|
|
2177
|
-
}
|
|
2178
|
-
return false
|
|
2179
|
-
}
|
|
2180
|
-
|
|
2181
|
-
try {
|
|
2182
|
-
await new Promise<void>((resolveAll, rejectAll) => {
|
|
2183
|
-
;(async () => {
|
|
2184
|
-
try {
|
|
2185
|
-
const handleSerialError = (
|
|
2186
|
-
index: number,
|
|
2187
|
-
err: any,
|
|
2188
|
-
routerCode: string,
|
|
2189
|
-
) => {
|
|
2190
|
-
const { id: matchId, routeId } = matches[index]!
|
|
2191
|
-
const route = this.looseRoutesById[routeId]!
|
|
2192
|
-
|
|
2193
|
-
// Much like suspense, we use a promise here to know if
|
|
2194
|
-
// we've been outdated by a new loadMatches call and
|
|
2195
|
-
// should abort the current async operation
|
|
2196
|
-
if (err instanceof Promise) {
|
|
2197
|
-
throw err
|
|
2198
|
-
}
|
|
2199
|
-
|
|
2200
|
-
err.routerCode = routerCode
|
|
2201
|
-
firstBadMatchIndex = firstBadMatchIndex ?? index
|
|
2202
|
-
handleRedirectAndNotFound(this.getMatch(matchId)!, err)
|
|
2203
|
-
|
|
2204
|
-
try {
|
|
2205
|
-
route.options.onError?.(err)
|
|
2206
|
-
} catch (errorHandlerErr) {
|
|
2207
|
-
err = errorHandlerErr
|
|
2208
|
-
handleRedirectAndNotFound(this.getMatch(matchId)!, err)
|
|
2209
|
-
}
|
|
2210
|
-
|
|
2211
|
-
updateMatch(matchId, (prev) => {
|
|
2212
|
-
prev.beforeLoadPromise?.resolve()
|
|
2213
|
-
prev.loadPromise?.resolve()
|
|
2214
|
-
|
|
2215
|
-
return {
|
|
2216
|
-
...prev,
|
|
2217
|
-
error: err,
|
|
2218
|
-
status: 'error',
|
|
2219
|
-
isFetching: false,
|
|
2220
|
-
updatedAt: Date.now(),
|
|
2221
|
-
abortController: new AbortController(),
|
|
2222
|
-
beforeLoadPromise: undefined,
|
|
2223
|
-
}
|
|
2224
|
-
})
|
|
2225
|
-
}
|
|
2226
|
-
|
|
2227
|
-
for (const [index, { id: matchId, routeId }] of matches.entries()) {
|
|
2228
|
-
const existingMatch = this.getMatch(matchId)!
|
|
2229
|
-
const parentMatchId = matches[index - 1]?.id
|
|
2230
|
-
const parentMatch = parentMatchId
|
|
2231
|
-
? this.getMatch(parentMatchId)!
|
|
2232
|
-
: undefined
|
|
2233
|
-
|
|
2234
|
-
const route = this.looseRoutesById[routeId]!
|
|
2235
|
-
|
|
2236
|
-
const pendingMs =
|
|
2237
|
-
route.options.pendingMs ?? this.options.defaultPendingMs
|
|
2238
|
-
|
|
2239
|
-
// on the server, determine whether SSR the current match or not
|
|
2240
|
-
if (this.isServer) {
|
|
2241
|
-
let ssr: boolean | 'data-only'
|
|
2242
|
-
// in SPA mode, only SSR the root route
|
|
2243
|
-
if (this.isShell()) {
|
|
2244
|
-
ssr = matchId === rootRouteId
|
|
2245
|
-
} else {
|
|
2246
|
-
const defaultSsr = this.options.defaultSsr ?? true
|
|
2247
|
-
if (parentMatch?.ssr === false) {
|
|
2248
|
-
ssr = false
|
|
2249
|
-
} else {
|
|
2250
|
-
let tempSsr: boolean | 'data-only'
|
|
2251
|
-
if (route.options.ssr === undefined) {
|
|
2252
|
-
tempSsr = defaultSsr
|
|
2253
|
-
} else if (typeof route.options.ssr === 'function') {
|
|
2254
|
-
const { search, params } = this.getMatch(matchId)!
|
|
2255
|
-
|
|
2256
|
-
function makeMaybe(value: any, error: any) {
|
|
2257
|
-
if (error) {
|
|
2258
|
-
return { status: 'error' as const, error }
|
|
2259
|
-
}
|
|
2260
|
-
return { status: 'success' as const, value }
|
|
2261
|
-
}
|
|
2262
|
-
|
|
2263
|
-
const ssrFnContext: SsrContextOptions<any, any, any> = {
|
|
2264
|
-
search: makeMaybe(search, existingMatch.searchError),
|
|
2265
|
-
params: makeMaybe(params, existingMatch.paramsError),
|
|
2266
|
-
location,
|
|
2267
|
-
matches: matches.map((match) => ({
|
|
2268
|
-
index: match.index,
|
|
2269
|
-
pathname: match.pathname,
|
|
2270
|
-
fullPath: match.fullPath,
|
|
2271
|
-
staticData: match.staticData,
|
|
2272
|
-
id: match.id,
|
|
2273
|
-
routeId: match.routeId,
|
|
2274
|
-
search: makeMaybe(match.search, match.searchError),
|
|
2275
|
-
params: makeMaybe(match.params, match.paramsError),
|
|
2276
|
-
ssr: match.ssr,
|
|
2277
|
-
})),
|
|
2278
|
-
}
|
|
2279
|
-
tempSsr =
|
|
2280
|
-
(await route.options.ssr(ssrFnContext)) ?? defaultSsr
|
|
2281
|
-
} else {
|
|
2282
|
-
tempSsr = route.options.ssr
|
|
2283
|
-
}
|
|
2284
|
-
|
|
2285
|
-
if (tempSsr === true && parentMatch?.ssr === 'data-only') {
|
|
2286
|
-
ssr = 'data-only'
|
|
2287
|
-
} else {
|
|
2288
|
-
ssr = tempSsr
|
|
2289
|
-
}
|
|
2290
|
-
}
|
|
2291
|
-
}
|
|
2292
|
-
updateMatch(matchId, (prev) => ({
|
|
2293
|
-
...prev,
|
|
2294
|
-
ssr,
|
|
2295
|
-
}))
|
|
2296
|
-
}
|
|
2297
|
-
|
|
2298
|
-
if (shouldSkipLoader(matchId)) {
|
|
2299
|
-
continue
|
|
2300
|
-
}
|
|
2301
|
-
|
|
2302
|
-
const shouldPending = !!(
|
|
2303
|
-
onReady &&
|
|
2304
|
-
!this.isServer &&
|
|
2305
|
-
!resolvePreload(matchId) &&
|
|
2306
|
-
(route.options.loader ||
|
|
2307
|
-
route.options.beforeLoad ||
|
|
2308
|
-
routeNeedsPreload(route)) &&
|
|
2309
|
-
typeof pendingMs === 'number' &&
|
|
2310
|
-
pendingMs !== Infinity &&
|
|
2311
|
-
(route.options.pendingComponent ??
|
|
2312
|
-
(this.options as any)?.defaultPendingComponent)
|
|
2313
|
-
)
|
|
2314
|
-
|
|
2315
|
-
let executeBeforeLoad = true
|
|
2316
|
-
const setupPendingTimeout = () => {
|
|
2317
|
-
if (
|
|
2318
|
-
shouldPending &&
|
|
2319
|
-
this.getMatch(matchId)!.pendingTimeout === undefined
|
|
2320
|
-
) {
|
|
2321
|
-
const pendingTimeout = setTimeout(() => {
|
|
2322
|
-
try {
|
|
2323
|
-
// Update the match and prematurely resolve the loadMatches promise so that
|
|
2324
|
-
// the pending component can start rendering
|
|
2325
|
-
triggerOnReady()
|
|
2326
|
-
} catch {}
|
|
2327
|
-
}, pendingMs)
|
|
2328
|
-
updateMatch(matchId, (prev) => ({
|
|
2329
|
-
...prev,
|
|
2330
|
-
pendingTimeout,
|
|
2331
|
-
}))
|
|
2332
|
-
}
|
|
2333
|
-
}
|
|
2334
|
-
if (
|
|
2335
|
-
// If we are in the middle of a load, either of these will be present
|
|
2336
|
-
// (not to be confused with `loadPromise`, which is always defined)
|
|
2337
|
-
existingMatch.beforeLoadPromise ||
|
|
2338
|
-
existingMatch.loaderPromise
|
|
2339
|
-
) {
|
|
2340
|
-
setupPendingTimeout()
|
|
2341
|
-
|
|
2342
|
-
// Wait for the beforeLoad to resolve before we continue
|
|
2343
|
-
await existingMatch.beforeLoadPromise
|
|
2344
|
-
const match = this.getMatch(matchId)!
|
|
2345
|
-
if (match.status === 'error') {
|
|
2346
|
-
executeBeforeLoad = true
|
|
2347
|
-
} else if (
|
|
2348
|
-
match.preload &&
|
|
2349
|
-
(match.status === 'redirected' || match.status === 'notFound')
|
|
2350
|
-
) {
|
|
2351
|
-
handleRedirectAndNotFound(match, match.error)
|
|
2352
|
-
}
|
|
2353
|
-
}
|
|
2354
|
-
if (executeBeforeLoad) {
|
|
2355
|
-
// If we are not in the middle of a load OR the previous load failed, start it
|
|
2356
|
-
try {
|
|
2357
|
-
updateMatch(matchId, (prev) => {
|
|
2358
|
-
// explicitly capture the previous loadPromise
|
|
2359
|
-
const prevLoadPromise = prev.loadPromise
|
|
2360
|
-
return {
|
|
2361
|
-
...prev,
|
|
2362
|
-
loadPromise: createControlledPromise<void>(() => {
|
|
2363
|
-
prevLoadPromise?.resolve()
|
|
2364
|
-
}),
|
|
2365
|
-
beforeLoadPromise: createControlledPromise<void>(),
|
|
2366
|
-
}
|
|
2367
|
-
})
|
|
2368
|
-
|
|
2369
|
-
const { paramsError, searchError } = this.getMatch(matchId)!
|
|
2370
|
-
|
|
2371
|
-
if (paramsError) {
|
|
2372
|
-
handleSerialError(index, paramsError, 'PARSE_PARAMS')
|
|
2373
|
-
}
|
|
2374
|
-
|
|
2375
|
-
if (searchError) {
|
|
2376
|
-
handleSerialError(index, searchError, 'VALIDATE_SEARCH')
|
|
2377
|
-
}
|
|
2378
|
-
|
|
2379
|
-
setupPendingTimeout()
|
|
2380
|
-
|
|
2381
|
-
const abortController = new AbortController()
|
|
2382
|
-
|
|
2383
|
-
const parentMatchContext =
|
|
2384
|
-
parentMatch?.context ?? this.options.context ?? {}
|
|
2385
|
-
|
|
2386
|
-
updateMatch(matchId, (prev) => ({
|
|
2387
|
-
...prev,
|
|
2388
|
-
isFetching: 'beforeLoad',
|
|
2389
|
-
fetchCount: prev.fetchCount + 1,
|
|
2390
|
-
abortController,
|
|
2391
|
-
context: {
|
|
2392
|
-
...parentMatchContext,
|
|
2393
|
-
...prev.__routeContext,
|
|
2394
|
-
},
|
|
2395
|
-
}))
|
|
2396
|
-
|
|
2397
|
-
const { search, params, context, cause } =
|
|
2398
|
-
this.getMatch(matchId)!
|
|
2399
|
-
|
|
2400
|
-
const preload = resolvePreload(matchId)
|
|
2401
|
-
|
|
2402
|
-
const beforeLoadFnContext: BeforeLoadContextOptions<
|
|
2403
|
-
any,
|
|
2404
|
-
any,
|
|
2405
|
-
any,
|
|
2406
|
-
any,
|
|
2407
|
-
any
|
|
2408
|
-
> = {
|
|
2409
|
-
search,
|
|
2410
|
-
abortController,
|
|
2411
|
-
params,
|
|
2412
|
-
preload,
|
|
2413
|
-
context,
|
|
2414
|
-
location,
|
|
2415
|
-
navigate: (opts: any) =>
|
|
2416
|
-
this.navigate({ ...opts, _fromLocation: location }),
|
|
2417
|
-
buildLocation: this.buildLocation,
|
|
2418
|
-
cause: preload ? 'preload' : cause,
|
|
2419
|
-
matches,
|
|
2420
|
-
}
|
|
2421
|
-
|
|
2422
|
-
const beforeLoadContext =
|
|
2423
|
-
await route.options.beforeLoad?.(beforeLoadFnContext)
|
|
2424
|
-
|
|
2425
|
-
if (
|
|
2426
|
-
isRedirect(beforeLoadContext) ||
|
|
2427
|
-
isNotFound(beforeLoadContext)
|
|
2428
|
-
) {
|
|
2429
|
-
handleSerialError(index, beforeLoadContext, 'BEFORE_LOAD')
|
|
2430
|
-
}
|
|
2431
|
-
|
|
2432
|
-
updateMatch(matchId, (prev) => {
|
|
2433
|
-
return {
|
|
2434
|
-
...prev,
|
|
2435
|
-
__beforeLoadContext: beforeLoadContext,
|
|
2436
|
-
context: {
|
|
2437
|
-
...parentMatchContext,
|
|
2438
|
-
...prev.__routeContext,
|
|
2439
|
-
...beforeLoadContext,
|
|
2440
|
-
},
|
|
2441
|
-
abortController,
|
|
2442
|
-
}
|
|
2443
|
-
})
|
|
2444
|
-
} catch (err) {
|
|
2445
|
-
handleSerialError(index, err, 'BEFORE_LOAD')
|
|
2446
|
-
}
|
|
2447
|
-
|
|
2448
|
-
updateMatch(matchId, (prev) => {
|
|
2449
|
-
prev.beforeLoadPromise?.resolve()
|
|
2450
|
-
|
|
2451
|
-
return {
|
|
2452
|
-
...prev,
|
|
2453
|
-
beforeLoadPromise: undefined,
|
|
2454
|
-
isFetching: false,
|
|
2455
|
-
}
|
|
2456
|
-
})
|
|
2457
|
-
}
|
|
2458
|
-
}
|
|
2459
|
-
|
|
2460
|
-
const validResolvedMatches = matches.slice(0, firstBadMatchIndex)
|
|
2461
|
-
const matchPromises: Array<Promise<AnyRouteMatch>> = []
|
|
2462
|
-
|
|
2463
|
-
validResolvedMatches.forEach(({ id: matchId, routeId }, index) => {
|
|
2464
|
-
matchPromises.push(
|
|
2465
|
-
(async () => {
|
|
2466
|
-
let loaderShouldRunAsync = false
|
|
2467
|
-
let loaderIsRunningAsync = false
|
|
2468
|
-
const route = this.looseRoutesById[routeId]!
|
|
2469
|
-
|
|
2470
|
-
const executeHead = async () => {
|
|
2471
|
-
const match = this.getMatch(matchId)
|
|
2472
|
-
// in case of a redirecting match during preload, the match does not exist
|
|
2473
|
-
if (!match) {
|
|
2474
|
-
return
|
|
2475
|
-
}
|
|
2476
|
-
const assetContext = {
|
|
2477
|
-
matches,
|
|
2478
|
-
match,
|
|
2479
|
-
params: match.params,
|
|
2480
|
-
loaderData: match.loaderData,
|
|
2481
|
-
}
|
|
2482
|
-
const headFnContent =
|
|
2483
|
-
await route.options.head?.(assetContext)
|
|
2484
|
-
const meta = headFnContent?.meta
|
|
2485
|
-
const links = headFnContent?.links
|
|
2486
|
-
const headScripts = headFnContent?.scripts
|
|
2487
|
-
const styles = headFnContent?.styles
|
|
2488
|
-
|
|
2489
|
-
const scripts = await route.options.scripts?.(assetContext)
|
|
2490
|
-
const headers = await route.options.headers?.(assetContext)
|
|
2491
|
-
return {
|
|
2492
|
-
meta,
|
|
2493
|
-
links,
|
|
2494
|
-
headScripts,
|
|
2495
|
-
headers,
|
|
2496
|
-
scripts,
|
|
2497
|
-
styles,
|
|
2498
|
-
}
|
|
2499
|
-
}
|
|
2500
|
-
|
|
2501
|
-
const potentialPendingMinPromise = async () => {
|
|
2502
|
-
const latestMatch = this.getMatch(matchId)!
|
|
2503
|
-
if (latestMatch.minPendingPromise) {
|
|
2504
|
-
await latestMatch.minPendingPromise
|
|
2505
|
-
}
|
|
2506
|
-
}
|
|
2507
|
-
|
|
2508
|
-
const prevMatch = this.getMatch(matchId)!
|
|
2509
|
-
if (shouldSkipLoader(matchId)) {
|
|
2510
|
-
if (this.isServer) {
|
|
2511
|
-
const head = await executeHead()
|
|
2512
|
-
updateMatch(matchId, (prev) => ({
|
|
2513
|
-
...prev,
|
|
2514
|
-
...head,
|
|
2515
|
-
}))
|
|
2516
|
-
return this.getMatch(matchId)!
|
|
2517
|
-
}
|
|
2518
|
-
}
|
|
2519
|
-
// there is a loaderPromise, so we are in the middle of a load
|
|
2520
|
-
else if (prevMatch.loaderPromise) {
|
|
2521
|
-
// do not block if we already have stale data we can show
|
|
2522
|
-
// but only if the ongoing load is not a preload since error handling is different for preloads
|
|
2523
|
-
// and we don't want to swallow errors
|
|
2524
|
-
if (
|
|
2525
|
-
prevMatch.status === 'success' &&
|
|
2526
|
-
!sync &&
|
|
2527
|
-
!prevMatch.preload
|
|
2528
|
-
) {
|
|
2529
|
-
return this.getMatch(matchId)!
|
|
2530
|
-
}
|
|
2531
|
-
await prevMatch.loaderPromise
|
|
2532
|
-
const match = this.getMatch(matchId)!
|
|
2533
|
-
if (match.error) {
|
|
2534
|
-
handleRedirectAndNotFound(match, match.error)
|
|
2535
|
-
}
|
|
2536
|
-
} else {
|
|
2537
|
-
const parentMatchPromise = matchPromises[index - 1] as any
|
|
2538
|
-
|
|
2539
|
-
const getLoaderContext = (): LoaderFnContext => {
|
|
2540
|
-
const {
|
|
2541
|
-
params,
|
|
2542
|
-
loaderDeps,
|
|
2543
|
-
abortController,
|
|
2544
|
-
context,
|
|
2545
|
-
cause,
|
|
2546
|
-
} = this.getMatch(matchId)!
|
|
2547
|
-
|
|
2548
|
-
const preload = resolvePreload(matchId)
|
|
2549
|
-
|
|
2550
|
-
return {
|
|
2551
|
-
params,
|
|
2552
|
-
deps: loaderDeps,
|
|
2553
|
-
preload: !!preload,
|
|
2554
|
-
parentMatchPromise,
|
|
2555
|
-
abortController: abortController,
|
|
2556
|
-
context,
|
|
2557
|
-
location,
|
|
2558
|
-
navigate: (opts) =>
|
|
2559
|
-
this.navigate({ ...opts, _fromLocation: location }),
|
|
2560
|
-
cause: preload ? 'preload' : cause,
|
|
2561
|
-
route,
|
|
2562
|
-
}
|
|
2563
|
-
}
|
|
2564
|
-
|
|
2565
|
-
// This is where all of the stale-while-revalidate magic happens
|
|
2566
|
-
const age = Date.now() - this.getMatch(matchId)!.updatedAt
|
|
2567
|
-
|
|
2568
|
-
const preload = resolvePreload(matchId)
|
|
2569
|
-
|
|
2570
|
-
const staleAge = preload
|
|
2571
|
-
? (route.options.preloadStaleTime ??
|
|
2572
|
-
this.options.defaultPreloadStaleTime ??
|
|
2573
|
-
30_000) // 30 seconds for preloads by default
|
|
2574
|
-
: (route.options.staleTime ??
|
|
2575
|
-
this.options.defaultStaleTime ??
|
|
2576
|
-
0)
|
|
2577
|
-
|
|
2578
|
-
const shouldReloadOption = route.options.shouldReload
|
|
2579
|
-
|
|
2580
|
-
// Default to reloading the route all the time
|
|
2581
|
-
// Allow shouldReload to get the last say,
|
|
2582
|
-
// if provided.
|
|
2583
|
-
const shouldReload =
|
|
2584
|
-
typeof shouldReloadOption === 'function'
|
|
2585
|
-
? shouldReloadOption(getLoaderContext())
|
|
2586
|
-
: shouldReloadOption
|
|
2587
|
-
|
|
2588
|
-
updateMatch(matchId, (prev) => ({
|
|
2589
|
-
...prev,
|
|
2590
|
-
loaderPromise: createControlledPromise<void>(),
|
|
2591
|
-
preload:
|
|
2592
|
-
!!preload &&
|
|
2593
|
-
!this.state.matches.some((d) => d.id === matchId),
|
|
2594
|
-
}))
|
|
2595
|
-
|
|
2596
|
-
const runLoader = async () => {
|
|
2597
|
-
try {
|
|
2598
|
-
// If the Matches component rendered
|
|
2599
|
-
// the pending component and needs to show it for
|
|
2600
|
-
// a minimum duration, we''ll wait for it to resolve
|
|
2601
|
-
// before committing to the match and resolving
|
|
2602
|
-
// the loadPromise
|
|
2603
|
-
|
|
2604
|
-
// Actually run the loader and handle the result
|
|
2605
|
-
try {
|
|
2606
|
-
if (
|
|
2607
|
-
!this.isServer ||
|
|
2608
|
-
(this.isServer &&
|
|
2609
|
-
this.getMatch(matchId)!.ssr === true)
|
|
2610
|
-
) {
|
|
2611
|
-
this.loadRouteChunk(route)
|
|
2612
|
-
}
|
|
2613
|
-
|
|
2614
|
-
updateMatch(matchId, (prev) => ({
|
|
2615
|
-
...prev,
|
|
2616
|
-
isFetching: 'loader',
|
|
2617
|
-
}))
|
|
2618
|
-
|
|
2619
|
-
// Kick off the loader!
|
|
2620
|
-
const loaderData =
|
|
2621
|
-
await route.options.loader?.(getLoaderContext())
|
|
2622
|
-
|
|
2623
|
-
handleRedirectAndNotFound(
|
|
2624
|
-
this.getMatch(matchId)!,
|
|
2625
|
-
loaderData,
|
|
2626
|
-
)
|
|
2627
|
-
updateMatch(matchId, (prev) => ({
|
|
2628
|
-
...prev,
|
|
2629
|
-
loaderData,
|
|
2630
|
-
}))
|
|
2631
|
-
|
|
2632
|
-
// Lazy option can modify the route options,
|
|
2633
|
-
// so we need to wait for it to resolve before
|
|
2634
|
-
// we can use the options
|
|
2635
|
-
await route._lazyPromise
|
|
2636
|
-
const head = await executeHead()
|
|
2637
|
-
await potentialPendingMinPromise()
|
|
2638
|
-
|
|
2639
|
-
// Last but not least, wait for the the components
|
|
2640
|
-
// to be preloaded before we resolve the match
|
|
2641
|
-
await route._componentsPromise
|
|
2642
|
-
updateMatch(matchId, (prev) => ({
|
|
2643
|
-
...prev,
|
|
2644
|
-
error: undefined,
|
|
2645
|
-
status: 'success',
|
|
2646
|
-
isFetching: false,
|
|
2647
|
-
updatedAt: Date.now(),
|
|
2648
|
-
...head,
|
|
2649
|
-
}))
|
|
2650
|
-
} catch (e) {
|
|
2651
|
-
let error = e
|
|
2652
|
-
|
|
2653
|
-
await potentialPendingMinPromise()
|
|
2654
|
-
|
|
2655
|
-
handleRedirectAndNotFound(this.getMatch(matchId)!, e)
|
|
2656
|
-
|
|
2657
|
-
try {
|
|
2658
|
-
route.options.onError?.(e)
|
|
2659
|
-
} catch (onErrorError) {
|
|
2660
|
-
error = onErrorError
|
|
2661
|
-
handleRedirectAndNotFound(
|
|
2662
|
-
this.getMatch(matchId)!,
|
|
2663
|
-
onErrorError,
|
|
2664
|
-
)
|
|
2665
|
-
}
|
|
2666
|
-
const head = await executeHead()
|
|
2667
|
-
updateMatch(matchId, (prev) => ({
|
|
2668
|
-
...prev,
|
|
2669
|
-
error,
|
|
2670
|
-
status: 'error',
|
|
2671
|
-
isFetching: false,
|
|
2672
|
-
...head,
|
|
2673
|
-
}))
|
|
2674
|
-
}
|
|
2675
|
-
} catch (err) {
|
|
2676
|
-
const head = await executeHead()
|
|
2677
|
-
|
|
2678
|
-
updateMatch(matchId, (prev) => ({
|
|
2679
|
-
...prev,
|
|
2680
|
-
loaderPromise: undefined,
|
|
2681
|
-
...head,
|
|
2682
|
-
}))
|
|
2683
|
-
handleRedirectAndNotFound(this.getMatch(matchId)!, err)
|
|
2684
|
-
}
|
|
2685
|
-
}
|
|
2686
|
-
|
|
2687
|
-
// If the route is successful and still fresh, just resolve
|
|
2688
|
-
const { status, invalid } = this.getMatch(matchId)!
|
|
2689
|
-
loaderShouldRunAsync =
|
|
2690
|
-
status === 'success' &&
|
|
2691
|
-
(invalid || (shouldReload ?? age > staleAge))
|
|
2692
|
-
if (preload && route.options.preload === false) {
|
|
2693
|
-
// Do nothing
|
|
2694
|
-
} else if (loaderShouldRunAsync && !sync) {
|
|
2695
|
-
loaderIsRunningAsync = true
|
|
2696
|
-
;(async () => {
|
|
2697
|
-
try {
|
|
2698
|
-
await runLoader()
|
|
2699
|
-
const { loaderPromise, loadPromise } =
|
|
2700
|
-
this.getMatch(matchId)!
|
|
2701
|
-
loaderPromise?.resolve()
|
|
2702
|
-
loadPromise?.resolve()
|
|
2703
|
-
updateMatch(matchId, (prev) => ({
|
|
2704
|
-
...prev,
|
|
2705
|
-
loaderPromise: undefined,
|
|
2706
|
-
}))
|
|
2707
|
-
} catch (err) {
|
|
2708
|
-
if (isRedirect(err)) {
|
|
2709
|
-
await this.navigate(err.options)
|
|
2710
|
-
}
|
|
2711
|
-
}
|
|
2712
|
-
})()
|
|
2713
|
-
} else if (
|
|
2714
|
-
status !== 'success' ||
|
|
2715
|
-
(loaderShouldRunAsync && sync)
|
|
2716
|
-
) {
|
|
2717
|
-
await runLoader()
|
|
2718
|
-
} else {
|
|
2719
|
-
// if the loader did not run, still update head.
|
|
2720
|
-
// reason: parent's beforeLoad may have changed the route context
|
|
2721
|
-
// and only now do we know the route context (and that the loader would not run)
|
|
2722
|
-
const head = await executeHead()
|
|
2723
|
-
updateMatch(matchId, (prev) => ({
|
|
2724
|
-
...prev,
|
|
2725
|
-
...head,
|
|
2726
|
-
}))
|
|
2727
|
-
}
|
|
2728
|
-
}
|
|
2729
|
-
if (!loaderIsRunningAsync) {
|
|
2730
|
-
const { loaderPromise, loadPromise } =
|
|
2731
|
-
this.getMatch(matchId)!
|
|
2732
|
-
loaderPromise?.resolve()
|
|
2733
|
-
loadPromise?.resolve()
|
|
2734
|
-
}
|
|
2735
|
-
|
|
2736
|
-
updateMatch(matchId, (prev) => {
|
|
2737
|
-
clearTimeout(prev.pendingTimeout)
|
|
2738
|
-
return {
|
|
2739
|
-
...prev,
|
|
2740
|
-
isFetching: loaderIsRunningAsync
|
|
2741
|
-
? prev.isFetching
|
|
2742
|
-
: false,
|
|
2743
|
-
loaderPromise: loaderIsRunningAsync
|
|
2744
|
-
? prev.loaderPromise
|
|
2745
|
-
: undefined,
|
|
2746
|
-
invalid: false,
|
|
2747
|
-
pendingTimeout: undefined,
|
|
2748
|
-
_dehydrated: undefined,
|
|
2749
|
-
}
|
|
2750
|
-
})
|
|
2751
|
-
return this.getMatch(matchId)!
|
|
2752
|
-
})(),
|
|
2753
|
-
)
|
|
2754
|
-
})
|
|
2755
|
-
|
|
2756
|
-
await Promise.all(matchPromises)
|
|
2757
|
-
|
|
2758
|
-
resolveAll()
|
|
2759
|
-
} catch (err) {
|
|
2760
|
-
rejectAll(err)
|
|
2761
|
-
}
|
|
2762
|
-
})()
|
|
2763
|
-
})
|
|
2764
|
-
await triggerOnReady()
|
|
2765
|
-
} catch (err) {
|
|
2766
|
-
if (isRedirect(err) || isNotFound(err)) {
|
|
2767
|
-
if (isNotFound(err) && !allPreload) {
|
|
2768
|
-
await triggerOnReady()
|
|
2769
|
-
}
|
|
2770
|
-
|
|
2771
|
-
throw err
|
|
2772
|
-
}
|
|
2773
|
-
}
|
|
2774
|
-
|
|
2775
|
-
return matches
|
|
2776
|
-
}
|
|
2777
|
-
|
|
2778
2089
|
invalidate: InvalidateFn<
|
|
2779
2090
|
RouterCore<
|
|
2780
2091
|
TRouteTree,
|
|
@@ -2791,7 +2102,7 @@ export class RouterCore<
|
|
|
2791
2102
|
invalid: true,
|
|
2792
2103
|
...(opts?.forcePending || d.status === 'error'
|
|
2793
2104
|
? ({ status: 'pending', error: undefined } as const)
|
|
2794
|
-
:
|
|
2105
|
+
: undefined),
|
|
2795
2106
|
}
|
|
2796
2107
|
}
|
|
2797
2108
|
return d
|
|
@@ -2868,36 +2179,7 @@ export class RouterCore<
|
|
|
2868
2179
|
this.clearCache({ filter })
|
|
2869
2180
|
}
|
|
2870
2181
|
|
|
2871
|
-
loadRouteChunk =
|
|
2872
|
-
if (route._lazyPromise === undefined) {
|
|
2873
|
-
if (route.lazyFn) {
|
|
2874
|
-
route._lazyPromise = route.lazyFn().then((lazyRoute) => {
|
|
2875
|
-
// explicitly don't copy over the lazy route's id
|
|
2876
|
-
const { id: _id, ...options } = lazyRoute.options
|
|
2877
|
-
Object.assign(route.options, options)
|
|
2878
|
-
})
|
|
2879
|
-
} else {
|
|
2880
|
-
route._lazyPromise = Promise.resolve()
|
|
2881
|
-
}
|
|
2882
|
-
}
|
|
2883
|
-
|
|
2884
|
-
// If for some reason lazy resolves more lazy components...
|
|
2885
|
-
// We'll wait for that before pre attempt to preload any
|
|
2886
|
-
// components themselves.
|
|
2887
|
-
if (route._componentsPromise === undefined) {
|
|
2888
|
-
route._componentsPromise = route._lazyPromise.then(() =>
|
|
2889
|
-
Promise.all(
|
|
2890
|
-
componentTypes.map(async (type) => {
|
|
2891
|
-
const component = route.options[type]
|
|
2892
|
-
if ((component as any)?.preload) {
|
|
2893
|
-
await (component as any).preload()
|
|
2894
|
-
}
|
|
2895
|
-
}),
|
|
2896
|
-
),
|
|
2897
|
-
)
|
|
2898
|
-
}
|
|
2899
|
-
return route._componentsPromise
|
|
2900
|
-
}
|
|
2182
|
+
loadRouteChunk = loadRouteChunk
|
|
2901
2183
|
|
|
2902
2184
|
preloadRoute: PreloadRouteFn<
|
|
2903
2185
|
TRouteTree,
|
|
@@ -2937,7 +2219,8 @@ export class RouterCore<
|
|
|
2937
2219
|
})
|
|
2938
2220
|
|
|
2939
2221
|
try {
|
|
2940
|
-
matches = await
|
|
2222
|
+
matches = await loadMatches({
|
|
2223
|
+
router: this,
|
|
2941
2224
|
matches,
|
|
2942
2225
|
location: next,
|
|
2943
2226
|
preload: true,
|
|
@@ -3035,68 +2318,6 @@ export class RouterCore<
|
|
|
3035
2318
|
|
|
3036
2319
|
serverSsr?: ServerSsr
|
|
3037
2320
|
|
|
3038
|
-
_handleNotFound = (
|
|
3039
|
-
matches: Array<AnyRouteMatch>,
|
|
3040
|
-
err: NotFoundError,
|
|
3041
|
-
{
|
|
3042
|
-
updateMatch = this.updateMatch,
|
|
3043
|
-
}: {
|
|
3044
|
-
updateMatch?: (
|
|
3045
|
-
id: string,
|
|
3046
|
-
updater: (match: AnyRouteMatch) => AnyRouteMatch,
|
|
3047
|
-
) => void
|
|
3048
|
-
} = {},
|
|
3049
|
-
) => {
|
|
3050
|
-
// Find the route that should handle the not found error
|
|
3051
|
-
// First check if a specific route is requested to show the error
|
|
3052
|
-
const routeCursor = this.routesById[err.routeId ?? ''] ?? this.routeTree
|
|
3053
|
-
const matchesByRouteId: Record<string, AnyRouteMatch> = {}
|
|
3054
|
-
|
|
3055
|
-
// Setup routesByRouteId object for quick access
|
|
3056
|
-
for (const match of matches) {
|
|
3057
|
-
matchesByRouteId[match.routeId] = match
|
|
3058
|
-
}
|
|
3059
|
-
|
|
3060
|
-
// Ensure a NotFoundComponent exists on the route
|
|
3061
|
-
if (
|
|
3062
|
-
!routeCursor.options.notFoundComponent &&
|
|
3063
|
-
(this.options as any)?.defaultNotFoundComponent
|
|
3064
|
-
) {
|
|
3065
|
-
routeCursor.options.notFoundComponent = (
|
|
3066
|
-
this.options as any
|
|
3067
|
-
).defaultNotFoundComponent
|
|
3068
|
-
}
|
|
3069
|
-
|
|
3070
|
-
// Ensure we have a notFoundComponent
|
|
3071
|
-
invariant(
|
|
3072
|
-
routeCursor.options.notFoundComponent,
|
|
3073
|
-
'No notFoundComponent found. Please set a notFoundComponent on your route or provide a defaultNotFoundComponent to the router.',
|
|
3074
|
-
)
|
|
3075
|
-
|
|
3076
|
-
// Find the match for this route
|
|
3077
|
-
const matchForRoute = matchesByRouteId[routeCursor.id]
|
|
3078
|
-
|
|
3079
|
-
invariant(
|
|
3080
|
-
matchForRoute,
|
|
3081
|
-
'Could not find match for route: ' + routeCursor.id,
|
|
3082
|
-
)
|
|
3083
|
-
|
|
3084
|
-
// Assign the error to the match - using non-null assertion since we've checked with invariant
|
|
3085
|
-
updateMatch(matchForRoute.id, (prev) => ({
|
|
3086
|
-
...prev,
|
|
3087
|
-
status: 'notFound',
|
|
3088
|
-
error: err,
|
|
3089
|
-
isFetching: false,
|
|
3090
|
-
}))
|
|
3091
|
-
|
|
3092
|
-
if ((err as any).routerCode === 'BEFORE_LOAD' && routeCursor.parentRoute) {
|
|
3093
|
-
err.routeId = routeCursor.parentRoute.id
|
|
3094
|
-
this._handleNotFound(matches, err, {
|
|
3095
|
-
updateMatch,
|
|
3096
|
-
})
|
|
3097
|
-
}
|
|
3098
|
-
}
|
|
3099
|
-
|
|
3100
2321
|
hasNotFoundMatch = () => {
|
|
3101
2322
|
return this.__store.state.matches.some(
|
|
3102
2323
|
(d) => d.status === 'notFound' || d.globalNotFound,
|
|
@@ -3174,22 +2395,6 @@ function validateSearch(validateSearch: AnyValidator, input: unknown): unknown {
|
|
|
3174
2395
|
return {}
|
|
3175
2396
|
}
|
|
3176
2397
|
|
|
3177
|
-
export const componentTypes = [
|
|
3178
|
-
'component',
|
|
3179
|
-
'errorComponent',
|
|
3180
|
-
'pendingComponent',
|
|
3181
|
-
'notFoundComponent',
|
|
3182
|
-
] as const
|
|
3183
|
-
|
|
3184
|
-
function routeNeedsPreload(route: AnyRoute) {
|
|
3185
|
-
for (const componentType of componentTypes) {
|
|
3186
|
-
if ((route.options[componentType] as any)?.preload) {
|
|
3187
|
-
return true
|
|
3188
|
-
}
|
|
3189
|
-
}
|
|
3190
|
-
return false
|
|
3191
|
-
}
|
|
3192
|
-
|
|
3193
2398
|
interface RouteLike {
|
|
3194
2399
|
id: string
|
|
3195
2400
|
isRoot?: boolean
|
|
@@ -3562,7 +2767,8 @@ function applySearchMiddleware({
|
|
|
3562
2767
|
try {
|
|
3563
2768
|
const validatedSearch = {
|
|
3564
2769
|
...result,
|
|
3565
|
-
...(validateSearch(route.options.validateSearch, result) ??
|
|
2770
|
+
...(validateSearch(route.options.validateSearch, result) ??
|
|
2771
|
+
undefined),
|
|
3566
2772
|
}
|
|
3567
2773
|
return validatedSearch
|
|
3568
2774
|
} catch {
|