@tanstack/router-core 1.171.15 → 1.171.16-pre.0
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 +3 -12
- package/dist/cjs/await-signal.cjs +21 -0
- package/dist/cjs/await-signal.cjs.map +1 -0
- package/dist/cjs/await-signal.d.cts +1 -0
- package/dist/cjs/index.cjs +3 -1
- package/dist/cjs/index.d.cts +3 -2
- package/dist/cjs/isServer/client.cjs +2 -0
- package/dist/cjs/isServer/client.cjs.map +1 -1
- package/dist/cjs/isServer/client.d.cts +1 -0
- package/dist/cjs/isServer/development.cjs +2 -0
- package/dist/cjs/isServer/development.cjs.map +1 -1
- package/dist/cjs/isServer/development.d.cts +1 -0
- package/dist/cjs/isServer/server.cjs +2 -0
- package/dist/cjs/isServer/server.cjs.map +1 -1
- package/dist/cjs/isServer/server.d.cts +1 -0
- package/dist/cjs/load-client.cjs +1470 -0
- package/dist/cjs/load-client.cjs.map +1 -0
- package/dist/cjs/load-client.d.cts +147 -0
- package/dist/cjs/load-server.cjs +565 -0
- package/dist/cjs/load-server.cjs.map +1 -0
- package/dist/cjs/load-server.d.cts +16 -0
- package/dist/cjs/redirect.cjs.map +1 -1
- package/dist/cjs/redirect.d.cts +0 -1
- package/dist/cjs/route.cjs.map +1 -1
- package/dist/cjs/router.cjs +187 -442
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +61 -51
- package/dist/cjs/ssr/client.cjs +2 -2
- package/dist/cjs/ssr/createRequestHandler.cjs +60 -9
- package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -1
- package/dist/cjs/ssr/createRequestHandler.d.cts +1 -0
- package/dist/cjs/ssr/handlerCallback.cjs +49 -4
- package/dist/cjs/ssr/handlerCallback.cjs.map +1 -1
- package/dist/cjs/ssr/handlerCallback.d.cts +3 -0
- package/dist/cjs/ssr/server.cjs +4 -0
- package/dist/cjs/ssr/server.d.cts +2 -2
- package/dist/cjs/ssr/ssr-client.d.cts +1 -10
- package/dist/cjs/ssr/ssr-match-id.cjs +2 -2
- package/dist/cjs/ssr/ssr-match-id.cjs.map +1 -1
- package/dist/cjs/ssr/ssr-server.cjs +6 -6
- package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
- package/dist/cjs/ssr/transformStreamWithRouter.cjs +27 -1
- package/dist/cjs/ssr/transformStreamWithRouter.cjs.map +1 -1
- package/dist/cjs/ssr/transformStreamWithRouter.d.cts +2 -0
- package/dist/cjs/ssr/types.d.cts +0 -1
- package/dist/cjs/stores.cjs +30 -104
- package/dist/cjs/stores.cjs.map +1 -1
- package/dist/cjs/stores.d.cts +7 -32
- package/dist/cjs/utils.cjs +0 -4
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/esm/Matches.d.ts +3 -12
- package/dist/esm/Matches.js.map +1 -1
- package/dist/esm/await-signal.d.ts +1 -0
- package/dist/esm/await-signal.js +21 -0
- package/dist/esm/await-signal.js.map +1 -0
- package/dist/esm/index.d.ts +3 -2
- package/dist/esm/index.js +3 -2
- package/dist/esm/isServer/client.d.ts +1 -0
- package/dist/esm/isServer/client.js +2 -1
- package/dist/esm/isServer/client.js.map +1 -1
- package/dist/esm/isServer/development.d.ts +1 -0
- package/dist/esm/isServer/development.js +2 -1
- package/dist/esm/isServer/development.js.map +1 -1
- package/dist/esm/isServer/server.d.ts +1 -0
- package/dist/esm/isServer/server.js +2 -1
- package/dist/esm/isServer/server.js.map +1 -1
- package/dist/esm/load-client.d.ts +147 -0
- package/dist/esm/load-client.js +1462 -0
- package/dist/esm/load-client.js.map +1 -0
- package/dist/esm/load-server.d.ts +16 -0
- package/dist/esm/load-server.js +565 -0
- package/dist/esm/load-server.js.map +1 -0
- package/dist/esm/redirect.d.ts +0 -1
- package/dist/esm/redirect.js.map +1 -1
- package/dist/esm/route.js.map +1 -1
- package/dist/esm/router.d.ts +61 -51
- package/dist/esm/router.js +188 -444
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/ssr/client.js +1 -1
- package/dist/esm/ssr/createRequestHandler.d.ts +1 -0
- package/dist/esm/ssr/createRequestHandler.js +61 -11
- package/dist/esm/ssr/createRequestHandler.js.map +1 -1
- package/dist/esm/ssr/handlerCallback.d.ts +3 -0
- package/dist/esm/ssr/handlerCallback.js +47 -5
- package/dist/esm/ssr/handlerCallback.js.map +1 -1
- package/dist/esm/ssr/server.d.ts +2 -2
- package/dist/esm/ssr/server.js +3 -3
- package/dist/esm/ssr/ssr-client.d.ts +1 -10
- package/dist/esm/ssr/ssr-match-id.js +2 -2
- package/dist/esm/ssr/ssr-match-id.js.map +1 -1
- package/dist/esm/ssr/ssr-server.js +6 -6
- package/dist/esm/ssr/ssr-server.js.map +1 -1
- package/dist/esm/ssr/transformStreamWithRouter.d.ts +2 -0
- package/dist/esm/ssr/transformStreamWithRouter.js +27 -1
- package/dist/esm/ssr/transformStreamWithRouter.js.map +1 -1
- package/dist/esm/ssr/types.d.ts +0 -1
- package/dist/esm/stores.d.ts +7 -32
- package/dist/esm/stores.js +30 -104
- package/dist/esm/stores.js.map +1 -1
- package/dist/esm/utils.js +1 -4
- package/dist/esm/utils.js.map +1 -1
- package/package.json +3 -3
- package/src/Matches.ts +6 -21
- package/src/await-signal.ts +27 -0
- package/src/index.ts +1 -3
- package/src/isServer/client.ts +1 -0
- package/src/isServer/development.ts +1 -0
- package/src/isServer/server.ts +1 -0
- package/src/load-client.ts +2701 -0
- package/src/load-server.ts +849 -0
- package/src/redirect.ts +0 -1
- package/src/route.ts +2 -11
- package/src/router.ts +379 -769
- package/src/ssr/createRequestHandler.ts +109 -16
- package/src/ssr/handlerCallback.ts +76 -10
- package/src/ssr/server.ts +4 -1
- package/src/ssr/ssr-client.ts +1 -310
- package/src/ssr/ssr-match-id.ts +11 -2
- package/src/ssr/ssr-server.ts +9 -8
- package/src/ssr/transformStreamWithRouter.ts +37 -1
- package/src/ssr/types.ts +0 -1
- package/src/stores.ts +46 -208
- package/dist/cjs/load-matches.cjs +0 -658
- package/dist/cjs/load-matches.cjs.map +0 -1
- package/dist/cjs/load-matches.d.cts +0 -18
- package/dist/cjs/ssr/ssr-client.cjs +0 -183
- package/dist/cjs/ssr/ssr-client.cjs.map +0 -1
- package/dist/esm/load-matches.d.ts +0 -18
- package/dist/esm/load-matches.js +0 -656
- package/dist/esm/load-matches.js.map +0 -1
- package/dist/esm/ssr/ssr-client.js +0 -183
- package/dist/esm/ssr/ssr-client.js.map +0 -1
- package/src/load-matches.ts +0 -1278
package/src/router.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { createBrowserHistory, parseHref } from '@tanstack/history'
|
|
2
|
-
import { isServer } from '@tanstack/router-core/isServer'
|
|
2
|
+
import { isServer, loadServerRoute } from '@tanstack/router-core/isServer'
|
|
3
3
|
import {
|
|
4
4
|
DEFAULT_PROTOCOL_ALLOWLIST,
|
|
5
|
-
createControlledPromise,
|
|
6
5
|
decodePath,
|
|
7
6
|
deepEqual,
|
|
8
7
|
encodePathLikeUrl,
|
|
@@ -35,8 +34,15 @@ import { isNotFound } from './not-found'
|
|
|
35
34
|
import { setupScrollRestoration } from './scroll-restoration'
|
|
36
35
|
import { defaultParseSearch, defaultStringifySearch } from './searchParams'
|
|
37
36
|
import { rootRouteId } from './root'
|
|
38
|
-
import { isRedirect
|
|
39
|
-
import {
|
|
37
|
+
import { isRedirect } from './redirect'
|
|
38
|
+
import {
|
|
39
|
+
loadClientRoute,
|
|
40
|
+
loadRouteChunk,
|
|
41
|
+
preloadClientRoute,
|
|
42
|
+
refreshClientRoute,
|
|
43
|
+
replaceRouteChunk,
|
|
44
|
+
transferMatchResources,
|
|
45
|
+
} from './load-client'
|
|
40
46
|
import {
|
|
41
47
|
composeRewrites,
|
|
42
48
|
executeRewriteInput,
|
|
@@ -51,6 +57,13 @@ import type {
|
|
|
51
57
|
} from './new-process-route-tree'
|
|
52
58
|
import type { SearchParser, SearchSerializer } from './searchParams'
|
|
53
59
|
import type { AnyRedirect, ResolvedRedirect } from './redirect'
|
|
60
|
+
import type {
|
|
61
|
+
ActivePreload,
|
|
62
|
+
LoadTransaction,
|
|
63
|
+
LoaderFlight,
|
|
64
|
+
PendingSession,
|
|
65
|
+
} from './load-client'
|
|
66
|
+
import type { ServerLoadResult } from './load-server'
|
|
54
67
|
import type {
|
|
55
68
|
HistoryAction,
|
|
56
69
|
HistoryLocation,
|
|
@@ -62,7 +75,6 @@ import type {
|
|
|
62
75
|
import type {
|
|
63
76
|
Awaitable,
|
|
64
77
|
Constrain,
|
|
65
|
-
ControlledPromise,
|
|
66
78
|
NoInfer,
|
|
67
79
|
NonNullableUpdater,
|
|
68
80
|
PickAsRequired,
|
|
@@ -75,8 +87,6 @@ import type {
|
|
|
75
87
|
AnyRouteWithContext,
|
|
76
88
|
LoaderStaleReloadMode,
|
|
77
89
|
MakeRemountDepsOptionsUnion,
|
|
78
|
-
RouteContextOptions,
|
|
79
|
-
RouteLike,
|
|
80
90
|
RouteMask,
|
|
81
91
|
SearchMiddleware,
|
|
82
92
|
SearchMiddlewareMeta,
|
|
@@ -106,7 +116,6 @@ import type {
|
|
|
106
116
|
} from './manifest'
|
|
107
117
|
import type { AnySchema, AnyValidator } from './validators'
|
|
108
118
|
import type { NavigateOptions, ResolveRelativePath, ToOptions } from './link'
|
|
109
|
-
import type { NotFoundError } from './not-found'
|
|
110
119
|
import type {
|
|
111
120
|
AnySerializationAdapter,
|
|
112
121
|
ValidateSerializableInput,
|
|
@@ -263,9 +272,9 @@ export interface RouterOptions<
|
|
|
263
272
|
*/
|
|
264
273
|
defaultPreloadStaleTime?: number
|
|
265
274
|
/**
|
|
266
|
-
* The default `
|
|
275
|
+
* The default `preloadGcTime` a route should use if none is provided.
|
|
267
276
|
*
|
|
268
|
-
* @default
|
|
277
|
+
* @default 300_000 `(5 minutes)`
|
|
269
278
|
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultpreloadgctime-property)
|
|
270
279
|
* @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/preloading)
|
|
271
280
|
*/
|
|
@@ -297,7 +306,7 @@ export interface RouterOptions<
|
|
|
297
306
|
/**
|
|
298
307
|
* The default `gcTime` a route should use if no gcTime is provided.
|
|
299
308
|
*
|
|
300
|
-
* @default
|
|
309
|
+
* @default 300_000 `(5 minutes)`
|
|
301
310
|
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultgctime-property)
|
|
302
311
|
* @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#key-options)
|
|
303
312
|
*/
|
|
@@ -544,14 +553,10 @@ export interface RouterState<
|
|
|
544
553
|
in out TRouteMatch = MakeRouteMatchUnion,
|
|
545
554
|
> {
|
|
546
555
|
status: 'pending' | 'idle'
|
|
547
|
-
loadedAt: number
|
|
548
556
|
isLoading: boolean
|
|
549
|
-
isTransitioning: boolean
|
|
550
557
|
matches: Array<TRouteMatch>
|
|
551
558
|
location: ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
552
559
|
resolvedLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
553
|
-
statusCode: number
|
|
554
|
-
redirect?: AnyRedirect
|
|
555
560
|
}
|
|
556
561
|
|
|
557
562
|
export interface BuildNextOptions {
|
|
@@ -619,9 +624,21 @@ export type SubscribeFn = <TType extends keyof RouterEvents>(
|
|
|
619
624
|
) => () => void
|
|
620
625
|
|
|
621
626
|
export interface MatchRoutesOpts {
|
|
622
|
-
preload?: boolean
|
|
623
627
|
throwOnError?: boolean
|
|
624
|
-
|
|
628
|
+
/** @internal */
|
|
629
|
+
_controller?: AbortController
|
|
630
|
+
/** @internal */
|
|
631
|
+
_rematerialize?: boolean
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function routeNeedsLoad(route: AnyRoute): unknown {
|
|
635
|
+
return (
|
|
636
|
+
route.options.loader ||
|
|
637
|
+
route.options.beforeLoad ||
|
|
638
|
+
route.lazyFn ||
|
|
639
|
+
(route.options.component as any)?.preload ||
|
|
640
|
+
(route.options.pendingComponent as any)?.preload
|
|
641
|
+
)
|
|
625
642
|
}
|
|
626
643
|
|
|
627
644
|
export type InferRouterContext<TRouteTree extends AnyRoute> =
|
|
@@ -748,6 +765,8 @@ export type EmitFn = (routerEvent: RouterEvent) => void
|
|
|
748
765
|
export type LoadFn = (opts?: {
|
|
749
766
|
sync?: boolean
|
|
750
767
|
action?: { type: HistoryAction }
|
|
768
|
+
_signal?: AbortSignal
|
|
769
|
+
_dedupe?: boolean
|
|
751
770
|
}) => Promise<void>
|
|
752
771
|
|
|
753
772
|
export type CommitLocationFn = ({
|
|
@@ -756,7 +775,11 @@ export type CommitLocationFn = ({
|
|
|
756
775
|
...next
|
|
757
776
|
}: ParsedLocation & CommitLocationOptions) => Promise<void>
|
|
758
777
|
|
|
759
|
-
export type StartTransitionFn = (
|
|
778
|
+
export type StartTransitionFn = (
|
|
779
|
+
fn: () => void,
|
|
780
|
+
expected: Array<AnyRouteMatch>,
|
|
781
|
+
urgent?: boolean,
|
|
782
|
+
) => Promise<boolean>
|
|
760
783
|
|
|
761
784
|
export interface MatchRoutesFn {
|
|
762
785
|
(
|
|
@@ -775,13 +798,6 @@ export interface MatchRoutesFn {
|
|
|
775
798
|
): Array<AnyRouteMatch>
|
|
776
799
|
}
|
|
777
800
|
|
|
778
|
-
export type GetMatchFn = (matchId: string) => AnyRouteMatch | undefined
|
|
779
|
-
|
|
780
|
-
export type UpdateMatchFn = (
|
|
781
|
-
id: string,
|
|
782
|
-
updater: (match: AnyRouteMatch) => AnyRouteMatch,
|
|
783
|
-
) => void
|
|
784
|
-
|
|
785
801
|
export type LoadRouteChunkFn = (route: AnyRoute) => Promise<Array<void>>
|
|
786
802
|
|
|
787
803
|
export type ResolveRedirect = (err: AnyRedirect) => ResolvedRedirect
|
|
@@ -896,12 +912,60 @@ export function getLocationChangeInfo(
|
|
|
896
912
|
location: ParsedLocation,
|
|
897
913
|
resolvedLocation?: ParsedLocation,
|
|
898
914
|
) {
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
915
|
+
return {
|
|
916
|
+
fromLocation: resolvedLocation,
|
|
917
|
+
toLocation: location,
|
|
918
|
+
pathChanged: resolvedLocation?.pathname !== location.pathname,
|
|
919
|
+
hrefChanged: resolvedLocation?.href !== location.href,
|
|
920
|
+
hashChanged: resolvedLocation?.hash !== location.hash,
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* Return only state owned by the application, excluding volatile history
|
|
926
|
+
* bookkeeping. Mask payloads (`__tempLocation`/`__tempKey`) are kept: they
|
|
927
|
+
* distinguish otherwise-identical locations.
|
|
928
|
+
*/
|
|
929
|
+
export function _getUserHistoryState({
|
|
930
|
+
key: _key,
|
|
931
|
+
__TSR_key: _tsrKey,
|
|
932
|
+
__TSR_index: _tsrIndex,
|
|
933
|
+
__hashScrollIntoViewOptions: _hashScroll,
|
|
934
|
+
...state
|
|
935
|
+
}: ParsedHistoryState): HistoryState {
|
|
936
|
+
return state
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
/** Run route lifecycle callbacks in leave/enter/stay phases. */
|
|
940
|
+
export function runRouteLifecycle(
|
|
941
|
+
router: AnyRouter,
|
|
942
|
+
previous: Array<AnyRouteMatch>,
|
|
943
|
+
matches: Array<AnyRouteMatch>,
|
|
944
|
+
isCurrent?: () => boolean,
|
|
945
|
+
): void {
|
|
946
|
+
for (const match of previous) {
|
|
947
|
+
if (isCurrent?.() === false) {
|
|
948
|
+
return
|
|
949
|
+
}
|
|
950
|
+
if (!matches.some((candidate) => candidate.routeId === match.routeId)) {
|
|
951
|
+
;(router.routesById as Record<string, AnyRoute>)[
|
|
952
|
+
match.routeId
|
|
953
|
+
]!.options.onLeave?.(match)
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
for (const match of matches) {
|
|
957
|
+
if (isCurrent?.() === false) {
|
|
958
|
+
return
|
|
959
|
+
}
|
|
960
|
+
const route = (router.routesById as Record<string, AnyRoute>)[
|
|
961
|
+
match.routeId
|
|
962
|
+
]!
|
|
963
|
+
route.options[
|
|
964
|
+
previous.some((candidate) => candidate.routeId === match.routeId)
|
|
965
|
+
? 'onStay'
|
|
966
|
+
: 'onEnter'
|
|
967
|
+
]?.(match)
|
|
968
|
+
}
|
|
905
969
|
}
|
|
906
970
|
|
|
907
971
|
type LightweightRouteMatchResult = {
|
|
@@ -951,6 +1015,43 @@ declare global {
|
|
|
951
1015
|
| undefined
|
|
952
1016
|
}
|
|
953
1017
|
|
|
1018
|
+
export interface RouterCore<
|
|
1019
|
+
in out TRouteTree extends AnyRoute,
|
|
1020
|
+
in out TTrailingSlashOption extends TrailingSlashOption,
|
|
1021
|
+
in out TDefaultStructuralSharingOption extends boolean,
|
|
1022
|
+
in out TRouterHistory extends RouterHistory = RouterHistory,
|
|
1023
|
+
in out TDehydrated extends Record<string, any> = Record<string, any>,
|
|
1024
|
+
> {
|
|
1025
|
+
shouldViewTransition?: boolean | ViewTransitionOptions
|
|
1026
|
+
/** Current client load transaction and owner of navigation writes. */
|
|
1027
|
+
_tx?: LoadTransaction
|
|
1028
|
+
/** Joinable in-flight loader generations keyed by match ID. */
|
|
1029
|
+
_flights?: Map<string, LoaderFlight>
|
|
1030
|
+
/** Whole speculative lanes that an identical navigation may adopt. */
|
|
1031
|
+
_preloads?: Map<string, ActivePreload>
|
|
1032
|
+
/** Owns cancellable work before a client transaction publishes. */
|
|
1033
|
+
_preflight?: AbortController
|
|
1034
|
+
/** Transfers one reconstructed SSR prefix into its initial client load. */
|
|
1035
|
+
_handoff?: HydrationHandoff
|
|
1036
|
+
/** Pending-boundary reveal and minimum-visible timing state. */
|
|
1037
|
+
_pending?: PendingSession
|
|
1038
|
+
/** Result of the latest server load, used to render or redirect. */
|
|
1039
|
+
_serverResult?: ServerLoadResult
|
|
1040
|
+
/** Framework callback that acknowledges an exact matches publication. */
|
|
1041
|
+
_rendered?: (matches: Array<AnyRouteMatch>) => void
|
|
1042
|
+
/** Development-only HMR reload for a route and its descendants. */
|
|
1043
|
+
_refreshRoute: (() => Promise<void>) | undefined
|
|
1044
|
+
/** Development-only replacement for a route's lazy chunk owner. */
|
|
1045
|
+
_replaceRouteChunk:
|
|
1046
|
+
| ((route: AnyRoute, lazyFn: AnyRoute['lazyFn']) => void)
|
|
1047
|
+
| undefined
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
export type HydrationHandoff = [
|
|
1051
|
+
claim: () => AbortController | undefined,
|
|
1052
|
+
finish: (matches?: Array<AnyRouteMatch>) => number | undefined,
|
|
1053
|
+
]
|
|
1054
|
+
|
|
954
1055
|
/**
|
|
955
1056
|
* Core, framework-agnostic router engine that powers TanStack Router.
|
|
956
1057
|
*
|
|
@@ -979,10 +1080,11 @@ export class RouterCore<
|
|
|
979
1080
|
restoration?: boolean
|
|
980
1081
|
reset?: boolean
|
|
981
1082
|
} = { next: true }
|
|
982
|
-
shouldViewTransition?: boolean | ViewTransitionOptions = undefined
|
|
983
|
-
isViewTransitionTypesSupported?: boolean = undefined
|
|
984
1083
|
subscribers = new Set<RouterListener<RouterEvent>>()
|
|
985
|
-
|
|
1084
|
+
/** Accepted off-screen loader generations keyed by match ID. */
|
|
1085
|
+
_cache = new Map<string, AnyRouteMatch>()
|
|
1086
|
+
/** Accepted semantic lane, excluding temporary pending presentation. */
|
|
1087
|
+
_committed: Array<AnyRouteMatch> = []
|
|
986
1088
|
|
|
987
1089
|
// Must build in constructor
|
|
988
1090
|
stores!: RouterStores<TRouteTree>
|
|
@@ -1003,7 +1105,7 @@ export class RouterCore<
|
|
|
1003
1105
|
rewrite?: LocationRewrite
|
|
1004
1106
|
origin?: string
|
|
1005
1107
|
latestLocation!: ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
1006
|
-
|
|
1108
|
+
_pendingLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
1007
1109
|
basepath!: string
|
|
1008
1110
|
routeTree!: TRouteTree
|
|
1009
1111
|
routesById!: RoutesById<TRouteTree>
|
|
@@ -1048,24 +1150,19 @@ export class RouterCore<
|
|
|
1048
1150
|
options.protocolAllowlist ?? DEFAULT_PROTOCOL_ALLOWLIST,
|
|
1049
1151
|
})
|
|
1050
1152
|
|
|
1051
|
-
if (typeof document
|
|
1153
|
+
if (!(isServer ?? typeof document === 'undefined')) {
|
|
1052
1154
|
self.__TSR_ROUTER__ = this
|
|
1053
1155
|
}
|
|
1054
1156
|
}
|
|
1055
1157
|
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1158
|
+
startTransition: StartTransitionFn = async (fn) => {
|
|
1159
|
+
fn()
|
|
1160
|
+
return false
|
|
1161
|
+
}
|
|
1061
1162
|
isShell() {
|
|
1062
1163
|
return !!this.options.isShell
|
|
1063
1164
|
}
|
|
1064
1165
|
|
|
1065
|
-
isPrerendering() {
|
|
1066
|
-
return !!this.options.isPrerendering
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
1166
|
update: UpdateFn<
|
|
1070
1167
|
TRouteTree,
|
|
1071
1168
|
TTrailingSlashOption,
|
|
@@ -1091,7 +1188,8 @@ export class RouterCore<
|
|
|
1091
1188
|
...newOptions,
|
|
1092
1189
|
}
|
|
1093
1190
|
|
|
1094
|
-
this.isServer =
|
|
1191
|
+
this.isServer =
|
|
1192
|
+
this.options.isServer ?? isServer ?? typeof document === 'undefined'
|
|
1095
1193
|
|
|
1096
1194
|
this.protocolAllowlist = new Set(this.options.protocolAllowlist)
|
|
1097
1195
|
|
|
@@ -1165,17 +1263,13 @@ export class RouterCore<
|
|
|
1165
1263
|
if (!this.stores && this.latestLocation) {
|
|
1166
1264
|
const config = this.getStoreConfig(this)
|
|
1167
1265
|
this.batch = config.batch
|
|
1168
|
-
this.stores = createRouterStores(
|
|
1169
|
-
getInitialRouterState(this.latestLocation),
|
|
1170
|
-
config,
|
|
1171
|
-
)
|
|
1266
|
+
this.stores = createRouterStores(this.latestLocation, config)
|
|
1172
1267
|
|
|
1173
1268
|
if (!(isServer ?? this.isServer)) {
|
|
1174
1269
|
setupScrollRestoration(this)
|
|
1175
1270
|
}
|
|
1176
1271
|
}
|
|
1177
1272
|
|
|
1178
|
-
let needsLocationUpdate = false
|
|
1179
1273
|
const nextBasepath = this.options.basepath ?? '/'
|
|
1180
1274
|
const nextRewriteOption = this.options.rewrite
|
|
1181
1275
|
const basepathChanged = basepathWasUnset || prevBasepath !== nextBasepath
|
|
@@ -1208,21 +1302,9 @@ export class RouterCore<
|
|
|
1208
1302
|
this.updateLatestLocation()
|
|
1209
1303
|
}
|
|
1210
1304
|
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
if (needsLocationUpdate && this.stores) {
|
|
1215
|
-
this.stores.location.set(this.latestLocation)
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
if (
|
|
1219
|
-
typeof window !== 'undefined' &&
|
|
1220
|
-
'CSS' in window &&
|
|
1221
|
-
typeof window.CSS?.supports === 'function'
|
|
1222
|
-
) {
|
|
1223
|
-
this.isViewTransitionTypesSupported = window.CSS.supports(
|
|
1224
|
-
'selector(:active-view-transition-type(a))',
|
|
1225
|
-
)
|
|
1305
|
+
if (this.stores) {
|
|
1306
|
+
this.stores.location.set(this.latestLocation)
|
|
1307
|
+
}
|
|
1226
1308
|
}
|
|
1227
1309
|
}
|
|
1228
1310
|
|
|
@@ -1293,11 +1375,15 @@ export class RouterCore<
|
|
|
1293
1375
|
}
|
|
1294
1376
|
|
|
1295
1377
|
emit: EmitFn = (routerEvent) => {
|
|
1296
|
-
this.subscribers
|
|
1378
|
+
for (const listener of this.subscribers) {
|
|
1297
1379
|
if (listener.eventType === routerEvent.type) {
|
|
1298
|
-
|
|
1380
|
+
try {
|
|
1381
|
+
listener.fn(routerEvent)
|
|
1382
|
+
} catch (e) {
|
|
1383
|
+
console.error(e)
|
|
1384
|
+
}
|
|
1299
1385
|
}
|
|
1300
|
-
}
|
|
1386
|
+
}
|
|
1301
1387
|
}
|
|
1302
1388
|
|
|
1303
1389
|
/**
|
|
@@ -1405,10 +1491,6 @@ export class RouterCore<
|
|
|
1405
1491
|
return branch
|
|
1406
1492
|
}
|
|
1407
1493
|
|
|
1408
|
-
get looseRoutesById() {
|
|
1409
|
-
return this.routesById as Record<string, AnyRoute>
|
|
1410
|
-
}
|
|
1411
|
-
|
|
1412
1494
|
matchRoutes: MatchRoutesFn = (
|
|
1413
1495
|
pathnameOrNext: string | ParsedLocation,
|
|
1414
1496
|
locationSearchOrOpts?: AnySchema | MatchRoutesOpts,
|
|
@@ -1427,16 +1509,6 @@ export class RouterCore<
|
|
|
1427
1509
|
return this.matchRoutesInternal(pathnameOrNext, locationSearchOrOpts)
|
|
1428
1510
|
}
|
|
1429
1511
|
|
|
1430
|
-
private getParentContext(parentMatch?: AnyRouteMatch) {
|
|
1431
|
-
const parentMatchId = parentMatch?.id
|
|
1432
|
-
|
|
1433
|
-
const parentContext = !parentMatchId
|
|
1434
|
-
? ((this.options.context as any) ?? undefined)
|
|
1435
|
-
: (parentMatch.context ?? this.options.context ?? undefined)
|
|
1436
|
-
|
|
1437
|
-
return parentContext
|
|
1438
|
-
}
|
|
1439
|
-
|
|
1440
1512
|
private matchRoutesInternal(
|
|
1441
1513
|
next: ParsedLocation,
|
|
1442
1514
|
opts?: MatchRoutesOpts,
|
|
@@ -1463,18 +1535,19 @@ export class RouterCore<
|
|
|
1463
1535
|
}
|
|
1464
1536
|
}
|
|
1465
1537
|
|
|
1466
|
-
const
|
|
1538
|
+
const _notFoundRouteId = isGlobalNotFound
|
|
1467
1539
|
? findGlobalNotFoundRouteId(this.options.notFoundMode, matchedRoutes)
|
|
1468
1540
|
: undefined
|
|
1469
1541
|
|
|
1470
1542
|
const matches = new Array<AnyRouteMatch>(matchedRoutes.length)
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1543
|
+
const committed = this._committed
|
|
1544
|
+
const previousAt = (route: AnyRoute, index: number) => {
|
|
1545
|
+
const match = committed[index]
|
|
1546
|
+
return match?.routeId === route.id
|
|
1547
|
+
? match
|
|
1548
|
+
: route === this.options.notFoundRoute
|
|
1549
|
+
? committed.find((candidate) => candidate.routeId === route.id)
|
|
1550
|
+
: undefined
|
|
1478
1551
|
}
|
|
1479
1552
|
|
|
1480
1553
|
for (let index = 0; index < matchedRoutes.length; index++) {
|
|
@@ -1506,7 +1579,6 @@ export class RouterCore<
|
|
|
1506
1579
|
...strictSearch,
|
|
1507
1580
|
}
|
|
1508
1581
|
strictMatchSearch = { ...parentStrictSearch, ...strictSearch }
|
|
1509
|
-
searchError = undefined
|
|
1510
1582
|
} catch (err: any) {
|
|
1511
1583
|
let searchParamError = err
|
|
1512
1584
|
if (!(err instanceof SearchParamError)) {
|
|
@@ -1524,19 +1596,25 @@ export class RouterCore<
|
|
|
1524
1596
|
searchError = searchParamError
|
|
1525
1597
|
}
|
|
1526
1598
|
}
|
|
1527
|
-
|
|
1528
1599
|
// This is where we need to call route.options.loaderDeps() to get any additional
|
|
1529
1600
|
// deps that the route's loader function might need to run. We need to do this
|
|
1530
1601
|
// before we create the match so that we can pass the deps to the route's
|
|
1531
1602
|
// potential key function which is used to uniquely identify the route match in state
|
|
1532
1603
|
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1604
|
+
let loaderDeps: any = ''
|
|
1605
|
+
let loaderDepsHash = ''
|
|
1606
|
+
try {
|
|
1607
|
+
loaderDeps =
|
|
1608
|
+
route.options.loaderDeps?.({
|
|
1609
|
+
search: preMatchSearch,
|
|
1610
|
+
}) ?? ''
|
|
1611
|
+
loaderDepsHash = loaderDeps ? JSON.stringify(loaderDeps) || '' : ''
|
|
1612
|
+
} catch (cause) {
|
|
1613
|
+
if (opts?.throwOnError) {
|
|
1614
|
+
throw cause
|
|
1615
|
+
}
|
|
1616
|
+
searchError ??= cause
|
|
1617
|
+
}
|
|
1540
1618
|
const { interpolatedPath, usedParams } = interpolatePath({
|
|
1541
1619
|
path: route.fullPath,
|
|
1542
1620
|
params: routeParams,
|
|
@@ -1544,12 +1622,9 @@ export class RouterCore<
|
|
|
1544
1622
|
server: this.isServer,
|
|
1545
1623
|
})
|
|
1546
1624
|
|
|
1547
|
-
//
|
|
1548
|
-
//
|
|
1549
|
-
//
|
|
1550
|
-
|
|
1551
|
-
// Existing matches are matches that are already loaded along with
|
|
1552
|
-
// pending matches that are still loading
|
|
1625
|
+
// Seed planning from the accepted same-ID cache generation first, then
|
|
1626
|
+
// from the committed generation for this route. Presentation stores are
|
|
1627
|
+
// deliberately not a semantic reuse authority.
|
|
1553
1628
|
const matchId =
|
|
1554
1629
|
// route.id for disambiguation
|
|
1555
1630
|
route.id +
|
|
@@ -1558,13 +1633,16 @@ export class RouterCore<
|
|
|
1558
1633
|
// explicit deps
|
|
1559
1634
|
loaderDepsHash
|
|
1560
1635
|
|
|
1561
|
-
const
|
|
1562
|
-
|
|
1563
|
-
|
|
1636
|
+
const previousMatch = previousAt(route, index)
|
|
1637
|
+
const existingMatch =
|
|
1638
|
+
process.env.NODE_ENV !== 'production' && opts?._rematerialize
|
|
1639
|
+
? undefined
|
|
1640
|
+
: (this._cache.get(matchId) ??
|
|
1641
|
+
(previousMatch?.id === matchId ? previousMatch : undefined))
|
|
1564
1642
|
|
|
1565
1643
|
const strictParams = existingMatch?._strictParams ?? usedParams
|
|
1566
1644
|
|
|
1567
|
-
let paramsError: unknown
|
|
1645
|
+
let paramsError: unknown
|
|
1568
1646
|
|
|
1569
1647
|
if (!existingMatch) {
|
|
1570
1648
|
try {
|
|
@@ -1600,15 +1678,10 @@ export class RouterCore<
|
|
|
1600
1678
|
? nullReplaceEqualDeep(previousMatch.search, preMatchSearch)
|
|
1601
1679
|
: nullReplaceEqualDeep(existingMatch.search, preMatchSearch),
|
|
1602
1680
|
_strictSearch: strictMatchSearch,
|
|
1681
|
+
searchError,
|
|
1603
1682
|
}
|
|
1604
1683
|
} else {
|
|
1605
|
-
const status =
|
|
1606
|
-
route.options.loader ||
|
|
1607
|
-
route.options.beforeLoad ||
|
|
1608
|
-
route.lazyFn ||
|
|
1609
|
-
routeNeedsPreload(route)
|
|
1610
|
-
? 'pending'
|
|
1611
|
-
: 'success'
|
|
1684
|
+
const status = routeNeedsLoad(route) ? 'pending' : 'success'
|
|
1612
1685
|
|
|
1613
1686
|
match = {
|
|
1614
1687
|
id: matchId,
|
|
@@ -1623,96 +1696,42 @@ export class RouterCore<
|
|
|
1623
1696
|
? nullReplaceEqualDeep(previousMatch.search, preMatchSearch)
|
|
1624
1697
|
: preMatchSearch,
|
|
1625
1698
|
_strictSearch: strictMatchSearch,
|
|
1626
|
-
searchError
|
|
1699
|
+
searchError,
|
|
1627
1700
|
status,
|
|
1628
1701
|
isFetching: false,
|
|
1629
1702
|
error: undefined,
|
|
1630
1703
|
paramsError,
|
|
1631
|
-
__routeContext: undefined,
|
|
1632
|
-
_nonReactive: {
|
|
1633
|
-
loadPromise: createControlledPromise(),
|
|
1634
|
-
},
|
|
1635
|
-
__beforeLoadContext: undefined,
|
|
1636
1704
|
context: {},
|
|
1637
|
-
abortController: new AbortController(),
|
|
1638
|
-
fetchCount: 0,
|
|
1705
|
+
abortController: opts?._controller ?? new AbortController(),
|
|
1639
1706
|
cause,
|
|
1640
1707
|
loaderDeps: previousMatch
|
|
1641
1708
|
? replaceEqualDeep(previousMatch.loaderDeps, loaderDeps)
|
|
1642
1709
|
: loaderDeps,
|
|
1643
1710
|
invalid: false,
|
|
1644
1711
|
preload: false,
|
|
1645
|
-
links: undefined,
|
|
1646
|
-
scripts: undefined,
|
|
1647
|
-
headScripts: undefined,
|
|
1648
|
-
meta: undefined,
|
|
1649
1712
|
staticData: route.options.staticData || {},
|
|
1650
1713
|
fullPath: route.fullPath,
|
|
1651
1714
|
}
|
|
1652
1715
|
}
|
|
1653
1716
|
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
// update the searchError if there is one
|
|
1660
|
-
match.searchError = searchError
|
|
1661
|
-
|
|
1662
|
-
const parentContext = this.getParentContext(parentMatch)
|
|
1663
|
-
|
|
1664
|
-
match.context = {
|
|
1665
|
-
...parentContext,
|
|
1666
|
-
...match.__routeContext,
|
|
1667
|
-
...match.__beforeLoadContext,
|
|
1717
|
+
// If we have a global not found, mark the right match as global not found
|
|
1718
|
+
const _notFound = _notFoundRouteId === route.id
|
|
1719
|
+
if (match._notFound && !_notFound) {
|
|
1720
|
+
match.error = undefined
|
|
1668
1721
|
}
|
|
1722
|
+
match._notFound = _notFound
|
|
1669
1723
|
|
|
1670
1724
|
matches[index] = match
|
|
1671
1725
|
}
|
|
1672
1726
|
|
|
1673
1727
|
for (let index = 0; index < matches.length; index++) {
|
|
1674
1728
|
const match = matches[index]!
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
? nullReplaceEqualDeep(previousMatch.params, routeParams)
|
|
1682
|
-
: routeParams
|
|
1683
|
-
|
|
1684
|
-
if (!existingMatch) {
|
|
1685
|
-
const parentMatch = matches[index - 1]
|
|
1686
|
-
const parentContext = this.getParentContext(parentMatch)
|
|
1687
|
-
|
|
1688
|
-
// Update the match's context
|
|
1689
|
-
|
|
1690
|
-
if (route.options.context) {
|
|
1691
|
-
const contextFnContext: RouteContextOptions<any, any, any, any, any> =
|
|
1692
|
-
{
|
|
1693
|
-
deps: match.loaderDeps,
|
|
1694
|
-
params: match.params,
|
|
1695
|
-
context: parentContext ?? {},
|
|
1696
|
-
location: next,
|
|
1697
|
-
navigate: (opts: any) =>
|
|
1698
|
-
this.navigate({ ...opts, _fromLocation: next }),
|
|
1699
|
-
buildLocation: this.buildLocation,
|
|
1700
|
-
cause: match.cause,
|
|
1701
|
-
abortController: match.abortController,
|
|
1702
|
-
preload: !!match.preload,
|
|
1703
|
-
matches,
|
|
1704
|
-
routeId: route.id,
|
|
1705
|
-
}
|
|
1706
|
-
// Get the route context
|
|
1707
|
-
match.__routeContext =
|
|
1708
|
-
route.options.context(contextFnContext) ?? undefined
|
|
1709
|
-
}
|
|
1710
|
-
|
|
1711
|
-
match.context = {
|
|
1712
|
-
...parentContext,
|
|
1713
|
-
...match.__routeContext,
|
|
1714
|
-
...match.__beforeLoadContext,
|
|
1715
|
-
}
|
|
1729
|
+
match.params =
|
|
1730
|
+
match.cause === 'stay'
|
|
1731
|
+
? nullReplaceEqualDeep(match.params, routeParams)
|
|
1732
|
+
: routeParams
|
|
1733
|
+
if (opts?._controller) {
|
|
1734
|
+
match.context = {}
|
|
1716
1735
|
}
|
|
1717
1736
|
}
|
|
1718
1737
|
|
|
@@ -1720,22 +1739,35 @@ export class RouterCore<
|
|
|
1720
1739
|
}
|
|
1721
1740
|
|
|
1722
1741
|
getMatchedRoutes: GetMatchRoutesFn = (pathname) => {
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1742
|
+
const routeParams: Record<string, string> = Object.create(null)
|
|
1743
|
+
const match = findRouteMatch(
|
|
1744
|
+
trimPathRight(pathname),
|
|
1745
|
+
this.processedTree,
|
|
1746
|
+
true,
|
|
1747
|
+
)
|
|
1748
|
+
if (match) {
|
|
1749
|
+
Object.assign(routeParams, match.rawParams)
|
|
1750
|
+
}
|
|
1751
|
+
return {
|
|
1752
|
+
matchedRoutes: match?.branch || [this.routesById[rootRouteId]!],
|
|
1753
|
+
routeParams,
|
|
1754
|
+
foundRoute: match?.route,
|
|
1755
|
+
}
|
|
1728
1756
|
}
|
|
1729
1757
|
|
|
1730
1758
|
/**
|
|
1731
1759
|
* Lightweight route matching for buildLocation.
|
|
1732
1760
|
* Only computes fullPath, accumulated search, and params - skipping expensive
|
|
1733
|
-
* operations like AbortController,
|
|
1761
|
+
* operations like AbortController, loaderDeps, and full match objects.
|
|
1734
1762
|
*/
|
|
1735
1763
|
private matchRoutesLightweight(
|
|
1736
1764
|
location: ParsedLocation,
|
|
1737
1765
|
): LightweightRouteMatchResult {
|
|
1738
|
-
const
|
|
1766
|
+
const lastRouteId = last(this.stores.ids.get())
|
|
1767
|
+
const lastStateMatch = lastRouteId
|
|
1768
|
+
? this.stores.byRoute.get(lastRouteId)!.get()
|
|
1769
|
+
: undefined
|
|
1770
|
+
const lastStateMatchId = lastStateMatch?.id
|
|
1739
1771
|
const cached = this.lightweightCache.get(location)
|
|
1740
1772
|
if (cached && cached[0] === lastStateMatchId) {
|
|
1741
1773
|
return cached[1]
|
|
@@ -1769,8 +1801,6 @@ export class RouterCore<
|
|
|
1769
1801
|
}
|
|
1770
1802
|
|
|
1771
1803
|
// Determine params: reuse from state if possible, otherwise parse
|
|
1772
|
-
const lastStateMatch =
|
|
1773
|
-
lastStateMatchId && this.stores.matchStores.get(lastStateMatchId)?.get()
|
|
1774
1804
|
const canReuseParams =
|
|
1775
1805
|
lastStateMatch &&
|
|
1776
1806
|
lastStateMatch.routeId === lastRoute.id &&
|
|
@@ -1805,37 +1835,6 @@ export class RouterCore<
|
|
|
1805
1835
|
return result
|
|
1806
1836
|
}
|
|
1807
1837
|
|
|
1808
|
-
cancelMatch = (id: string) => {
|
|
1809
|
-
const match = this.getMatch(id)
|
|
1810
|
-
|
|
1811
|
-
if (!match) return
|
|
1812
|
-
|
|
1813
|
-
match.abortController.abort()
|
|
1814
|
-
clearTimeout(match._nonReactive.pendingTimeout)
|
|
1815
|
-
match._nonReactive.pendingTimeout = undefined
|
|
1816
|
-
}
|
|
1817
|
-
|
|
1818
|
-
cancelMatches = () => {
|
|
1819
|
-
this.stores.pendingIds.get().forEach((matchId) => {
|
|
1820
|
-
this.cancelMatch(matchId)
|
|
1821
|
-
})
|
|
1822
|
-
|
|
1823
|
-
this.stores.matchesId.get().forEach((matchId) => {
|
|
1824
|
-
if (this.stores.pendingMatchStores.has(matchId)) {
|
|
1825
|
-
return
|
|
1826
|
-
}
|
|
1827
|
-
|
|
1828
|
-
const match = this.stores.matchStores.get(matchId)?.get()
|
|
1829
|
-
if (!match) {
|
|
1830
|
-
return
|
|
1831
|
-
}
|
|
1832
|
-
|
|
1833
|
-
if (match.status === 'pending' || match.isFetching === 'loader') {
|
|
1834
|
-
this.cancelMatch(matchId)
|
|
1835
|
-
}
|
|
1836
|
-
})
|
|
1837
|
-
}
|
|
1838
|
-
|
|
1839
1838
|
/**
|
|
1840
1839
|
* Build the next ParsedLocation from navigation options without committing.
|
|
1841
1840
|
* Resolves `to`/`from`, params/search/hash/state, applies search validation
|
|
@@ -1851,7 +1850,7 @@ export class RouterCore<
|
|
|
1851
1850
|
): ParsedLocation => {
|
|
1852
1851
|
// We allow the caller to override the current location
|
|
1853
1852
|
const currentLocation =
|
|
1854
|
-
dest._fromLocation || this.
|
|
1853
|
+
dest._fromLocation || this._pendingLocation || this.latestLocation
|
|
1855
1854
|
|
|
1856
1855
|
// Use lightweight matching - only computes what buildLocation needs
|
|
1857
1856
|
// (fullPath, search, params) without creating full match objects
|
|
@@ -1906,15 +1905,7 @@ export class RouterCore<
|
|
|
1906
1905
|
: sourcePath
|
|
1907
1906
|
|
|
1908
1907
|
// Resolve the next params
|
|
1909
|
-
const nextParams =
|
|
1910
|
-
dest.params === false || dest.params === null
|
|
1911
|
-
? Object.create(null)
|
|
1912
|
-
: (dest.params ?? true) === true
|
|
1913
|
-
? fromParams
|
|
1914
|
-
: Object.assign(
|
|
1915
|
-
fromParams,
|
|
1916
|
-
functionalUpdate(dest.params as any, fromParams),
|
|
1917
|
-
)
|
|
1908
|
+
const nextParams = resolveNextParams(dest.params, fromParams)
|
|
1918
1909
|
|
|
1919
1910
|
const destRoute = this.routesByPath[
|
|
1920
1911
|
trimPathRight(nextTo) as keyof typeof this.routesByPath
|
|
@@ -2012,12 +2003,12 @@ export class RouterCore<
|
|
|
2012
2003
|
nextSearch = validatedSearch
|
|
2013
2004
|
}
|
|
2014
2005
|
|
|
2015
|
-
nextSearch = applySearchMiddleware(
|
|
2016
|
-
|
|
2006
|
+
nextSearch = applySearchMiddleware(
|
|
2007
|
+
nextSearch,
|
|
2017
2008
|
dest,
|
|
2018
2009
|
destRoutes,
|
|
2019
|
-
|
|
2020
|
-
|
|
2010
|
+
opts._includeValidateSearch,
|
|
2011
|
+
)
|
|
2021
2012
|
|
|
2022
2013
|
// Replace the equal deep
|
|
2023
2014
|
nextSearch = nullReplaceEqualDeep(fromSearch, nextSearch)
|
|
@@ -2117,12 +2108,7 @@ export class RouterCore<
|
|
|
2117
2108
|
|
|
2118
2109
|
// If mask has a params function, call it with the matched params as context
|
|
2119
2110
|
// Otherwise, use the matched params or the provided params value
|
|
2120
|
-
const nextParams =
|
|
2121
|
-
maskParams === false || maskParams === null
|
|
2122
|
-
? Object.create(null)
|
|
2123
|
-
: (maskParams ?? true) === true
|
|
2124
|
-
? params
|
|
2125
|
-
: Object.assign(params, functionalUpdate(maskParams, params))
|
|
2111
|
+
const nextParams = resolveNextParams(maskParams, params)
|
|
2126
2112
|
|
|
2127
2113
|
maskedDest = {
|
|
2128
2114
|
from: opts.from,
|
|
@@ -2151,7 +2137,7 @@ export class RouterCore<
|
|
|
2151
2137
|
return buildWithMatches(opts)
|
|
2152
2138
|
}
|
|
2153
2139
|
|
|
2154
|
-
|
|
2140
|
+
_commitPromise: (Promise<void> & { resolve: () => void }) | undefined
|
|
2155
2141
|
|
|
2156
2142
|
/**
|
|
2157
2143
|
* Commit a previously built location to history (push/replace), optionally
|
|
@@ -2163,38 +2149,27 @@ export class RouterCore<
|
|
|
2163
2149
|
...next
|
|
2164
2150
|
}) => {
|
|
2165
2151
|
let historyAction: HistoryAction | undefined
|
|
2166
|
-
const
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
'__TSR_key',
|
|
2173
|
-
'__TSR_index',
|
|
2174
|
-
'__hashScrollIntoViewOptions',
|
|
2175
|
-
] as const
|
|
2176
|
-
ignoredProps.forEach((prop) => {
|
|
2177
|
-
;(next.state as any)[prop] = this.latestLocation.state[prop]
|
|
2178
|
-
})
|
|
2179
|
-
const isEqual = deepEqual(next.state, this.latestLocation.state)
|
|
2180
|
-
ignoredProps.forEach((prop) => {
|
|
2181
|
-
delete next.state[prop]
|
|
2182
|
-
})
|
|
2183
|
-
return isEqual
|
|
2184
|
-
}
|
|
2185
|
-
|
|
2186
|
-
const isSameUrl =
|
|
2187
|
-
trimPathRight(this.latestLocation.href) === trimPathRight(next.href)
|
|
2152
|
+
const isSameLocation =
|
|
2153
|
+
trimPathRight(this.latestLocation.href) === trimPathRight(next.href) &&
|
|
2154
|
+
deepEqual(
|
|
2155
|
+
_getUserHistoryState(next.state),
|
|
2156
|
+
_getUserHistoryState(this.latestLocation.state),
|
|
2157
|
+
)
|
|
2188
2158
|
|
|
2189
|
-
|
|
2190
|
-
|
|
2159
|
+
const previousCommitPromise = this._commitPromise
|
|
2160
|
+
let resolve!: () => void
|
|
2161
|
+
const commitPromise = new Promise<void>((done) => {
|
|
2162
|
+
resolve = done
|
|
2163
|
+
}) as Promise<void> & { resolve: () => void }
|
|
2164
|
+
commitPromise.resolve = () => {
|
|
2165
|
+
resolve()
|
|
2191
2166
|
previousCommitPromise?.resolve()
|
|
2192
|
-
|
|
2193
|
-
|
|
2167
|
+
}
|
|
2168
|
+
this._commitPromise = commitPromise
|
|
2194
2169
|
|
|
2195
2170
|
// Don't commit to history if nothing changed
|
|
2196
|
-
if (
|
|
2197
|
-
this.load()
|
|
2171
|
+
if (isSameLocation) {
|
|
2172
|
+
this.load({ _dedupe: true })
|
|
2198
2173
|
} else {
|
|
2199
2174
|
let {
|
|
2200
2175
|
// eslint-disable-next-line prefer-const
|
|
@@ -2245,21 +2220,17 @@ export class RouterCore<
|
|
|
2245
2220
|
nextHistory.state,
|
|
2246
2221
|
{ ignoreBlocker },
|
|
2247
2222
|
)
|
|
2223
|
+
|
|
2224
|
+
// Without a subscribed adapter the history commit cannot trigger the
|
|
2225
|
+
// load itself. The same-location branch already loads directly.
|
|
2226
|
+
if (!this.history.subscribers.size) {
|
|
2227
|
+
this.load({ action: { type: historyAction } })
|
|
2228
|
+
}
|
|
2248
2229
|
}
|
|
2249
2230
|
|
|
2250
2231
|
this._scroll.next = next.resetScroll ?? true
|
|
2251
2232
|
|
|
2252
|
-
|
|
2253
|
-
this.load(
|
|
2254
|
-
historyAction
|
|
2255
|
-
? {
|
|
2256
|
-
action: { type: historyAction },
|
|
2257
|
-
}
|
|
2258
|
-
: undefined,
|
|
2259
|
-
)
|
|
2260
|
-
}
|
|
2261
|
-
|
|
2262
|
-
return this.commitLocationPromise
|
|
2233
|
+
return this._commitPromise
|
|
2263
2234
|
}
|
|
2264
2235
|
|
|
2265
2236
|
/** Convenience helper: build a location from options, then commit it. */
|
|
@@ -2269,9 +2240,11 @@ export class RouterCore<
|
|
|
2269
2240
|
hashScrollIntoView,
|
|
2270
2241
|
viewTransition,
|
|
2271
2242
|
ignoreBlocker,
|
|
2243
|
+
_redirects,
|
|
2272
2244
|
href,
|
|
2273
2245
|
...rest
|
|
2274
|
-
}: BuildNextOptions &
|
|
2246
|
+
}: BuildNextOptions &
|
|
2247
|
+
CommitLocationOptions & { _redirects?: number } = {}) => {
|
|
2275
2248
|
if (href) {
|
|
2276
2249
|
const currentIndex = this.history.location.state.__TSR_index
|
|
2277
2250
|
|
|
@@ -2295,8 +2268,12 @@ export class RouterCore<
|
|
|
2295
2268
|
...(rest as any),
|
|
2296
2269
|
_includeValidateSearch: true,
|
|
2297
2270
|
})
|
|
2271
|
+
if (_redirects) {
|
|
2272
|
+
;(location as typeof location & { _redirects?: number })._redirects =
|
|
2273
|
+
_redirects
|
|
2274
|
+
}
|
|
2298
2275
|
|
|
2299
|
-
this.
|
|
2276
|
+
this._pendingLocation = location as ParsedLocation<
|
|
2300
2277
|
FullSearchSchema<TRouteTree>
|
|
2301
2278
|
>
|
|
2302
2279
|
|
|
@@ -2312,8 +2289,8 @@ export class RouterCore<
|
|
|
2312
2289
|
// Clear pending location after commit starts
|
|
2313
2290
|
// We do this on next microtask to allow synchronous navigate calls to chain
|
|
2314
2291
|
queueMicrotask(() => {
|
|
2315
|
-
if (this.
|
|
2316
|
-
this.
|
|
2292
|
+
if (this._pendingLocation === location) {
|
|
2293
|
+
this._pendingLocation = undefined
|
|
2317
2294
|
}
|
|
2318
2295
|
})
|
|
2319
2296
|
|
|
@@ -2409,253 +2386,17 @@ export class RouterCore<
|
|
|
2409
2386
|
})
|
|
2410
2387
|
}
|
|
2411
2388
|
|
|
2412
|
-
latestLoadPromise: undefined | Promise<void>
|
|
2413
|
-
|
|
2414
|
-
beforeLoad = () => {
|
|
2415
|
-
// Cancel any pending matches
|
|
2416
|
-
this.cancelMatches()
|
|
2417
|
-
this.updateLatestLocation()
|
|
2418
|
-
|
|
2419
|
-
if (isServer ?? this.isServer) {
|
|
2420
|
-
// for SPAs on the initial load, this is handled by the Transitioner
|
|
2421
|
-
const nextLocation = this.buildLocation({
|
|
2422
|
-
to: this.latestLocation.pathname,
|
|
2423
|
-
search: true,
|
|
2424
|
-
params: true,
|
|
2425
|
-
hash: true,
|
|
2426
|
-
state: true,
|
|
2427
|
-
_includeValidateSearch: true,
|
|
2428
|
-
})
|
|
2429
|
-
|
|
2430
|
-
// Check if location changed - origin check is unnecessary since buildLocation
|
|
2431
|
-
// always uses this.origin when constructing URLs
|
|
2432
|
-
if (this.latestLocation.publicHref !== nextLocation.publicHref) {
|
|
2433
|
-
const href = this.getParsedLocationHref(nextLocation)
|
|
2434
|
-
if (nextLocation.external) {
|
|
2435
|
-
throw redirect({ href })
|
|
2436
|
-
} else {
|
|
2437
|
-
throw redirect({ href, _builtLocation: nextLocation })
|
|
2438
|
-
}
|
|
2439
|
-
}
|
|
2440
|
-
}
|
|
2441
|
-
|
|
2442
|
-
// Match the routes
|
|
2443
|
-
const pendingMatches = this.matchRoutes(this.latestLocation)
|
|
2444
|
-
|
|
2445
|
-
const nextCachedMatches = this.stores.cachedMatches
|
|
2446
|
-
.get()
|
|
2447
|
-
.filter((d) => !pendingMatches.some((e) => e.id === d.id))
|
|
2448
|
-
|
|
2449
|
-
// Ingest the new matches
|
|
2450
|
-
this.batch(() => {
|
|
2451
|
-
this.stores.status.set('pending')
|
|
2452
|
-
this.stores.statusCode.set(200)
|
|
2453
|
-
this.stores.isLoading.set(true)
|
|
2454
|
-
this.stores.location.set(this.latestLocation)
|
|
2455
|
-
this.stores.setPending(pendingMatches)
|
|
2456
|
-
// If a cached match moved to pending matches, remove it from cached matches
|
|
2457
|
-
this.stores.setCached(nextCachedMatches)
|
|
2458
|
-
})
|
|
2459
|
-
}
|
|
2460
|
-
|
|
2461
2389
|
load: LoadFn = async (opts): Promise<void> => {
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
let notFound: NotFoundError | undefined
|
|
2465
|
-
let loadPromise: Promise<void>
|
|
2466
|
-
const previousLocation =
|
|
2467
|
-
this.stores.resolvedLocation.get() ?? this.stores.location.get()
|
|
2468
|
-
|
|
2469
|
-
// eslint-disable-next-line prefer-const
|
|
2470
|
-
loadPromise = new Promise<void>((resolve) => {
|
|
2471
|
-
this.startTransition(async () => {
|
|
2472
|
-
try {
|
|
2473
|
-
this.beforeLoad()
|
|
2474
|
-
if (historyAction) {
|
|
2475
|
-
this._scroll.hash =
|
|
2476
|
-
historyAction === 'PUSH' || historyAction === 'REPLACE'
|
|
2477
|
-
}
|
|
2478
|
-
const next = this.latestLocation
|
|
2479
|
-
const prevLocation = this.stores.resolvedLocation.get()
|
|
2480
|
-
const locationChangeInfo = getLocationChangeInfo(next, prevLocation)
|
|
2481
|
-
|
|
2482
|
-
if (!this.stores.redirect.get()) {
|
|
2483
|
-
this.emit({
|
|
2484
|
-
type: 'onBeforeNavigate',
|
|
2485
|
-
...locationChangeInfo,
|
|
2486
|
-
})
|
|
2487
|
-
}
|
|
2488
|
-
|
|
2489
|
-
this.emit({
|
|
2490
|
-
type: 'onBeforeLoad',
|
|
2491
|
-
...locationChangeInfo,
|
|
2492
|
-
})
|
|
2493
|
-
|
|
2494
|
-
await loadMatches({
|
|
2495
|
-
router: this,
|
|
2496
|
-
sync: opts?.sync,
|
|
2497
|
-
forceStaleReload: previousLocation.href === next.href,
|
|
2498
|
-
matches: this.stores.pendingMatches.get(),
|
|
2499
|
-
location: next,
|
|
2500
|
-
updateMatch: this.updateMatch,
|
|
2501
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
2502
|
-
onReady: async () => {
|
|
2503
|
-
// Wrap batch in framework-specific transition wrapper (e.g., Solid's startTransition)
|
|
2504
|
-
this.startTransition(() => {
|
|
2505
|
-
this.startViewTransition(async () => {
|
|
2506
|
-
// this.viewTransitionPromise = createControlledPromise<true>()
|
|
2507
|
-
|
|
2508
|
-
// Commit the pending matches. If a previous match was
|
|
2509
|
-
// removed, place it in the cachedMatches
|
|
2510
|
-
//
|
|
2511
|
-
// exitingMatches uses match.id (routeId + params + loaderDeps) so
|
|
2512
|
-
// navigating /foo?page=1 → /foo?page=2 correctly caches the page=1 entry.
|
|
2513
|
-
let exitingMatches: Array<AnyRouteMatch> | null = null
|
|
2514
|
-
|
|
2515
|
-
// Lifecycle-hook identity uses routeId only so that navigating between
|
|
2516
|
-
// different params/deps of the same route fires onStay (not onLeave+onEnter).
|
|
2517
|
-
let hookExitingMatches: Array<AnyRouteMatch> | null = null
|
|
2518
|
-
let hookEnteringMatches: Array<AnyRouteMatch> | null = null
|
|
2519
|
-
let hookStayingMatches: Array<AnyRouteMatch> | null = null
|
|
2520
|
-
|
|
2521
|
-
this.batch(() => {
|
|
2522
|
-
const pendingMatches = this.stores.pendingMatches.get()
|
|
2523
|
-
const mountPending = pendingMatches.length
|
|
2524
|
-
const currentMatches = this.stores.matches.get()
|
|
2525
|
-
|
|
2526
|
-
exitingMatches = mountPending
|
|
2527
|
-
? currentMatches.filter(
|
|
2528
|
-
(match) =>
|
|
2529
|
-
!this.stores.pendingMatchStores.has(match.id),
|
|
2530
|
-
)
|
|
2531
|
-
: null
|
|
2532
|
-
|
|
2533
|
-
// Lifecycle-hook identity: routeId only (route presence in tree)
|
|
2534
|
-
// Build routeId sets from pools to avoid derived stores.
|
|
2535
|
-
const pendingRouteIds = new Set<string>()
|
|
2536
|
-
for (const s of this.stores.pendingMatchStores.values()) {
|
|
2537
|
-
if (s.routeId) pendingRouteIds.add(s.routeId)
|
|
2538
|
-
}
|
|
2539
|
-
const activeRouteIds = new Set<string>()
|
|
2540
|
-
for (const s of this.stores.matchStores.values()) {
|
|
2541
|
-
if (s.routeId) activeRouteIds.add(s.routeId)
|
|
2542
|
-
}
|
|
2543
|
-
|
|
2544
|
-
hookExitingMatches = mountPending
|
|
2545
|
-
? currentMatches.filter(
|
|
2546
|
-
(match) => !pendingRouteIds.has(match.routeId),
|
|
2547
|
-
)
|
|
2548
|
-
: null
|
|
2549
|
-
hookEnteringMatches = mountPending
|
|
2550
|
-
? pendingMatches.filter(
|
|
2551
|
-
(match) => !activeRouteIds.has(match.routeId),
|
|
2552
|
-
)
|
|
2553
|
-
: null
|
|
2554
|
-
hookStayingMatches = mountPending
|
|
2555
|
-
? pendingMatches.filter((match) =>
|
|
2556
|
-
activeRouteIds.has(match.routeId),
|
|
2557
|
-
)
|
|
2558
|
-
: currentMatches
|
|
2559
|
-
|
|
2560
|
-
this.stores.isLoading.set(false)
|
|
2561
|
-
this.stores.loadedAt.set(Date.now())
|
|
2562
|
-
/**
|
|
2563
|
-
* When committing new matches, cache any exiting matches that are still usable.
|
|
2564
|
-
* Routes that resolved with `status: 'error'` or `status: 'notFound'` are
|
|
2565
|
-
* deliberately excluded from `cachedMatches` so that subsequent invalidations
|
|
2566
|
-
* or reloads re-run their loaders instead of reusing the failed/not-found data.
|
|
2567
|
-
*/
|
|
2568
|
-
if (mountPending) {
|
|
2569
|
-
this.stores.setMatches(pendingMatches)
|
|
2570
|
-
this.stores.setPending([])
|
|
2571
|
-
this.stores.setCached([
|
|
2572
|
-
...this.stores.cachedMatches.get(),
|
|
2573
|
-
...exitingMatches!.filter(
|
|
2574
|
-
(d) =>
|
|
2575
|
-
d.status !== 'error' &&
|
|
2576
|
-
d.status !== 'notFound' &&
|
|
2577
|
-
d.status !== 'redirected',
|
|
2578
|
-
),
|
|
2579
|
-
])
|
|
2580
|
-
this.clearExpiredCache()
|
|
2581
|
-
}
|
|
2582
|
-
})
|
|
2583
|
-
|
|
2584
|
-
//
|
|
2585
|
-
for (const [matches, hook] of [
|
|
2586
|
-
[hookExitingMatches, 'onLeave'],
|
|
2587
|
-
[hookEnteringMatches, 'onEnter'],
|
|
2588
|
-
[hookStayingMatches, 'onStay'],
|
|
2589
|
-
] as const) {
|
|
2590
|
-
if (!matches) continue
|
|
2591
|
-
for (const match of matches as Array<AnyRouteMatch>) {
|
|
2592
|
-
this.looseRoutesById[match.routeId]!.options[hook]?.(
|
|
2593
|
-
match,
|
|
2594
|
-
)
|
|
2595
|
-
}
|
|
2596
|
-
}
|
|
2597
|
-
})
|
|
2598
|
-
})
|
|
2599
|
-
},
|
|
2600
|
-
})
|
|
2601
|
-
} catch (err) {
|
|
2602
|
-
if (isRedirect(err)) {
|
|
2603
|
-
redirect = err
|
|
2604
|
-
if (!(isServer ?? this.isServer)) {
|
|
2605
|
-
this.navigate({
|
|
2606
|
-
...redirect.options,
|
|
2607
|
-
replace: true,
|
|
2608
|
-
ignoreBlocker: true,
|
|
2609
|
-
})
|
|
2610
|
-
}
|
|
2611
|
-
} else if (isNotFound(err)) {
|
|
2612
|
-
notFound = err
|
|
2613
|
-
}
|
|
2614
|
-
|
|
2615
|
-
const nextStatusCode = redirect
|
|
2616
|
-
? redirect.status
|
|
2617
|
-
: notFound
|
|
2618
|
-
? 404
|
|
2619
|
-
: this.stores.matches.get().some((d) => d.status === 'error')
|
|
2620
|
-
? 500
|
|
2621
|
-
: 200
|
|
2622
|
-
|
|
2623
|
-
this.batch(() => {
|
|
2624
|
-
this.stores.statusCode.set(nextStatusCode)
|
|
2625
|
-
this.stores.redirect.set(redirect)
|
|
2626
|
-
})
|
|
2627
|
-
}
|
|
2628
|
-
|
|
2629
|
-
if (this.latestLoadPromise === loadPromise) {
|
|
2630
|
-
this.commitLocationPromise?.resolve()
|
|
2631
|
-
this.latestLoadPromise = undefined
|
|
2632
|
-
this.commitLocationPromise = undefined
|
|
2633
|
-
}
|
|
2634
|
-
|
|
2635
|
-
resolve()
|
|
2636
|
-
})
|
|
2637
|
-
})
|
|
2638
|
-
|
|
2639
|
-
this.latestLoadPromise = loadPromise
|
|
2640
|
-
|
|
2641
|
-
await loadPromise
|
|
2642
|
-
|
|
2643
|
-
while (
|
|
2644
|
-
(this.latestLoadPromise as any) &&
|
|
2645
|
-
loadPromise !== this.latestLoadPromise
|
|
2646
|
-
) {
|
|
2647
|
-
await this.latestLoadPromise
|
|
2390
|
+
if (isServer ?? this.isServer) {
|
|
2391
|
+
return loadServerRoute(this, opts)
|
|
2648
2392
|
}
|
|
2649
2393
|
|
|
2650
|
-
|
|
2651
|
-
if (
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
newStatusCode = 500
|
|
2655
|
-
}
|
|
2656
|
-
if (newStatusCode !== undefined) {
|
|
2657
|
-
this.stores.statusCode.set(newStatusCode)
|
|
2394
|
+
this.updateLatestLocation()
|
|
2395
|
+
if (opts?.action) {
|
|
2396
|
+
this._scroll.hash =
|
|
2397
|
+
opts.action.type === 'PUSH' || opts.action.type === 'REPLACE'
|
|
2658
2398
|
}
|
|
2399
|
+
await loadClientRoute(this, opts)
|
|
2659
2400
|
}
|
|
2660
2401
|
|
|
2661
2402
|
startViewTransition = (fn: () => Promise<void>) => {
|
|
@@ -2670,9 +2411,8 @@ export class RouterCore<
|
|
|
2670
2411
|
// Attempt to start a view transition (or just apply the changes if we can't)
|
|
2671
2412
|
if (
|
|
2672
2413
|
shouldViewTransition &&
|
|
2673
|
-
typeof document
|
|
2674
|
-
|
|
2675
|
-
typeof document.startViewTransition === 'function'
|
|
2414
|
+
!(isServer ?? typeof document === 'undefined') &&
|
|
2415
|
+
typeof (document as any).startViewTransition === 'function'
|
|
2676
2416
|
) {
|
|
2677
2417
|
// lib.dom.ts doesn't support viewTransition types variant yet.
|
|
2678
2418
|
// TODO: Fix this when dom types are updated
|
|
@@ -2680,7 +2420,7 @@ export class RouterCore<
|
|
|
2680
2420
|
|
|
2681
2421
|
if (
|
|
2682
2422
|
typeof shouldViewTransition === 'object' &&
|
|
2683
|
-
|
|
2423
|
+
window.CSS?.supports?.('selector(:active-view-transition-type(a))')
|
|
2684
2424
|
) {
|
|
2685
2425
|
const next = this.latestLocation
|
|
2686
2426
|
const prevLocation = this.stores.resolvedLocation.get()
|
|
@@ -2693,8 +2433,7 @@ export class RouterCore<
|
|
|
2693
2433
|
: shouldViewTransition.types
|
|
2694
2434
|
|
|
2695
2435
|
if (resolvedViewTransitionTypes === false) {
|
|
2696
|
-
fn()
|
|
2697
|
-
return
|
|
2436
|
+
return fn()
|
|
2698
2437
|
}
|
|
2699
2438
|
|
|
2700
2439
|
startViewTransitionParams = {
|
|
@@ -2705,58 +2444,19 @@ export class RouterCore<
|
|
|
2705
2444
|
startViewTransitionParams = fn
|
|
2706
2445
|
}
|
|
2707
2446
|
|
|
2708
|
-
document.startViewTransition(startViewTransitionParams)
|
|
2709
|
-
|
|
2710
|
-
fn()
|
|
2447
|
+
return (document as any).startViewTransition(startViewTransitionParams)
|
|
2448
|
+
.updateCallbackDone
|
|
2711
2449
|
}
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
updateMatch: UpdateMatchFn = (id, updater) => {
|
|
2715
|
-
this.startTransition(() => {
|
|
2716
|
-
const pendingMatch = this.stores.pendingMatchStores.get(id)
|
|
2717
|
-
if (pendingMatch) {
|
|
2718
|
-
pendingMatch.set(updater)
|
|
2719
|
-
return
|
|
2720
|
-
}
|
|
2721
|
-
|
|
2722
|
-
const activeMatch = this.stores.matchStores.get(id)
|
|
2723
|
-
if (activeMatch) {
|
|
2724
|
-
activeMatch.set(updater)
|
|
2725
|
-
return
|
|
2726
|
-
}
|
|
2727
|
-
|
|
2728
|
-
const cachedMatch = this.stores.cachedMatchStores.get(id)
|
|
2729
|
-
if (cachedMatch) {
|
|
2730
|
-
const next = updater(cachedMatch.get())
|
|
2731
|
-
if (next.status === 'redirected') {
|
|
2732
|
-
const deleted = this.stores.cachedMatchStores.delete(id)
|
|
2733
|
-
if (deleted) {
|
|
2734
|
-
this.stores.cachedIds.set((prev) =>
|
|
2735
|
-
prev.filter((matchId) => matchId !== id),
|
|
2736
|
-
)
|
|
2737
|
-
}
|
|
2738
|
-
} else {
|
|
2739
|
-
cachedMatch.set(next)
|
|
2740
|
-
}
|
|
2741
|
-
}
|
|
2742
|
-
})
|
|
2743
|
-
}
|
|
2744
|
-
|
|
2745
|
-
getMatch: GetMatchFn = (matchId: string): AnyRouteMatch | undefined => {
|
|
2746
|
-
return (
|
|
2747
|
-
this.stores.cachedMatchStores.get(matchId)?.get() ??
|
|
2748
|
-
this.stores.pendingMatchStores.get(matchId)?.get() ??
|
|
2749
|
-
this.stores.matchStores.get(matchId)?.get()
|
|
2750
|
-
)
|
|
2450
|
+
return fn()
|
|
2751
2451
|
}
|
|
2752
2452
|
|
|
2753
2453
|
/**
|
|
2754
2454
|
* Invalidate the current matches and optionally force them back into a pending state.
|
|
2755
2455
|
*
|
|
2756
2456
|
* - Marks all matches that pass the optional `filter` as `invalid: true`.
|
|
2757
|
-
*
|
|
2758
|
-
*
|
|
2759
|
-
*
|
|
2457
|
+
*
|
|
2458
|
+
* The next load decides when to publish pending UI, so invalidation does not
|
|
2459
|
+
* mutate the currently rendered status.
|
|
2760
2460
|
*/
|
|
2761
2461
|
invalidate: InvalidateFn<
|
|
2762
2462
|
RouterCore<
|
|
@@ -2767,44 +2467,54 @@ export class RouterCore<
|
|
|
2767
2467
|
TDehydrated
|
|
2768
2468
|
>
|
|
2769
2469
|
> = (opts) => {
|
|
2470
|
+
const committedMatches = this._committed
|
|
2471
|
+
const filter = opts?.filter
|
|
2472
|
+
const invalidIds = filter
|
|
2473
|
+
? new Set(
|
|
2474
|
+
[...committedMatches, ...this._cache.values()]
|
|
2475
|
+
.filter((match) => filter(match as MakeRouteMatchUnion<this>))
|
|
2476
|
+
.map((match) => match.id),
|
|
2477
|
+
)
|
|
2478
|
+
: undefined
|
|
2770
2479
|
const invalidate = (d: MakeRouteMatch<TRouteTree>) => {
|
|
2771
|
-
if (
|
|
2772
|
-
|
|
2480
|
+
if (!invalidIds || invalidIds.has(d.id)) {
|
|
2481
|
+
const route = this.routesById[d.routeId] as AnyRoute
|
|
2482
|
+
const next = {
|
|
2773
2483
|
...d,
|
|
2774
2484
|
invalid: true,
|
|
2775
|
-
...(opts?.forcePending ||
|
|
2776
|
-
|
|
2777
|
-
|
|
2485
|
+
...((opts?.forcePending ||
|
|
2486
|
+
d.status === 'error' ||
|
|
2487
|
+
d.status === 'notFound') &&
|
|
2488
|
+
routeNeedsLoad(route)
|
|
2778
2489
|
? ({ status: 'pending', error: undefined } as const)
|
|
2779
2490
|
: undefined),
|
|
2780
2491
|
}
|
|
2492
|
+
// Invalidation replaces this owner; it does not create another lease.
|
|
2493
|
+
;(d as AnyRouteMatch & { _flight?: LoaderFlight })._flight = undefined
|
|
2494
|
+
return next
|
|
2781
2495
|
}
|
|
2782
2496
|
return d
|
|
2783
2497
|
}
|
|
2784
2498
|
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2499
|
+
const committed = committedMatches.map(invalidate)
|
|
2500
|
+
this._committed = committed
|
|
2501
|
+
const cache = new Map<string, AnyRouteMatch>()
|
|
2502
|
+
for (const [id, match] of this._cache) {
|
|
2503
|
+
cache.set(id, invalidate(match))
|
|
2504
|
+
}
|
|
2505
|
+
this._cache = cache
|
|
2790
2506
|
|
|
2791
2507
|
this.shouldViewTransition = false
|
|
2792
2508
|
return this.load({ sync: opts?.sync })
|
|
2793
2509
|
}
|
|
2794
2510
|
|
|
2795
|
-
getParsedLocationHref = (location: ParsedLocation) => {
|
|
2796
|
-
// For redirects and external use, we need publicHref (with rewrite output applied)
|
|
2797
|
-
// href is the internal path after rewrite input, publicHref is user-facing
|
|
2798
|
-
return location.publicHref || '/'
|
|
2799
|
-
}
|
|
2800
|
-
|
|
2801
2511
|
resolveRedirect = (redirect: AnyRedirect): AnyRedirect => {
|
|
2802
2512
|
const locationHeader = redirect.headers.get('Location')
|
|
2803
2513
|
|
|
2804
2514
|
if (!redirect.options.href || redirect.options._builtLocation) {
|
|
2805
2515
|
const location =
|
|
2806
2516
|
redirect.options._builtLocation ?? this.buildLocation(redirect.options)
|
|
2807
|
-
const href =
|
|
2517
|
+
const href = location.publicHref || '/'
|
|
2808
2518
|
redirect.options.href = href
|
|
2809
2519
|
redirect.headers.set('Location', href)
|
|
2810
2520
|
} else if (locationHeader) {
|
|
@@ -2841,43 +2551,37 @@ export class RouterCore<
|
|
|
2841
2551
|
}
|
|
2842
2552
|
|
|
2843
2553
|
clearCache: ClearCacheFn<this> = (opts) => {
|
|
2554
|
+
const cached = this._cache
|
|
2555
|
+
const preloads = this._preloads
|
|
2844
2556
|
const filter = opts?.filter
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2557
|
+
const retained = new Map<string, AnyRouteMatch>()
|
|
2558
|
+
const discarded: Array<AnyRouteMatch> = []
|
|
2559
|
+
for (const [id, match] of cached) {
|
|
2560
|
+
if (filter && !filter(match as MakeRouteMatchUnion<this>)) {
|
|
2561
|
+
retained.set(id, match)
|
|
2562
|
+
} else {
|
|
2563
|
+
discarded.push(match)
|
|
2564
|
+
}
|
|
2853
2565
|
}
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
if (!route.options.loader) {
|
|
2863
|
-
return true
|
|
2566
|
+
const retainedPreloads = new Map<string, ActivePreload>()
|
|
2567
|
+
const discardedPreloads: Array<ActivePreload> = []
|
|
2568
|
+
for (const [href, preload] of preloads ?? []) {
|
|
2569
|
+
if (!filter || preload[0].some(filter as any)) {
|
|
2570
|
+
discardedPreloads.push(preload)
|
|
2571
|
+
discarded.push(...preload[0])
|
|
2572
|
+
} else {
|
|
2573
|
+
retainedPreloads.set(href, preload)
|
|
2864
2574
|
}
|
|
2575
|
+
}
|
|
2865
2576
|
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
const isError = d.status === 'error'
|
|
2875
|
-
if (isError) return true
|
|
2876
|
-
|
|
2877
|
-
const gcEligible = now - d.updatedAt >= gcTime
|
|
2878
|
-
return gcEligible
|
|
2577
|
+
// Install both replacement authorities before releasing a public loader
|
|
2578
|
+
// signal, whose abort listeners can synchronously reenter the router.
|
|
2579
|
+
this._cache = retained
|
|
2580
|
+
this._preloads = retainedPreloads
|
|
2581
|
+
transferMatchResources(this, discarded)
|
|
2582
|
+
for (const preload of discardedPreloads) {
|
|
2583
|
+
preload[1].abort()
|
|
2879
2584
|
}
|
|
2880
|
-
this.clearCache({ filter })
|
|
2881
2585
|
}
|
|
2882
2586
|
|
|
2883
2587
|
loadRouteChunk = loadRouteChunk
|
|
@@ -2887,69 +2591,7 @@ export class RouterCore<
|
|
|
2887
2591
|
TTrailingSlashOption,
|
|
2888
2592
|
TDefaultStructuralSharingOption,
|
|
2889
2593
|
TRouterHistory
|
|
2890
|
-
> =
|
|
2891
|
-
const next = opts._builtLocation ?? this.buildLocation(opts as any)
|
|
2892
|
-
|
|
2893
|
-
let matches = this.matchRoutes(next, {
|
|
2894
|
-
throwOnError: true,
|
|
2895
|
-
preload: true,
|
|
2896
|
-
dest: opts,
|
|
2897
|
-
})
|
|
2898
|
-
|
|
2899
|
-
const activeMatchIds = new Set([
|
|
2900
|
-
...this.stores.matchesId.get(),
|
|
2901
|
-
...this.stores.pendingIds.get(),
|
|
2902
|
-
])
|
|
2903
|
-
|
|
2904
|
-
const loadedMatchIds = new Set([
|
|
2905
|
-
...activeMatchIds,
|
|
2906
|
-
...this.stores.cachedIds.get(),
|
|
2907
|
-
])
|
|
2908
|
-
|
|
2909
|
-
// If the matches are already loaded, we need to add them to the cached matches.
|
|
2910
|
-
const matchesToCache = matches.filter(
|
|
2911
|
-
(match) => !loadedMatchIds.has(match.id),
|
|
2912
|
-
)
|
|
2913
|
-
if (matchesToCache.length) {
|
|
2914
|
-
const cachedMatches = this.stores.cachedMatches.get()
|
|
2915
|
-
this.stores.setCached([...cachedMatches, ...matchesToCache])
|
|
2916
|
-
}
|
|
2917
|
-
|
|
2918
|
-
try {
|
|
2919
|
-
matches = await loadMatches({
|
|
2920
|
-
router: this,
|
|
2921
|
-
matches,
|
|
2922
|
-
location: next,
|
|
2923
|
-
preload: true,
|
|
2924
|
-
updateMatch: (id, updater) => {
|
|
2925
|
-
// Don't update the match if it's currently loaded
|
|
2926
|
-
if (activeMatchIds.has(id)) {
|
|
2927
|
-
matches = matches.map((d) => (d.id === id ? updater(d) : d))
|
|
2928
|
-
} else {
|
|
2929
|
-
this.updateMatch(id, updater)
|
|
2930
|
-
}
|
|
2931
|
-
},
|
|
2932
|
-
})
|
|
2933
|
-
|
|
2934
|
-
return matches
|
|
2935
|
-
} catch (err) {
|
|
2936
|
-
if (isRedirect(err)) {
|
|
2937
|
-
if (err.options.reloadDocument) {
|
|
2938
|
-
return undefined
|
|
2939
|
-
}
|
|
2940
|
-
|
|
2941
|
-
return await this.preloadRoute({
|
|
2942
|
-
...err.options,
|
|
2943
|
-
_fromLocation: next,
|
|
2944
|
-
})
|
|
2945
|
-
}
|
|
2946
|
-
if (!isNotFound(err)) {
|
|
2947
|
-
// Preload errors are not fatal, but we should still log them
|
|
2948
|
-
console.error(err)
|
|
2949
|
-
}
|
|
2950
|
-
return undefined
|
|
2951
|
-
}
|
|
2952
|
-
}
|
|
2594
|
+
> = (opts) => preloadClientRoute(this, opts)
|
|
2953
2595
|
|
|
2954
2596
|
matchRoute: MatchRouteFn<
|
|
2955
2597
|
TRouteTree,
|
|
@@ -2967,12 +2609,12 @@ export class RouterCore<
|
|
|
2967
2609
|
}
|
|
2968
2610
|
const next = this.buildLocation(matchLocation as any)
|
|
2969
2611
|
|
|
2970
|
-
|
|
2612
|
+
const isPending = this.stores.status.get() === 'pending'
|
|
2613
|
+
if (opts?.pending && !isPending) {
|
|
2971
2614
|
return false
|
|
2972
2615
|
}
|
|
2973
2616
|
|
|
2974
|
-
const pending =
|
|
2975
|
-
opts?.pending === undefined ? !this.stores.isLoading.get() : opts.pending
|
|
2617
|
+
const pending = opts?.pending ?? !isPending
|
|
2976
2618
|
|
|
2977
2619
|
const baseLocation = pending
|
|
2978
2620
|
? this.latestLocation
|
|
@@ -3012,11 +2654,19 @@ export class RouterCore<
|
|
|
3012
2654
|
serverSsr?: ServerSsr
|
|
3013
2655
|
|
|
3014
2656
|
serverSsrLifecycle?: RouterSsrLifecycle
|
|
2657
|
+
}
|
|
3015
2658
|
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
2659
|
+
/**
|
|
2660
|
+
* In non-production environments,
|
|
2661
|
+
* augment the RouterCore class with a `_refreshRoute` method
|
|
2662
|
+
* dedicated to HMR.
|
|
2663
|
+
*/
|
|
2664
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
2665
|
+
RouterCore.prototype._replaceRouteChunk = replaceRouteChunk
|
|
2666
|
+
RouterCore.prototype._refreshRoute = async function () {
|
|
2667
|
+
this._serverResult = undefined
|
|
2668
|
+
this.updateLatestLocation()
|
|
2669
|
+
await refreshClientRoute(this)
|
|
3020
2670
|
}
|
|
3021
2671
|
}
|
|
3022
2672
|
|
|
@@ -3053,14 +2703,11 @@ export function getInitialRouterState(
|
|
|
3053
2703
|
location: ParsedLocation,
|
|
3054
2704
|
): RouterState<any> {
|
|
3055
2705
|
return {
|
|
3056
|
-
loadedAt: 0,
|
|
3057
2706
|
isLoading: false,
|
|
3058
|
-
isTransitioning: false,
|
|
3059
2707
|
status: 'idle',
|
|
3060
2708
|
resolvedLocation: undefined,
|
|
3061
2709
|
location,
|
|
3062
2710
|
matches: [],
|
|
3063
|
-
statusCode: 200,
|
|
3064
2711
|
}
|
|
3065
2712
|
}
|
|
3066
2713
|
|
|
@@ -3092,56 +2739,12 @@ function validateSearch(validateSearch: AnyValidator, input: unknown): unknown {
|
|
|
3092
2739
|
return {}
|
|
3093
2740
|
}
|
|
3094
2741
|
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
routesById,
|
|
3102
|
-
processedTree,
|
|
3103
|
-
}: {
|
|
3104
|
-
pathname: string
|
|
3105
|
-
routesById: Record<string, TRouteLike>
|
|
3106
|
-
processedTree: ProcessedTree<any, any, any>
|
|
3107
|
-
}) {
|
|
3108
|
-
const routeParams: Record<string, string> = Object.create(null)
|
|
3109
|
-
const trimmedPath = trimPathRight(pathname)
|
|
3110
|
-
|
|
3111
|
-
let foundRoute: TRouteLike | undefined = undefined
|
|
3112
|
-
const match = findRouteMatch<TRouteLike>(trimmedPath, processedTree, true)
|
|
3113
|
-
if (match) {
|
|
3114
|
-
foundRoute = match.route
|
|
3115
|
-
Object.assign(routeParams, match.rawParams) // Copy params, because they're cached
|
|
3116
|
-
}
|
|
3117
|
-
|
|
3118
|
-
const matchedRoutes = match?.branch || [routesById[rootRouteId]!]
|
|
3119
|
-
|
|
3120
|
-
return { matchedRoutes, routeParams, foundRoute }
|
|
3121
|
-
}
|
|
3122
|
-
|
|
3123
|
-
/**
|
|
3124
|
-
* TODO: once caches are persisted across requests on the server,
|
|
3125
|
-
* we can cache the built middleware chain using `last(destRoutes)` as the key
|
|
3126
|
-
*/
|
|
3127
|
-
function applySearchMiddleware({
|
|
3128
|
-
search,
|
|
3129
|
-
dest,
|
|
3130
|
-
destRoutes,
|
|
3131
|
-
_includeValidateSearch,
|
|
3132
|
-
}: {
|
|
3133
|
-
search: any
|
|
3134
|
-
dest: { search?: unknown }
|
|
3135
|
-
destRoutes: ReadonlyArray<AnyRoute>
|
|
3136
|
-
_includeValidateSearch: boolean | undefined
|
|
3137
|
-
}) {
|
|
3138
|
-
const middleware = buildMiddlewareChain(destRoutes)
|
|
3139
|
-
return middleware(search, dest, _includeValidateSearch ?? false)
|
|
3140
|
-
}
|
|
3141
|
-
|
|
3142
|
-
function buildMiddlewareChain(destRoutes: ReadonlyArray<AnyRoute>) {
|
|
3143
|
-
let dest: BuildNextOptions
|
|
3144
|
-
let includeValidateSearch: boolean | undefined
|
|
2742
|
+
function applySearchMiddleware(
|
|
2743
|
+
search: any,
|
|
2744
|
+
dest: BuildNextOptions,
|
|
2745
|
+
destRoutes: ReadonlyArray<AnyRoute>,
|
|
2746
|
+
includeValidateSearch: boolean | undefined,
|
|
2747
|
+
) {
|
|
3145
2748
|
const middlewares = [] as Array<SearchMiddleware<any>>
|
|
3146
2749
|
|
|
3147
2750
|
for (const route of destRoutes) {
|
|
@@ -3234,15 +2837,7 @@ function buildMiddlewareChain(destRoutes: ReadonlyArray<AnyRoute>) {
|
|
|
3234
2837
|
return (middlewares[index]! as any)({ search: currentSearch, next, meta })
|
|
3235
2838
|
}
|
|
3236
2839
|
|
|
3237
|
-
return
|
|
3238
|
-
search: any,
|
|
3239
|
-
nextDest: BuildNextOptions,
|
|
3240
|
-
_includeValidateSearch: boolean,
|
|
3241
|
-
) {
|
|
3242
|
-
dest = nextDest
|
|
3243
|
-
includeValidateSearch = _includeValidateSearch
|
|
3244
|
-
return applyNext(0, search)
|
|
3245
|
-
}
|
|
2840
|
+
return applyNext(0, search)
|
|
3246
2841
|
}
|
|
3247
2842
|
|
|
3248
2843
|
function findGlobalNotFoundRouteId(
|
|
@@ -3250,26 +2845,41 @@ function findGlobalNotFoundRouteId(
|
|
|
3250
2845
|
routes: ReadonlyArray<AnyRoute>,
|
|
3251
2846
|
) {
|
|
3252
2847
|
if (notFoundMode !== 'root') {
|
|
2848
|
+
let fallback
|
|
3253
2849
|
for (let i = routes.length - 1; i >= 0; i--) {
|
|
3254
2850
|
const route = routes[i]!
|
|
3255
|
-
if (route.
|
|
2851
|
+
if (route.options.notFoundComponent) {
|
|
3256
2852
|
return route.id
|
|
3257
2853
|
}
|
|
2854
|
+
fallback ||= route.children && route.id
|
|
2855
|
+
}
|
|
2856
|
+
if (fallback) {
|
|
2857
|
+
return fallback
|
|
3258
2858
|
}
|
|
3259
2859
|
}
|
|
3260
2860
|
return rootRouteId
|
|
3261
2861
|
}
|
|
3262
2862
|
|
|
2863
|
+
function resolveNextParams(
|
|
2864
|
+
spec: unknown,
|
|
2865
|
+
base: Record<string, unknown>,
|
|
2866
|
+
): Record<string, unknown> {
|
|
2867
|
+
return spec === false || spec === null
|
|
2868
|
+
? Object.create(null)
|
|
2869
|
+
: (spec ?? true) === true
|
|
2870
|
+
? base
|
|
2871
|
+
: Object.assign(base, functionalUpdate(spec as any, base))
|
|
2872
|
+
}
|
|
2873
|
+
|
|
3263
2874
|
function extractStrictParams(
|
|
3264
2875
|
route: AnyRoute,
|
|
3265
2876
|
accumulatedParams: Record<string, unknown>,
|
|
3266
2877
|
) {
|
|
3267
2878
|
const parseParams = route.options.params?.parse ?? route.options.parseParams
|
|
3268
2879
|
if (parseParams) {
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
Object.assign(accumulatedParams, result)
|
|
2880
|
+
Object.assign(
|
|
2881
|
+
accumulatedParams,
|
|
2882
|
+
parseParams(accumulatedParams as Record<string, string>),
|
|
2883
|
+
)
|
|
3274
2884
|
}
|
|
3275
2885
|
}
|