@tanstack/router-core 1.132.0-alpha.1 → 1.132.0-alpha.15
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 +9 -11
- package/dist/cjs/config.cjs +10 -0
- package/dist/cjs/config.cjs.map +1 -0
- package/dist/cjs/config.d.cts +17 -0
- package/dist/cjs/fileRoute.d.cts +3 -2
- package/dist/cjs/index.cjs +15 -3
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +11 -4
- 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/location.d.cts +38 -0
- package/dist/cjs/path.cjs +6 -49
- package/dist/cjs/path.cjs.map +1 -1
- package/dist/cjs/path.d.cts +3 -6
- 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/rewrite.cjs +63 -0
- package/dist/cjs/rewrite.cjs.map +1 -0
- package/dist/cjs/rewrite.d.cts +22 -0
- package/dist/cjs/route.cjs.map +1 -1
- package/dist/cjs/route.d.cts +42 -41
- package/dist/cjs/router.cjs +134 -681
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +68 -25
- package/dist/cjs/scroll-restoration.cjs +32 -29
- package/dist/cjs/scroll-restoration.cjs.map +1 -1
- package/dist/cjs/scroll-restoration.d.cts +1 -10
- 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 +52 -0
- package/dist/cjs/ssr/serializer/transformer.cjs.map +1 -0
- package/dist/cjs/ssr/serializer/transformer.d.cts +56 -0
- package/dist/cjs/ssr/server.d.cts +5 -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 +9 -11
- package/dist/esm/Matches.js.map +1 -1
- package/dist/esm/config.d.ts +17 -0
- package/dist/esm/config.js +10 -0
- package/dist/esm/config.js.map +1 -0
- package/dist/esm/fileRoute.d.ts +3 -2
- package/dist/esm/index.d.ts +11 -4
- package/dist/esm/index.js +17 -5
- 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/location.d.ts +38 -0
- package/dist/esm/path.d.ts +3 -6
- package/dist/esm/path.js +6 -49
- package/dist/esm/path.js.map +1 -1
- 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/rewrite.d.ts +22 -0
- package/dist/esm/rewrite.js +63 -0
- package/dist/esm/rewrite.js.map +1 -0
- package/dist/esm/route.d.ts +42 -41
- package/dist/esm/route.js.map +1 -1
- package/dist/esm/router.d.ts +68 -25
- package/dist/esm/router.js +136 -683
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/scroll-restoration.d.ts +1 -10
- package/dist/esm/scroll-restoration.js +32 -29
- 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 +56 -0
- package/dist/esm/ssr/serializer/transformer.js +52 -0
- package/dist/esm/ssr/serializer/transformer.js.map +1 -0
- package/dist/esm/ssr/server.d.ts +5 -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 +18 -10
- package/src/config.ts +42 -0
- package/src/fileRoute.ts +15 -3
- package/src/index.ts +32 -3
- package/src/load-matches.ts +955 -0
- package/src/location.ts +38 -0
- package/src/path.ts +5 -66
- package/src/qss.ts +27 -24
- package/src/redirect.ts +3 -3
- package/src/rewrite.ts +70 -0
- package/src/route.ts +146 -35
- package/src/router.ts +263 -972
- package/src/scroll-restoration.ts +42 -37
- 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 +215 -0
- package/src/ssr/server.ts +6 -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
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
import { Store, batch } from '@tanstack/store'
|
|
2
|
-
import {
|
|
3
|
-
createBrowserHistory,
|
|
4
|
-
createMemoryHistory,
|
|
5
|
-
parseHref,
|
|
6
|
-
} from '@tanstack/history'
|
|
2
|
+
import { createBrowserHistory, parseHref } from '@tanstack/history'
|
|
7
3
|
import invariant from 'tiny-invariant'
|
|
8
4
|
import {
|
|
9
5
|
createControlledPromise,
|
|
10
6
|
deepEqual,
|
|
7
|
+
findLast,
|
|
11
8
|
functionalUpdate,
|
|
12
9
|
last,
|
|
13
|
-
pick,
|
|
14
10
|
replaceEqualDeep,
|
|
15
11
|
} from './utils'
|
|
16
12
|
import {
|
|
@@ -20,7 +16,6 @@ import {
|
|
|
20
16
|
SEGMENT_TYPE_WILDCARD,
|
|
21
17
|
cleanPath,
|
|
22
18
|
interpolatePath,
|
|
23
|
-
joinPaths,
|
|
24
19
|
matchPathname,
|
|
25
20
|
parsePathname,
|
|
26
21
|
resolvePath,
|
|
@@ -34,6 +29,13 @@ import { defaultParseSearch, defaultStringifySearch } from './searchParams'
|
|
|
34
29
|
import { rootRouteId } from './root'
|
|
35
30
|
import { isRedirect, redirect } from './redirect'
|
|
36
31
|
import { createLRUCache } from './lru-cache'
|
|
32
|
+
import { loadMatches, loadRouteChunk, routeNeedsPreload } from './load-matches'
|
|
33
|
+
import {
|
|
34
|
+
composeRewrites,
|
|
35
|
+
executeRewriteInput,
|
|
36
|
+
executeRewriteOutput,
|
|
37
|
+
rewriteBasepath,
|
|
38
|
+
} from './rewrite'
|
|
37
39
|
import type { ParsePathnameCache, Segment } from './path'
|
|
38
40
|
import type { SearchParser, SearchSerializer } from './searchParams'
|
|
39
41
|
import type { AnyRedirect, ResolvedRedirect } from './redirect'
|
|
@@ -45,6 +47,7 @@ import type {
|
|
|
45
47
|
} from '@tanstack/history'
|
|
46
48
|
import type {
|
|
47
49
|
Awaitable,
|
|
50
|
+
Constrain,
|
|
48
51
|
ControlledPromise,
|
|
49
52
|
NoInfer,
|
|
50
53
|
NonNullableUpdater,
|
|
@@ -56,13 +59,10 @@ import type {
|
|
|
56
59
|
AnyContext,
|
|
57
60
|
AnyRoute,
|
|
58
61
|
AnyRouteWithContext,
|
|
59
|
-
BeforeLoadContextOptions,
|
|
60
|
-
LoaderFnContext,
|
|
61
62
|
MakeRemountDepsOptionsUnion,
|
|
62
63
|
RouteContextOptions,
|
|
63
64
|
RouteMask,
|
|
64
65
|
SearchMiddleware,
|
|
65
|
-
SsrContextOptions,
|
|
66
66
|
} from './route'
|
|
67
67
|
import type {
|
|
68
68
|
FullSearchSchema,
|
|
@@ -86,6 +86,11 @@ import type { Manifest } from './manifest'
|
|
|
86
86
|
import type { AnySchema, AnyValidator } from './validators'
|
|
87
87
|
import type { NavigateOptions, ResolveRelativePath, ToOptions } from './link'
|
|
88
88
|
import type { NotFoundError } from './not-found'
|
|
89
|
+
import type {
|
|
90
|
+
AnySerializationAdapter,
|
|
91
|
+
ValidateSerializableInput,
|
|
92
|
+
} from './ssr/serializer/transformer'
|
|
93
|
+
import type { AnyRouterConfig } from './config'
|
|
89
94
|
|
|
90
95
|
export type ControllablePromise<T = any> = Promise<T> & {
|
|
91
96
|
resolve: (value: T) => void
|
|
@@ -96,6 +101,8 @@ export type InjectedHtmlEntry = Promise<string>
|
|
|
96
101
|
|
|
97
102
|
export interface DefaultRegister {
|
|
98
103
|
router: AnyRouter
|
|
104
|
+
config: AnyRouterConfig
|
|
105
|
+
ssr: SSROption
|
|
99
106
|
}
|
|
100
107
|
|
|
101
108
|
export interface Register extends DefaultRegister {
|
|
@@ -113,12 +120,14 @@ export interface DefaultRouterOptionsExtensions {}
|
|
|
113
120
|
export interface RouterOptionsExtensions
|
|
114
121
|
extends DefaultRouterOptionsExtensions {}
|
|
115
122
|
|
|
123
|
+
export type SSROption = boolean | 'data-only'
|
|
124
|
+
|
|
116
125
|
export interface RouterOptions<
|
|
117
126
|
TRouteTree extends AnyRoute,
|
|
118
127
|
TTrailingSlashOption extends TrailingSlashOption,
|
|
119
128
|
TDefaultStructuralSharingOption extends boolean = false,
|
|
120
129
|
TRouterHistory extends RouterHistory = RouterHistory,
|
|
121
|
-
TDehydrated
|
|
130
|
+
TDehydrated = undefined,
|
|
122
131
|
> extends RouterOptionsExtensions {
|
|
123
132
|
/**
|
|
124
133
|
* The history object that will be used to manage the browser history.
|
|
@@ -263,6 +272,18 @@ export interface RouterOptions<
|
|
|
263
272
|
/**
|
|
264
273
|
* The basepath for then entire router. This is useful for mounting a router instance at a subpath.
|
|
265
274
|
*
|
|
275
|
+
* @deprecated - use `rewrite.input` with the new `rewriteBasepath` utility instead:
|
|
276
|
+
* ```ts
|
|
277
|
+
* const router = createRouter({
|
|
278
|
+
* routeTree,
|
|
279
|
+
* rewrite: rewriteBasepath('/basepath')
|
|
280
|
+
* // Or wrap existing rewrite functionality
|
|
281
|
+
* rewrite: rewriteBasepath('/basepath', {
|
|
282
|
+
* output: ({ url }) => {...},
|
|
283
|
+
* input: ({ url }) => {...},
|
|
284
|
+
* })
|
|
285
|
+
* })
|
|
286
|
+
* ```
|
|
266
287
|
* @default '/'
|
|
267
288
|
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#basepath-property)
|
|
268
289
|
*/
|
|
@@ -286,7 +307,10 @@ export interface RouterOptions<
|
|
|
286
307
|
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#dehydrate-method)
|
|
287
308
|
* @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/external-data-loading#critical-dehydrationhydration)
|
|
288
309
|
*/
|
|
289
|
-
dehydrate?: () =>
|
|
310
|
+
dehydrate?: () => Constrain<
|
|
311
|
+
TDehydrated,
|
|
312
|
+
ValidateSerializableInput<Register, TDehydrated>
|
|
313
|
+
>
|
|
290
314
|
/**
|
|
291
315
|
* A function that will be called when the router is hydrated.
|
|
292
316
|
*
|
|
@@ -356,7 +380,7 @@ export interface RouterOptions<
|
|
|
356
380
|
*
|
|
357
381
|
* @default true
|
|
358
382
|
*/
|
|
359
|
-
defaultSsr?:
|
|
383
|
+
defaultSsr?: SSROption
|
|
360
384
|
|
|
361
385
|
search?: {
|
|
362
386
|
/**
|
|
@@ -392,7 +416,9 @@ export interface RouterOptions<
|
|
|
392
416
|
*
|
|
393
417
|
* @default false
|
|
394
418
|
*/
|
|
395
|
-
scrollRestoration?:
|
|
419
|
+
scrollRestoration?:
|
|
420
|
+
| boolean
|
|
421
|
+
| ((opts: { location: ParsedLocation }) => boolean)
|
|
396
422
|
|
|
397
423
|
/**
|
|
398
424
|
* A function that will be called to get the key for the scroll restoration cache.
|
|
@@ -423,8 +449,48 @@ export interface RouterOptions<
|
|
|
423
449
|
* @default false
|
|
424
450
|
*/
|
|
425
451
|
disableGlobalCatchBoundary?: boolean
|
|
452
|
+
|
|
453
|
+
serializationAdapters?: ReadonlyArray<AnySerializationAdapter>
|
|
454
|
+
/**
|
|
455
|
+
* Configures how the router will rewrite the location between the actual href and the internal href of the router.
|
|
456
|
+
*
|
|
457
|
+
* @default undefined
|
|
458
|
+
* @description You can provide a custom rewrite pair (in/out) or use the utilities like `rewriteBasepath` as a convenience for common use cases, or even do both!
|
|
459
|
+
* This is useful for basepath rewriting, shifting data from the origin to the path (for things like )
|
|
460
|
+
*/
|
|
461
|
+
rewrite?: LocationRewrite
|
|
462
|
+
origin?: string
|
|
426
463
|
}
|
|
427
464
|
|
|
465
|
+
export type LocationRewrite = {
|
|
466
|
+
/**
|
|
467
|
+
* A function that will be called to rewrite the URL before it is interpreted by the router from the history instance.
|
|
468
|
+
* Utilities like `rewriteBasepath` are provided as a convenience for common use cases.
|
|
469
|
+
*
|
|
470
|
+
* @default undefined
|
|
471
|
+
*/
|
|
472
|
+
input?: LocationRewriteFunction
|
|
473
|
+
/**
|
|
474
|
+
* A function that will be called to rewrite the URL before it is committed to the actual history instance from the router.
|
|
475
|
+
* Utilities like `rewriteBasepath` are provided as a convenience for common use cases.
|
|
476
|
+
*
|
|
477
|
+
* @default undefined
|
|
478
|
+
*/
|
|
479
|
+
output?: LocationRewriteFunction
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* A function that will be called to rewrite the URL.
|
|
484
|
+
*
|
|
485
|
+
* @param url The URL to rewrite.
|
|
486
|
+
* @returns The rewritten URL (as a URL instance or full href string) or undefined if no rewrite is needed.
|
|
487
|
+
*/
|
|
488
|
+
export type LocationRewriteFunction = ({
|
|
489
|
+
url,
|
|
490
|
+
}: {
|
|
491
|
+
url: URL
|
|
492
|
+
}) => undefined | string | URL
|
|
493
|
+
|
|
428
494
|
export interface RouterState<
|
|
429
495
|
in out TRouteTree extends AnyRoute = AnyRoute,
|
|
430
496
|
in out TRouteMatch = MakeRouteMatchUnion,
|
|
@@ -614,8 +680,8 @@ export type InvalidateFn<TRouter extends AnyRouter> = (opts?: {
|
|
|
614
680
|
}) => Promise<void>
|
|
615
681
|
|
|
616
682
|
export type ParseLocationFn<TRouteTree extends AnyRoute> = (
|
|
683
|
+
locationToParse: HistoryLocation,
|
|
617
684
|
previousLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>,
|
|
618
|
-
locationToParse?: HistoryLocation,
|
|
619
685
|
) => ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
620
686
|
|
|
621
687
|
export type GetMatchRoutesFn = (
|
|
@@ -705,6 +771,7 @@ export interface ViewTransitionOptions {
|
|
|
705
771
|
}) => Array<string>)
|
|
706
772
|
}
|
|
707
773
|
|
|
774
|
+
// TODO where is this used? can we remove this?
|
|
708
775
|
export function defaultSerializeError(err: unknown) {
|
|
709
776
|
if (err instanceof Error) {
|
|
710
777
|
const obj = {
|
|
@@ -794,7 +861,10 @@ export class RouterCore<
|
|
|
794
861
|
'stringifySearch' | 'parseSearch' | 'context'
|
|
795
862
|
>
|
|
796
863
|
history!: TRouterHistory
|
|
864
|
+
rewrite?: LocationRewrite
|
|
865
|
+
origin?: string
|
|
797
866
|
latestLocation!: ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
867
|
+
// @deprecated - basepath functionality is now implemented via the `rewrite` option
|
|
798
868
|
basepath!: string
|
|
799
869
|
routeTree!: TRouteTree
|
|
800
870
|
routesById!: RoutesById<TRouteTree>
|
|
@@ -858,7 +928,6 @@ export class RouterCore<
|
|
|
858
928
|
)
|
|
859
929
|
}
|
|
860
930
|
|
|
861
|
-
const previousOptions = this.options
|
|
862
931
|
this.options = {
|
|
863
932
|
...this.options,
|
|
864
933
|
...newOptions,
|
|
@@ -876,32 +945,42 @@ export class RouterCore<
|
|
|
876
945
|
: undefined
|
|
877
946
|
|
|
878
947
|
if (
|
|
879
|
-
!this.
|
|
880
|
-
(
|
|
948
|
+
!this.history ||
|
|
949
|
+
(this.options.history && this.options.history !== this.history)
|
|
881
950
|
) {
|
|
882
|
-
if (
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
this.
|
|
951
|
+
if (!this.options.history) {
|
|
952
|
+
if (!this.isServer) {
|
|
953
|
+
this.history = createBrowserHistory() as TRouterHistory
|
|
954
|
+
}
|
|
955
|
+
} else {
|
|
956
|
+
this.history = this.options.history
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
// For backwards compatibility, we support a basepath option, which we now implement as a rewrite
|
|
960
|
+
if (this.options.basepath) {
|
|
961
|
+
const basepathRewrite = rewriteBasepath({
|
|
962
|
+
basepath: this.options.basepath,
|
|
963
|
+
})
|
|
964
|
+
if (this.options.rewrite) {
|
|
965
|
+
this.rewrite = composeRewrites([basepathRewrite, this.options.rewrite])
|
|
888
966
|
} else {
|
|
889
|
-
this.
|
|
967
|
+
this.rewrite = basepathRewrite
|
|
890
968
|
}
|
|
969
|
+
} else {
|
|
970
|
+
this.rewrite = this.options.rewrite
|
|
891
971
|
}
|
|
892
972
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
(this.
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
this.latestLocation = this.parseLocation()
|
|
973
|
+
this.origin = this.options.origin
|
|
974
|
+
if (!this.origin) {
|
|
975
|
+
if (!this.isServer) {
|
|
976
|
+
this.origin = window.origin
|
|
977
|
+
} else {
|
|
978
|
+
// fallback for the server, can be overridden by calling router.update({origin}) on the server
|
|
979
|
+
this.origin = 'http://localhost'
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
if (this.history) {
|
|
983
|
+
this.updateLatestLocation()
|
|
905
984
|
}
|
|
906
985
|
|
|
907
986
|
if (this.options.routeTree !== this.routeTree) {
|
|
@@ -909,7 +988,7 @@ export class RouterCore<
|
|
|
909
988
|
this.buildRouteTree()
|
|
910
989
|
}
|
|
911
990
|
|
|
912
|
-
if (!this.__store) {
|
|
991
|
+
if (!this.__store && this.latestLocation) {
|
|
913
992
|
this.__store = new Store(getInitialRouterState(this.latestLocation), {
|
|
914
993
|
onUpdate: () => {
|
|
915
994
|
this.__store.state = {
|
|
@@ -935,10 +1014,17 @@ export class RouterCore<
|
|
|
935
1014
|
}
|
|
936
1015
|
}
|
|
937
1016
|
|
|
938
|
-
get state() {
|
|
1017
|
+
get state(): RouterState<TRouteTree> {
|
|
939
1018
|
return this.__store.state
|
|
940
1019
|
}
|
|
941
1020
|
|
|
1021
|
+
updateLatestLocation = () => {
|
|
1022
|
+
this.latestLocation = this.parseLocation(
|
|
1023
|
+
this.history.location,
|
|
1024
|
+
this.latestLocation,
|
|
1025
|
+
)
|
|
1026
|
+
}
|
|
1027
|
+
|
|
942
1028
|
buildRouteTree = () => {
|
|
943
1029
|
const { routesById, routesByPath, flatRoutes } = processRouteTree({
|
|
944
1030
|
routeTree: this.routeTree,
|
|
@@ -985,29 +1071,41 @@ export class RouterCore<
|
|
|
985
1071
|
}
|
|
986
1072
|
|
|
987
1073
|
parseLocation: ParseLocationFn<TRouteTree> = (
|
|
988
|
-
previousLocation,
|
|
989
1074
|
locationToParse,
|
|
1075
|
+
previousLocation,
|
|
990
1076
|
) => {
|
|
991
1077
|
const parse = ({
|
|
992
|
-
|
|
993
|
-
search,
|
|
994
|
-
hash,
|
|
1078
|
+
href,
|
|
995
1079
|
state,
|
|
996
1080
|
}: HistoryLocation): ParsedLocation<FullSearchSchema<TRouteTree>> => {
|
|
997
|
-
|
|
1081
|
+
// Before we do any processing, we need to allow rewrites to modify the URL
|
|
1082
|
+
// build up the full URL by combining the href from history with the router's origin
|
|
1083
|
+
const fullUrl = new URL(href, this.origin)
|
|
1084
|
+
const url = executeRewriteInput(this.rewrite, fullUrl)
|
|
1085
|
+
|
|
1086
|
+
const parsedSearch = this.options.parseSearch(url.search)
|
|
998
1087
|
const searchStr = this.options.stringifySearch(parsedSearch)
|
|
1088
|
+
// Make sure our final url uses the re-stringified pathname, search, and has for consistency
|
|
1089
|
+
// (We were already doing this, so just keeping it for now)
|
|
1090
|
+
url.search = searchStr
|
|
1091
|
+
|
|
1092
|
+
const fullPath = url.href.replace(url.origin, '')
|
|
1093
|
+
|
|
1094
|
+
const { pathname, hash } = url
|
|
999
1095
|
|
|
1000
1096
|
return {
|
|
1097
|
+
href: fullPath,
|
|
1098
|
+
publicHref: href,
|
|
1099
|
+
url: url.href,
|
|
1001
1100
|
pathname,
|
|
1002
1101
|
searchStr,
|
|
1003
1102
|
search: replaceEqualDeep(previousLocation?.search, parsedSearch) as any,
|
|
1004
1103
|
hash: hash.split('#').reverse()[0] ?? '',
|
|
1005
|
-
href: `${pathname}${searchStr}${hash}`,
|
|
1006
1104
|
state: replaceEqualDeep(previousLocation?.state, state),
|
|
1007
1105
|
}
|
|
1008
1106
|
}
|
|
1009
1107
|
|
|
1010
|
-
const location = parse(locationToParse
|
|
1108
|
+
const location = parse(locationToParse)
|
|
1011
1109
|
|
|
1012
1110
|
const { __tempLocation, __tempKey } = location.state
|
|
1013
1111
|
|
|
@@ -1030,11 +1128,9 @@ export class RouterCore<
|
|
|
1030
1128
|
|
|
1031
1129
|
resolvePathWithBase = (from: string, path: string) => {
|
|
1032
1130
|
const resolvedPath = resolvePath({
|
|
1033
|
-
basepath: this.basepath,
|
|
1034
1131
|
base: from,
|
|
1035
1132
|
to: cleanPath(path),
|
|
1036
1133
|
trailingSlash: this.options.trailingSlash,
|
|
1037
|
-
caseSensitive: this.options.caseSensitive,
|
|
1038
1134
|
parseCache: this.parsePathnameCache,
|
|
1039
1135
|
})
|
|
1040
1136
|
return resolvedPath
|
|
@@ -1139,8 +1235,8 @@ export class RouterCore<
|
|
|
1139
1235
|
const parentMatchId = parentMatch?.id
|
|
1140
1236
|
|
|
1141
1237
|
const parentContext = !parentMatchId
|
|
1142
|
-
? ((this.options.context as any) ??
|
|
1143
|
-
: (parentMatch.context ?? this.options.context ??
|
|
1238
|
+
? ((this.options.context as any) ?? undefined)
|
|
1239
|
+
: (parentMatch.context ?? this.options.context ?? undefined)
|
|
1144
1240
|
|
|
1145
1241
|
return parentContext
|
|
1146
1242
|
}
|
|
@@ -1162,12 +1258,12 @@ export class RouterCore<
|
|
|
1162
1258
|
] = (() => {
|
|
1163
1259
|
// Validate the search params and stabilize them
|
|
1164
1260
|
const parentSearch = parentMatch?.search ?? next.search
|
|
1165
|
-
const parentStrictSearch = parentMatch?._strictSearch ??
|
|
1261
|
+
const parentStrictSearch = parentMatch?._strictSearch ?? undefined
|
|
1166
1262
|
|
|
1167
1263
|
try {
|
|
1168
1264
|
const strictSearch =
|
|
1169
1265
|
validateSearch(route.options.validateSearch, { ...parentSearch }) ??
|
|
1170
|
-
|
|
1266
|
+
undefined
|
|
1171
1267
|
|
|
1172
1268
|
return [
|
|
1173
1269
|
{
|
|
@@ -1266,7 +1362,7 @@ export class RouterCore<
|
|
|
1266
1362
|
? replaceEqualDeep(previousMatch.params, routeParams)
|
|
1267
1363
|
: routeParams,
|
|
1268
1364
|
_strictParams: usedParams,
|
|
1269
|
-
pathname:
|
|
1365
|
+
pathname: interpolatedPath,
|
|
1270
1366
|
updatedAt: Date.now(),
|
|
1271
1367
|
search: previousMatch
|
|
1272
1368
|
? replaceEqualDeep(previousMatch.search, preMatchSearch)
|
|
@@ -1277,7 +1373,10 @@ export class RouterCore<
|
|
|
1277
1373
|
isFetching: false,
|
|
1278
1374
|
error: undefined,
|
|
1279
1375
|
paramsError: parseErrors[index],
|
|
1280
|
-
__routeContext:
|
|
1376
|
+
__routeContext: undefined,
|
|
1377
|
+
_nonReactive: {
|
|
1378
|
+
loadPromise: createControlledPromise(),
|
|
1379
|
+
},
|
|
1281
1380
|
__beforeLoadContext: undefined,
|
|
1282
1381
|
context: {},
|
|
1283
1382
|
abortController: new AbortController(),
|
|
@@ -1293,7 +1392,6 @@ export class RouterCore<
|
|
|
1293
1392
|
headScripts: undefined,
|
|
1294
1393
|
meta: undefined,
|
|
1295
1394
|
staticData: route.options.staticData || {},
|
|
1296
|
-
loadPromise: createControlledPromise(),
|
|
1297
1395
|
fullPath: route.fullPath,
|
|
1298
1396
|
}
|
|
1299
1397
|
}
|
|
@@ -1328,22 +1426,25 @@ export class RouterCore<
|
|
|
1328
1426
|
const parentContext = getParentContext(parentMatch)
|
|
1329
1427
|
|
|
1330
1428
|
// 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
1429
|
|
|
1345
|
-
|
|
1346
|
-
|
|
1430
|
+
if (route.options.context) {
|
|
1431
|
+
const contextFnContext: RouteContextOptions<any, any, any, any> = {
|
|
1432
|
+
deps: match.loaderDeps,
|
|
1433
|
+
params: match.params,
|
|
1434
|
+
context: parentContext ?? {},
|
|
1435
|
+
location: next,
|
|
1436
|
+
navigate: (opts: any) =>
|
|
1437
|
+
this.navigate({ ...opts, _fromLocation: next }),
|
|
1438
|
+
buildLocation: this.buildLocation,
|
|
1439
|
+
cause: match.cause,
|
|
1440
|
+
abortController: match.abortController,
|
|
1441
|
+
preload: !!match.preload,
|
|
1442
|
+
matches,
|
|
1443
|
+
}
|
|
1444
|
+
// Get the route context
|
|
1445
|
+
match.__routeContext =
|
|
1446
|
+
route.options.context(contextFnContext) ?? undefined
|
|
1447
|
+
}
|
|
1347
1448
|
|
|
1348
1449
|
match.context = {
|
|
1349
1450
|
...parentContext,
|
|
@@ -1366,7 +1467,6 @@ export class RouterCore<
|
|
|
1366
1467
|
return getMatchedRoutes({
|
|
1367
1468
|
pathname,
|
|
1368
1469
|
routePathname,
|
|
1369
|
-
basepath: this.basepath,
|
|
1370
1470
|
caseSensitive: this.options.caseSensitive,
|
|
1371
1471
|
routesByPath: this.routesByPath,
|
|
1372
1472
|
routesById: this.routesById,
|
|
@@ -1381,13 +1481,8 @@ export class RouterCore<
|
|
|
1381
1481
|
if (!match) return
|
|
1382
1482
|
|
|
1383
1483
|
match.abortController.abort()
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
return {
|
|
1387
|
-
...prev,
|
|
1388
|
-
pendingTimeout: undefined,
|
|
1389
|
-
}
|
|
1390
|
-
})
|
|
1484
|
+
clearTimeout(match._nonReactive.pendingTimeout)
|
|
1485
|
+
match._nonReactive.pendingTimeout = undefined
|
|
1391
1486
|
}
|
|
1392
1487
|
|
|
1393
1488
|
cancelMatches = () => {
|
|
@@ -1409,106 +1504,94 @@ export class RouterCore<
|
|
|
1409
1504
|
_buildLocation: true,
|
|
1410
1505
|
})
|
|
1411
1506
|
|
|
1507
|
+
// Now let's find the starting pathname
|
|
1508
|
+
// This should default to the current location if no from is provided
|
|
1412
1509
|
const lastMatch = last(allCurrentLocationMatches)!
|
|
1413
1510
|
|
|
1414
|
-
//
|
|
1415
|
-
//
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
// If the route is changing we need to find the relative fromPath
|
|
1427
|
-
if (dest.unsafeRelative === 'path') {
|
|
1428
|
-
fromPath = currentLocation.pathname
|
|
1429
|
-
} else if (routeIsChanging && dest.from) {
|
|
1430
|
-
fromPath = dest.from
|
|
1431
|
-
|
|
1432
|
-
// do this check only on navigations during test or development
|
|
1433
|
-
if (process.env.NODE_ENV !== 'production' && dest._isNavigate) {
|
|
1434
|
-
const allFromMatches = this.getMatchedRoutes(
|
|
1435
|
-
dest.from,
|
|
1436
|
-
undefined,
|
|
1437
|
-
).matchedRoutes
|
|
1511
|
+
// check that from path exists in the current route tree
|
|
1512
|
+
// do this check only on navigations during test or development
|
|
1513
|
+
if (
|
|
1514
|
+
dest.from &&
|
|
1515
|
+
process.env.NODE_ENV !== 'production' &&
|
|
1516
|
+
dest._isNavigate
|
|
1517
|
+
) {
|
|
1518
|
+
const allFromMatches = this.getMatchedRoutes(
|
|
1519
|
+
dest.from,
|
|
1520
|
+
undefined,
|
|
1521
|
+
).matchedRoutes
|
|
1438
1522
|
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
return comparePaths(d.fullPath, fromPath)
|
|
1443
|
-
})
|
|
1523
|
+
const matchedFrom = findLast(allCurrentLocationMatches, (d) => {
|
|
1524
|
+
return comparePaths(d.fullPath, dest.from!)
|
|
1525
|
+
})
|
|
1444
1526
|
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1527
|
+
const matchedCurrent = findLast(allFromMatches, (d) => {
|
|
1528
|
+
return comparePaths(d.fullPath, lastMatch.fullPath)
|
|
1529
|
+
})
|
|
1448
1530
|
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
}
|
|
1531
|
+
// for from to be invalid it shouldn't just be unmatched to currentLocation
|
|
1532
|
+
// but the currentLocation should also be unmatched to from
|
|
1533
|
+
if (!matchedFrom && !matchedCurrent) {
|
|
1534
|
+
console.warn(`Could not find match for from: ${dest.from}`)
|
|
1454
1535
|
}
|
|
1455
1536
|
}
|
|
1456
1537
|
|
|
1538
|
+
const defaultedFromPath =
|
|
1539
|
+
dest.unsafeRelative === 'path'
|
|
1540
|
+
? currentLocation.pathname
|
|
1541
|
+
: (dest.from ?? lastMatch.fullPath)
|
|
1542
|
+
|
|
1543
|
+
// ensure this includes the basePath if set
|
|
1544
|
+
const fromPath = this.resolvePathWithBase(defaultedFromPath, '.')
|
|
1545
|
+
|
|
1457
1546
|
// From search should always use the current location
|
|
1458
1547
|
const fromSearch = lastMatch.search
|
|
1459
1548
|
// Same with params. It can't hurt to provide as many as possible
|
|
1460
1549
|
const fromParams = { ...lastMatch.params }
|
|
1461
1550
|
|
|
1462
1551
|
// Resolve the next to
|
|
1552
|
+
// ensure this includes the basePath if set
|
|
1463
1553
|
const nextTo = dest.to
|
|
1464
1554
|
? this.resolvePathWithBase(fromPath, `${dest.to}`)
|
|
1465
1555
|
: this.resolvePathWithBase(fromPath, '.')
|
|
1466
1556
|
|
|
1467
1557
|
// Resolve the next params
|
|
1468
|
-
|
|
1558
|
+
const nextParams =
|
|
1469
1559
|
dest.params === false || dest.params === null
|
|
1470
1560
|
? {}
|
|
1471
1561
|
: (dest.params ?? true) === true
|
|
1472
1562
|
? fromParams
|
|
1473
|
-
:
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1563
|
+
: Object.assign(
|
|
1564
|
+
fromParams,
|
|
1565
|
+
functionalUpdate(dest.params as any, fromParams),
|
|
1566
|
+
)
|
|
1477
1567
|
|
|
1478
1568
|
// Interpolate the path first to get the actual resolved path, then match against that
|
|
1479
1569
|
const interpolatedNextTo = interpolatePath({
|
|
1480
1570
|
path: nextTo,
|
|
1481
|
-
params: nextParams
|
|
1571
|
+
params: nextParams,
|
|
1482
1572
|
parseCache: this.parsePathnameCache,
|
|
1483
1573
|
}).interpolatedPath
|
|
1484
1574
|
|
|
1485
|
-
const destRoutes = this.matchRoutes(
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
{
|
|
1489
|
-
_buildLocation: true,
|
|
1490
|
-
},
|
|
1491
|
-
).map((d) => this.looseRoutesById[d.routeId]!)
|
|
1575
|
+
const destRoutes = this.matchRoutes(interpolatedNextTo, undefined, {
|
|
1576
|
+
_buildLocation: true,
|
|
1577
|
+
}).map((d) => this.looseRoutesById[d.routeId]!)
|
|
1492
1578
|
|
|
1493
1579
|
// If there are any params, we need to stringify them
|
|
1494
1580
|
if (Object.keys(nextParams).length > 0) {
|
|
1495
|
-
destRoutes
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
)
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
|
-
.forEach((fn) => {
|
|
1503
|
-
nextParams = { ...nextParams!, ...fn!(nextParams) }
|
|
1504
|
-
})
|
|
1581
|
+
for (const route of destRoutes) {
|
|
1582
|
+
const fn =
|
|
1583
|
+
route.options.params?.stringify ?? route.options.stringifyParams
|
|
1584
|
+
if (fn) {
|
|
1585
|
+
Object.assign(nextParams, fn(nextParams))
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1505
1588
|
}
|
|
1506
1589
|
|
|
1507
1590
|
const nextPathname = interpolatePath({
|
|
1508
1591
|
// Use the original template path for interpolation
|
|
1509
1592
|
// This preserves the original parameter syntax including optional parameters
|
|
1510
1593
|
path: nextTo,
|
|
1511
|
-
params: nextParams
|
|
1594
|
+
params: nextParams,
|
|
1512
1595
|
leaveWildcards: false,
|
|
1513
1596
|
leaveParams: opts.leaveParams,
|
|
1514
1597
|
decodeCharMap: this.pathParamsDecodeCharMap,
|
|
@@ -1518,20 +1601,20 @@ export class RouterCore<
|
|
|
1518
1601
|
// Resolve the next search
|
|
1519
1602
|
let nextSearch = fromSearch
|
|
1520
1603
|
if (opts._includeValidateSearch && this.options.search?.strict) {
|
|
1521
|
-
|
|
1604
|
+
const validatedSearch = {}
|
|
1522
1605
|
destRoutes.forEach((route) => {
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1606
|
+
if (route.options.validateSearch) {
|
|
1607
|
+
try {
|
|
1608
|
+
Object.assign(
|
|
1609
|
+
validatedSearch,
|
|
1610
|
+
validateSearch(route.options.validateSearch, {
|
|
1528
1611
|
...validatedSearch,
|
|
1529
1612
|
...nextSearch,
|
|
1530
|
-
})
|
|
1531
|
-
|
|
1613
|
+
}),
|
|
1614
|
+
)
|
|
1615
|
+
} catch {
|
|
1616
|
+
// ignore errors here because they are already handled in matchRoutes
|
|
1532
1617
|
}
|
|
1533
|
-
} catch {
|
|
1534
|
-
// ignore errors here because they are already handled in matchRoutes
|
|
1535
1618
|
}
|
|
1536
1619
|
})
|
|
1537
1620
|
nextSearch = validatedSearch
|
|
@@ -1572,14 +1655,25 @@ export class RouterCore<
|
|
|
1572
1655
|
// Replace the equal deep
|
|
1573
1656
|
nextState = replaceEqualDeep(currentLocation.state, nextState)
|
|
1574
1657
|
|
|
1575
|
-
//
|
|
1658
|
+
// Create the full path of the location
|
|
1659
|
+
const fullPath = `${nextPathname}${searchStr}${hashStr}`
|
|
1660
|
+
|
|
1661
|
+
// Create the new href with full origin
|
|
1662
|
+
const url = new URL(fullPath, this.origin)
|
|
1663
|
+
|
|
1664
|
+
// If a rewrite function is provided, use it to rewrite the URL
|
|
1665
|
+
const rewrittenUrl = executeRewriteOutput(this.rewrite, url)
|
|
1666
|
+
|
|
1576
1667
|
return {
|
|
1668
|
+
publicHref:
|
|
1669
|
+
rewrittenUrl.pathname + rewrittenUrl.search + rewrittenUrl.hash,
|
|
1670
|
+
href: fullPath,
|
|
1671
|
+
url: rewrittenUrl.href,
|
|
1577
1672
|
pathname: nextPathname,
|
|
1578
1673
|
search: nextSearch,
|
|
1579
1674
|
searchStr,
|
|
1580
1675
|
state: nextState as any,
|
|
1581
1676
|
hash: hash ?? '',
|
|
1582
|
-
href: `${nextPathname}${searchStr}${hashStr}`,
|
|
1583
1677
|
unmaskOnReload: dest.unmaskOnReload,
|
|
1584
1678
|
}
|
|
1585
1679
|
}
|
|
@@ -1597,7 +1691,6 @@ export class RouterCore<
|
|
|
1597
1691
|
|
|
1598
1692
|
const foundMask = this.options.routeMasks?.find((d) => {
|
|
1599
1693
|
const match = matchPathname(
|
|
1600
|
-
this.basepath,
|
|
1601
1694
|
next.pathname,
|
|
1602
1695
|
{
|
|
1603
1696
|
to: d.from,
|
|
@@ -1618,7 +1711,7 @@ export class RouterCore<
|
|
|
1618
1711
|
if (foundMask) {
|
|
1619
1712
|
const { from: _from, ...maskProps } = foundMask
|
|
1620
1713
|
maskedDest = {
|
|
1621
|
-
|
|
1714
|
+
from: opts.from,
|
|
1622
1715
|
...maskProps,
|
|
1623
1716
|
params,
|
|
1624
1717
|
}
|
|
@@ -1636,7 +1729,7 @@ export class RouterCore<
|
|
|
1636
1729
|
|
|
1637
1730
|
if (opts.mask) {
|
|
1638
1731
|
return buildWithMatches(opts, {
|
|
1639
|
-
|
|
1732
|
+
from: opts.from,
|
|
1640
1733
|
...opts.mask,
|
|
1641
1734
|
})
|
|
1642
1735
|
}
|
|
@@ -1671,7 +1764,8 @@ export class RouterCore<
|
|
|
1671
1764
|
return isEqual
|
|
1672
1765
|
}
|
|
1673
1766
|
|
|
1674
|
-
const isSameUrl =
|
|
1767
|
+
const isSameUrl =
|
|
1768
|
+
trimPathRight(this.latestLocation.href) === trimPathRight(next.href)
|
|
1675
1769
|
|
|
1676
1770
|
const previousCommitPromise = this.commitLocationPromise
|
|
1677
1771
|
this.commitLocationPromise = createControlledPromise<void>(() => {
|
|
@@ -1720,7 +1814,7 @@ export class RouterCore<
|
|
|
1720
1814
|
this.shouldViewTransition = viewTransition
|
|
1721
1815
|
|
|
1722
1816
|
this.history[next.replace ? 'replace' : 'push'](
|
|
1723
|
-
nextHistory.
|
|
1817
|
+
nextHistory.publicHref,
|
|
1724
1818
|
nextHistory.state,
|
|
1725
1819
|
{ ignoreBlocker },
|
|
1726
1820
|
)
|
|
@@ -1782,7 +1876,7 @@ export class RouterCore<
|
|
|
1782
1876
|
if (reloadDocument) {
|
|
1783
1877
|
if (!href) {
|
|
1784
1878
|
const location = this.buildLocation({ to, ...rest } as any)
|
|
1785
|
-
href =
|
|
1879
|
+
href = location.href
|
|
1786
1880
|
}
|
|
1787
1881
|
if (rest.replace) {
|
|
1788
1882
|
window.location.replace(href)
|
|
@@ -1805,7 +1899,7 @@ export class RouterCore<
|
|
|
1805
1899
|
beforeLoad = () => {
|
|
1806
1900
|
// Cancel any pending matches
|
|
1807
1901
|
this.cancelMatches()
|
|
1808
|
-
this.
|
|
1902
|
+
this.updateLatestLocation()
|
|
1809
1903
|
|
|
1810
1904
|
if (this.isServer) {
|
|
1811
1905
|
// for SPAs on the initial load, this is handled by the Transitioner
|
|
@@ -1835,6 +1929,7 @@ export class RouterCore<
|
|
|
1835
1929
|
throw redirect({ href: nextLocation.href })
|
|
1836
1930
|
}
|
|
1837
1931
|
}
|
|
1932
|
+
|
|
1838
1933
|
// Match the routes
|
|
1839
1934
|
const pendingMatches = this.matchRoutes(this.latestLocation)
|
|
1840
1935
|
|
|
@@ -1884,10 +1979,12 @@ export class RouterCore<
|
|
|
1884
1979
|
}),
|
|
1885
1980
|
})
|
|
1886
1981
|
|
|
1887
|
-
await
|
|
1982
|
+
await loadMatches({
|
|
1983
|
+
router: this,
|
|
1888
1984
|
sync: opts?.sync,
|
|
1889
1985
|
matches: this.state.pendingMatches as Array<AnyRouteMatch>,
|
|
1890
1986
|
location: next,
|
|
1987
|
+
updateMatch: this.updateMatch,
|
|
1891
1988
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
1892
1989
|
onReady: async () => {
|
|
1893
1990
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
@@ -1978,6 +2075,7 @@ export class RouterCore<
|
|
|
1978
2075
|
this.latestLoadPromise = undefined
|
|
1979
2076
|
this.commitLocationPromise = undefined
|
|
1980
2077
|
}
|
|
2078
|
+
|
|
1981
2079
|
resolve()
|
|
1982
2080
|
})
|
|
1983
2081
|
})
|
|
@@ -2077,704 +2175,6 @@ export class RouterCore<
|
|
|
2077
2175
|
)
|
|
2078
2176
|
}
|
|
2079
2177
|
|
|
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
2178
|
invalidate: InvalidateFn<
|
|
2779
2179
|
RouterCore<
|
|
2780
2180
|
TRouteTree,
|
|
@@ -2791,7 +2191,7 @@ export class RouterCore<
|
|
|
2791
2191
|
invalid: true,
|
|
2792
2192
|
...(opts?.forcePending || d.status === 'error'
|
|
2793
2193
|
? ({ status: 'pending', error: undefined } as const)
|
|
2794
|
-
:
|
|
2194
|
+
: undefined),
|
|
2795
2195
|
}
|
|
2796
2196
|
}
|
|
2797
2197
|
return d
|
|
@@ -2868,36 +2268,7 @@ export class RouterCore<
|
|
|
2868
2268
|
this.clearCache({ filter })
|
|
2869
2269
|
}
|
|
2870
2270
|
|
|
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
|
-
}
|
|
2271
|
+
loadRouteChunk = loadRouteChunk
|
|
2901
2272
|
|
|
2902
2273
|
preloadRoute: PreloadRouteFn<
|
|
2903
2274
|
TRouteTree,
|
|
@@ -2937,7 +2308,8 @@ export class RouterCore<
|
|
|
2937
2308
|
})
|
|
2938
2309
|
|
|
2939
2310
|
try {
|
|
2940
|
-
matches = await
|
|
2311
|
+
matches = await loadMatches({
|
|
2312
|
+
router: this,
|
|
2941
2313
|
matches,
|
|
2942
2314
|
location: next,
|
|
2943
2315
|
preload: true,
|
|
@@ -3002,7 +2374,6 @@ export class RouterCore<
|
|
|
3002
2374
|
: this.state.resolvedLocation || this.state.location
|
|
3003
2375
|
|
|
3004
2376
|
const match = matchPathname(
|
|
3005
|
-
this.basepath,
|
|
3006
2377
|
baseLocation.pathname,
|
|
3007
2378
|
{
|
|
3008
2379
|
...opts,
|
|
@@ -3035,68 +2406,6 @@ export class RouterCore<
|
|
|
3035
2406
|
|
|
3036
2407
|
serverSsr?: ServerSsr
|
|
3037
2408
|
|
|
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
2409
|
hasNotFoundMatch = () => {
|
|
3101
2410
|
return this.__store.state.matches.some(
|
|
3102
2411
|
(d) => d.status === 'notFound' || d.globalNotFound,
|
|
@@ -3174,22 +2483,6 @@ function validateSearch(validateSearch: AnyValidator, input: unknown): unknown {
|
|
|
3174
2483
|
return {}
|
|
3175
2484
|
}
|
|
3176
2485
|
|
|
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
2486
|
interface RouteLike {
|
|
3194
2487
|
id: string
|
|
3195
2488
|
isRoot?: boolean
|
|
@@ -3416,7 +2709,6 @@ export function processRouteTree<TRouteLike extends RouteLike>({
|
|
|
3416
2709
|
export function getMatchedRoutes<TRouteLike extends RouteLike>({
|
|
3417
2710
|
pathname,
|
|
3418
2711
|
routePathname,
|
|
3419
|
-
basepath,
|
|
3420
2712
|
caseSensitive,
|
|
3421
2713
|
routesByPath,
|
|
3422
2714
|
routesById,
|
|
@@ -3425,7 +2717,6 @@ export function getMatchedRoutes<TRouteLike extends RouteLike>({
|
|
|
3425
2717
|
}: {
|
|
3426
2718
|
pathname: string
|
|
3427
2719
|
routePathname?: string
|
|
3428
|
-
basepath: string
|
|
3429
2720
|
caseSensitive?: boolean
|
|
3430
2721
|
routesByPath: Record<string, TRouteLike>
|
|
3431
2722
|
routesById: Record<string, TRouteLike>
|
|
@@ -3436,7 +2727,6 @@ export function getMatchedRoutes<TRouteLike extends RouteLike>({
|
|
|
3436
2727
|
const trimmedPath = trimPathRight(pathname)
|
|
3437
2728
|
const getMatchedParams = (route: TRouteLike) => {
|
|
3438
2729
|
const result = matchPathname(
|
|
3439
|
-
basepath,
|
|
3440
2730
|
trimmedPath,
|
|
3441
2731
|
{
|
|
3442
2732
|
to: route.fullPath,
|
|
@@ -3562,7 +2852,8 @@ function applySearchMiddleware({
|
|
|
3562
2852
|
try {
|
|
3563
2853
|
const validatedSearch = {
|
|
3564
2854
|
...result,
|
|
3565
|
-
...(validateSearch(route.options.validateSearch, result) ??
|
|
2855
|
+
...(validateSearch(route.options.validateSearch, result) ??
|
|
2856
|
+
undefined),
|
|
3566
2857
|
}
|
|
3567
2858
|
return validatedSearch
|
|
3568
2859
|
} catch {
|