@tanstack/router-core 1.132.0-alpha.2 → 1.132.0-alpha.21
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 +2 -2
- 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 +7 -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 -37
- package/dist/cjs/router.cjs +131 -778
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +68 -36
- 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 -1
- 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 +15 -1
- 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/utils.cjs +8 -7
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +1 -1
- package/dist/esm/Matches.d.ts +2 -2
- 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 +7 -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 -37
- package/dist/esm/route.js.map +1 -1
- package/dist/esm/router.d.ts +68 -36
- package/dist/esm/router.js +133 -780
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/scroll-restoration.d.ts +1 -1
- 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 +15 -1
- 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/utils.d.ts +1 -1
- package/dist/esm/utils.js +8 -7
- package/dist/esm/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/Matches.ts +2 -2
- 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 +9 -66
- package/src/qss.ts +27 -24
- package/src/redirect.ts +3 -3
- package/src/rewrite.ts +70 -0
- package/src/route.ts +136 -33
- package/src/router.ts +267 -1168
- 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 +30 -3
- package/src/ssr/ssr-server.ts +18 -10
- package/src/ssr/tsrScript.ts +5 -1
- package/src/utils.ts +11 -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,17 +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
|
-
isPromise,
|
|
13
9
|
last,
|
|
14
|
-
pick,
|
|
15
10
|
replaceEqualDeep,
|
|
16
11
|
} from './utils'
|
|
17
12
|
import {
|
|
@@ -21,7 +16,6 @@ import {
|
|
|
21
16
|
SEGMENT_TYPE_WILDCARD,
|
|
22
17
|
cleanPath,
|
|
23
18
|
interpolatePath,
|
|
24
|
-
joinPaths,
|
|
25
19
|
matchPathname,
|
|
26
20
|
parsePathname,
|
|
27
21
|
resolvePath,
|
|
@@ -35,6 +29,13 @@ import { defaultParseSearch, defaultStringifySearch } from './searchParams'
|
|
|
35
29
|
import { rootRouteId } from './root'
|
|
36
30
|
import { isRedirect, redirect } from './redirect'
|
|
37
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'
|
|
38
39
|
import type { ParsePathnameCache, Segment } from './path'
|
|
39
40
|
import type { SearchParser, SearchSerializer } from './searchParams'
|
|
40
41
|
import type { AnyRedirect, ResolvedRedirect } from './redirect'
|
|
@@ -46,6 +47,7 @@ import type {
|
|
|
46
47
|
} from '@tanstack/history'
|
|
47
48
|
import type {
|
|
48
49
|
Awaitable,
|
|
50
|
+
Constrain,
|
|
49
51
|
ControlledPromise,
|
|
50
52
|
NoInfer,
|
|
51
53
|
NonNullableUpdater,
|
|
@@ -57,13 +59,10 @@ import type {
|
|
|
57
59
|
AnyContext,
|
|
58
60
|
AnyRoute,
|
|
59
61
|
AnyRouteWithContext,
|
|
60
|
-
BeforeLoadContextOptions,
|
|
61
|
-
LoaderFnContext,
|
|
62
62
|
MakeRemountDepsOptionsUnion,
|
|
63
63
|
RouteContextOptions,
|
|
64
64
|
RouteMask,
|
|
65
65
|
SearchMiddleware,
|
|
66
|
-
SsrContextOptions,
|
|
67
66
|
} from './route'
|
|
68
67
|
import type {
|
|
69
68
|
FullSearchSchema,
|
|
@@ -87,6 +86,11 @@ import type { Manifest } from './manifest'
|
|
|
87
86
|
import type { AnySchema, AnyValidator } from './validators'
|
|
88
87
|
import type { NavigateOptions, ResolveRelativePath, ToOptions } from './link'
|
|
89
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'
|
|
90
94
|
|
|
91
95
|
export type ControllablePromise<T = any> = Promise<T> & {
|
|
92
96
|
resolve: (value: T) => void
|
|
@@ -97,6 +101,8 @@ export type InjectedHtmlEntry = Promise<string>
|
|
|
97
101
|
|
|
98
102
|
export interface DefaultRegister {
|
|
99
103
|
router: AnyRouter
|
|
104
|
+
config: AnyRouterConfig
|
|
105
|
+
ssr: SSROption
|
|
100
106
|
}
|
|
101
107
|
|
|
102
108
|
export interface Register extends DefaultRegister {
|
|
@@ -114,12 +120,14 @@ export interface DefaultRouterOptionsExtensions {}
|
|
|
114
120
|
export interface RouterOptionsExtensions
|
|
115
121
|
extends DefaultRouterOptionsExtensions {}
|
|
116
122
|
|
|
123
|
+
export type SSROption = boolean | 'data-only'
|
|
124
|
+
|
|
117
125
|
export interface RouterOptions<
|
|
118
126
|
TRouteTree extends AnyRoute,
|
|
119
127
|
TTrailingSlashOption extends TrailingSlashOption,
|
|
120
128
|
TDefaultStructuralSharingOption extends boolean = false,
|
|
121
129
|
TRouterHistory extends RouterHistory = RouterHistory,
|
|
122
|
-
TDehydrated
|
|
130
|
+
TDehydrated = undefined,
|
|
123
131
|
> extends RouterOptionsExtensions {
|
|
124
132
|
/**
|
|
125
133
|
* The history object that will be used to manage the browser history.
|
|
@@ -264,6 +272,18 @@ export interface RouterOptions<
|
|
|
264
272
|
/**
|
|
265
273
|
* The basepath for then entire router. This is useful for mounting a router instance at a subpath.
|
|
266
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
|
+
* ```
|
|
267
287
|
* @default '/'
|
|
268
288
|
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#basepath-property)
|
|
269
289
|
*/
|
|
@@ -287,7 +307,10 @@ export interface RouterOptions<
|
|
|
287
307
|
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#dehydrate-method)
|
|
288
308
|
* @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/external-data-loading#critical-dehydrationhydration)
|
|
289
309
|
*/
|
|
290
|
-
dehydrate?: () =>
|
|
310
|
+
dehydrate?: () => Constrain<
|
|
311
|
+
TDehydrated,
|
|
312
|
+
ValidateSerializableInput<Register, TDehydrated>
|
|
313
|
+
>
|
|
291
314
|
/**
|
|
292
315
|
* A function that will be called when the router is hydrated.
|
|
293
316
|
*
|
|
@@ -357,7 +380,7 @@ export interface RouterOptions<
|
|
|
357
380
|
*
|
|
358
381
|
* @default true
|
|
359
382
|
*/
|
|
360
|
-
defaultSsr?:
|
|
383
|
+
defaultSsr?: SSROption
|
|
361
384
|
|
|
362
385
|
search?: {
|
|
363
386
|
/**
|
|
@@ -393,7 +416,9 @@ export interface RouterOptions<
|
|
|
393
416
|
*
|
|
394
417
|
* @default false
|
|
395
418
|
*/
|
|
396
|
-
scrollRestoration?:
|
|
419
|
+
scrollRestoration?:
|
|
420
|
+
| boolean
|
|
421
|
+
| ((opts: { location: ParsedLocation }) => boolean)
|
|
397
422
|
|
|
398
423
|
/**
|
|
399
424
|
* A function that will be called to get the key for the scroll restoration cache.
|
|
@@ -424,8 +449,48 @@ export interface RouterOptions<
|
|
|
424
449
|
* @default false
|
|
425
450
|
*/
|
|
426
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
|
|
427
463
|
}
|
|
428
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
|
+
|
|
429
494
|
export interface RouterState<
|
|
430
495
|
in out TRouteTree extends AnyRoute = AnyRoute,
|
|
431
496
|
in out TRouteMatch = MakeRouteMatchUnion,
|
|
@@ -615,8 +680,8 @@ export type InvalidateFn<TRouter extends AnyRouter> = (opts?: {
|
|
|
615
680
|
}) => Promise<void>
|
|
616
681
|
|
|
617
682
|
export type ParseLocationFn<TRouteTree extends AnyRoute> = (
|
|
683
|
+
locationToParse: HistoryLocation,
|
|
618
684
|
previousLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>,
|
|
619
|
-
locationToParse?: HistoryLocation,
|
|
620
685
|
) => ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
621
686
|
|
|
622
687
|
export type GetMatchRoutesFn = (
|
|
@@ -706,6 +771,7 @@ export interface ViewTransitionOptions {
|
|
|
706
771
|
}) => Array<string>)
|
|
707
772
|
}
|
|
708
773
|
|
|
774
|
+
// TODO where is this used? can we remove this?
|
|
709
775
|
export function defaultSerializeError(err: unknown) {
|
|
710
776
|
if (err instanceof Error) {
|
|
711
777
|
const obj = {
|
|
@@ -763,18 +829,6 @@ export type CreateRouterFn = <
|
|
|
763
829
|
TDehydrated
|
|
764
830
|
>
|
|
765
831
|
|
|
766
|
-
type InnerLoadContext = {
|
|
767
|
-
location: ParsedLocation
|
|
768
|
-
firstBadMatchIndex?: number
|
|
769
|
-
rendered?: boolean
|
|
770
|
-
updateMatch: UpdateMatchFn
|
|
771
|
-
matches: Array<AnyRouteMatch>
|
|
772
|
-
preload?: boolean
|
|
773
|
-
onReady?: () => Promise<void>
|
|
774
|
-
sync?: boolean
|
|
775
|
-
matchPromises: Array<Promise<AnyRouteMatch>>
|
|
776
|
-
}
|
|
777
|
-
|
|
778
832
|
export class RouterCore<
|
|
779
833
|
in out TRouteTree extends AnyRoute,
|
|
780
834
|
in out TTrailingSlashOption extends TrailingSlashOption,
|
|
@@ -807,7 +861,10 @@ export class RouterCore<
|
|
|
807
861
|
'stringifySearch' | 'parseSearch' | 'context'
|
|
808
862
|
>
|
|
809
863
|
history!: TRouterHistory
|
|
864
|
+
rewrite?: LocationRewrite
|
|
865
|
+
origin?: string
|
|
810
866
|
latestLocation!: ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
867
|
+
// @deprecated - basepath functionality is now implemented via the `rewrite` option
|
|
811
868
|
basepath!: string
|
|
812
869
|
routeTree!: TRouteTree
|
|
813
870
|
routesById!: RoutesById<TRouteTree>
|
|
@@ -871,7 +928,6 @@ export class RouterCore<
|
|
|
871
928
|
)
|
|
872
929
|
}
|
|
873
930
|
|
|
874
|
-
const previousOptions = this.options
|
|
875
931
|
this.options = {
|
|
876
932
|
...this.options,
|
|
877
933
|
...newOptions,
|
|
@@ -889,32 +945,42 @@ export class RouterCore<
|
|
|
889
945
|
: undefined
|
|
890
946
|
|
|
891
947
|
if (
|
|
892
|
-
!this.
|
|
893
|
-
(
|
|
948
|
+
!this.history ||
|
|
949
|
+
(this.options.history && this.options.history !== this.history)
|
|
894
950
|
) {
|
|
895
|
-
if (
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
) {
|
|
900
|
-
this.basepath = '/'
|
|
951
|
+
if (!this.options.history) {
|
|
952
|
+
if (!this.isServer) {
|
|
953
|
+
this.history = createBrowserHistory() as TRouterHistory
|
|
954
|
+
}
|
|
901
955
|
} else {
|
|
902
|
-
this.
|
|
956
|
+
this.history = this.options.history
|
|
903
957
|
}
|
|
904
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])
|
|
966
|
+
} else {
|
|
967
|
+
this.rewrite = basepathRewrite
|
|
968
|
+
}
|
|
969
|
+
} else {
|
|
970
|
+
this.rewrite = this.options.rewrite
|
|
971
|
+
}
|
|
905
972
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
(this.
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
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()
|
|
918
984
|
}
|
|
919
985
|
|
|
920
986
|
if (this.options.routeTree !== this.routeTree) {
|
|
@@ -922,7 +988,7 @@ export class RouterCore<
|
|
|
922
988
|
this.buildRouteTree()
|
|
923
989
|
}
|
|
924
990
|
|
|
925
|
-
if (!this.__store) {
|
|
991
|
+
if (!this.__store && this.latestLocation) {
|
|
926
992
|
this.__store = new Store(getInitialRouterState(this.latestLocation), {
|
|
927
993
|
onUpdate: () => {
|
|
928
994
|
this.__store.state = {
|
|
@@ -948,10 +1014,17 @@ export class RouterCore<
|
|
|
948
1014
|
}
|
|
949
1015
|
}
|
|
950
1016
|
|
|
951
|
-
get state() {
|
|
1017
|
+
get state(): RouterState<TRouteTree> {
|
|
952
1018
|
return this.__store.state
|
|
953
1019
|
}
|
|
954
1020
|
|
|
1021
|
+
updateLatestLocation = () => {
|
|
1022
|
+
this.latestLocation = this.parseLocation(
|
|
1023
|
+
this.history.location,
|
|
1024
|
+
this.latestLocation,
|
|
1025
|
+
)
|
|
1026
|
+
}
|
|
1027
|
+
|
|
955
1028
|
buildRouteTree = () => {
|
|
956
1029
|
const { routesById, routesByPath, flatRoutes } = processRouteTree({
|
|
957
1030
|
routeTree: this.routeTree,
|
|
@@ -998,29 +1071,41 @@ export class RouterCore<
|
|
|
998
1071
|
}
|
|
999
1072
|
|
|
1000
1073
|
parseLocation: ParseLocationFn<TRouteTree> = (
|
|
1001
|
-
previousLocation,
|
|
1002
1074
|
locationToParse,
|
|
1075
|
+
previousLocation,
|
|
1003
1076
|
) => {
|
|
1004
1077
|
const parse = ({
|
|
1005
|
-
|
|
1006
|
-
search,
|
|
1007
|
-
hash,
|
|
1078
|
+
href,
|
|
1008
1079
|
state,
|
|
1009
1080
|
}: HistoryLocation): ParsedLocation<FullSearchSchema<TRouteTree>> => {
|
|
1010
|
-
|
|
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)
|
|
1011
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
|
|
1012
1095
|
|
|
1013
1096
|
return {
|
|
1097
|
+
href: fullPath,
|
|
1098
|
+
publicHref: href,
|
|
1099
|
+
url: url.href,
|
|
1014
1100
|
pathname,
|
|
1015
1101
|
searchStr,
|
|
1016
1102
|
search: replaceEqualDeep(previousLocation?.search, parsedSearch) as any,
|
|
1017
1103
|
hash: hash.split('#').reverse()[0] ?? '',
|
|
1018
|
-
href: `${pathname}${searchStr}${hash}`,
|
|
1019
1104
|
state: replaceEqualDeep(previousLocation?.state, state),
|
|
1020
1105
|
}
|
|
1021
1106
|
}
|
|
1022
1107
|
|
|
1023
|
-
const location = parse(locationToParse
|
|
1108
|
+
const location = parse(locationToParse)
|
|
1024
1109
|
|
|
1025
1110
|
const { __tempLocation, __tempKey } = location.state
|
|
1026
1111
|
|
|
@@ -1043,11 +1128,9 @@ export class RouterCore<
|
|
|
1043
1128
|
|
|
1044
1129
|
resolvePathWithBase = (from: string, path: string) => {
|
|
1045
1130
|
const resolvedPath = resolvePath({
|
|
1046
|
-
basepath: this.basepath,
|
|
1047
1131
|
base: from,
|
|
1048
1132
|
to: cleanPath(path),
|
|
1049
1133
|
trailingSlash: this.options.trailingSlash,
|
|
1050
|
-
caseSensitive: this.options.caseSensitive,
|
|
1051
1134
|
parseCache: this.parsePathnameCache,
|
|
1052
1135
|
})
|
|
1053
1136
|
return resolvedPath
|
|
@@ -1119,33 +1202,6 @@ export class RouterCore<
|
|
|
1119
1202
|
return rootRouteId
|
|
1120
1203
|
})()
|
|
1121
1204
|
|
|
1122
|
-
const parseErrors = matchedRoutes.map((route) => {
|
|
1123
|
-
let parsedParamsError
|
|
1124
|
-
|
|
1125
|
-
const parseParams =
|
|
1126
|
-
route.options.params?.parse ?? route.options.parseParams
|
|
1127
|
-
|
|
1128
|
-
if (parseParams) {
|
|
1129
|
-
try {
|
|
1130
|
-
const parsedParams = parseParams(routeParams)
|
|
1131
|
-
// Add the parsed params to the accumulated params bag
|
|
1132
|
-
Object.assign(routeParams, parsedParams)
|
|
1133
|
-
} catch (err: any) {
|
|
1134
|
-
parsedParamsError = new PathParamError(err.message, {
|
|
1135
|
-
cause: err,
|
|
1136
|
-
})
|
|
1137
|
-
|
|
1138
|
-
if (opts?.throwOnError) {
|
|
1139
|
-
throw parsedParamsError
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
return parsedParamsError
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
return
|
|
1147
|
-
})
|
|
1148
|
-
|
|
1149
1205
|
const matches: Array<AnyRouteMatch> = []
|
|
1150
1206
|
|
|
1151
1207
|
const getParentContext = (parentMatch?: AnyRouteMatch) => {
|
|
@@ -1218,12 +1274,18 @@ export class RouterCore<
|
|
|
1218
1274
|
|
|
1219
1275
|
const loaderDepsHash = loaderDeps ? JSON.stringify(loaderDeps) : ''
|
|
1220
1276
|
|
|
1221
|
-
const {
|
|
1277
|
+
const { interpolatedPath, usedParams } = interpolatePath({
|
|
1222
1278
|
path: route.fullPath,
|
|
1223
1279
|
params: routeParams,
|
|
1224
1280
|
decodeCharMap: this.pathParamsDecodeCharMap,
|
|
1225
1281
|
})
|
|
1226
1282
|
|
|
1283
|
+
// Waste not, want not. If we already have a match for this route,
|
|
1284
|
+
// reuse it. This is important for layout routes, which might stick
|
|
1285
|
+
// around between navigation actions that only change leaf routes.
|
|
1286
|
+
|
|
1287
|
+
// Existing matches are matches that are already loaded along with
|
|
1288
|
+
// pending matches that are still loading
|
|
1227
1289
|
const matchId =
|
|
1228
1290
|
interpolatePath({
|
|
1229
1291
|
path: route.id,
|
|
@@ -1233,18 +1295,40 @@ export class RouterCore<
|
|
|
1233
1295
|
parseCache: this.parsePathnameCache,
|
|
1234
1296
|
}).interpolatedPath + loaderDepsHash
|
|
1235
1297
|
|
|
1236
|
-
// Waste not, want not. If we already have a match for this route,
|
|
1237
|
-
// reuse it. This is important for layout routes, which might stick
|
|
1238
|
-
// around between navigation actions that only change leaf routes.
|
|
1239
|
-
|
|
1240
|
-
// Existing matches are matches that are already loaded along with
|
|
1241
|
-
// pending matches that are still loading
|
|
1242
1298
|
const existingMatch = this.getMatch(matchId)
|
|
1243
1299
|
|
|
1244
1300
|
const previousMatch = this.state.matches.find(
|
|
1245
1301
|
(d) => d.routeId === route.id,
|
|
1246
1302
|
)
|
|
1247
1303
|
|
|
1304
|
+
const strictParams = existingMatch?._strictParams ?? usedParams
|
|
1305
|
+
|
|
1306
|
+
let paramsError: PathParamError | undefined = undefined
|
|
1307
|
+
|
|
1308
|
+
if (!existingMatch) {
|
|
1309
|
+
const strictParseParams =
|
|
1310
|
+
route.options.params?.parse ?? route.options.parseParams
|
|
1311
|
+
|
|
1312
|
+
if (strictParseParams) {
|
|
1313
|
+
try {
|
|
1314
|
+
Object.assign(
|
|
1315
|
+
strictParams,
|
|
1316
|
+
strictParseParams(strictParams as Record<string, string>),
|
|
1317
|
+
)
|
|
1318
|
+
} catch (err: any) {
|
|
1319
|
+
paramsError = new PathParamError(err.message, {
|
|
1320
|
+
cause: err,
|
|
1321
|
+
})
|
|
1322
|
+
|
|
1323
|
+
if (opts?.throwOnError) {
|
|
1324
|
+
throw paramsError
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
Object.assign(routeParams, strictParams)
|
|
1331
|
+
|
|
1248
1332
|
const cause = previousMatch ? 'stay' : 'enter'
|
|
1249
1333
|
|
|
1250
1334
|
let match: AnyRouteMatch
|
|
@@ -1256,7 +1340,7 @@ export class RouterCore<
|
|
|
1256
1340
|
params: previousMatch
|
|
1257
1341
|
? replaceEqualDeep(previousMatch.params, routeParams)
|
|
1258
1342
|
: routeParams,
|
|
1259
|
-
_strictParams:
|
|
1343
|
+
_strictParams: strictParams,
|
|
1260
1344
|
search: previousMatch
|
|
1261
1345
|
? replaceEqualDeep(previousMatch.search, preMatchSearch)
|
|
1262
1346
|
: replaceEqualDeep(existingMatch.search, preMatchSearch),
|
|
@@ -1278,8 +1362,8 @@ export class RouterCore<
|
|
|
1278
1362
|
params: previousMatch
|
|
1279
1363
|
? replaceEqualDeep(previousMatch.params, routeParams)
|
|
1280
1364
|
: routeParams,
|
|
1281
|
-
_strictParams:
|
|
1282
|
-
pathname:
|
|
1365
|
+
_strictParams: strictParams,
|
|
1366
|
+
pathname: interpolatedPath,
|
|
1283
1367
|
updatedAt: Date.now(),
|
|
1284
1368
|
search: previousMatch
|
|
1285
1369
|
? replaceEqualDeep(previousMatch.search, preMatchSearch)
|
|
@@ -1289,7 +1373,7 @@ export class RouterCore<
|
|
|
1289
1373
|
status,
|
|
1290
1374
|
isFetching: false,
|
|
1291
1375
|
error: undefined,
|
|
1292
|
-
paramsError
|
|
1376
|
+
paramsError,
|
|
1293
1377
|
__routeContext: undefined,
|
|
1294
1378
|
_nonReactive: {
|
|
1295
1379
|
loadPromise: createControlledPromise(),
|
|
@@ -1384,7 +1468,6 @@ export class RouterCore<
|
|
|
1384
1468
|
return getMatchedRoutes({
|
|
1385
1469
|
pathname,
|
|
1386
1470
|
routePathname,
|
|
1387
|
-
basepath: this.basepath,
|
|
1388
1471
|
caseSensitive: this.options.caseSensitive,
|
|
1389
1472
|
routesByPath: this.routesByPath,
|
|
1390
1473
|
routesById: this.routesById,
|
|
@@ -1422,52 +1505,44 @@ export class RouterCore<
|
|
|
1422
1505
|
_buildLocation: true,
|
|
1423
1506
|
})
|
|
1424
1507
|
|
|
1508
|
+
// Now let's find the starting pathname
|
|
1509
|
+
// This should default to the current location if no from is provided
|
|
1425
1510
|
const lastMatch = last(allCurrentLocationMatches)!
|
|
1426
1511
|
|
|
1427
|
-
//
|
|
1428
|
-
//
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
// If the route is changing we need to find the relative fromPath
|
|
1440
|
-
if (dest.unsafeRelative === 'path') {
|
|
1441
|
-
fromPath = currentLocation.pathname
|
|
1442
|
-
} else if (routeIsChanging && dest.from) {
|
|
1443
|
-
fromPath = dest.from
|
|
1444
|
-
|
|
1445
|
-
// do this check only on navigations during test or development
|
|
1446
|
-
if (process.env.NODE_ENV !== 'production' && dest._isNavigate) {
|
|
1447
|
-
const allFromMatches = this.getMatchedRoutes(
|
|
1448
|
-
dest.from,
|
|
1449
|
-
undefined,
|
|
1450
|
-
).matchedRoutes
|
|
1512
|
+
// check that from path exists in the current route tree
|
|
1513
|
+
// do this check only on navigations during test or development
|
|
1514
|
+
if (
|
|
1515
|
+
dest.from &&
|
|
1516
|
+
process.env.NODE_ENV !== 'production' &&
|
|
1517
|
+
dest._isNavigate
|
|
1518
|
+
) {
|
|
1519
|
+
const allFromMatches = this.getMatchedRoutes(
|
|
1520
|
+
dest.from,
|
|
1521
|
+
undefined,
|
|
1522
|
+
).matchedRoutes
|
|
1451
1523
|
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
return comparePaths(d.fullPath, fromPath)
|
|
1456
|
-
})
|
|
1524
|
+
const matchedFrom = findLast(allCurrentLocationMatches, (d) => {
|
|
1525
|
+
return comparePaths(d.fullPath, dest.from!)
|
|
1526
|
+
})
|
|
1457
1527
|
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1528
|
+
const matchedCurrent = findLast(allFromMatches, (d) => {
|
|
1529
|
+
return comparePaths(d.fullPath, lastMatch.fullPath)
|
|
1530
|
+
})
|
|
1461
1531
|
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
}
|
|
1532
|
+
// for from to be invalid it shouldn't just be unmatched to currentLocation
|
|
1533
|
+
// but the currentLocation should also be unmatched to from
|
|
1534
|
+
if (!matchedFrom && !matchedCurrent) {
|
|
1535
|
+
console.warn(`Could not find match for from: ${dest.from}`)
|
|
1467
1536
|
}
|
|
1468
1537
|
}
|
|
1469
1538
|
|
|
1470
|
-
|
|
1539
|
+
const defaultedFromPath =
|
|
1540
|
+
dest.unsafeRelative === 'path'
|
|
1541
|
+
? currentLocation.pathname
|
|
1542
|
+
: (dest.from ?? lastMatch.fullPath)
|
|
1543
|
+
|
|
1544
|
+
// ensure this includes the basePath if set
|
|
1545
|
+
const fromPath = this.resolvePathWithBase(defaultedFromPath, '.')
|
|
1471
1546
|
|
|
1472
1547
|
// From search should always use the current location
|
|
1473
1548
|
const fromSearch = lastMatch.search
|
|
@@ -1475,25 +1550,26 @@ export class RouterCore<
|
|
|
1475
1550
|
const fromParams = { ...lastMatch.params }
|
|
1476
1551
|
|
|
1477
1552
|
// Resolve the next to
|
|
1553
|
+
// ensure this includes the basePath if set
|
|
1478
1554
|
const nextTo = dest.to
|
|
1479
1555
|
? this.resolvePathWithBase(fromPath, `${dest.to}`)
|
|
1480
1556
|
: this.resolvePathWithBase(fromPath, '.')
|
|
1481
1557
|
|
|
1482
1558
|
// Resolve the next params
|
|
1483
|
-
|
|
1559
|
+
const nextParams =
|
|
1484
1560
|
dest.params === false || dest.params === null
|
|
1485
1561
|
? {}
|
|
1486
1562
|
: (dest.params ?? true) === true
|
|
1487
1563
|
? fromParams
|
|
1488
|
-
:
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1564
|
+
: Object.assign(
|
|
1565
|
+
fromParams,
|
|
1566
|
+
functionalUpdate(dest.params as any, fromParams),
|
|
1567
|
+
)
|
|
1492
1568
|
|
|
1493
1569
|
// Interpolate the path first to get the actual resolved path, then match against that
|
|
1494
1570
|
const interpolatedNextTo = interpolatePath({
|
|
1495
1571
|
path: nextTo,
|
|
1496
|
-
params: nextParams
|
|
1572
|
+
params: nextParams,
|
|
1497
1573
|
parseCache: this.parsePathnameCache,
|
|
1498
1574
|
}).interpolatedPath
|
|
1499
1575
|
|
|
@@ -1503,23 +1579,20 @@ export class RouterCore<
|
|
|
1503
1579
|
|
|
1504
1580
|
// If there are any params, we need to stringify them
|
|
1505
1581
|
if (Object.keys(nextParams).length > 0) {
|
|
1506
|
-
destRoutes
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
)
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
|
-
.forEach((fn) => {
|
|
1514
|
-
nextParams = { ...nextParams!, ...fn!(nextParams) }
|
|
1515
|
-
})
|
|
1582
|
+
for (const route of destRoutes) {
|
|
1583
|
+
const fn =
|
|
1584
|
+
route.options.params?.stringify ?? route.options.stringifyParams
|
|
1585
|
+
if (fn) {
|
|
1586
|
+
Object.assign(nextParams, fn(nextParams))
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1516
1589
|
}
|
|
1517
1590
|
|
|
1518
1591
|
const nextPathname = interpolatePath({
|
|
1519
1592
|
// Use the original template path for interpolation
|
|
1520
1593
|
// This preserves the original parameter syntax including optional parameters
|
|
1521
1594
|
path: nextTo,
|
|
1522
|
-
params: nextParams
|
|
1595
|
+
params: nextParams,
|
|
1523
1596
|
leaveWildcards: false,
|
|
1524
1597
|
leaveParams: opts.leaveParams,
|
|
1525
1598
|
decodeCharMap: this.pathParamsDecodeCharMap,
|
|
@@ -1529,20 +1602,20 @@ export class RouterCore<
|
|
|
1529
1602
|
// Resolve the next search
|
|
1530
1603
|
let nextSearch = fromSearch
|
|
1531
1604
|
if (opts._includeValidateSearch && this.options.search?.strict) {
|
|
1532
|
-
|
|
1605
|
+
const validatedSearch = {}
|
|
1533
1606
|
destRoutes.forEach((route) => {
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1607
|
+
if (route.options.validateSearch) {
|
|
1608
|
+
try {
|
|
1609
|
+
Object.assign(
|
|
1610
|
+
validatedSearch,
|
|
1611
|
+
validateSearch(route.options.validateSearch, {
|
|
1539
1612
|
...validatedSearch,
|
|
1540
1613
|
...nextSearch,
|
|
1541
|
-
})
|
|
1542
|
-
|
|
1614
|
+
}),
|
|
1615
|
+
)
|
|
1616
|
+
} catch {
|
|
1617
|
+
// ignore errors here because they are already handled in matchRoutes
|
|
1543
1618
|
}
|
|
1544
|
-
} catch {
|
|
1545
|
-
// ignore errors here because they are already handled in matchRoutes
|
|
1546
1619
|
}
|
|
1547
1620
|
})
|
|
1548
1621
|
nextSearch = validatedSearch
|
|
@@ -1583,14 +1656,25 @@ export class RouterCore<
|
|
|
1583
1656
|
// Replace the equal deep
|
|
1584
1657
|
nextState = replaceEqualDeep(currentLocation.state, nextState)
|
|
1585
1658
|
|
|
1586
|
-
//
|
|
1659
|
+
// Create the full path of the location
|
|
1660
|
+
const fullPath = `${nextPathname}${searchStr}${hashStr}`
|
|
1661
|
+
|
|
1662
|
+
// Create the new href with full origin
|
|
1663
|
+
const url = new URL(fullPath, this.origin)
|
|
1664
|
+
|
|
1665
|
+
// If a rewrite function is provided, use it to rewrite the URL
|
|
1666
|
+
const rewrittenUrl = executeRewriteOutput(this.rewrite, url)
|
|
1667
|
+
|
|
1587
1668
|
return {
|
|
1669
|
+
publicHref:
|
|
1670
|
+
rewrittenUrl.pathname + rewrittenUrl.search + rewrittenUrl.hash,
|
|
1671
|
+
href: fullPath,
|
|
1672
|
+
url: rewrittenUrl.href,
|
|
1588
1673
|
pathname: nextPathname,
|
|
1589
1674
|
search: nextSearch,
|
|
1590
1675
|
searchStr,
|
|
1591
1676
|
state: nextState as any,
|
|
1592
1677
|
hash: hash ?? '',
|
|
1593
|
-
href: `${nextPathname}${searchStr}${hashStr}`,
|
|
1594
1678
|
unmaskOnReload: dest.unmaskOnReload,
|
|
1595
1679
|
}
|
|
1596
1680
|
}
|
|
@@ -1608,7 +1692,6 @@ export class RouterCore<
|
|
|
1608
1692
|
|
|
1609
1693
|
const foundMask = this.options.routeMasks?.find((d) => {
|
|
1610
1694
|
const match = matchPathname(
|
|
1611
|
-
this.basepath,
|
|
1612
1695
|
next.pathname,
|
|
1613
1696
|
{
|
|
1614
1697
|
to: d.from,
|
|
@@ -1629,7 +1712,7 @@ export class RouterCore<
|
|
|
1629
1712
|
if (foundMask) {
|
|
1630
1713
|
const { from: _from, ...maskProps } = foundMask
|
|
1631
1714
|
maskedDest = {
|
|
1632
|
-
|
|
1715
|
+
from: opts.from,
|
|
1633
1716
|
...maskProps,
|
|
1634
1717
|
params,
|
|
1635
1718
|
}
|
|
@@ -1638,8 +1721,7 @@ export class RouterCore<
|
|
|
1638
1721
|
}
|
|
1639
1722
|
|
|
1640
1723
|
if (maskedNext) {
|
|
1641
|
-
|
|
1642
|
-
next.maskedLocation = maskedFinal
|
|
1724
|
+
next.maskedLocation = maskedNext
|
|
1643
1725
|
}
|
|
1644
1726
|
|
|
1645
1727
|
return next
|
|
@@ -1647,7 +1729,7 @@ export class RouterCore<
|
|
|
1647
1729
|
|
|
1648
1730
|
if (opts.mask) {
|
|
1649
1731
|
return buildWithMatches(opts, {
|
|
1650
|
-
|
|
1732
|
+
from: opts.from,
|
|
1651
1733
|
...opts.mask,
|
|
1652
1734
|
})
|
|
1653
1735
|
}
|
|
@@ -1682,7 +1764,8 @@ export class RouterCore<
|
|
|
1682
1764
|
return isEqual
|
|
1683
1765
|
}
|
|
1684
1766
|
|
|
1685
|
-
const isSameUrl =
|
|
1767
|
+
const isSameUrl =
|
|
1768
|
+
trimPathRight(this.latestLocation.href) === trimPathRight(next.href)
|
|
1686
1769
|
|
|
1687
1770
|
const previousCommitPromise = this.commitLocationPromise
|
|
1688
1771
|
this.commitLocationPromise = createControlledPromise<void>(() => {
|
|
@@ -1731,7 +1814,7 @@ export class RouterCore<
|
|
|
1731
1814
|
this.shouldViewTransition = viewTransition
|
|
1732
1815
|
|
|
1733
1816
|
this.history[next.replace ? 'replace' : 'push'](
|
|
1734
|
-
nextHistory.
|
|
1817
|
+
nextHistory.publicHref,
|
|
1735
1818
|
nextHistory.state,
|
|
1736
1819
|
{ ignoreBlocker },
|
|
1737
1820
|
)
|
|
@@ -1793,7 +1876,7 @@ export class RouterCore<
|
|
|
1793
1876
|
if (reloadDocument) {
|
|
1794
1877
|
if (!href) {
|
|
1795
1878
|
const location = this.buildLocation({ to, ...rest } as any)
|
|
1796
|
-
href =
|
|
1879
|
+
href = location.href
|
|
1797
1880
|
}
|
|
1798
1881
|
if (rest.replace) {
|
|
1799
1882
|
window.location.replace(href)
|
|
@@ -1816,7 +1899,7 @@ export class RouterCore<
|
|
|
1816
1899
|
beforeLoad = () => {
|
|
1817
1900
|
// Cancel any pending matches
|
|
1818
1901
|
this.cancelMatches()
|
|
1819
|
-
this.
|
|
1902
|
+
this.updateLatestLocation()
|
|
1820
1903
|
|
|
1821
1904
|
if (this.isServer) {
|
|
1822
1905
|
// for SPAs on the initial load, this is handled by the Transitioner
|
|
@@ -1846,6 +1929,7 @@ export class RouterCore<
|
|
|
1846
1929
|
throw redirect({ href: nextLocation.href })
|
|
1847
1930
|
}
|
|
1848
1931
|
}
|
|
1932
|
+
|
|
1849
1933
|
// Match the routes
|
|
1850
1934
|
const pendingMatches = this.matchRoutes(this.latestLocation)
|
|
1851
1935
|
|
|
@@ -1895,10 +1979,12 @@ export class RouterCore<
|
|
|
1895
1979
|
}),
|
|
1896
1980
|
})
|
|
1897
1981
|
|
|
1898
|
-
await
|
|
1982
|
+
await loadMatches({
|
|
1983
|
+
router: this,
|
|
1899
1984
|
sync: opts?.sync,
|
|
1900
1985
|
matches: this.state.pendingMatches as Array<AnyRouteMatch>,
|
|
1901
1986
|
location: next,
|
|
1987
|
+
updateMatch: this.updateMatch,
|
|
1902
1988
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
1903
1989
|
onReady: async () => {
|
|
1904
1990
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
@@ -1989,6 +2075,7 @@ export class RouterCore<
|
|
|
1989
2075
|
this.latestLoadPromise = undefined
|
|
1990
2076
|
this.commitLocationPromise = undefined
|
|
1991
2077
|
}
|
|
2078
|
+
|
|
1992
2079
|
resolve()
|
|
1993
2080
|
})
|
|
1994
2081
|
})
|
|
@@ -2088,873 +2175,6 @@ export class RouterCore<
|
|
|
2088
2175
|
)
|
|
2089
2176
|
}
|
|
2090
2177
|
|
|
2091
|
-
private triggerOnReady = (
|
|
2092
|
-
innerLoadContext: InnerLoadContext,
|
|
2093
|
-
): void | Promise<void> => {
|
|
2094
|
-
if (!innerLoadContext.rendered) {
|
|
2095
|
-
innerLoadContext.rendered = true
|
|
2096
|
-
return innerLoadContext.onReady?.()
|
|
2097
|
-
}
|
|
2098
|
-
}
|
|
2099
|
-
|
|
2100
|
-
private resolvePreload = (
|
|
2101
|
-
innerLoadContext: InnerLoadContext,
|
|
2102
|
-
matchId: string,
|
|
2103
|
-
): boolean => {
|
|
2104
|
-
return !!(
|
|
2105
|
-
innerLoadContext.preload &&
|
|
2106
|
-
!this.state.matches.some((d) => d.id === matchId)
|
|
2107
|
-
)
|
|
2108
|
-
}
|
|
2109
|
-
|
|
2110
|
-
private handleRedirectAndNotFound = (
|
|
2111
|
-
innerLoadContext: InnerLoadContext,
|
|
2112
|
-
match: AnyRouteMatch | undefined,
|
|
2113
|
-
err: unknown,
|
|
2114
|
-
): void => {
|
|
2115
|
-
if (!isRedirect(err) && !isNotFound(err)) return
|
|
2116
|
-
|
|
2117
|
-
if (isRedirect(err) && err.redirectHandled && !err.options.reloadDocument) {
|
|
2118
|
-
throw err
|
|
2119
|
-
}
|
|
2120
|
-
|
|
2121
|
-
// in case of a redirecting match during preload, the match does not exist
|
|
2122
|
-
if (match) {
|
|
2123
|
-
match._nonReactive.beforeLoadPromise?.resolve()
|
|
2124
|
-
match._nonReactive.loaderPromise?.resolve()
|
|
2125
|
-
match._nonReactive.beforeLoadPromise = undefined
|
|
2126
|
-
match._nonReactive.loaderPromise = undefined
|
|
2127
|
-
|
|
2128
|
-
const status = isRedirect(err) ? 'redirected' : 'notFound'
|
|
2129
|
-
|
|
2130
|
-
innerLoadContext.updateMatch(match.id, (prev) => ({
|
|
2131
|
-
...prev,
|
|
2132
|
-
status,
|
|
2133
|
-
isFetching: false,
|
|
2134
|
-
error: err,
|
|
2135
|
-
}))
|
|
2136
|
-
|
|
2137
|
-
if (isNotFound(err) && !err.routeId) {
|
|
2138
|
-
err.routeId = match.routeId
|
|
2139
|
-
}
|
|
2140
|
-
|
|
2141
|
-
match._nonReactive.loadPromise?.resolve()
|
|
2142
|
-
}
|
|
2143
|
-
|
|
2144
|
-
if (isRedirect(err)) {
|
|
2145
|
-
innerLoadContext.rendered = true
|
|
2146
|
-
err.options._fromLocation = innerLoadContext.location
|
|
2147
|
-
err.redirectHandled = true
|
|
2148
|
-
err = this.resolveRedirect(err)
|
|
2149
|
-
throw err
|
|
2150
|
-
} else {
|
|
2151
|
-
this._handleNotFound(innerLoadContext, err)
|
|
2152
|
-
throw err
|
|
2153
|
-
}
|
|
2154
|
-
}
|
|
2155
|
-
|
|
2156
|
-
private shouldSkipLoader = (matchId: string): boolean => {
|
|
2157
|
-
const match = this.getMatch(matchId)!
|
|
2158
|
-
// upon hydration, we skip the loader if the match has been dehydrated on the server
|
|
2159
|
-
if (!this.isServer && match._nonReactive.dehydrated) {
|
|
2160
|
-
return true
|
|
2161
|
-
}
|
|
2162
|
-
|
|
2163
|
-
if (this.isServer) {
|
|
2164
|
-
if (match.ssr === false) {
|
|
2165
|
-
return true
|
|
2166
|
-
}
|
|
2167
|
-
}
|
|
2168
|
-
return false
|
|
2169
|
-
}
|
|
2170
|
-
|
|
2171
|
-
private handleSerialError = (
|
|
2172
|
-
innerLoadContext: InnerLoadContext,
|
|
2173
|
-
index: number,
|
|
2174
|
-
err: any,
|
|
2175
|
-
routerCode: string,
|
|
2176
|
-
): void => {
|
|
2177
|
-
const { id: matchId, routeId } = innerLoadContext.matches[index]!
|
|
2178
|
-
const route = this.looseRoutesById[routeId]!
|
|
2179
|
-
|
|
2180
|
-
// Much like suspense, we use a promise here to know if
|
|
2181
|
-
// we've been outdated by a new loadMatches call and
|
|
2182
|
-
// should abort the current async operation
|
|
2183
|
-
if (err instanceof Promise) {
|
|
2184
|
-
throw err
|
|
2185
|
-
}
|
|
2186
|
-
|
|
2187
|
-
err.routerCode = routerCode
|
|
2188
|
-
innerLoadContext.firstBadMatchIndex ??= index
|
|
2189
|
-
this.handleRedirectAndNotFound(
|
|
2190
|
-
innerLoadContext,
|
|
2191
|
-
this.getMatch(matchId),
|
|
2192
|
-
err,
|
|
2193
|
-
)
|
|
2194
|
-
|
|
2195
|
-
try {
|
|
2196
|
-
route.options.onError?.(err)
|
|
2197
|
-
} catch (errorHandlerErr) {
|
|
2198
|
-
err = errorHandlerErr
|
|
2199
|
-
this.handleRedirectAndNotFound(
|
|
2200
|
-
innerLoadContext,
|
|
2201
|
-
this.getMatch(matchId),
|
|
2202
|
-
err,
|
|
2203
|
-
)
|
|
2204
|
-
}
|
|
2205
|
-
|
|
2206
|
-
innerLoadContext.updateMatch(matchId, (prev) => {
|
|
2207
|
-
prev._nonReactive.beforeLoadPromise?.resolve()
|
|
2208
|
-
prev._nonReactive.beforeLoadPromise = undefined
|
|
2209
|
-
prev._nonReactive.loadPromise?.resolve()
|
|
2210
|
-
|
|
2211
|
-
return {
|
|
2212
|
-
...prev,
|
|
2213
|
-
error: err,
|
|
2214
|
-
status: 'error',
|
|
2215
|
-
isFetching: false,
|
|
2216
|
-
updatedAt: Date.now(),
|
|
2217
|
-
abortController: new AbortController(),
|
|
2218
|
-
}
|
|
2219
|
-
})
|
|
2220
|
-
}
|
|
2221
|
-
|
|
2222
|
-
private isBeforeLoadSsr = (
|
|
2223
|
-
innerLoadContext: InnerLoadContext,
|
|
2224
|
-
matchId: string,
|
|
2225
|
-
index: number,
|
|
2226
|
-
route: AnyRoute,
|
|
2227
|
-
): void | Promise<void> => {
|
|
2228
|
-
const existingMatch = this.getMatch(matchId)!
|
|
2229
|
-
const parentMatchId = innerLoadContext.matches[index - 1]?.id
|
|
2230
|
-
const parentMatch = parentMatchId
|
|
2231
|
-
? this.getMatch(parentMatchId)!
|
|
2232
|
-
: undefined
|
|
2233
|
-
|
|
2234
|
-
// in SPA mode, only SSR the root route
|
|
2235
|
-
if (this.isShell()) {
|
|
2236
|
-
existingMatch.ssr = matchId === rootRouteId
|
|
2237
|
-
return
|
|
2238
|
-
}
|
|
2239
|
-
|
|
2240
|
-
if (parentMatch?.ssr === false) {
|
|
2241
|
-
existingMatch.ssr = false
|
|
2242
|
-
return
|
|
2243
|
-
}
|
|
2244
|
-
|
|
2245
|
-
const parentOverride = (tempSsr: boolean | 'data-only') => {
|
|
2246
|
-
if (tempSsr === true && parentMatch?.ssr === 'data-only') {
|
|
2247
|
-
return 'data-only'
|
|
2248
|
-
}
|
|
2249
|
-
return tempSsr
|
|
2250
|
-
}
|
|
2251
|
-
|
|
2252
|
-
const defaultSsr = this.options.defaultSsr ?? true
|
|
2253
|
-
|
|
2254
|
-
if (route.options.ssr === undefined) {
|
|
2255
|
-
existingMatch.ssr = parentOverride(defaultSsr)
|
|
2256
|
-
return
|
|
2257
|
-
}
|
|
2258
|
-
|
|
2259
|
-
if (typeof route.options.ssr !== 'function') {
|
|
2260
|
-
existingMatch.ssr = parentOverride(route.options.ssr)
|
|
2261
|
-
return
|
|
2262
|
-
}
|
|
2263
|
-
const { search, params } = this.getMatch(matchId)!
|
|
2264
|
-
|
|
2265
|
-
const ssrFnContext: SsrContextOptions<any, any, any> = {
|
|
2266
|
-
search: makeMaybe(search, existingMatch.searchError),
|
|
2267
|
-
params: makeMaybe(params, existingMatch.paramsError),
|
|
2268
|
-
location: innerLoadContext.location,
|
|
2269
|
-
matches: innerLoadContext.matches.map((match) => ({
|
|
2270
|
-
index: match.index,
|
|
2271
|
-
pathname: match.pathname,
|
|
2272
|
-
fullPath: match.fullPath,
|
|
2273
|
-
staticData: match.staticData,
|
|
2274
|
-
id: match.id,
|
|
2275
|
-
routeId: match.routeId,
|
|
2276
|
-
search: makeMaybe(match.search, match.searchError),
|
|
2277
|
-
params: makeMaybe(match.params, match.paramsError),
|
|
2278
|
-
ssr: match.ssr,
|
|
2279
|
-
})),
|
|
2280
|
-
}
|
|
2281
|
-
|
|
2282
|
-
const tempSsr = route.options.ssr(ssrFnContext)
|
|
2283
|
-
if (isPromise(tempSsr)) {
|
|
2284
|
-
return tempSsr.then((ssr) => {
|
|
2285
|
-
existingMatch.ssr = parentOverride(ssr ?? defaultSsr)
|
|
2286
|
-
})
|
|
2287
|
-
}
|
|
2288
|
-
|
|
2289
|
-
existingMatch.ssr = parentOverride(tempSsr ?? defaultSsr)
|
|
2290
|
-
return
|
|
2291
|
-
}
|
|
2292
|
-
|
|
2293
|
-
private setupPendingTimeout = (
|
|
2294
|
-
innerLoadContext: InnerLoadContext,
|
|
2295
|
-
matchId: string,
|
|
2296
|
-
route: AnyRoute,
|
|
2297
|
-
): void => {
|
|
2298
|
-
const pendingMs = route.options.pendingMs ?? this.options.defaultPendingMs
|
|
2299
|
-
const shouldPending = !!(
|
|
2300
|
-
innerLoadContext.onReady &&
|
|
2301
|
-
!this.isServer &&
|
|
2302
|
-
!this.resolvePreload(innerLoadContext, matchId) &&
|
|
2303
|
-
(route.options.loader ||
|
|
2304
|
-
route.options.beforeLoad ||
|
|
2305
|
-
routeNeedsPreload(route)) &&
|
|
2306
|
-
typeof pendingMs === 'number' &&
|
|
2307
|
-
pendingMs !== Infinity &&
|
|
2308
|
-
(route.options.pendingComponent ??
|
|
2309
|
-
(this.options as any)?.defaultPendingComponent)
|
|
2310
|
-
)
|
|
2311
|
-
const match = this.getMatch(matchId)!
|
|
2312
|
-
if (shouldPending && match._nonReactive.pendingTimeout === undefined) {
|
|
2313
|
-
const pendingTimeout = setTimeout(() => {
|
|
2314
|
-
// Update the match and prematurely resolve the loadMatches promise so that
|
|
2315
|
-
// the pending component can start rendering
|
|
2316
|
-
this.triggerOnReady(innerLoadContext)
|
|
2317
|
-
}, pendingMs)
|
|
2318
|
-
match._nonReactive.pendingTimeout = pendingTimeout
|
|
2319
|
-
}
|
|
2320
|
-
}
|
|
2321
|
-
|
|
2322
|
-
private shouldExecuteBeforeLoad = (
|
|
2323
|
-
innerLoadContext: InnerLoadContext,
|
|
2324
|
-
matchId: string,
|
|
2325
|
-
route: AnyRoute,
|
|
2326
|
-
): boolean | Promise<boolean> => {
|
|
2327
|
-
const existingMatch = this.getMatch(matchId)!
|
|
2328
|
-
|
|
2329
|
-
// If we are in the middle of a load, either of these will be present
|
|
2330
|
-
// (not to be confused with `loadPromise`, which is always defined)
|
|
2331
|
-
if (
|
|
2332
|
-
!existingMatch._nonReactive.beforeLoadPromise &&
|
|
2333
|
-
!existingMatch._nonReactive.loaderPromise
|
|
2334
|
-
)
|
|
2335
|
-
return true
|
|
2336
|
-
|
|
2337
|
-
this.setupPendingTimeout(innerLoadContext, matchId, route)
|
|
2338
|
-
|
|
2339
|
-
const then = () => {
|
|
2340
|
-
let shouldExecuteBeforeLoad = true
|
|
2341
|
-
const match = this.getMatch(matchId)!
|
|
2342
|
-
if (match.status === 'error') {
|
|
2343
|
-
shouldExecuteBeforeLoad = true
|
|
2344
|
-
} else if (
|
|
2345
|
-
match.preload &&
|
|
2346
|
-
(match.status === 'redirected' || match.status === 'notFound')
|
|
2347
|
-
) {
|
|
2348
|
-
this.handleRedirectAndNotFound(innerLoadContext, match, match.error)
|
|
2349
|
-
}
|
|
2350
|
-
return shouldExecuteBeforeLoad
|
|
2351
|
-
}
|
|
2352
|
-
|
|
2353
|
-
// Wait for the beforeLoad to resolve before we continue
|
|
2354
|
-
return existingMatch._nonReactive.beforeLoadPromise
|
|
2355
|
-
? existingMatch._nonReactive.beforeLoadPromise.then(then)
|
|
2356
|
-
: then()
|
|
2357
|
-
}
|
|
2358
|
-
|
|
2359
|
-
private executeBeforeLoad = (
|
|
2360
|
-
innerLoadContext: InnerLoadContext,
|
|
2361
|
-
matchId: string,
|
|
2362
|
-
index: number,
|
|
2363
|
-
route: AnyRoute,
|
|
2364
|
-
): void | Promise<void> => {
|
|
2365
|
-
const match = this.getMatch(matchId)!
|
|
2366
|
-
|
|
2367
|
-
match._nonReactive.beforeLoadPromise = createControlledPromise<void>()
|
|
2368
|
-
// explicitly capture the previous loadPromise
|
|
2369
|
-
const prevLoadPromise = match._nonReactive.loadPromise
|
|
2370
|
-
match._nonReactive.loadPromise = createControlledPromise<void>(() => {
|
|
2371
|
-
prevLoadPromise?.resolve()
|
|
2372
|
-
})
|
|
2373
|
-
|
|
2374
|
-
const { paramsError, searchError } = match
|
|
2375
|
-
|
|
2376
|
-
if (paramsError) {
|
|
2377
|
-
this.handleSerialError(
|
|
2378
|
-
innerLoadContext,
|
|
2379
|
-
index,
|
|
2380
|
-
paramsError,
|
|
2381
|
-
'PARSE_PARAMS',
|
|
2382
|
-
)
|
|
2383
|
-
}
|
|
2384
|
-
|
|
2385
|
-
if (searchError) {
|
|
2386
|
-
this.handleSerialError(
|
|
2387
|
-
innerLoadContext,
|
|
2388
|
-
index,
|
|
2389
|
-
searchError,
|
|
2390
|
-
'VALIDATE_SEARCH',
|
|
2391
|
-
)
|
|
2392
|
-
}
|
|
2393
|
-
|
|
2394
|
-
this.setupPendingTimeout(innerLoadContext, matchId, route)
|
|
2395
|
-
|
|
2396
|
-
const abortController = new AbortController()
|
|
2397
|
-
|
|
2398
|
-
const parentMatchId = innerLoadContext.matches[index - 1]?.id
|
|
2399
|
-
const parentMatch = parentMatchId
|
|
2400
|
-
? this.getMatch(parentMatchId)!
|
|
2401
|
-
: undefined
|
|
2402
|
-
const parentMatchContext =
|
|
2403
|
-
parentMatch?.context ?? this.options.context ?? undefined
|
|
2404
|
-
|
|
2405
|
-
const context = { ...parentMatchContext, ...match.__routeContext }
|
|
2406
|
-
|
|
2407
|
-
let isPending = false
|
|
2408
|
-
const pending = () => {
|
|
2409
|
-
if (isPending) return
|
|
2410
|
-
isPending = true
|
|
2411
|
-
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2412
|
-
...prev,
|
|
2413
|
-
isFetching: 'beforeLoad',
|
|
2414
|
-
fetchCount: prev.fetchCount + 1,
|
|
2415
|
-
abortController,
|
|
2416
|
-
context,
|
|
2417
|
-
}))
|
|
2418
|
-
}
|
|
2419
|
-
|
|
2420
|
-
const resolve = () => {
|
|
2421
|
-
match._nonReactive.beforeLoadPromise?.resolve()
|
|
2422
|
-
match._nonReactive.beforeLoadPromise = undefined
|
|
2423
|
-
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2424
|
-
...prev,
|
|
2425
|
-
isFetching: false,
|
|
2426
|
-
}))
|
|
2427
|
-
}
|
|
2428
|
-
|
|
2429
|
-
// if there is no `beforeLoad` option, skip everything, batch update the store, return early
|
|
2430
|
-
if (!route.options.beforeLoad) {
|
|
2431
|
-
batch(() => {
|
|
2432
|
-
pending()
|
|
2433
|
-
resolve()
|
|
2434
|
-
})
|
|
2435
|
-
return
|
|
2436
|
-
}
|
|
2437
|
-
|
|
2438
|
-
const { search, params, cause } = match
|
|
2439
|
-
const preload = this.resolvePreload(innerLoadContext, matchId)
|
|
2440
|
-
const beforeLoadFnContext: BeforeLoadContextOptions<
|
|
2441
|
-
any,
|
|
2442
|
-
any,
|
|
2443
|
-
any,
|
|
2444
|
-
any,
|
|
2445
|
-
any
|
|
2446
|
-
> = {
|
|
2447
|
-
search,
|
|
2448
|
-
abortController,
|
|
2449
|
-
params,
|
|
2450
|
-
preload,
|
|
2451
|
-
context,
|
|
2452
|
-
location: innerLoadContext.location,
|
|
2453
|
-
navigate: (opts: any) =>
|
|
2454
|
-
this.navigate({ ...opts, _fromLocation: innerLoadContext.location }),
|
|
2455
|
-
buildLocation: this.buildLocation,
|
|
2456
|
-
cause: preload ? 'preload' : cause,
|
|
2457
|
-
matches: innerLoadContext.matches,
|
|
2458
|
-
}
|
|
2459
|
-
|
|
2460
|
-
const updateContext = (beforeLoadContext: any) => {
|
|
2461
|
-
if (beforeLoadContext === undefined) {
|
|
2462
|
-
batch(() => {
|
|
2463
|
-
pending()
|
|
2464
|
-
resolve()
|
|
2465
|
-
})
|
|
2466
|
-
return
|
|
2467
|
-
}
|
|
2468
|
-
if (isRedirect(beforeLoadContext) || isNotFound(beforeLoadContext)) {
|
|
2469
|
-
pending()
|
|
2470
|
-
this.handleSerialError(
|
|
2471
|
-
innerLoadContext,
|
|
2472
|
-
index,
|
|
2473
|
-
beforeLoadContext,
|
|
2474
|
-
'BEFORE_LOAD',
|
|
2475
|
-
)
|
|
2476
|
-
}
|
|
2477
|
-
|
|
2478
|
-
batch(() => {
|
|
2479
|
-
pending()
|
|
2480
|
-
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2481
|
-
...prev,
|
|
2482
|
-
__beforeLoadContext: beforeLoadContext,
|
|
2483
|
-
context: {
|
|
2484
|
-
...prev.context,
|
|
2485
|
-
...beforeLoadContext,
|
|
2486
|
-
},
|
|
2487
|
-
}))
|
|
2488
|
-
resolve()
|
|
2489
|
-
})
|
|
2490
|
-
}
|
|
2491
|
-
|
|
2492
|
-
let beforeLoadContext
|
|
2493
|
-
try {
|
|
2494
|
-
beforeLoadContext = route.options.beforeLoad(beforeLoadFnContext)
|
|
2495
|
-
if (isPromise(beforeLoadContext)) {
|
|
2496
|
-
pending()
|
|
2497
|
-
return beforeLoadContext
|
|
2498
|
-
.catch((err) => {
|
|
2499
|
-
this.handleSerialError(innerLoadContext, index, err, 'BEFORE_LOAD')
|
|
2500
|
-
})
|
|
2501
|
-
.then(updateContext)
|
|
2502
|
-
}
|
|
2503
|
-
} catch (err) {
|
|
2504
|
-
pending()
|
|
2505
|
-
this.handleSerialError(innerLoadContext, index, err, 'BEFORE_LOAD')
|
|
2506
|
-
}
|
|
2507
|
-
|
|
2508
|
-
updateContext(beforeLoadContext)
|
|
2509
|
-
return
|
|
2510
|
-
}
|
|
2511
|
-
|
|
2512
|
-
private handleBeforeLoad = (
|
|
2513
|
-
innerLoadContext: InnerLoadContext,
|
|
2514
|
-
index: number,
|
|
2515
|
-
): void | Promise<void> => {
|
|
2516
|
-
const { id: matchId, routeId } = innerLoadContext.matches[index]!
|
|
2517
|
-
const route = this.looseRoutesById[routeId]!
|
|
2518
|
-
|
|
2519
|
-
const serverSsr = () => {
|
|
2520
|
-
// on the server, determine whether SSR the current match or not
|
|
2521
|
-
if (this.isServer) {
|
|
2522
|
-
const maybePromise = this.isBeforeLoadSsr(
|
|
2523
|
-
innerLoadContext,
|
|
2524
|
-
matchId,
|
|
2525
|
-
index,
|
|
2526
|
-
route,
|
|
2527
|
-
)
|
|
2528
|
-
if (isPromise(maybePromise)) return maybePromise.then(queueExecution)
|
|
2529
|
-
}
|
|
2530
|
-
return queueExecution()
|
|
2531
|
-
}
|
|
2532
|
-
|
|
2533
|
-
const queueExecution = () => {
|
|
2534
|
-
if (this.shouldSkipLoader(matchId)) return
|
|
2535
|
-
const shouldExecuteBeforeLoadResult = this.shouldExecuteBeforeLoad(
|
|
2536
|
-
innerLoadContext,
|
|
2537
|
-
matchId,
|
|
2538
|
-
route,
|
|
2539
|
-
)
|
|
2540
|
-
return isPromise(shouldExecuteBeforeLoadResult)
|
|
2541
|
-
? shouldExecuteBeforeLoadResult.then(execute)
|
|
2542
|
-
: execute(shouldExecuteBeforeLoadResult)
|
|
2543
|
-
}
|
|
2544
|
-
|
|
2545
|
-
const execute = (shouldExecuteBeforeLoad: boolean) => {
|
|
2546
|
-
if (shouldExecuteBeforeLoad) {
|
|
2547
|
-
// If we are not in the middle of a load OR the previous load failed, start it
|
|
2548
|
-
return this.executeBeforeLoad(innerLoadContext, matchId, index, route)
|
|
2549
|
-
}
|
|
2550
|
-
return
|
|
2551
|
-
}
|
|
2552
|
-
|
|
2553
|
-
return serverSsr()
|
|
2554
|
-
}
|
|
2555
|
-
|
|
2556
|
-
private executeHead = (
|
|
2557
|
-
innerLoadContext: InnerLoadContext,
|
|
2558
|
-
matchId: string,
|
|
2559
|
-
route: AnyRoute,
|
|
2560
|
-
): void | Promise<
|
|
2561
|
-
Pick<
|
|
2562
|
-
AnyRouteMatch,
|
|
2563
|
-
'meta' | 'links' | 'headScripts' | 'headers' | 'scripts' | 'styles'
|
|
2564
|
-
>
|
|
2565
|
-
> => {
|
|
2566
|
-
const match = this.getMatch(matchId)
|
|
2567
|
-
// in case of a redirecting match during preload, the match does not exist
|
|
2568
|
-
if (!match) {
|
|
2569
|
-
return
|
|
2570
|
-
}
|
|
2571
|
-
if (
|
|
2572
|
-
!route.options.head &&
|
|
2573
|
-
!route.options.scripts &&
|
|
2574
|
-
!route.options.headers
|
|
2575
|
-
) {
|
|
2576
|
-
return
|
|
2577
|
-
}
|
|
2578
|
-
const assetContext = {
|
|
2579
|
-
matches: innerLoadContext.matches,
|
|
2580
|
-
match,
|
|
2581
|
-
params: match.params,
|
|
2582
|
-
loaderData: match.loaderData,
|
|
2583
|
-
}
|
|
2584
|
-
|
|
2585
|
-
return Promise.all([
|
|
2586
|
-
route.options.head?.(assetContext),
|
|
2587
|
-
route.options.scripts?.(assetContext),
|
|
2588
|
-
route.options.headers?.(assetContext),
|
|
2589
|
-
]).then(([headFnContent, scripts, headers]) => {
|
|
2590
|
-
const meta = headFnContent?.meta
|
|
2591
|
-
const links = headFnContent?.links
|
|
2592
|
-
const headScripts = headFnContent?.scripts
|
|
2593
|
-
const styles = headFnContent?.styles
|
|
2594
|
-
|
|
2595
|
-
return {
|
|
2596
|
-
meta,
|
|
2597
|
-
links,
|
|
2598
|
-
headScripts,
|
|
2599
|
-
headers,
|
|
2600
|
-
scripts,
|
|
2601
|
-
styles,
|
|
2602
|
-
}
|
|
2603
|
-
})
|
|
2604
|
-
}
|
|
2605
|
-
|
|
2606
|
-
private potentialPendingMinPromise = (
|
|
2607
|
-
matchId: string,
|
|
2608
|
-
): void | ControlledPromise<void> => {
|
|
2609
|
-
const latestMatch = this.getMatch(matchId)!
|
|
2610
|
-
return latestMatch._nonReactive.minPendingPromise
|
|
2611
|
-
}
|
|
2612
|
-
|
|
2613
|
-
private getLoaderContext = (
|
|
2614
|
-
innerLoadContext: InnerLoadContext,
|
|
2615
|
-
matchId: string,
|
|
2616
|
-
index: number,
|
|
2617
|
-
route: AnyRoute,
|
|
2618
|
-
): LoaderFnContext => {
|
|
2619
|
-
const parentMatchPromise = innerLoadContext.matchPromises[index - 1] as any
|
|
2620
|
-
const { params, loaderDeps, abortController, context, cause } =
|
|
2621
|
-
this.getMatch(matchId)!
|
|
2622
|
-
|
|
2623
|
-
const preload = this.resolvePreload(innerLoadContext, matchId)
|
|
2624
|
-
|
|
2625
|
-
return {
|
|
2626
|
-
params,
|
|
2627
|
-
deps: loaderDeps,
|
|
2628
|
-
preload: !!preload,
|
|
2629
|
-
parentMatchPromise,
|
|
2630
|
-
abortController: abortController,
|
|
2631
|
-
context,
|
|
2632
|
-
location: innerLoadContext.location,
|
|
2633
|
-
navigate: (opts) =>
|
|
2634
|
-
this.navigate({ ...opts, _fromLocation: innerLoadContext.location }),
|
|
2635
|
-
cause: preload ? 'preload' : cause,
|
|
2636
|
-
route,
|
|
2637
|
-
}
|
|
2638
|
-
}
|
|
2639
|
-
|
|
2640
|
-
private runLoader = async (
|
|
2641
|
-
innerLoadContext: InnerLoadContext,
|
|
2642
|
-
matchId: string,
|
|
2643
|
-
index: number,
|
|
2644
|
-
route: AnyRoute,
|
|
2645
|
-
): Promise<void> => {
|
|
2646
|
-
try {
|
|
2647
|
-
// If the Matches component rendered
|
|
2648
|
-
// the pending component and needs to show it for
|
|
2649
|
-
// a minimum duration, we''ll wait for it to resolve
|
|
2650
|
-
// before committing to the match and resolving
|
|
2651
|
-
// the loadPromise
|
|
2652
|
-
|
|
2653
|
-
// Actually run the loader and handle the result
|
|
2654
|
-
try {
|
|
2655
|
-
if (!this.isServer || this.getMatch(matchId)!.ssr === true) {
|
|
2656
|
-
this.loadRouteChunk(route)
|
|
2657
|
-
}
|
|
2658
|
-
|
|
2659
|
-
// Kick off the loader!
|
|
2660
|
-
const loaderResult = route.options.loader?.(
|
|
2661
|
-
this.getLoaderContext(innerLoadContext, matchId, index, route),
|
|
2662
|
-
)
|
|
2663
|
-
const loaderResultIsPromise =
|
|
2664
|
-
route.options.loader && isPromise(loaderResult)
|
|
2665
|
-
|
|
2666
|
-
const willLoadSomething = !!(
|
|
2667
|
-
loaderResultIsPromise ||
|
|
2668
|
-
route._lazyPromise ||
|
|
2669
|
-
route._componentsPromise ||
|
|
2670
|
-
route.options.head ||
|
|
2671
|
-
route.options.scripts ||
|
|
2672
|
-
route.options.headers ||
|
|
2673
|
-
this.getMatch(matchId)!._nonReactive.minPendingPromise
|
|
2674
|
-
)
|
|
2675
|
-
|
|
2676
|
-
if (willLoadSomething) {
|
|
2677
|
-
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2678
|
-
...prev,
|
|
2679
|
-
isFetching: 'loader',
|
|
2680
|
-
}))
|
|
2681
|
-
}
|
|
2682
|
-
|
|
2683
|
-
if (route.options.loader) {
|
|
2684
|
-
const loaderData = loaderResultIsPromise
|
|
2685
|
-
? await loaderResult
|
|
2686
|
-
: loaderResult
|
|
2687
|
-
|
|
2688
|
-
this.handleRedirectAndNotFound(
|
|
2689
|
-
innerLoadContext,
|
|
2690
|
-
this.getMatch(matchId),
|
|
2691
|
-
loaderData,
|
|
2692
|
-
)
|
|
2693
|
-
if (loaderData !== undefined) {
|
|
2694
|
-
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2695
|
-
...prev,
|
|
2696
|
-
loaderData,
|
|
2697
|
-
}))
|
|
2698
|
-
}
|
|
2699
|
-
}
|
|
2700
|
-
|
|
2701
|
-
// Lazy option can modify the route options,
|
|
2702
|
-
// so we need to wait for it to resolve before
|
|
2703
|
-
// we can use the options
|
|
2704
|
-
if (route._lazyPromise) await route._lazyPromise
|
|
2705
|
-
const headResult = this.executeHead(innerLoadContext, matchId, route)
|
|
2706
|
-
const head = headResult ? await headResult : undefined
|
|
2707
|
-
const pendingPromise = this.potentialPendingMinPromise(matchId)
|
|
2708
|
-
if (pendingPromise) await pendingPromise
|
|
2709
|
-
|
|
2710
|
-
// Last but not least, wait for the the components
|
|
2711
|
-
// to be preloaded before we resolve the match
|
|
2712
|
-
if (route._componentsPromise) await route._componentsPromise
|
|
2713
|
-
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2714
|
-
...prev,
|
|
2715
|
-
error: undefined,
|
|
2716
|
-
status: 'success',
|
|
2717
|
-
isFetching: false,
|
|
2718
|
-
updatedAt: Date.now(),
|
|
2719
|
-
...head,
|
|
2720
|
-
}))
|
|
2721
|
-
} catch (e) {
|
|
2722
|
-
let error = e
|
|
2723
|
-
|
|
2724
|
-
const pendingPromise = this.potentialPendingMinPromise(matchId)
|
|
2725
|
-
if (pendingPromise) await pendingPromise
|
|
2726
|
-
|
|
2727
|
-
this.handleRedirectAndNotFound(
|
|
2728
|
-
innerLoadContext,
|
|
2729
|
-
this.getMatch(matchId),
|
|
2730
|
-
e,
|
|
2731
|
-
)
|
|
2732
|
-
|
|
2733
|
-
try {
|
|
2734
|
-
route.options.onError?.(e)
|
|
2735
|
-
} catch (onErrorError) {
|
|
2736
|
-
error = onErrorError
|
|
2737
|
-
this.handleRedirectAndNotFound(
|
|
2738
|
-
innerLoadContext,
|
|
2739
|
-
this.getMatch(matchId),
|
|
2740
|
-
onErrorError,
|
|
2741
|
-
)
|
|
2742
|
-
}
|
|
2743
|
-
const headResult = this.executeHead(innerLoadContext, matchId, route)
|
|
2744
|
-
const head = headResult ? await headResult : undefined
|
|
2745
|
-
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2746
|
-
...prev,
|
|
2747
|
-
error,
|
|
2748
|
-
status: 'error',
|
|
2749
|
-
isFetching: false,
|
|
2750
|
-
...head,
|
|
2751
|
-
}))
|
|
2752
|
-
}
|
|
2753
|
-
} catch (err) {
|
|
2754
|
-
const match = this.getMatch(matchId)
|
|
2755
|
-
// in case of a redirecting match during preload, the match does not exist
|
|
2756
|
-
if (match) {
|
|
2757
|
-
const headResult = this.executeHead(innerLoadContext, matchId, route)
|
|
2758
|
-
if (headResult) {
|
|
2759
|
-
const head = await headResult
|
|
2760
|
-
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2761
|
-
...prev,
|
|
2762
|
-
...head,
|
|
2763
|
-
}))
|
|
2764
|
-
}
|
|
2765
|
-
match._nonReactive.loaderPromise = undefined
|
|
2766
|
-
}
|
|
2767
|
-
this.handleRedirectAndNotFound(innerLoadContext, match, err)
|
|
2768
|
-
}
|
|
2769
|
-
}
|
|
2770
|
-
|
|
2771
|
-
private loadRouteMatch = async (
|
|
2772
|
-
innerLoadContext: InnerLoadContext,
|
|
2773
|
-
index: number,
|
|
2774
|
-
): Promise<AnyRouteMatch> => {
|
|
2775
|
-
const { id: matchId, routeId } = innerLoadContext.matches[index]!
|
|
2776
|
-
let loaderShouldRunAsync = false
|
|
2777
|
-
let loaderIsRunningAsync = false
|
|
2778
|
-
const route = this.looseRoutesById[routeId]!
|
|
2779
|
-
|
|
2780
|
-
const prevMatch = this.getMatch(matchId)!
|
|
2781
|
-
if (this.shouldSkipLoader(matchId)) {
|
|
2782
|
-
if (this.isServer) {
|
|
2783
|
-
const headResult = this.executeHead(innerLoadContext, matchId, route)
|
|
2784
|
-
if (headResult) {
|
|
2785
|
-
const head = await headResult
|
|
2786
|
-
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2787
|
-
...prev,
|
|
2788
|
-
...head,
|
|
2789
|
-
}))
|
|
2790
|
-
}
|
|
2791
|
-
return this.getMatch(matchId)!
|
|
2792
|
-
}
|
|
2793
|
-
}
|
|
2794
|
-
// there is a loaderPromise, so we are in the middle of a load
|
|
2795
|
-
else if (prevMatch._nonReactive.loaderPromise) {
|
|
2796
|
-
// do not block if we already have stale data we can show
|
|
2797
|
-
// but only if the ongoing load is not a preload since error handling is different for preloads
|
|
2798
|
-
// and we don't want to swallow errors
|
|
2799
|
-
if (
|
|
2800
|
-
prevMatch.status === 'success' &&
|
|
2801
|
-
!innerLoadContext.sync &&
|
|
2802
|
-
!prevMatch.preload
|
|
2803
|
-
) {
|
|
2804
|
-
return this.getMatch(matchId)!
|
|
2805
|
-
}
|
|
2806
|
-
await prevMatch._nonReactive.loaderPromise
|
|
2807
|
-
const match = this.getMatch(matchId)!
|
|
2808
|
-
if (match.error) {
|
|
2809
|
-
this.handleRedirectAndNotFound(innerLoadContext, match, match.error)
|
|
2810
|
-
}
|
|
2811
|
-
} else {
|
|
2812
|
-
// This is where all of the stale-while-revalidate magic happens
|
|
2813
|
-
const age = Date.now() - this.getMatch(matchId)!.updatedAt
|
|
2814
|
-
|
|
2815
|
-
const preload = this.resolvePreload(innerLoadContext, matchId)
|
|
2816
|
-
|
|
2817
|
-
const staleAge = preload
|
|
2818
|
-
? (route.options.preloadStaleTime ??
|
|
2819
|
-
this.options.defaultPreloadStaleTime ??
|
|
2820
|
-
30_000) // 30 seconds for preloads by default
|
|
2821
|
-
: (route.options.staleTime ?? this.options.defaultStaleTime ?? 0)
|
|
2822
|
-
|
|
2823
|
-
const shouldReloadOption = route.options.shouldReload
|
|
2824
|
-
|
|
2825
|
-
// Default to reloading the route all the time
|
|
2826
|
-
// Allow shouldReload to get the last say,
|
|
2827
|
-
// if provided.
|
|
2828
|
-
const shouldReload =
|
|
2829
|
-
typeof shouldReloadOption === 'function'
|
|
2830
|
-
? shouldReloadOption(
|
|
2831
|
-
this.getLoaderContext(innerLoadContext, matchId, index, route),
|
|
2832
|
-
)
|
|
2833
|
-
: shouldReloadOption
|
|
2834
|
-
|
|
2835
|
-
const nextPreload =
|
|
2836
|
-
!!preload && !this.state.matches.some((d) => d.id === matchId)
|
|
2837
|
-
const match = this.getMatch(matchId)!
|
|
2838
|
-
match._nonReactive.loaderPromise = createControlledPromise<void>()
|
|
2839
|
-
if (nextPreload !== match.preload) {
|
|
2840
|
-
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2841
|
-
...prev,
|
|
2842
|
-
preload: nextPreload,
|
|
2843
|
-
}))
|
|
2844
|
-
}
|
|
2845
|
-
|
|
2846
|
-
// If the route is successful and still fresh, just resolve
|
|
2847
|
-
const { status, invalid } = this.getMatch(matchId)!
|
|
2848
|
-
loaderShouldRunAsync =
|
|
2849
|
-
status === 'success' && (invalid || (shouldReload ?? age > staleAge))
|
|
2850
|
-
if (preload && route.options.preload === false) {
|
|
2851
|
-
// Do nothing
|
|
2852
|
-
} else if (loaderShouldRunAsync && !innerLoadContext.sync) {
|
|
2853
|
-
loaderIsRunningAsync = true
|
|
2854
|
-
;(async () => {
|
|
2855
|
-
try {
|
|
2856
|
-
await this.runLoader(innerLoadContext, matchId, index, route)
|
|
2857
|
-
const match = this.getMatch(matchId)!
|
|
2858
|
-
match._nonReactive.loaderPromise?.resolve()
|
|
2859
|
-
match._nonReactive.loadPromise?.resolve()
|
|
2860
|
-
match._nonReactive.loaderPromise = undefined
|
|
2861
|
-
} catch (err) {
|
|
2862
|
-
if (isRedirect(err)) {
|
|
2863
|
-
await this.navigate(err.options)
|
|
2864
|
-
}
|
|
2865
|
-
}
|
|
2866
|
-
})()
|
|
2867
|
-
} else if (
|
|
2868
|
-
status !== 'success' ||
|
|
2869
|
-
(loaderShouldRunAsync && innerLoadContext.sync)
|
|
2870
|
-
) {
|
|
2871
|
-
await this.runLoader(innerLoadContext, matchId, index, route)
|
|
2872
|
-
} else {
|
|
2873
|
-
// if the loader did not run, still update head.
|
|
2874
|
-
// reason: parent's beforeLoad may have changed the route context
|
|
2875
|
-
// and only now do we know the route context (and that the loader would not run)
|
|
2876
|
-
const headResult = this.executeHead(innerLoadContext, matchId, route)
|
|
2877
|
-
if (headResult) {
|
|
2878
|
-
const head = await headResult
|
|
2879
|
-
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2880
|
-
...prev,
|
|
2881
|
-
...head,
|
|
2882
|
-
}))
|
|
2883
|
-
}
|
|
2884
|
-
}
|
|
2885
|
-
}
|
|
2886
|
-
const match = this.getMatch(matchId)!
|
|
2887
|
-
if (!loaderIsRunningAsync) {
|
|
2888
|
-
match._nonReactive.loaderPromise?.resolve()
|
|
2889
|
-
match._nonReactive.loadPromise?.resolve()
|
|
2890
|
-
}
|
|
2891
|
-
|
|
2892
|
-
clearTimeout(match._nonReactive.pendingTimeout)
|
|
2893
|
-
match._nonReactive.pendingTimeout = undefined
|
|
2894
|
-
if (!loaderIsRunningAsync) match._nonReactive.loaderPromise = undefined
|
|
2895
|
-
match._nonReactive.dehydrated = undefined
|
|
2896
|
-
const nextIsFetching = loaderIsRunningAsync ? match.isFetching : false
|
|
2897
|
-
if (nextIsFetching !== match.isFetching || match.invalid !== false) {
|
|
2898
|
-
innerLoadContext.updateMatch(matchId, (prev) => ({
|
|
2899
|
-
...prev,
|
|
2900
|
-
isFetching: nextIsFetching,
|
|
2901
|
-
invalid: false,
|
|
2902
|
-
}))
|
|
2903
|
-
}
|
|
2904
|
-
return this.getMatch(matchId)!
|
|
2905
|
-
}
|
|
2906
|
-
|
|
2907
|
-
loadMatches = async (baseContext: {
|
|
2908
|
-
location: ParsedLocation
|
|
2909
|
-
matches: Array<AnyRouteMatch>
|
|
2910
|
-
preload?: boolean
|
|
2911
|
-
onReady?: () => Promise<void>
|
|
2912
|
-
updateMatch?: UpdateMatchFn
|
|
2913
|
-
sync?: boolean
|
|
2914
|
-
}): Promise<Array<MakeRouteMatch>> => {
|
|
2915
|
-
const innerLoadContext = baseContext as InnerLoadContext
|
|
2916
|
-
innerLoadContext.updateMatch ??= this.updateMatch
|
|
2917
|
-
innerLoadContext.matchPromises = []
|
|
2918
|
-
|
|
2919
|
-
// make sure the pending component is immediately rendered when hydrating a match that is not SSRed
|
|
2920
|
-
// the pending component was already rendered on the server and we want to keep it shown on the client until minPendingMs is reached
|
|
2921
|
-
if (!this.isServer && this.state.matches.some((d) => d._forcePending)) {
|
|
2922
|
-
this.triggerOnReady(innerLoadContext)
|
|
2923
|
-
}
|
|
2924
|
-
|
|
2925
|
-
try {
|
|
2926
|
-
// Execute all beforeLoads one by one
|
|
2927
|
-
for (let i = 0; i < innerLoadContext.matches.length; i++) {
|
|
2928
|
-
const beforeLoad = this.handleBeforeLoad(innerLoadContext, i)
|
|
2929
|
-
if (isPromise(beforeLoad)) await beforeLoad
|
|
2930
|
-
}
|
|
2931
|
-
|
|
2932
|
-
// Execute all loaders in parallel
|
|
2933
|
-
const max =
|
|
2934
|
-
innerLoadContext.firstBadMatchIndex ?? innerLoadContext.matches.length
|
|
2935
|
-
for (let i = 0; i < max; i++) {
|
|
2936
|
-
innerLoadContext.matchPromises.push(
|
|
2937
|
-
this.loadRouteMatch(innerLoadContext, i),
|
|
2938
|
-
)
|
|
2939
|
-
}
|
|
2940
|
-
await Promise.all(innerLoadContext.matchPromises)
|
|
2941
|
-
|
|
2942
|
-
const readyPromise = this.triggerOnReady(innerLoadContext)
|
|
2943
|
-
if (isPromise(readyPromise)) await readyPromise
|
|
2944
|
-
} catch (err) {
|
|
2945
|
-
if (isNotFound(err) && !innerLoadContext.preload) {
|
|
2946
|
-
const readyPromise = this.triggerOnReady(innerLoadContext)
|
|
2947
|
-
if (isPromise(readyPromise)) await readyPromise
|
|
2948
|
-
throw err
|
|
2949
|
-
}
|
|
2950
|
-
if (isRedirect(err)) {
|
|
2951
|
-
throw err
|
|
2952
|
-
}
|
|
2953
|
-
}
|
|
2954
|
-
|
|
2955
|
-
return innerLoadContext.matches
|
|
2956
|
-
}
|
|
2957
|
-
|
|
2958
2178
|
invalidate: InvalidateFn<
|
|
2959
2179
|
RouterCore<
|
|
2960
2180
|
TRouteTree,
|
|
@@ -3048,47 +2268,7 @@ export class RouterCore<
|
|
|
3048
2268
|
this.clearCache({ filter })
|
|
3049
2269
|
}
|
|
3050
2270
|
|
|
3051
|
-
loadRouteChunk =
|
|
3052
|
-
if (!route._lazyLoaded && route._lazyPromise === undefined) {
|
|
3053
|
-
if (route.lazyFn) {
|
|
3054
|
-
route._lazyPromise = route.lazyFn().then((lazyRoute) => {
|
|
3055
|
-
// explicitly don't copy over the lazy route's id
|
|
3056
|
-
const { id: _id, ...options } = lazyRoute.options
|
|
3057
|
-
Object.assign(route.options, options)
|
|
3058
|
-
route._lazyLoaded = true
|
|
3059
|
-
route._lazyPromise = undefined // gc promise, we won't need it anymore
|
|
3060
|
-
})
|
|
3061
|
-
} else {
|
|
3062
|
-
route._lazyLoaded = true
|
|
3063
|
-
}
|
|
3064
|
-
}
|
|
3065
|
-
|
|
3066
|
-
// If for some reason lazy resolves more lazy components...
|
|
3067
|
-
// We'll wait for that before we attempt to preload the
|
|
3068
|
-
// components themselves.
|
|
3069
|
-
if (!route._componentsLoaded && route._componentsPromise === undefined) {
|
|
3070
|
-
const loadComponents = () => {
|
|
3071
|
-
const preloads = []
|
|
3072
|
-
for (const type of componentTypes) {
|
|
3073
|
-
const preload = (route.options[type] as any)?.preload
|
|
3074
|
-
if (preload) preloads.push(preload())
|
|
3075
|
-
}
|
|
3076
|
-
if (preloads.length)
|
|
3077
|
-
return Promise.all(preloads).then(() => {
|
|
3078
|
-
route._componentsLoaded = true
|
|
3079
|
-
route._componentsPromise = undefined // gc promise, we won't need it anymore
|
|
3080
|
-
})
|
|
3081
|
-
route._componentsLoaded = true
|
|
3082
|
-
route._componentsPromise = undefined // gc promise, we won't need it anymore
|
|
3083
|
-
return
|
|
3084
|
-
}
|
|
3085
|
-
route._componentsPromise = route._lazyPromise
|
|
3086
|
-
? route._lazyPromise.then(loadComponents)
|
|
3087
|
-
: loadComponents()
|
|
3088
|
-
}
|
|
3089
|
-
|
|
3090
|
-
return route._componentsPromise
|
|
3091
|
-
}
|
|
2271
|
+
loadRouteChunk = loadRouteChunk
|
|
3092
2272
|
|
|
3093
2273
|
preloadRoute: PreloadRouteFn<
|
|
3094
2274
|
TRouteTree,
|
|
@@ -3128,7 +2308,8 @@ export class RouterCore<
|
|
|
3128
2308
|
})
|
|
3129
2309
|
|
|
3130
2310
|
try {
|
|
3131
|
-
matches = await
|
|
2311
|
+
matches = await loadMatches({
|
|
2312
|
+
router: this,
|
|
3132
2313
|
matches,
|
|
3133
2314
|
location: next,
|
|
3134
2315
|
preload: true,
|
|
@@ -3193,7 +2374,6 @@ export class RouterCore<
|
|
|
3193
2374
|
: this.state.resolvedLocation || this.state.location
|
|
3194
2375
|
|
|
3195
2376
|
const match = matchPathname(
|
|
3196
|
-
this.basepath,
|
|
3197
2377
|
baseLocation.pathname,
|
|
3198
2378
|
{
|
|
3199
2379
|
...opts,
|
|
@@ -3226,58 +2406,6 @@ export class RouterCore<
|
|
|
3226
2406
|
|
|
3227
2407
|
serverSsr?: ServerSsr
|
|
3228
2408
|
|
|
3229
|
-
private _handleNotFound = (
|
|
3230
|
-
innerLoadContext: InnerLoadContext,
|
|
3231
|
-
err: NotFoundError,
|
|
3232
|
-
) => {
|
|
3233
|
-
// Find the route that should handle the not found error
|
|
3234
|
-
// First check if a specific route is requested to show the error
|
|
3235
|
-
const routeCursor = this.routesById[err.routeId ?? ''] ?? this.routeTree
|
|
3236
|
-
const matchesByRouteId: Record<string, AnyRouteMatch> = {}
|
|
3237
|
-
|
|
3238
|
-
// Setup routesByRouteId object for quick access
|
|
3239
|
-
for (const match of innerLoadContext.matches) {
|
|
3240
|
-
matchesByRouteId[match.routeId] = match
|
|
3241
|
-
}
|
|
3242
|
-
|
|
3243
|
-
// Ensure a NotFoundComponent exists on the route
|
|
3244
|
-
if (
|
|
3245
|
-
!routeCursor.options.notFoundComponent &&
|
|
3246
|
-
(this.options as any)?.defaultNotFoundComponent
|
|
3247
|
-
) {
|
|
3248
|
-
routeCursor.options.notFoundComponent = (
|
|
3249
|
-
this.options as any
|
|
3250
|
-
).defaultNotFoundComponent
|
|
3251
|
-
}
|
|
3252
|
-
|
|
3253
|
-
// Ensure we have a notFoundComponent
|
|
3254
|
-
invariant(
|
|
3255
|
-
routeCursor.options.notFoundComponent,
|
|
3256
|
-
'No notFoundComponent found. Please set a notFoundComponent on your route or provide a defaultNotFoundComponent to the router.',
|
|
3257
|
-
)
|
|
3258
|
-
|
|
3259
|
-
// Find the match for this route
|
|
3260
|
-
const matchForRoute = matchesByRouteId[routeCursor.id]
|
|
3261
|
-
|
|
3262
|
-
invariant(
|
|
3263
|
-
matchForRoute,
|
|
3264
|
-
'Could not find match for route: ' + routeCursor.id,
|
|
3265
|
-
)
|
|
3266
|
-
|
|
3267
|
-
// Assign the error to the match - using non-null assertion since we've checked with invariant
|
|
3268
|
-
innerLoadContext.updateMatch(matchForRoute.id, (prev) => ({
|
|
3269
|
-
...prev,
|
|
3270
|
-
status: 'notFound',
|
|
3271
|
-
error: err,
|
|
3272
|
-
isFetching: false,
|
|
3273
|
-
}))
|
|
3274
|
-
|
|
3275
|
-
if ((err as any).routerCode === 'BEFORE_LOAD' && routeCursor.parentRoute) {
|
|
3276
|
-
err.routeId = routeCursor.parentRoute.id
|
|
3277
|
-
this._handleNotFound(innerLoadContext, err)
|
|
3278
|
-
}
|
|
3279
|
-
}
|
|
3280
|
-
|
|
3281
2409
|
hasNotFoundMatch = () => {
|
|
3282
2410
|
return this.__store.state.matches.some(
|
|
3283
2411
|
(d) => d.status === 'notFound' || d.globalNotFound,
|
|
@@ -3289,16 +2417,6 @@ export class SearchParamError extends Error {}
|
|
|
3289
2417
|
|
|
3290
2418
|
export class PathParamError extends Error {}
|
|
3291
2419
|
|
|
3292
|
-
function makeMaybe<TValue, TError>(
|
|
3293
|
-
value: TValue,
|
|
3294
|
-
error: TError,
|
|
3295
|
-
): { status: 'success'; value: TValue } | { status: 'error'; error: TError } {
|
|
3296
|
-
if (error) {
|
|
3297
|
-
return { status: 'error' as const, error }
|
|
3298
|
-
}
|
|
3299
|
-
return { status: 'success' as const, value }
|
|
3300
|
-
}
|
|
3301
|
-
|
|
3302
2420
|
const normalize = (str: string) =>
|
|
3303
2421
|
str.endsWith('/') && str.length > 1 ? str.slice(0, -1) : str
|
|
3304
2422
|
function comparePaths(a: string, b: string) {
|
|
@@ -3365,22 +2483,6 @@ function validateSearch(validateSearch: AnyValidator, input: unknown): unknown {
|
|
|
3365
2483
|
return {}
|
|
3366
2484
|
}
|
|
3367
2485
|
|
|
3368
|
-
export const componentTypes = [
|
|
3369
|
-
'component',
|
|
3370
|
-
'errorComponent',
|
|
3371
|
-
'pendingComponent',
|
|
3372
|
-
'notFoundComponent',
|
|
3373
|
-
] as const
|
|
3374
|
-
|
|
3375
|
-
function routeNeedsPreload(route: AnyRoute) {
|
|
3376
|
-
for (const componentType of componentTypes) {
|
|
3377
|
-
if ((route.options[componentType] as any)?.preload) {
|
|
3378
|
-
return true
|
|
3379
|
-
}
|
|
3380
|
-
}
|
|
3381
|
-
return false
|
|
3382
|
-
}
|
|
3383
|
-
|
|
3384
2486
|
interface RouteLike {
|
|
3385
2487
|
id: string
|
|
3386
2488
|
isRoot?: boolean
|
|
@@ -3607,7 +2709,6 @@ export function processRouteTree<TRouteLike extends RouteLike>({
|
|
|
3607
2709
|
export function getMatchedRoutes<TRouteLike extends RouteLike>({
|
|
3608
2710
|
pathname,
|
|
3609
2711
|
routePathname,
|
|
3610
|
-
basepath,
|
|
3611
2712
|
caseSensitive,
|
|
3612
2713
|
routesByPath,
|
|
3613
2714
|
routesById,
|
|
@@ -3616,7 +2717,6 @@ export function getMatchedRoutes<TRouteLike extends RouteLike>({
|
|
|
3616
2717
|
}: {
|
|
3617
2718
|
pathname: string
|
|
3618
2719
|
routePathname?: string
|
|
3619
|
-
basepath: string
|
|
3620
2720
|
caseSensitive?: boolean
|
|
3621
2721
|
routesByPath: Record<string, TRouteLike>
|
|
3622
2722
|
routesById: Record<string, TRouteLike>
|
|
@@ -3627,7 +2727,6 @@ export function getMatchedRoutes<TRouteLike extends RouteLike>({
|
|
|
3627
2727
|
const trimmedPath = trimPathRight(pathname)
|
|
3628
2728
|
const getMatchedParams = (route: TRouteLike) => {
|
|
3629
2729
|
const result = matchPathname(
|
|
3630
|
-
basepath,
|
|
3631
2730
|
trimmedPath,
|
|
3632
2731
|
{
|
|
3633
2732
|
to: route.fullPath,
|