@tanstack/router-core 1.132.0-alpha.2 → 1.132.0-alpha.20
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 +134 -780
- 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 +136 -782
- 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 +271 -1170
- 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,20 +1274,19 @@ export class RouterCore<
|
|
|
1218
1274
|
|
|
1219
1275
|
const loaderDepsHash = loaderDeps ? JSON.stringify(loaderDeps) : ''
|
|
1220
1276
|
|
|
1221
|
-
const {
|
|
1277
|
+
const { interpolatedPath } = interpolatePath({
|
|
1222
1278
|
path: route.fullPath,
|
|
1223
1279
|
params: routeParams,
|
|
1224
1280
|
decodeCharMap: this.pathParamsDecodeCharMap,
|
|
1225
1281
|
})
|
|
1226
1282
|
|
|
1227
|
-
const
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
}).interpolatedPath + loaderDepsHash
|
|
1283
|
+
const interpolatePathResult = interpolatePath({
|
|
1284
|
+
path: route.id,
|
|
1285
|
+
params: routeParams,
|
|
1286
|
+
leaveWildcards: true,
|
|
1287
|
+
decodeCharMap: this.pathParamsDecodeCharMap,
|
|
1288
|
+
parseCache: this.parsePathnameCache,
|
|
1289
|
+
})
|
|
1235
1290
|
|
|
1236
1291
|
// Waste not, want not. If we already have a match for this route,
|
|
1237
1292
|
// reuse it. This is important for layout routes, which might stick
|
|
@@ -1239,12 +1294,43 @@ export class RouterCore<
|
|
|
1239
1294
|
|
|
1240
1295
|
// Existing matches are matches that are already loaded along with
|
|
1241
1296
|
// pending matches that are still loading
|
|
1297
|
+
const matchId = interpolatePathResult.interpolatedPath + loaderDepsHash
|
|
1298
|
+
|
|
1242
1299
|
const existingMatch = this.getMatch(matchId)
|
|
1243
1300
|
|
|
1244
1301
|
const previousMatch = this.state.matches.find(
|
|
1245
1302
|
(d) => d.routeId === route.id,
|
|
1246
1303
|
)
|
|
1247
1304
|
|
|
1305
|
+
const strictParams =
|
|
1306
|
+
existingMatch?._strictParams ?? interpolatePathResult.usedParams
|
|
1307
|
+
|
|
1308
|
+
let paramsError: PathParamError | undefined = undefined
|
|
1309
|
+
|
|
1310
|
+
if (!existingMatch) {
|
|
1311
|
+
const strictParseParams =
|
|
1312
|
+
route.options.params?.parse ?? route.options.parseParams
|
|
1313
|
+
|
|
1314
|
+
if (strictParseParams) {
|
|
1315
|
+
try {
|
|
1316
|
+
Object.assign(
|
|
1317
|
+
strictParams,
|
|
1318
|
+
strictParseParams(strictParams as Record<string, string>),
|
|
1319
|
+
)
|
|
1320
|
+
} catch (err: any) {
|
|
1321
|
+
paramsError = new PathParamError(err.message, {
|
|
1322
|
+
cause: err,
|
|
1323
|
+
})
|
|
1324
|
+
|
|
1325
|
+
if (opts?.throwOnError) {
|
|
1326
|
+
throw paramsError
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
Object.assign(routeParams, strictParams)
|
|
1333
|
+
|
|
1248
1334
|
const cause = previousMatch ? 'stay' : 'enter'
|
|
1249
1335
|
|
|
1250
1336
|
let match: AnyRouteMatch
|
|
@@ -1256,7 +1342,7 @@ export class RouterCore<
|
|
|
1256
1342
|
params: previousMatch
|
|
1257
1343
|
? replaceEqualDeep(previousMatch.params, routeParams)
|
|
1258
1344
|
: routeParams,
|
|
1259
|
-
_strictParams:
|
|
1345
|
+
_strictParams: strictParams,
|
|
1260
1346
|
search: previousMatch
|
|
1261
1347
|
? replaceEqualDeep(previousMatch.search, preMatchSearch)
|
|
1262
1348
|
: replaceEqualDeep(existingMatch.search, preMatchSearch),
|
|
@@ -1278,8 +1364,8 @@ export class RouterCore<
|
|
|
1278
1364
|
params: previousMatch
|
|
1279
1365
|
? replaceEqualDeep(previousMatch.params, routeParams)
|
|
1280
1366
|
: routeParams,
|
|
1281
|
-
_strictParams:
|
|
1282
|
-
pathname:
|
|
1367
|
+
_strictParams: strictParams,
|
|
1368
|
+
pathname: interpolatedPath,
|
|
1283
1369
|
updatedAt: Date.now(),
|
|
1284
1370
|
search: previousMatch
|
|
1285
1371
|
? replaceEqualDeep(previousMatch.search, preMatchSearch)
|
|
@@ -1289,7 +1375,7 @@ export class RouterCore<
|
|
|
1289
1375
|
status,
|
|
1290
1376
|
isFetching: false,
|
|
1291
1377
|
error: undefined,
|
|
1292
|
-
paramsError
|
|
1378
|
+
paramsError,
|
|
1293
1379
|
__routeContext: undefined,
|
|
1294
1380
|
_nonReactive: {
|
|
1295
1381
|
loadPromise: createControlledPromise(),
|
|
@@ -1384,7 +1470,6 @@ export class RouterCore<
|
|
|
1384
1470
|
return getMatchedRoutes({
|
|
1385
1471
|
pathname,
|
|
1386
1472
|
routePathname,
|
|
1387
|
-
basepath: this.basepath,
|
|
1388
1473
|
caseSensitive: this.options.caseSensitive,
|
|
1389
1474
|
routesByPath: this.routesByPath,
|
|
1390
1475
|
routesById: this.routesById,
|
|
@@ -1422,52 +1507,44 @@ export class RouterCore<
|
|
|
1422
1507
|
_buildLocation: true,
|
|
1423
1508
|
})
|
|
1424
1509
|
|
|
1510
|
+
// Now let's find the starting pathname
|
|
1511
|
+
// This should default to the current location if no from is provided
|
|
1425
1512
|
const lastMatch = last(allCurrentLocationMatches)!
|
|
1426
1513
|
|
|
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
|
|
1514
|
+
// check that from path exists in the current route tree
|
|
1515
|
+
// do this check only on navigations during test or development
|
|
1516
|
+
if (
|
|
1517
|
+
dest.from &&
|
|
1518
|
+
process.env.NODE_ENV !== 'production' &&
|
|
1519
|
+
dest._isNavigate
|
|
1520
|
+
) {
|
|
1521
|
+
const allFromMatches = this.getMatchedRoutes(
|
|
1522
|
+
dest.from,
|
|
1523
|
+
undefined,
|
|
1524
|
+
).matchedRoutes
|
|
1451
1525
|
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
return comparePaths(d.fullPath, fromPath)
|
|
1456
|
-
})
|
|
1526
|
+
const matchedFrom = findLast(allCurrentLocationMatches, (d) => {
|
|
1527
|
+
return comparePaths(d.fullPath, dest.from!)
|
|
1528
|
+
})
|
|
1457
1529
|
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1530
|
+
const matchedCurrent = findLast(allFromMatches, (d) => {
|
|
1531
|
+
return comparePaths(d.fullPath, lastMatch.fullPath)
|
|
1532
|
+
})
|
|
1461
1533
|
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
}
|
|
1534
|
+
// for from to be invalid it shouldn't just be unmatched to currentLocation
|
|
1535
|
+
// but the currentLocation should also be unmatched to from
|
|
1536
|
+
if (!matchedFrom && !matchedCurrent) {
|
|
1537
|
+
console.warn(`Could not find match for from: ${dest.from}`)
|
|
1467
1538
|
}
|
|
1468
1539
|
}
|
|
1469
1540
|
|
|
1470
|
-
|
|
1541
|
+
const defaultedFromPath =
|
|
1542
|
+
dest.unsafeRelative === 'path'
|
|
1543
|
+
? currentLocation.pathname
|
|
1544
|
+
: (dest.from ?? lastMatch.fullPath)
|
|
1545
|
+
|
|
1546
|
+
// ensure this includes the basePath if set
|
|
1547
|
+
const fromPath = this.resolvePathWithBase(defaultedFromPath, '.')
|
|
1471
1548
|
|
|
1472
1549
|
// From search should always use the current location
|
|
1473
1550
|
const fromSearch = lastMatch.search
|
|
@@ -1475,25 +1552,26 @@ export class RouterCore<
|
|
|
1475
1552
|
const fromParams = { ...lastMatch.params }
|
|
1476
1553
|
|
|
1477
1554
|
// Resolve the next to
|
|
1555
|
+
// ensure this includes the basePath if set
|
|
1478
1556
|
const nextTo = dest.to
|
|
1479
1557
|
? this.resolvePathWithBase(fromPath, `${dest.to}`)
|
|
1480
1558
|
: this.resolvePathWithBase(fromPath, '.')
|
|
1481
1559
|
|
|
1482
1560
|
// Resolve the next params
|
|
1483
|
-
|
|
1561
|
+
const nextParams =
|
|
1484
1562
|
dest.params === false || dest.params === null
|
|
1485
1563
|
? {}
|
|
1486
1564
|
: (dest.params ?? true) === true
|
|
1487
1565
|
? fromParams
|
|
1488
|
-
:
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1566
|
+
: Object.assign(
|
|
1567
|
+
fromParams,
|
|
1568
|
+
functionalUpdate(dest.params as any, fromParams),
|
|
1569
|
+
)
|
|
1492
1570
|
|
|
1493
1571
|
// Interpolate the path first to get the actual resolved path, then match against that
|
|
1494
1572
|
const interpolatedNextTo = interpolatePath({
|
|
1495
1573
|
path: nextTo,
|
|
1496
|
-
params: nextParams
|
|
1574
|
+
params: nextParams,
|
|
1497
1575
|
parseCache: this.parsePathnameCache,
|
|
1498
1576
|
}).interpolatedPath
|
|
1499
1577
|
|
|
@@ -1503,23 +1581,20 @@ export class RouterCore<
|
|
|
1503
1581
|
|
|
1504
1582
|
// If there are any params, we need to stringify them
|
|
1505
1583
|
if (Object.keys(nextParams).length > 0) {
|
|
1506
|
-
destRoutes
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
)
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
|
-
.forEach((fn) => {
|
|
1514
|
-
nextParams = { ...nextParams!, ...fn!(nextParams) }
|
|
1515
|
-
})
|
|
1584
|
+
for (const route of destRoutes) {
|
|
1585
|
+
const fn =
|
|
1586
|
+
route.options.params?.stringify ?? route.options.stringifyParams
|
|
1587
|
+
if (fn) {
|
|
1588
|
+
Object.assign(nextParams, fn(nextParams))
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1516
1591
|
}
|
|
1517
1592
|
|
|
1518
1593
|
const nextPathname = interpolatePath({
|
|
1519
1594
|
// Use the original template path for interpolation
|
|
1520
1595
|
// This preserves the original parameter syntax including optional parameters
|
|
1521
1596
|
path: nextTo,
|
|
1522
|
-
params: nextParams
|
|
1597
|
+
params: nextParams,
|
|
1523
1598
|
leaveWildcards: false,
|
|
1524
1599
|
leaveParams: opts.leaveParams,
|
|
1525
1600
|
decodeCharMap: this.pathParamsDecodeCharMap,
|
|
@@ -1529,20 +1604,20 @@ export class RouterCore<
|
|
|
1529
1604
|
// Resolve the next search
|
|
1530
1605
|
let nextSearch = fromSearch
|
|
1531
1606
|
if (opts._includeValidateSearch && this.options.search?.strict) {
|
|
1532
|
-
|
|
1607
|
+
const validatedSearch = {}
|
|
1533
1608
|
destRoutes.forEach((route) => {
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1609
|
+
if (route.options.validateSearch) {
|
|
1610
|
+
try {
|
|
1611
|
+
Object.assign(
|
|
1612
|
+
validatedSearch,
|
|
1613
|
+
validateSearch(route.options.validateSearch, {
|
|
1539
1614
|
...validatedSearch,
|
|
1540
1615
|
...nextSearch,
|
|
1541
|
-
})
|
|
1542
|
-
|
|
1616
|
+
}),
|
|
1617
|
+
)
|
|
1618
|
+
} catch {
|
|
1619
|
+
// ignore errors here because they are already handled in matchRoutes
|
|
1543
1620
|
}
|
|
1544
|
-
} catch {
|
|
1545
|
-
// ignore errors here because they are already handled in matchRoutes
|
|
1546
1621
|
}
|
|
1547
1622
|
})
|
|
1548
1623
|
nextSearch = validatedSearch
|
|
@@ -1583,14 +1658,25 @@ export class RouterCore<
|
|
|
1583
1658
|
// Replace the equal deep
|
|
1584
1659
|
nextState = replaceEqualDeep(currentLocation.state, nextState)
|
|
1585
1660
|
|
|
1586
|
-
//
|
|
1661
|
+
// Create the full path of the location
|
|
1662
|
+
const fullPath = `${nextPathname}${searchStr}${hashStr}`
|
|
1663
|
+
|
|
1664
|
+
// Create the new href with full origin
|
|
1665
|
+
const url = new URL(fullPath, this.origin)
|
|
1666
|
+
|
|
1667
|
+
// If a rewrite function is provided, use it to rewrite the URL
|
|
1668
|
+
const rewrittenUrl = executeRewriteOutput(this.rewrite, url)
|
|
1669
|
+
|
|
1587
1670
|
return {
|
|
1671
|
+
publicHref:
|
|
1672
|
+
rewrittenUrl.pathname + rewrittenUrl.search + rewrittenUrl.hash,
|
|
1673
|
+
href: fullPath,
|
|
1674
|
+
url: rewrittenUrl.href,
|
|
1588
1675
|
pathname: nextPathname,
|
|
1589
1676
|
search: nextSearch,
|
|
1590
1677
|
searchStr,
|
|
1591
1678
|
state: nextState as any,
|
|
1592
1679
|
hash: hash ?? '',
|
|
1593
|
-
href: `${nextPathname}${searchStr}${hashStr}`,
|
|
1594
1680
|
unmaskOnReload: dest.unmaskOnReload,
|
|
1595
1681
|
}
|
|
1596
1682
|
}
|
|
@@ -1608,7 +1694,6 @@ export class RouterCore<
|
|
|
1608
1694
|
|
|
1609
1695
|
const foundMask = this.options.routeMasks?.find((d) => {
|
|
1610
1696
|
const match = matchPathname(
|
|
1611
|
-
this.basepath,
|
|
1612
1697
|
next.pathname,
|
|
1613
1698
|
{
|
|
1614
1699
|
to: d.from,
|
|
@@ -1629,7 +1714,7 @@ export class RouterCore<
|
|
|
1629
1714
|
if (foundMask) {
|
|
1630
1715
|
const { from: _from, ...maskProps } = foundMask
|
|
1631
1716
|
maskedDest = {
|
|
1632
|
-
|
|
1717
|
+
from: opts.from,
|
|
1633
1718
|
...maskProps,
|
|
1634
1719
|
params,
|
|
1635
1720
|
}
|
|
@@ -1638,8 +1723,7 @@ export class RouterCore<
|
|
|
1638
1723
|
}
|
|
1639
1724
|
|
|
1640
1725
|
if (maskedNext) {
|
|
1641
|
-
|
|
1642
|
-
next.maskedLocation = maskedFinal
|
|
1726
|
+
next.maskedLocation = maskedNext
|
|
1643
1727
|
}
|
|
1644
1728
|
|
|
1645
1729
|
return next
|
|
@@ -1647,7 +1731,7 @@ export class RouterCore<
|
|
|
1647
1731
|
|
|
1648
1732
|
if (opts.mask) {
|
|
1649
1733
|
return buildWithMatches(opts, {
|
|
1650
|
-
|
|
1734
|
+
from: opts.from,
|
|
1651
1735
|
...opts.mask,
|
|
1652
1736
|
})
|
|
1653
1737
|
}
|
|
@@ -1682,7 +1766,8 @@ export class RouterCore<
|
|
|
1682
1766
|
return isEqual
|
|
1683
1767
|
}
|
|
1684
1768
|
|
|
1685
|
-
const isSameUrl =
|
|
1769
|
+
const isSameUrl =
|
|
1770
|
+
trimPathRight(this.latestLocation.href) === trimPathRight(next.href)
|
|
1686
1771
|
|
|
1687
1772
|
const previousCommitPromise = this.commitLocationPromise
|
|
1688
1773
|
this.commitLocationPromise = createControlledPromise<void>(() => {
|
|
@@ -1731,7 +1816,7 @@ export class RouterCore<
|
|
|
1731
1816
|
this.shouldViewTransition = viewTransition
|
|
1732
1817
|
|
|
1733
1818
|
this.history[next.replace ? 'replace' : 'push'](
|
|
1734
|
-
nextHistory.
|
|
1819
|
+
nextHistory.publicHref,
|
|
1735
1820
|
nextHistory.state,
|
|
1736
1821
|
{ ignoreBlocker },
|
|
1737
1822
|
)
|
|
@@ -1793,7 +1878,7 @@ export class RouterCore<
|
|
|
1793
1878
|
if (reloadDocument) {
|
|
1794
1879
|
if (!href) {
|
|
1795
1880
|
const location = this.buildLocation({ to, ...rest } as any)
|
|
1796
|
-
href =
|
|
1881
|
+
href = location.href
|
|
1797
1882
|
}
|
|
1798
1883
|
if (rest.replace) {
|
|
1799
1884
|
window.location.replace(href)
|
|
@@ -1816,7 +1901,7 @@ export class RouterCore<
|
|
|
1816
1901
|
beforeLoad = () => {
|
|
1817
1902
|
// Cancel any pending matches
|
|
1818
1903
|
this.cancelMatches()
|
|
1819
|
-
this.
|
|
1904
|
+
this.updateLatestLocation()
|
|
1820
1905
|
|
|
1821
1906
|
if (this.isServer) {
|
|
1822
1907
|
// for SPAs on the initial load, this is handled by the Transitioner
|
|
@@ -1846,6 +1931,7 @@ export class RouterCore<
|
|
|
1846
1931
|
throw redirect({ href: nextLocation.href })
|
|
1847
1932
|
}
|
|
1848
1933
|
}
|
|
1934
|
+
|
|
1849
1935
|
// Match the routes
|
|
1850
1936
|
const pendingMatches = this.matchRoutes(this.latestLocation)
|
|
1851
1937
|
|
|
@@ -1895,10 +1981,12 @@ export class RouterCore<
|
|
|
1895
1981
|
}),
|
|
1896
1982
|
})
|
|
1897
1983
|
|
|
1898
|
-
await
|
|
1984
|
+
await loadMatches({
|
|
1985
|
+
router: this,
|
|
1899
1986
|
sync: opts?.sync,
|
|
1900
1987
|
matches: this.state.pendingMatches as Array<AnyRouteMatch>,
|
|
1901
1988
|
location: next,
|
|
1989
|
+
updateMatch: this.updateMatch,
|
|
1902
1990
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
1903
1991
|
onReady: async () => {
|
|
1904
1992
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
@@ -1989,6 +2077,7 @@ export class RouterCore<
|
|
|
1989
2077
|
this.latestLoadPromise = undefined
|
|
1990
2078
|
this.commitLocationPromise = undefined
|
|
1991
2079
|
}
|
|
2080
|
+
|
|
1992
2081
|
resolve()
|
|
1993
2082
|
})
|
|
1994
2083
|
})
|
|
@@ -2088,873 +2177,6 @@ export class RouterCore<
|
|
|
2088
2177
|
)
|
|
2089
2178
|
}
|
|
2090
2179
|
|
|
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
2180
|
invalidate: InvalidateFn<
|
|
2959
2181
|
RouterCore<
|
|
2960
2182
|
TRouteTree,
|
|
@@ -3048,47 +2270,7 @@ export class RouterCore<
|
|
|
3048
2270
|
this.clearCache({ filter })
|
|
3049
2271
|
}
|
|
3050
2272
|
|
|
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
|
-
}
|
|
2273
|
+
loadRouteChunk = loadRouteChunk
|
|
3092
2274
|
|
|
3093
2275
|
preloadRoute: PreloadRouteFn<
|
|
3094
2276
|
TRouteTree,
|
|
@@ -3128,7 +2310,8 @@ export class RouterCore<
|
|
|
3128
2310
|
})
|
|
3129
2311
|
|
|
3130
2312
|
try {
|
|
3131
|
-
matches = await
|
|
2313
|
+
matches = await loadMatches({
|
|
2314
|
+
router: this,
|
|
3132
2315
|
matches,
|
|
3133
2316
|
location: next,
|
|
3134
2317
|
preload: true,
|
|
@@ -3193,7 +2376,6 @@ export class RouterCore<
|
|
|
3193
2376
|
: this.state.resolvedLocation || this.state.location
|
|
3194
2377
|
|
|
3195
2378
|
const match = matchPathname(
|
|
3196
|
-
this.basepath,
|
|
3197
2379
|
baseLocation.pathname,
|
|
3198
2380
|
{
|
|
3199
2381
|
...opts,
|
|
@@ -3226,58 +2408,6 @@ export class RouterCore<
|
|
|
3226
2408
|
|
|
3227
2409
|
serverSsr?: ServerSsr
|
|
3228
2410
|
|
|
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
2411
|
hasNotFoundMatch = () => {
|
|
3282
2412
|
return this.__store.state.matches.some(
|
|
3283
2413
|
(d) => d.status === 'notFound' || d.globalNotFound,
|
|
@@ -3289,16 +2419,6 @@ export class SearchParamError extends Error {}
|
|
|
3289
2419
|
|
|
3290
2420
|
export class PathParamError extends Error {}
|
|
3291
2421
|
|
|
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
2422
|
const normalize = (str: string) =>
|
|
3303
2423
|
str.endsWith('/') && str.length > 1 ? str.slice(0, -1) : str
|
|
3304
2424
|
function comparePaths(a: string, b: string) {
|
|
@@ -3365,22 +2485,6 @@ function validateSearch(validateSearch: AnyValidator, input: unknown): unknown {
|
|
|
3365
2485
|
return {}
|
|
3366
2486
|
}
|
|
3367
2487
|
|
|
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
2488
|
interface RouteLike {
|
|
3385
2489
|
id: string
|
|
3386
2490
|
isRoot?: boolean
|
|
@@ -3607,7 +2711,6 @@ export function processRouteTree<TRouteLike extends RouteLike>({
|
|
|
3607
2711
|
export function getMatchedRoutes<TRouteLike extends RouteLike>({
|
|
3608
2712
|
pathname,
|
|
3609
2713
|
routePathname,
|
|
3610
|
-
basepath,
|
|
3611
2714
|
caseSensitive,
|
|
3612
2715
|
routesByPath,
|
|
3613
2716
|
routesById,
|
|
@@ -3616,7 +2719,6 @@ export function getMatchedRoutes<TRouteLike extends RouteLike>({
|
|
|
3616
2719
|
}: {
|
|
3617
2720
|
pathname: string
|
|
3618
2721
|
routePathname?: string
|
|
3619
|
-
basepath: string
|
|
3620
2722
|
caseSensitive?: boolean
|
|
3621
2723
|
routesByPath: Record<string, TRouteLike>
|
|
3622
2724
|
routesById: Record<string, TRouteLike>
|
|
@@ -3627,7 +2729,6 @@ export function getMatchedRoutes<TRouteLike extends RouteLike>({
|
|
|
3627
2729
|
const trimmedPath = trimPathRight(pathname)
|
|
3628
2730
|
const getMatchedParams = (route: TRouteLike) => {
|
|
3629
2731
|
const result = matchPathname(
|
|
3630
|
-
basepath,
|
|
3631
2732
|
trimmedPath,
|
|
3632
2733
|
{
|
|
3633
2734
|
to: route.fullPath,
|