@tanstack/router-core 1.171.14 → 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 -445
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +62 -52
- package/dist/cjs/scroll-restoration.cjs +27 -34
- package/dist/cjs/scroll-restoration.cjs.map +1 -1
- 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 +62 -52
- package/dist/esm/router.js +188 -446
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/scroll-restoration.js +27 -34
- package/dist/esm/scroll-restoration.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 +380 -774
- package/src/scroll-restoration.ts +40 -56
- 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,18 +912,61 @@ 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
|
+
}
|
|
905
922
|
}
|
|
906
923
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
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
|
+
}
|
|
969
|
+
}
|
|
911
970
|
|
|
912
971
|
type LightweightRouteMatchResult = {
|
|
913
972
|
matchedRoutes: ReadonlyArray<AnyRoute>
|
|
@@ -956,6 +1015,43 @@ declare global {
|
|
|
956
1015
|
| undefined
|
|
957
1016
|
}
|
|
958
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
|
+
|
|
959
1055
|
/**
|
|
960
1056
|
* Core, framework-agnostic router engine that powers TanStack Router.
|
|
961
1057
|
*
|
|
@@ -978,14 +1074,17 @@ export class RouterCore<
|
|
|
978
1074
|
)}`
|
|
979
1075
|
_scroll: {
|
|
980
1076
|
next: boolean
|
|
1077
|
+
// True until the current PUSH/REPLACE renders, so its hash owns window scroll.
|
|
1078
|
+
hash?: boolean
|
|
981
1079
|
restoring?: boolean
|
|
982
1080
|
restoration?: boolean
|
|
983
1081
|
reset?: boolean
|
|
984
1082
|
} = { next: true }
|
|
985
|
-
shouldViewTransition?: boolean | ViewTransitionOptions = undefined
|
|
986
|
-
isViewTransitionTypesSupported?: boolean = undefined
|
|
987
1083
|
subscribers = new Set<RouterListener<RouterEvent>>()
|
|
988
|
-
|
|
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> = []
|
|
989
1088
|
|
|
990
1089
|
// Must build in constructor
|
|
991
1090
|
stores!: RouterStores<TRouteTree>
|
|
@@ -1006,7 +1105,7 @@ export class RouterCore<
|
|
|
1006
1105
|
rewrite?: LocationRewrite
|
|
1007
1106
|
origin?: string
|
|
1008
1107
|
latestLocation!: ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
1009
|
-
|
|
1108
|
+
_pendingLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
1010
1109
|
basepath!: string
|
|
1011
1110
|
routeTree!: TRouteTree
|
|
1012
1111
|
routesById!: RoutesById<TRouteTree>
|
|
@@ -1051,24 +1150,19 @@ export class RouterCore<
|
|
|
1051
1150
|
options.protocolAllowlist ?? DEFAULT_PROTOCOL_ALLOWLIST,
|
|
1052
1151
|
})
|
|
1053
1152
|
|
|
1054
|
-
if (typeof document
|
|
1153
|
+
if (!(isServer ?? typeof document === 'undefined')) {
|
|
1055
1154
|
self.__TSR_ROUTER__ = this
|
|
1056
1155
|
}
|
|
1057
1156
|
}
|
|
1058
1157
|
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1158
|
+
startTransition: StartTransitionFn = async (fn) => {
|
|
1159
|
+
fn()
|
|
1160
|
+
return false
|
|
1161
|
+
}
|
|
1064
1162
|
isShell() {
|
|
1065
1163
|
return !!this.options.isShell
|
|
1066
1164
|
}
|
|
1067
1165
|
|
|
1068
|
-
isPrerendering() {
|
|
1069
|
-
return !!this.options.isPrerendering
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
1166
|
update: UpdateFn<
|
|
1073
1167
|
TRouteTree,
|
|
1074
1168
|
TTrailingSlashOption,
|
|
@@ -1094,7 +1188,8 @@ export class RouterCore<
|
|
|
1094
1188
|
...newOptions,
|
|
1095
1189
|
}
|
|
1096
1190
|
|
|
1097
|
-
this.isServer =
|
|
1191
|
+
this.isServer =
|
|
1192
|
+
this.options.isServer ?? isServer ?? typeof document === 'undefined'
|
|
1098
1193
|
|
|
1099
1194
|
this.protocolAllowlist = new Set(this.options.protocolAllowlist)
|
|
1100
1195
|
|
|
@@ -1168,17 +1263,13 @@ export class RouterCore<
|
|
|
1168
1263
|
if (!this.stores && this.latestLocation) {
|
|
1169
1264
|
const config = this.getStoreConfig(this)
|
|
1170
1265
|
this.batch = config.batch
|
|
1171
|
-
this.stores = createRouterStores(
|
|
1172
|
-
getInitialRouterState(this.latestLocation),
|
|
1173
|
-
config,
|
|
1174
|
-
)
|
|
1266
|
+
this.stores = createRouterStores(this.latestLocation, config)
|
|
1175
1267
|
|
|
1176
1268
|
if (!(isServer ?? this.isServer)) {
|
|
1177
1269
|
setupScrollRestoration(this)
|
|
1178
1270
|
}
|
|
1179
1271
|
}
|
|
1180
1272
|
|
|
1181
|
-
let needsLocationUpdate = false
|
|
1182
1273
|
const nextBasepath = this.options.basepath ?? '/'
|
|
1183
1274
|
const nextRewriteOption = this.options.rewrite
|
|
1184
1275
|
const basepathChanged = basepathWasUnset || prevBasepath !== nextBasepath
|
|
@@ -1211,21 +1302,9 @@ export class RouterCore<
|
|
|
1211
1302
|
this.updateLatestLocation()
|
|
1212
1303
|
}
|
|
1213
1304
|
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
if (needsLocationUpdate && this.stores) {
|
|
1218
|
-
this.stores.location.set(this.latestLocation)
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
if (
|
|
1222
|
-
typeof window !== 'undefined' &&
|
|
1223
|
-
'CSS' in window &&
|
|
1224
|
-
typeof window.CSS?.supports === 'function'
|
|
1225
|
-
) {
|
|
1226
|
-
this.isViewTransitionTypesSupported = window.CSS.supports(
|
|
1227
|
-
'selector(:active-view-transition-type(a))',
|
|
1228
|
-
)
|
|
1305
|
+
if (this.stores) {
|
|
1306
|
+
this.stores.location.set(this.latestLocation)
|
|
1307
|
+
}
|
|
1229
1308
|
}
|
|
1230
1309
|
}
|
|
1231
1310
|
|
|
@@ -1296,11 +1375,15 @@ export class RouterCore<
|
|
|
1296
1375
|
}
|
|
1297
1376
|
|
|
1298
1377
|
emit: EmitFn = (routerEvent) => {
|
|
1299
|
-
this.subscribers
|
|
1378
|
+
for (const listener of this.subscribers) {
|
|
1300
1379
|
if (listener.eventType === routerEvent.type) {
|
|
1301
|
-
|
|
1380
|
+
try {
|
|
1381
|
+
listener.fn(routerEvent)
|
|
1382
|
+
} catch (e) {
|
|
1383
|
+
console.error(e)
|
|
1384
|
+
}
|
|
1302
1385
|
}
|
|
1303
|
-
}
|
|
1386
|
+
}
|
|
1304
1387
|
}
|
|
1305
1388
|
|
|
1306
1389
|
/**
|
|
@@ -1408,10 +1491,6 @@ export class RouterCore<
|
|
|
1408
1491
|
return branch
|
|
1409
1492
|
}
|
|
1410
1493
|
|
|
1411
|
-
get looseRoutesById() {
|
|
1412
|
-
return this.routesById as Record<string, AnyRoute>
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
1494
|
matchRoutes: MatchRoutesFn = (
|
|
1416
1495
|
pathnameOrNext: string | ParsedLocation,
|
|
1417
1496
|
locationSearchOrOpts?: AnySchema | MatchRoutesOpts,
|
|
@@ -1430,16 +1509,6 @@ export class RouterCore<
|
|
|
1430
1509
|
return this.matchRoutesInternal(pathnameOrNext, locationSearchOrOpts)
|
|
1431
1510
|
}
|
|
1432
1511
|
|
|
1433
|
-
private getParentContext(parentMatch?: AnyRouteMatch) {
|
|
1434
|
-
const parentMatchId = parentMatch?.id
|
|
1435
|
-
|
|
1436
|
-
const parentContext = !parentMatchId
|
|
1437
|
-
? ((this.options.context as any) ?? undefined)
|
|
1438
|
-
: (parentMatch.context ?? this.options.context ?? undefined)
|
|
1439
|
-
|
|
1440
|
-
return parentContext
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
1512
|
private matchRoutesInternal(
|
|
1444
1513
|
next: ParsedLocation,
|
|
1445
1514
|
opts?: MatchRoutesOpts,
|
|
@@ -1466,18 +1535,19 @@ export class RouterCore<
|
|
|
1466
1535
|
}
|
|
1467
1536
|
}
|
|
1468
1537
|
|
|
1469
|
-
const
|
|
1538
|
+
const _notFoundRouteId = isGlobalNotFound
|
|
1470
1539
|
? findGlobalNotFoundRouteId(this.options.notFoundMode, matchedRoutes)
|
|
1471
1540
|
: undefined
|
|
1472
1541
|
|
|
1473
1542
|
const matches = new Array<AnyRouteMatch>(matchedRoutes.length)
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
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
|
|
1481
1551
|
}
|
|
1482
1552
|
|
|
1483
1553
|
for (let index = 0; index < matchedRoutes.length; index++) {
|
|
@@ -1509,7 +1579,6 @@ export class RouterCore<
|
|
|
1509
1579
|
...strictSearch,
|
|
1510
1580
|
}
|
|
1511
1581
|
strictMatchSearch = { ...parentStrictSearch, ...strictSearch }
|
|
1512
|
-
searchError = undefined
|
|
1513
1582
|
} catch (err: any) {
|
|
1514
1583
|
let searchParamError = err
|
|
1515
1584
|
if (!(err instanceof SearchParamError)) {
|
|
@@ -1527,19 +1596,25 @@ export class RouterCore<
|
|
|
1527
1596
|
searchError = searchParamError
|
|
1528
1597
|
}
|
|
1529
1598
|
}
|
|
1530
|
-
|
|
1531
1599
|
// This is where we need to call route.options.loaderDeps() to get any additional
|
|
1532
1600
|
// deps that the route's loader function might need to run. We need to do this
|
|
1533
1601
|
// before we create the match so that we can pass the deps to the route's
|
|
1534
1602
|
// potential key function which is used to uniquely identify the route match in state
|
|
1535
1603
|
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
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
|
+
}
|
|
1543
1618
|
const { interpolatedPath, usedParams } = interpolatePath({
|
|
1544
1619
|
path: route.fullPath,
|
|
1545
1620
|
params: routeParams,
|
|
@@ -1547,12 +1622,9 @@ export class RouterCore<
|
|
|
1547
1622
|
server: this.isServer,
|
|
1548
1623
|
})
|
|
1549
1624
|
|
|
1550
|
-
//
|
|
1551
|
-
//
|
|
1552
|
-
//
|
|
1553
|
-
|
|
1554
|
-
// Existing matches are matches that are already loaded along with
|
|
1555
|
-
// 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.
|
|
1556
1628
|
const matchId =
|
|
1557
1629
|
// route.id for disambiguation
|
|
1558
1630
|
route.id +
|
|
@@ -1561,13 +1633,16 @@ export class RouterCore<
|
|
|
1561
1633
|
// explicit deps
|
|
1562
1634
|
loaderDepsHash
|
|
1563
1635
|
|
|
1564
|
-
const
|
|
1565
|
-
|
|
1566
|
-
|
|
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))
|
|
1567
1642
|
|
|
1568
1643
|
const strictParams = existingMatch?._strictParams ?? usedParams
|
|
1569
1644
|
|
|
1570
|
-
let paramsError: unknown
|
|
1645
|
+
let paramsError: unknown
|
|
1571
1646
|
|
|
1572
1647
|
if (!existingMatch) {
|
|
1573
1648
|
try {
|
|
@@ -1603,15 +1678,10 @@ export class RouterCore<
|
|
|
1603
1678
|
? nullReplaceEqualDeep(previousMatch.search, preMatchSearch)
|
|
1604
1679
|
: nullReplaceEqualDeep(existingMatch.search, preMatchSearch),
|
|
1605
1680
|
_strictSearch: strictMatchSearch,
|
|
1681
|
+
searchError,
|
|
1606
1682
|
}
|
|
1607
1683
|
} else {
|
|
1608
|
-
const status =
|
|
1609
|
-
route.options.loader ||
|
|
1610
|
-
route.options.beforeLoad ||
|
|
1611
|
-
route.lazyFn ||
|
|
1612
|
-
routeNeedsPreload(route)
|
|
1613
|
-
? 'pending'
|
|
1614
|
-
: 'success'
|
|
1684
|
+
const status = routeNeedsLoad(route) ? 'pending' : 'success'
|
|
1615
1685
|
|
|
1616
1686
|
match = {
|
|
1617
1687
|
id: matchId,
|
|
@@ -1626,96 +1696,42 @@ export class RouterCore<
|
|
|
1626
1696
|
? nullReplaceEqualDeep(previousMatch.search, preMatchSearch)
|
|
1627
1697
|
: preMatchSearch,
|
|
1628
1698
|
_strictSearch: strictMatchSearch,
|
|
1629
|
-
searchError
|
|
1699
|
+
searchError,
|
|
1630
1700
|
status,
|
|
1631
1701
|
isFetching: false,
|
|
1632
1702
|
error: undefined,
|
|
1633
1703
|
paramsError,
|
|
1634
|
-
__routeContext: undefined,
|
|
1635
|
-
_nonReactive: {
|
|
1636
|
-
loadPromise: createControlledPromise(),
|
|
1637
|
-
},
|
|
1638
|
-
__beforeLoadContext: undefined,
|
|
1639
1704
|
context: {},
|
|
1640
|
-
abortController: new AbortController(),
|
|
1641
|
-
fetchCount: 0,
|
|
1705
|
+
abortController: opts?._controller ?? new AbortController(),
|
|
1642
1706
|
cause,
|
|
1643
1707
|
loaderDeps: previousMatch
|
|
1644
1708
|
? replaceEqualDeep(previousMatch.loaderDeps, loaderDeps)
|
|
1645
1709
|
: loaderDeps,
|
|
1646
1710
|
invalid: false,
|
|
1647
1711
|
preload: false,
|
|
1648
|
-
links: undefined,
|
|
1649
|
-
scripts: undefined,
|
|
1650
|
-
headScripts: undefined,
|
|
1651
|
-
meta: undefined,
|
|
1652
1712
|
staticData: route.options.staticData || {},
|
|
1653
1713
|
fullPath: route.fullPath,
|
|
1654
1714
|
}
|
|
1655
1715
|
}
|
|
1656
1716
|
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
// update the searchError if there is one
|
|
1663
|
-
match.searchError = searchError
|
|
1664
|
-
|
|
1665
|
-
const parentContext = this.getParentContext(parentMatch)
|
|
1666
|
-
|
|
1667
|
-
match.context = {
|
|
1668
|
-
...parentContext,
|
|
1669
|
-
...match.__routeContext,
|
|
1670
|
-
...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
|
|
1671
1721
|
}
|
|
1722
|
+
match._notFound = _notFound
|
|
1672
1723
|
|
|
1673
1724
|
matches[index] = match
|
|
1674
1725
|
}
|
|
1675
1726
|
|
|
1676
1727
|
for (let index = 0; index < matches.length; index++) {
|
|
1677
1728
|
const match = matches[index]!
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
? nullReplaceEqualDeep(previousMatch.params, routeParams)
|
|
1685
|
-
: routeParams
|
|
1686
|
-
|
|
1687
|
-
if (!existingMatch) {
|
|
1688
|
-
const parentMatch = matches[index - 1]
|
|
1689
|
-
const parentContext = this.getParentContext(parentMatch)
|
|
1690
|
-
|
|
1691
|
-
// Update the match's context
|
|
1692
|
-
|
|
1693
|
-
if (route.options.context) {
|
|
1694
|
-
const contextFnContext: RouteContextOptions<any, any, any, any, any> =
|
|
1695
|
-
{
|
|
1696
|
-
deps: match.loaderDeps,
|
|
1697
|
-
params: match.params,
|
|
1698
|
-
context: parentContext ?? {},
|
|
1699
|
-
location: next,
|
|
1700
|
-
navigate: (opts: any) =>
|
|
1701
|
-
this.navigate({ ...opts, _fromLocation: next }),
|
|
1702
|
-
buildLocation: this.buildLocation,
|
|
1703
|
-
cause: match.cause,
|
|
1704
|
-
abortController: match.abortController,
|
|
1705
|
-
preload: !!match.preload,
|
|
1706
|
-
matches,
|
|
1707
|
-
routeId: route.id,
|
|
1708
|
-
}
|
|
1709
|
-
// Get the route context
|
|
1710
|
-
match.__routeContext =
|
|
1711
|
-
route.options.context(contextFnContext) ?? undefined
|
|
1712
|
-
}
|
|
1713
|
-
|
|
1714
|
-
match.context = {
|
|
1715
|
-
...parentContext,
|
|
1716
|
-
...match.__routeContext,
|
|
1717
|
-
...match.__beforeLoadContext,
|
|
1718
|
-
}
|
|
1729
|
+
match.params =
|
|
1730
|
+
match.cause === 'stay'
|
|
1731
|
+
? nullReplaceEqualDeep(match.params, routeParams)
|
|
1732
|
+
: routeParams
|
|
1733
|
+
if (opts?._controller) {
|
|
1734
|
+
match.context = {}
|
|
1719
1735
|
}
|
|
1720
1736
|
}
|
|
1721
1737
|
|
|
@@ -1723,22 +1739,35 @@ export class RouterCore<
|
|
|
1723
1739
|
}
|
|
1724
1740
|
|
|
1725
1741
|
getMatchedRoutes: GetMatchRoutesFn = (pathname) => {
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
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
|
+
}
|
|
1731
1756
|
}
|
|
1732
1757
|
|
|
1733
1758
|
/**
|
|
1734
1759
|
* Lightweight route matching for buildLocation.
|
|
1735
1760
|
* Only computes fullPath, accumulated search, and params - skipping expensive
|
|
1736
|
-
* operations like AbortController,
|
|
1761
|
+
* operations like AbortController, loaderDeps, and full match objects.
|
|
1737
1762
|
*/
|
|
1738
1763
|
private matchRoutesLightweight(
|
|
1739
1764
|
location: ParsedLocation,
|
|
1740
1765
|
): LightweightRouteMatchResult {
|
|
1741
|
-
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
|
|
1742
1771
|
const cached = this.lightweightCache.get(location)
|
|
1743
1772
|
if (cached && cached[0] === lastStateMatchId) {
|
|
1744
1773
|
return cached[1]
|
|
@@ -1772,8 +1801,6 @@ export class RouterCore<
|
|
|
1772
1801
|
}
|
|
1773
1802
|
|
|
1774
1803
|
// Determine params: reuse from state if possible, otherwise parse
|
|
1775
|
-
const lastStateMatch =
|
|
1776
|
-
lastStateMatchId && this.stores.matchStores.get(lastStateMatchId)?.get()
|
|
1777
1804
|
const canReuseParams =
|
|
1778
1805
|
lastStateMatch &&
|
|
1779
1806
|
lastStateMatch.routeId === lastRoute.id &&
|
|
@@ -1808,37 +1835,6 @@ export class RouterCore<
|
|
|
1808
1835
|
return result
|
|
1809
1836
|
}
|
|
1810
1837
|
|
|
1811
|
-
cancelMatch = (id: string) => {
|
|
1812
|
-
const match = this.getMatch(id)
|
|
1813
|
-
|
|
1814
|
-
if (!match) return
|
|
1815
|
-
|
|
1816
|
-
match.abortController.abort()
|
|
1817
|
-
clearTimeout(match._nonReactive.pendingTimeout)
|
|
1818
|
-
match._nonReactive.pendingTimeout = undefined
|
|
1819
|
-
}
|
|
1820
|
-
|
|
1821
|
-
cancelMatches = () => {
|
|
1822
|
-
this.stores.pendingIds.get().forEach((matchId) => {
|
|
1823
|
-
this.cancelMatch(matchId)
|
|
1824
|
-
})
|
|
1825
|
-
|
|
1826
|
-
this.stores.matchesId.get().forEach((matchId) => {
|
|
1827
|
-
if (this.stores.pendingMatchStores.has(matchId)) {
|
|
1828
|
-
return
|
|
1829
|
-
}
|
|
1830
|
-
|
|
1831
|
-
const match = this.stores.matchStores.get(matchId)?.get()
|
|
1832
|
-
if (!match) {
|
|
1833
|
-
return
|
|
1834
|
-
}
|
|
1835
|
-
|
|
1836
|
-
if (match.status === 'pending' || match.isFetching === 'loader') {
|
|
1837
|
-
this.cancelMatch(matchId)
|
|
1838
|
-
}
|
|
1839
|
-
})
|
|
1840
|
-
}
|
|
1841
|
-
|
|
1842
1838
|
/**
|
|
1843
1839
|
* Build the next ParsedLocation from navigation options without committing.
|
|
1844
1840
|
* Resolves `to`/`from`, params/search/hash/state, applies search validation
|
|
@@ -1854,7 +1850,7 @@ export class RouterCore<
|
|
|
1854
1850
|
): ParsedLocation => {
|
|
1855
1851
|
// We allow the caller to override the current location
|
|
1856
1852
|
const currentLocation =
|
|
1857
|
-
dest._fromLocation || this.
|
|
1853
|
+
dest._fromLocation || this._pendingLocation || this.latestLocation
|
|
1858
1854
|
|
|
1859
1855
|
// Use lightweight matching - only computes what buildLocation needs
|
|
1860
1856
|
// (fullPath, search, params) without creating full match objects
|
|
@@ -1909,15 +1905,7 @@ export class RouterCore<
|
|
|
1909
1905
|
: sourcePath
|
|
1910
1906
|
|
|
1911
1907
|
// Resolve the next params
|
|
1912
|
-
const nextParams =
|
|
1913
|
-
dest.params === false || dest.params === null
|
|
1914
|
-
? Object.create(null)
|
|
1915
|
-
: (dest.params ?? true) === true
|
|
1916
|
-
? fromParams
|
|
1917
|
-
: Object.assign(
|
|
1918
|
-
fromParams,
|
|
1919
|
-
functionalUpdate(dest.params as any, fromParams),
|
|
1920
|
-
)
|
|
1908
|
+
const nextParams = resolveNextParams(dest.params, fromParams)
|
|
1921
1909
|
|
|
1922
1910
|
const destRoute = this.routesByPath[
|
|
1923
1911
|
trimPathRight(nextTo) as keyof typeof this.routesByPath
|
|
@@ -2015,12 +2003,12 @@ export class RouterCore<
|
|
|
2015
2003
|
nextSearch = validatedSearch
|
|
2016
2004
|
}
|
|
2017
2005
|
|
|
2018
|
-
nextSearch = applySearchMiddleware(
|
|
2019
|
-
|
|
2006
|
+
nextSearch = applySearchMiddleware(
|
|
2007
|
+
nextSearch,
|
|
2020
2008
|
dest,
|
|
2021
2009
|
destRoutes,
|
|
2022
|
-
|
|
2023
|
-
|
|
2010
|
+
opts._includeValidateSearch,
|
|
2011
|
+
)
|
|
2024
2012
|
|
|
2025
2013
|
// Replace the equal deep
|
|
2026
2014
|
nextSearch = nullReplaceEqualDeep(fromSearch, nextSearch)
|
|
@@ -2120,12 +2108,7 @@ export class RouterCore<
|
|
|
2120
2108
|
|
|
2121
2109
|
// If mask has a params function, call it with the matched params as context
|
|
2122
2110
|
// Otherwise, use the matched params or the provided params value
|
|
2123
|
-
const nextParams =
|
|
2124
|
-
maskParams === false || maskParams === null
|
|
2125
|
-
? Object.create(null)
|
|
2126
|
-
: (maskParams ?? true) === true
|
|
2127
|
-
? params
|
|
2128
|
-
: Object.assign(params, functionalUpdate(maskParams, params))
|
|
2111
|
+
const nextParams = resolveNextParams(maskParams, params)
|
|
2129
2112
|
|
|
2130
2113
|
maskedDest = {
|
|
2131
2114
|
from: opts.from,
|
|
@@ -2154,7 +2137,7 @@ export class RouterCore<
|
|
|
2154
2137
|
return buildWithMatches(opts)
|
|
2155
2138
|
}
|
|
2156
2139
|
|
|
2157
|
-
|
|
2140
|
+
_commitPromise: (Promise<void> & { resolve: () => void }) | undefined
|
|
2158
2141
|
|
|
2159
2142
|
/**
|
|
2160
2143
|
* Commit a previously built location to history (push/replace), optionally
|
|
@@ -2166,38 +2149,27 @@ export class RouterCore<
|
|
|
2166
2149
|
...next
|
|
2167
2150
|
}) => {
|
|
2168
2151
|
let historyAction: HistoryAction | undefined
|
|
2169
|
-
const
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
'__TSR_key',
|
|
2176
|
-
'__TSR_index',
|
|
2177
|
-
'__hashScrollIntoViewOptions',
|
|
2178
|
-
] as const
|
|
2179
|
-
ignoredProps.forEach((prop) => {
|
|
2180
|
-
;(next.state as any)[prop] = this.latestLocation.state[prop]
|
|
2181
|
-
})
|
|
2182
|
-
const isEqual = deepEqual(next.state, this.latestLocation.state)
|
|
2183
|
-
ignoredProps.forEach((prop) => {
|
|
2184
|
-
delete next.state[prop]
|
|
2185
|
-
})
|
|
2186
|
-
return isEqual
|
|
2187
|
-
}
|
|
2188
|
-
|
|
2189
|
-
const isSameUrl =
|
|
2190
|
-
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
|
+
)
|
|
2191
2158
|
|
|
2192
|
-
|
|
2193
|
-
|
|
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()
|
|
2194
2166
|
previousCommitPromise?.resolve()
|
|
2195
|
-
|
|
2196
|
-
|
|
2167
|
+
}
|
|
2168
|
+
this._commitPromise = commitPromise
|
|
2197
2169
|
|
|
2198
2170
|
// Don't commit to history if nothing changed
|
|
2199
|
-
if (
|
|
2200
|
-
this.load()
|
|
2171
|
+
if (isSameLocation) {
|
|
2172
|
+
this.load({ _dedupe: true })
|
|
2201
2173
|
} else {
|
|
2202
2174
|
let {
|
|
2203
2175
|
// eslint-disable-next-line prefer-const
|
|
@@ -2248,21 +2220,17 @@ export class RouterCore<
|
|
|
2248
2220
|
nextHistory.state,
|
|
2249
2221
|
{ ignoreBlocker },
|
|
2250
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
|
+
}
|
|
2251
2229
|
}
|
|
2252
2230
|
|
|
2253
2231
|
this._scroll.next = next.resetScroll ?? true
|
|
2254
2232
|
|
|
2255
|
-
|
|
2256
|
-
this.load(
|
|
2257
|
-
historyAction
|
|
2258
|
-
? {
|
|
2259
|
-
action: { type: historyAction },
|
|
2260
|
-
}
|
|
2261
|
-
: undefined,
|
|
2262
|
-
)
|
|
2263
|
-
}
|
|
2264
|
-
|
|
2265
|
-
return this.commitLocationPromise
|
|
2233
|
+
return this._commitPromise
|
|
2266
2234
|
}
|
|
2267
2235
|
|
|
2268
2236
|
/** Convenience helper: build a location from options, then commit it. */
|
|
@@ -2272,9 +2240,11 @@ export class RouterCore<
|
|
|
2272
2240
|
hashScrollIntoView,
|
|
2273
2241
|
viewTransition,
|
|
2274
2242
|
ignoreBlocker,
|
|
2243
|
+
_redirects,
|
|
2275
2244
|
href,
|
|
2276
2245
|
...rest
|
|
2277
|
-
}: BuildNextOptions &
|
|
2246
|
+
}: BuildNextOptions &
|
|
2247
|
+
CommitLocationOptions & { _redirects?: number } = {}) => {
|
|
2278
2248
|
if (href) {
|
|
2279
2249
|
const currentIndex = this.history.location.state.__TSR_index
|
|
2280
2250
|
|
|
@@ -2298,8 +2268,12 @@ export class RouterCore<
|
|
|
2298
2268
|
...(rest as any),
|
|
2299
2269
|
_includeValidateSearch: true,
|
|
2300
2270
|
})
|
|
2271
|
+
if (_redirects) {
|
|
2272
|
+
;(location as typeof location & { _redirects?: number })._redirects =
|
|
2273
|
+
_redirects
|
|
2274
|
+
}
|
|
2301
2275
|
|
|
2302
|
-
this.
|
|
2276
|
+
this._pendingLocation = location as ParsedLocation<
|
|
2303
2277
|
FullSearchSchema<TRouteTree>
|
|
2304
2278
|
>
|
|
2305
2279
|
|
|
@@ -2315,8 +2289,8 @@ export class RouterCore<
|
|
|
2315
2289
|
// Clear pending location after commit starts
|
|
2316
2290
|
// We do this on next microtask to allow synchronous navigate calls to chain
|
|
2317
2291
|
queueMicrotask(() => {
|
|
2318
|
-
if (this.
|
|
2319
|
-
this.
|
|
2292
|
+
if (this._pendingLocation === location) {
|
|
2293
|
+
this._pendingLocation = undefined
|
|
2320
2294
|
}
|
|
2321
2295
|
})
|
|
2322
2296
|
|
|
@@ -2412,254 +2386,17 @@ export class RouterCore<
|
|
|
2412
2386
|
})
|
|
2413
2387
|
}
|
|
2414
2388
|
|
|
2415
|
-
latestLoadPromise: undefined | Promise<void>
|
|
2416
|
-
|
|
2417
|
-
beforeLoad = () => {
|
|
2418
|
-
// Cancel any pending matches
|
|
2419
|
-
this.cancelMatches()
|
|
2420
|
-
this.updateLatestLocation()
|
|
2421
|
-
|
|
2422
|
-
if (isServer ?? this.isServer) {
|
|
2423
|
-
// for SPAs on the initial load, this is handled by the Transitioner
|
|
2424
|
-
const nextLocation = this.buildLocation({
|
|
2425
|
-
to: this.latestLocation.pathname,
|
|
2426
|
-
search: true,
|
|
2427
|
-
params: true,
|
|
2428
|
-
hash: true,
|
|
2429
|
-
state: true,
|
|
2430
|
-
_includeValidateSearch: true,
|
|
2431
|
-
})
|
|
2432
|
-
|
|
2433
|
-
// Check if location changed - origin check is unnecessary since buildLocation
|
|
2434
|
-
// always uses this.origin when constructing URLs
|
|
2435
|
-
if (this.latestLocation.publicHref !== nextLocation.publicHref) {
|
|
2436
|
-
const href = this.getParsedLocationHref(nextLocation)
|
|
2437
|
-
if (nextLocation.external) {
|
|
2438
|
-
throw redirect({ href })
|
|
2439
|
-
} else {
|
|
2440
|
-
throw redirect({ href, _builtLocation: nextLocation })
|
|
2441
|
-
}
|
|
2442
|
-
}
|
|
2443
|
-
}
|
|
2444
|
-
|
|
2445
|
-
// Match the routes
|
|
2446
|
-
const pendingMatches = this.matchRoutes(this.latestLocation)
|
|
2447
|
-
|
|
2448
|
-
const nextCachedMatches = this.stores.cachedMatches
|
|
2449
|
-
.get()
|
|
2450
|
-
.filter((d) => !pendingMatches.some((e) => e.id === d.id))
|
|
2451
|
-
|
|
2452
|
-
// Ingest the new matches
|
|
2453
|
-
this.batch(() => {
|
|
2454
|
-
this.stores.status.set('pending')
|
|
2455
|
-
this.stores.statusCode.set(200)
|
|
2456
|
-
this.stores.isLoading.set(true)
|
|
2457
|
-
this.stores.location.set(this.latestLocation)
|
|
2458
|
-
this.stores.setPending(pendingMatches)
|
|
2459
|
-
// If a cached match moved to pending matches, remove it from cached matches
|
|
2460
|
-
this.stores.setCached(nextCachedMatches)
|
|
2461
|
-
})
|
|
2462
|
-
}
|
|
2463
|
-
|
|
2464
2389
|
load: LoadFn = async (opts): Promise<void> => {
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
let notFound: NotFoundError | undefined
|
|
2468
|
-
let loadPromise: Promise<void>
|
|
2469
|
-
const previousLocation =
|
|
2470
|
-
this.stores.resolvedLocation.get() ?? this.stores.location.get()
|
|
2471
|
-
|
|
2472
|
-
// eslint-disable-next-line prefer-const
|
|
2473
|
-
loadPromise = new Promise<void>((resolve) => {
|
|
2474
|
-
this.startTransition(async () => {
|
|
2475
|
-
try {
|
|
2476
|
-
this.beforeLoad()
|
|
2477
|
-
if (historyAction) {
|
|
2478
|
-
locationHistoryActions.set(this.latestLocation, historyAction)
|
|
2479
|
-
} else {
|
|
2480
|
-
locationHistoryActions.delete(this.latestLocation)
|
|
2481
|
-
}
|
|
2482
|
-
const next = this.latestLocation
|
|
2483
|
-
const prevLocation = this.stores.resolvedLocation.get()
|
|
2484
|
-
const locationChangeInfo = getLocationChangeInfo(next, prevLocation)
|
|
2485
|
-
|
|
2486
|
-
if (!this.stores.redirect.get()) {
|
|
2487
|
-
this.emit({
|
|
2488
|
-
type: 'onBeforeNavigate',
|
|
2489
|
-
...locationChangeInfo,
|
|
2490
|
-
})
|
|
2491
|
-
}
|
|
2492
|
-
|
|
2493
|
-
this.emit({
|
|
2494
|
-
type: 'onBeforeLoad',
|
|
2495
|
-
...locationChangeInfo,
|
|
2496
|
-
})
|
|
2497
|
-
|
|
2498
|
-
await loadMatches({
|
|
2499
|
-
router: this,
|
|
2500
|
-
sync: opts?.sync,
|
|
2501
|
-
forceStaleReload: previousLocation.href === next.href,
|
|
2502
|
-
matches: this.stores.pendingMatches.get(),
|
|
2503
|
-
location: next,
|
|
2504
|
-
updateMatch: this.updateMatch,
|
|
2505
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
2506
|
-
onReady: async () => {
|
|
2507
|
-
// Wrap batch in framework-specific transition wrapper (e.g., Solid's startTransition)
|
|
2508
|
-
this.startTransition(() => {
|
|
2509
|
-
this.startViewTransition(async () => {
|
|
2510
|
-
// this.viewTransitionPromise = createControlledPromise<true>()
|
|
2511
|
-
|
|
2512
|
-
// Commit the pending matches. If a previous match was
|
|
2513
|
-
// removed, place it in the cachedMatches
|
|
2514
|
-
//
|
|
2515
|
-
// exitingMatches uses match.id (routeId + params + loaderDeps) so
|
|
2516
|
-
// navigating /foo?page=1 → /foo?page=2 correctly caches the page=1 entry.
|
|
2517
|
-
let exitingMatches: Array<AnyRouteMatch> | null = null
|
|
2518
|
-
|
|
2519
|
-
// Lifecycle-hook identity uses routeId only so that navigating between
|
|
2520
|
-
// different params/deps of the same route fires onStay (not onLeave+onEnter).
|
|
2521
|
-
let hookExitingMatches: Array<AnyRouteMatch> | null = null
|
|
2522
|
-
let hookEnteringMatches: Array<AnyRouteMatch> | null = null
|
|
2523
|
-
let hookStayingMatches: Array<AnyRouteMatch> | null = null
|
|
2524
|
-
|
|
2525
|
-
this.batch(() => {
|
|
2526
|
-
const pendingMatches = this.stores.pendingMatches.get()
|
|
2527
|
-
const mountPending = pendingMatches.length
|
|
2528
|
-
const currentMatches = this.stores.matches.get()
|
|
2529
|
-
|
|
2530
|
-
exitingMatches = mountPending
|
|
2531
|
-
? currentMatches.filter(
|
|
2532
|
-
(match) =>
|
|
2533
|
-
!this.stores.pendingMatchStores.has(match.id),
|
|
2534
|
-
)
|
|
2535
|
-
: null
|
|
2536
|
-
|
|
2537
|
-
// Lifecycle-hook identity: routeId only (route presence in tree)
|
|
2538
|
-
// Build routeId sets from pools to avoid derived stores.
|
|
2539
|
-
const pendingRouteIds = new Set<string>()
|
|
2540
|
-
for (const s of this.stores.pendingMatchStores.values()) {
|
|
2541
|
-
if (s.routeId) pendingRouteIds.add(s.routeId)
|
|
2542
|
-
}
|
|
2543
|
-
const activeRouteIds = new Set<string>()
|
|
2544
|
-
for (const s of this.stores.matchStores.values()) {
|
|
2545
|
-
if (s.routeId) activeRouteIds.add(s.routeId)
|
|
2546
|
-
}
|
|
2547
|
-
|
|
2548
|
-
hookExitingMatches = mountPending
|
|
2549
|
-
? currentMatches.filter(
|
|
2550
|
-
(match) => !pendingRouteIds.has(match.routeId),
|
|
2551
|
-
)
|
|
2552
|
-
: null
|
|
2553
|
-
hookEnteringMatches = mountPending
|
|
2554
|
-
? pendingMatches.filter(
|
|
2555
|
-
(match) => !activeRouteIds.has(match.routeId),
|
|
2556
|
-
)
|
|
2557
|
-
: null
|
|
2558
|
-
hookStayingMatches = mountPending
|
|
2559
|
-
? pendingMatches.filter((match) =>
|
|
2560
|
-
activeRouteIds.has(match.routeId),
|
|
2561
|
-
)
|
|
2562
|
-
: currentMatches
|
|
2563
|
-
|
|
2564
|
-
this.stores.isLoading.set(false)
|
|
2565
|
-
this.stores.loadedAt.set(Date.now())
|
|
2566
|
-
/**
|
|
2567
|
-
* When committing new matches, cache any exiting matches that are still usable.
|
|
2568
|
-
* Routes that resolved with `status: 'error'` or `status: 'notFound'` are
|
|
2569
|
-
* deliberately excluded from `cachedMatches` so that subsequent invalidations
|
|
2570
|
-
* or reloads re-run their loaders instead of reusing the failed/not-found data.
|
|
2571
|
-
*/
|
|
2572
|
-
if (mountPending) {
|
|
2573
|
-
this.stores.setMatches(pendingMatches)
|
|
2574
|
-
this.stores.setPending([])
|
|
2575
|
-
this.stores.setCached([
|
|
2576
|
-
...this.stores.cachedMatches.get(),
|
|
2577
|
-
...exitingMatches!.filter(
|
|
2578
|
-
(d) =>
|
|
2579
|
-
d.status !== 'error' &&
|
|
2580
|
-
d.status !== 'notFound' &&
|
|
2581
|
-
d.status !== 'redirected',
|
|
2582
|
-
),
|
|
2583
|
-
])
|
|
2584
|
-
this.clearExpiredCache()
|
|
2585
|
-
}
|
|
2586
|
-
})
|
|
2587
|
-
|
|
2588
|
-
//
|
|
2589
|
-
for (const [matches, hook] of [
|
|
2590
|
-
[hookExitingMatches, 'onLeave'],
|
|
2591
|
-
[hookEnteringMatches, 'onEnter'],
|
|
2592
|
-
[hookStayingMatches, 'onStay'],
|
|
2593
|
-
] as const) {
|
|
2594
|
-
if (!matches) continue
|
|
2595
|
-
for (const match of matches as Array<AnyRouteMatch>) {
|
|
2596
|
-
this.looseRoutesById[match.routeId]!.options[hook]?.(
|
|
2597
|
-
match,
|
|
2598
|
-
)
|
|
2599
|
-
}
|
|
2600
|
-
}
|
|
2601
|
-
})
|
|
2602
|
-
})
|
|
2603
|
-
},
|
|
2604
|
-
})
|
|
2605
|
-
} catch (err) {
|
|
2606
|
-
if (isRedirect(err)) {
|
|
2607
|
-
redirect = err
|
|
2608
|
-
if (!(isServer ?? this.isServer)) {
|
|
2609
|
-
this.navigate({
|
|
2610
|
-
...redirect.options,
|
|
2611
|
-
replace: true,
|
|
2612
|
-
ignoreBlocker: true,
|
|
2613
|
-
})
|
|
2614
|
-
}
|
|
2615
|
-
} else if (isNotFound(err)) {
|
|
2616
|
-
notFound = err
|
|
2617
|
-
}
|
|
2618
|
-
|
|
2619
|
-
const nextStatusCode = redirect
|
|
2620
|
-
? redirect.status
|
|
2621
|
-
: notFound
|
|
2622
|
-
? 404
|
|
2623
|
-
: this.stores.matches.get().some((d) => d.status === 'error')
|
|
2624
|
-
? 500
|
|
2625
|
-
: 200
|
|
2626
|
-
|
|
2627
|
-
this.batch(() => {
|
|
2628
|
-
this.stores.statusCode.set(nextStatusCode)
|
|
2629
|
-
this.stores.redirect.set(redirect)
|
|
2630
|
-
})
|
|
2631
|
-
}
|
|
2632
|
-
|
|
2633
|
-
if (this.latestLoadPromise === loadPromise) {
|
|
2634
|
-
this.commitLocationPromise?.resolve()
|
|
2635
|
-
this.latestLoadPromise = undefined
|
|
2636
|
-
this.commitLocationPromise = undefined
|
|
2637
|
-
}
|
|
2638
|
-
|
|
2639
|
-
resolve()
|
|
2640
|
-
})
|
|
2641
|
-
})
|
|
2642
|
-
|
|
2643
|
-
this.latestLoadPromise = loadPromise
|
|
2644
|
-
|
|
2645
|
-
await loadPromise
|
|
2646
|
-
|
|
2647
|
-
while (
|
|
2648
|
-
(this.latestLoadPromise as any) &&
|
|
2649
|
-
loadPromise !== this.latestLoadPromise
|
|
2650
|
-
) {
|
|
2651
|
-
await this.latestLoadPromise
|
|
2390
|
+
if (isServer ?? this.isServer) {
|
|
2391
|
+
return loadServerRoute(this, opts)
|
|
2652
2392
|
}
|
|
2653
2393
|
|
|
2654
|
-
|
|
2655
|
-
if (
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
newStatusCode = 500
|
|
2659
|
-
}
|
|
2660
|
-
if (newStatusCode !== undefined) {
|
|
2661
|
-
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'
|
|
2662
2398
|
}
|
|
2399
|
+
await loadClientRoute(this, opts)
|
|
2663
2400
|
}
|
|
2664
2401
|
|
|
2665
2402
|
startViewTransition = (fn: () => Promise<void>) => {
|
|
@@ -2674,9 +2411,8 @@ export class RouterCore<
|
|
|
2674
2411
|
// Attempt to start a view transition (or just apply the changes if we can't)
|
|
2675
2412
|
if (
|
|
2676
2413
|
shouldViewTransition &&
|
|
2677
|
-
typeof document
|
|
2678
|
-
|
|
2679
|
-
typeof document.startViewTransition === 'function'
|
|
2414
|
+
!(isServer ?? typeof document === 'undefined') &&
|
|
2415
|
+
typeof (document as any).startViewTransition === 'function'
|
|
2680
2416
|
) {
|
|
2681
2417
|
// lib.dom.ts doesn't support viewTransition types variant yet.
|
|
2682
2418
|
// TODO: Fix this when dom types are updated
|
|
@@ -2684,7 +2420,7 @@ export class RouterCore<
|
|
|
2684
2420
|
|
|
2685
2421
|
if (
|
|
2686
2422
|
typeof shouldViewTransition === 'object' &&
|
|
2687
|
-
|
|
2423
|
+
window.CSS?.supports?.('selector(:active-view-transition-type(a))')
|
|
2688
2424
|
) {
|
|
2689
2425
|
const next = this.latestLocation
|
|
2690
2426
|
const prevLocation = this.stores.resolvedLocation.get()
|
|
@@ -2697,8 +2433,7 @@ export class RouterCore<
|
|
|
2697
2433
|
: shouldViewTransition.types
|
|
2698
2434
|
|
|
2699
2435
|
if (resolvedViewTransitionTypes === false) {
|
|
2700
|
-
fn()
|
|
2701
|
-
return
|
|
2436
|
+
return fn()
|
|
2702
2437
|
}
|
|
2703
2438
|
|
|
2704
2439
|
startViewTransitionParams = {
|
|
@@ -2709,58 +2444,19 @@ export class RouterCore<
|
|
|
2709
2444
|
startViewTransitionParams = fn
|
|
2710
2445
|
}
|
|
2711
2446
|
|
|
2712
|
-
document.startViewTransition(startViewTransitionParams)
|
|
2713
|
-
|
|
2714
|
-
fn()
|
|
2447
|
+
return (document as any).startViewTransition(startViewTransitionParams)
|
|
2448
|
+
.updateCallbackDone
|
|
2715
2449
|
}
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
updateMatch: UpdateMatchFn = (id, updater) => {
|
|
2719
|
-
this.startTransition(() => {
|
|
2720
|
-
const pendingMatch = this.stores.pendingMatchStores.get(id)
|
|
2721
|
-
if (pendingMatch) {
|
|
2722
|
-
pendingMatch.set(updater)
|
|
2723
|
-
return
|
|
2724
|
-
}
|
|
2725
|
-
|
|
2726
|
-
const activeMatch = this.stores.matchStores.get(id)
|
|
2727
|
-
if (activeMatch) {
|
|
2728
|
-
activeMatch.set(updater)
|
|
2729
|
-
return
|
|
2730
|
-
}
|
|
2731
|
-
|
|
2732
|
-
const cachedMatch = this.stores.cachedMatchStores.get(id)
|
|
2733
|
-
if (cachedMatch) {
|
|
2734
|
-
const next = updater(cachedMatch.get())
|
|
2735
|
-
if (next.status === 'redirected') {
|
|
2736
|
-
const deleted = this.stores.cachedMatchStores.delete(id)
|
|
2737
|
-
if (deleted) {
|
|
2738
|
-
this.stores.cachedIds.set((prev) =>
|
|
2739
|
-
prev.filter((matchId) => matchId !== id),
|
|
2740
|
-
)
|
|
2741
|
-
}
|
|
2742
|
-
} else {
|
|
2743
|
-
cachedMatch.set(next)
|
|
2744
|
-
}
|
|
2745
|
-
}
|
|
2746
|
-
})
|
|
2747
|
-
}
|
|
2748
|
-
|
|
2749
|
-
getMatch: GetMatchFn = (matchId: string): AnyRouteMatch | undefined => {
|
|
2750
|
-
return (
|
|
2751
|
-
this.stores.cachedMatchStores.get(matchId)?.get() ??
|
|
2752
|
-
this.stores.pendingMatchStores.get(matchId)?.get() ??
|
|
2753
|
-
this.stores.matchStores.get(matchId)?.get()
|
|
2754
|
-
)
|
|
2450
|
+
return fn()
|
|
2755
2451
|
}
|
|
2756
2452
|
|
|
2757
2453
|
/**
|
|
2758
2454
|
* Invalidate the current matches and optionally force them back into a pending state.
|
|
2759
2455
|
*
|
|
2760
2456
|
* - Marks all matches that pass the optional `filter` as `invalid: true`.
|
|
2761
|
-
*
|
|
2762
|
-
*
|
|
2763
|
-
*
|
|
2457
|
+
*
|
|
2458
|
+
* The next load decides when to publish pending UI, so invalidation does not
|
|
2459
|
+
* mutate the currently rendered status.
|
|
2764
2460
|
*/
|
|
2765
2461
|
invalidate: InvalidateFn<
|
|
2766
2462
|
RouterCore<
|
|
@@ -2771,44 +2467,54 @@ export class RouterCore<
|
|
|
2771
2467
|
TDehydrated
|
|
2772
2468
|
>
|
|
2773
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
|
|
2774
2479
|
const invalidate = (d: MakeRouteMatch<TRouteTree>) => {
|
|
2775
|
-
if (
|
|
2776
|
-
|
|
2480
|
+
if (!invalidIds || invalidIds.has(d.id)) {
|
|
2481
|
+
const route = this.routesById[d.routeId] as AnyRoute
|
|
2482
|
+
const next = {
|
|
2777
2483
|
...d,
|
|
2778
2484
|
invalid: true,
|
|
2779
|
-
...(opts?.forcePending ||
|
|
2780
|
-
|
|
2781
|
-
|
|
2485
|
+
...((opts?.forcePending ||
|
|
2486
|
+
d.status === 'error' ||
|
|
2487
|
+
d.status === 'notFound') &&
|
|
2488
|
+
routeNeedsLoad(route)
|
|
2782
2489
|
? ({ status: 'pending', error: undefined } as const)
|
|
2783
2490
|
: undefined),
|
|
2784
2491
|
}
|
|
2492
|
+
// Invalidation replaces this owner; it does not create another lease.
|
|
2493
|
+
;(d as AnyRouteMatch & { _flight?: LoaderFlight })._flight = undefined
|
|
2494
|
+
return next
|
|
2785
2495
|
}
|
|
2786
2496
|
return d
|
|
2787
2497
|
}
|
|
2788
2498
|
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
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
|
|
2794
2506
|
|
|
2795
2507
|
this.shouldViewTransition = false
|
|
2796
2508
|
return this.load({ sync: opts?.sync })
|
|
2797
2509
|
}
|
|
2798
2510
|
|
|
2799
|
-
getParsedLocationHref = (location: ParsedLocation) => {
|
|
2800
|
-
// For redirects and external use, we need publicHref (with rewrite output applied)
|
|
2801
|
-
// href is the internal path after rewrite input, publicHref is user-facing
|
|
2802
|
-
return location.publicHref || '/'
|
|
2803
|
-
}
|
|
2804
|
-
|
|
2805
2511
|
resolveRedirect = (redirect: AnyRedirect): AnyRedirect => {
|
|
2806
2512
|
const locationHeader = redirect.headers.get('Location')
|
|
2807
2513
|
|
|
2808
2514
|
if (!redirect.options.href || redirect.options._builtLocation) {
|
|
2809
2515
|
const location =
|
|
2810
2516
|
redirect.options._builtLocation ?? this.buildLocation(redirect.options)
|
|
2811
|
-
const href =
|
|
2517
|
+
const href = location.publicHref || '/'
|
|
2812
2518
|
redirect.options.href = href
|
|
2813
2519
|
redirect.headers.set('Location', href)
|
|
2814
2520
|
} else if (locationHeader) {
|
|
@@ -2845,43 +2551,37 @@ export class RouterCore<
|
|
|
2845
2551
|
}
|
|
2846
2552
|
|
|
2847
2553
|
clearCache: ClearCacheFn<this> = (opts) => {
|
|
2554
|
+
const cached = this._cache
|
|
2555
|
+
const preloads = this._preloads
|
|
2848
2556
|
const filter = opts?.filter
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
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
|
+
}
|
|
2857
2565
|
}
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
if (!route.options.loader) {
|
|
2867
|
-
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)
|
|
2868
2574
|
}
|
|
2575
|
+
}
|
|
2869
2576
|
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
const isError = d.status === 'error'
|
|
2879
|
-
if (isError) return true
|
|
2880
|
-
|
|
2881
|
-
const gcEligible = now - d.updatedAt >= gcTime
|
|
2882
|
-
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()
|
|
2883
2584
|
}
|
|
2884
|
-
this.clearCache({ filter })
|
|
2885
2585
|
}
|
|
2886
2586
|
|
|
2887
2587
|
loadRouteChunk = loadRouteChunk
|
|
@@ -2891,69 +2591,7 @@ export class RouterCore<
|
|
|
2891
2591
|
TTrailingSlashOption,
|
|
2892
2592
|
TDefaultStructuralSharingOption,
|
|
2893
2593
|
TRouterHistory
|
|
2894
|
-
> =
|
|
2895
|
-
const next = opts._builtLocation ?? this.buildLocation(opts as any)
|
|
2896
|
-
|
|
2897
|
-
let matches = this.matchRoutes(next, {
|
|
2898
|
-
throwOnError: true,
|
|
2899
|
-
preload: true,
|
|
2900
|
-
dest: opts,
|
|
2901
|
-
})
|
|
2902
|
-
|
|
2903
|
-
const activeMatchIds = new Set([
|
|
2904
|
-
...this.stores.matchesId.get(),
|
|
2905
|
-
...this.stores.pendingIds.get(),
|
|
2906
|
-
])
|
|
2907
|
-
|
|
2908
|
-
const loadedMatchIds = new Set([
|
|
2909
|
-
...activeMatchIds,
|
|
2910
|
-
...this.stores.cachedIds.get(),
|
|
2911
|
-
])
|
|
2912
|
-
|
|
2913
|
-
// If the matches are already loaded, we need to add them to the cached matches.
|
|
2914
|
-
const matchesToCache = matches.filter(
|
|
2915
|
-
(match) => !loadedMatchIds.has(match.id),
|
|
2916
|
-
)
|
|
2917
|
-
if (matchesToCache.length) {
|
|
2918
|
-
const cachedMatches = this.stores.cachedMatches.get()
|
|
2919
|
-
this.stores.setCached([...cachedMatches, ...matchesToCache])
|
|
2920
|
-
}
|
|
2921
|
-
|
|
2922
|
-
try {
|
|
2923
|
-
matches = await loadMatches({
|
|
2924
|
-
router: this,
|
|
2925
|
-
matches,
|
|
2926
|
-
location: next,
|
|
2927
|
-
preload: true,
|
|
2928
|
-
updateMatch: (id, updater) => {
|
|
2929
|
-
// Don't update the match if it's currently loaded
|
|
2930
|
-
if (activeMatchIds.has(id)) {
|
|
2931
|
-
matches = matches.map((d) => (d.id === id ? updater(d) : d))
|
|
2932
|
-
} else {
|
|
2933
|
-
this.updateMatch(id, updater)
|
|
2934
|
-
}
|
|
2935
|
-
},
|
|
2936
|
-
})
|
|
2937
|
-
|
|
2938
|
-
return matches
|
|
2939
|
-
} catch (err) {
|
|
2940
|
-
if (isRedirect(err)) {
|
|
2941
|
-
if (err.options.reloadDocument) {
|
|
2942
|
-
return undefined
|
|
2943
|
-
}
|
|
2944
|
-
|
|
2945
|
-
return await this.preloadRoute({
|
|
2946
|
-
...err.options,
|
|
2947
|
-
_fromLocation: next,
|
|
2948
|
-
})
|
|
2949
|
-
}
|
|
2950
|
-
if (!isNotFound(err)) {
|
|
2951
|
-
// Preload errors are not fatal, but we should still log them
|
|
2952
|
-
console.error(err)
|
|
2953
|
-
}
|
|
2954
|
-
return undefined
|
|
2955
|
-
}
|
|
2956
|
-
}
|
|
2594
|
+
> = (opts) => preloadClientRoute(this, opts)
|
|
2957
2595
|
|
|
2958
2596
|
matchRoute: MatchRouteFn<
|
|
2959
2597
|
TRouteTree,
|
|
@@ -2971,12 +2609,12 @@ export class RouterCore<
|
|
|
2971
2609
|
}
|
|
2972
2610
|
const next = this.buildLocation(matchLocation as any)
|
|
2973
2611
|
|
|
2974
|
-
|
|
2612
|
+
const isPending = this.stores.status.get() === 'pending'
|
|
2613
|
+
if (opts?.pending && !isPending) {
|
|
2975
2614
|
return false
|
|
2976
2615
|
}
|
|
2977
2616
|
|
|
2978
|
-
const pending =
|
|
2979
|
-
opts?.pending === undefined ? !this.stores.isLoading.get() : opts.pending
|
|
2617
|
+
const pending = opts?.pending ?? !isPending
|
|
2980
2618
|
|
|
2981
2619
|
const baseLocation = pending
|
|
2982
2620
|
? this.latestLocation
|
|
@@ -3016,11 +2654,19 @@ export class RouterCore<
|
|
|
3016
2654
|
serverSsr?: ServerSsr
|
|
3017
2655
|
|
|
3018
2656
|
serverSsrLifecycle?: RouterSsrLifecycle
|
|
2657
|
+
}
|
|
3019
2658
|
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
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)
|
|
3024
2670
|
}
|
|
3025
2671
|
}
|
|
3026
2672
|
|
|
@@ -3057,14 +2703,11 @@ export function getInitialRouterState(
|
|
|
3057
2703
|
location: ParsedLocation,
|
|
3058
2704
|
): RouterState<any> {
|
|
3059
2705
|
return {
|
|
3060
|
-
loadedAt: 0,
|
|
3061
2706
|
isLoading: false,
|
|
3062
|
-
isTransitioning: false,
|
|
3063
2707
|
status: 'idle',
|
|
3064
2708
|
resolvedLocation: undefined,
|
|
3065
2709
|
location,
|
|
3066
2710
|
matches: [],
|
|
3067
|
-
statusCode: 200,
|
|
3068
2711
|
}
|
|
3069
2712
|
}
|
|
3070
2713
|
|
|
@@ -3096,56 +2739,12 @@ function validateSearch(validateSearch: AnyValidator, input: unknown): unknown {
|
|
|
3096
2739
|
return {}
|
|
3097
2740
|
}
|
|
3098
2741
|
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
routesById,
|
|
3106
|
-
processedTree,
|
|
3107
|
-
}: {
|
|
3108
|
-
pathname: string
|
|
3109
|
-
routesById: Record<string, TRouteLike>
|
|
3110
|
-
processedTree: ProcessedTree<any, any, any>
|
|
3111
|
-
}) {
|
|
3112
|
-
const routeParams: Record<string, string> = Object.create(null)
|
|
3113
|
-
const trimmedPath = trimPathRight(pathname)
|
|
3114
|
-
|
|
3115
|
-
let foundRoute: TRouteLike | undefined = undefined
|
|
3116
|
-
const match = findRouteMatch<TRouteLike>(trimmedPath, processedTree, true)
|
|
3117
|
-
if (match) {
|
|
3118
|
-
foundRoute = match.route
|
|
3119
|
-
Object.assign(routeParams, match.rawParams) // Copy params, because they're cached
|
|
3120
|
-
}
|
|
3121
|
-
|
|
3122
|
-
const matchedRoutes = match?.branch || [routesById[rootRouteId]!]
|
|
3123
|
-
|
|
3124
|
-
return { matchedRoutes, routeParams, foundRoute }
|
|
3125
|
-
}
|
|
3126
|
-
|
|
3127
|
-
/**
|
|
3128
|
-
* TODO: once caches are persisted across requests on the server,
|
|
3129
|
-
* we can cache the built middleware chain using `last(destRoutes)` as the key
|
|
3130
|
-
*/
|
|
3131
|
-
function applySearchMiddleware({
|
|
3132
|
-
search,
|
|
3133
|
-
dest,
|
|
3134
|
-
destRoutes,
|
|
3135
|
-
_includeValidateSearch,
|
|
3136
|
-
}: {
|
|
3137
|
-
search: any
|
|
3138
|
-
dest: { search?: unknown }
|
|
3139
|
-
destRoutes: ReadonlyArray<AnyRoute>
|
|
3140
|
-
_includeValidateSearch: boolean | undefined
|
|
3141
|
-
}) {
|
|
3142
|
-
const middleware = buildMiddlewareChain(destRoutes)
|
|
3143
|
-
return middleware(search, dest, _includeValidateSearch ?? false)
|
|
3144
|
-
}
|
|
3145
|
-
|
|
3146
|
-
function buildMiddlewareChain(destRoutes: ReadonlyArray<AnyRoute>) {
|
|
3147
|
-
let dest: BuildNextOptions
|
|
3148
|
-
let includeValidateSearch: boolean | undefined
|
|
2742
|
+
function applySearchMiddleware(
|
|
2743
|
+
search: any,
|
|
2744
|
+
dest: BuildNextOptions,
|
|
2745
|
+
destRoutes: ReadonlyArray<AnyRoute>,
|
|
2746
|
+
includeValidateSearch: boolean | undefined,
|
|
2747
|
+
) {
|
|
3149
2748
|
const middlewares = [] as Array<SearchMiddleware<any>>
|
|
3150
2749
|
|
|
3151
2750
|
for (const route of destRoutes) {
|
|
@@ -3238,15 +2837,7 @@ function buildMiddlewareChain(destRoutes: ReadonlyArray<AnyRoute>) {
|
|
|
3238
2837
|
return (middlewares[index]! as any)({ search: currentSearch, next, meta })
|
|
3239
2838
|
}
|
|
3240
2839
|
|
|
3241
|
-
return
|
|
3242
|
-
search: any,
|
|
3243
|
-
nextDest: BuildNextOptions,
|
|
3244
|
-
_includeValidateSearch: boolean,
|
|
3245
|
-
) {
|
|
3246
|
-
dest = nextDest
|
|
3247
|
-
includeValidateSearch = _includeValidateSearch
|
|
3248
|
-
return applyNext(0, search)
|
|
3249
|
-
}
|
|
2840
|
+
return applyNext(0, search)
|
|
3250
2841
|
}
|
|
3251
2842
|
|
|
3252
2843
|
function findGlobalNotFoundRouteId(
|
|
@@ -3254,26 +2845,41 @@ function findGlobalNotFoundRouteId(
|
|
|
3254
2845
|
routes: ReadonlyArray<AnyRoute>,
|
|
3255
2846
|
) {
|
|
3256
2847
|
if (notFoundMode !== 'root') {
|
|
2848
|
+
let fallback
|
|
3257
2849
|
for (let i = routes.length - 1; i >= 0; i--) {
|
|
3258
2850
|
const route = routes[i]!
|
|
3259
|
-
if (route.
|
|
2851
|
+
if (route.options.notFoundComponent) {
|
|
3260
2852
|
return route.id
|
|
3261
2853
|
}
|
|
2854
|
+
fallback ||= route.children && route.id
|
|
2855
|
+
}
|
|
2856
|
+
if (fallback) {
|
|
2857
|
+
return fallback
|
|
3262
2858
|
}
|
|
3263
2859
|
}
|
|
3264
2860
|
return rootRouteId
|
|
3265
2861
|
}
|
|
3266
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
|
+
|
|
3267
2874
|
function extractStrictParams(
|
|
3268
2875
|
route: AnyRoute,
|
|
3269
2876
|
accumulatedParams: Record<string, unknown>,
|
|
3270
2877
|
) {
|
|
3271
2878
|
const parseParams = route.options.params?.parse ?? route.options.parseParams
|
|
3272
2879
|
if (parseParams) {
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
Object.assign(accumulatedParams, result)
|
|
2880
|
+
Object.assign(
|
|
2881
|
+
accumulatedParams,
|
|
2882
|
+
parseParams(accumulatedParams as Record<string, string>),
|
|
2883
|
+
)
|
|
3278
2884
|
}
|
|
3279
2885
|
}
|