@tanstack/router-core 1.167.5 → 1.168.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/index.cjs +3 -0
- package/dist/cjs/index.d.cts +2 -0
- package/dist/cjs/load-matches.cjs +14 -9
- package/dist/cjs/load-matches.cjs.map +1 -1
- package/dist/cjs/router.cjs +135 -151
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +16 -10
- package/dist/cjs/scroll-restoration.cjs +5 -4
- package/dist/cjs/scroll-restoration.cjs.map +1 -1
- package/dist/cjs/ssr/createRequestHandler.cjs +2 -2
- package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -1
- package/dist/cjs/ssr/ssr-client.cjs +14 -17
- package/dist/cjs/ssr/ssr-client.cjs.map +1 -1
- package/dist/cjs/ssr/ssr-server.cjs +1 -1
- package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
- package/dist/cjs/stores.cjs +148 -0
- package/dist/cjs/stores.cjs.map +1 -0
- package/dist/cjs/stores.d.cts +70 -0
- package/dist/cjs/utils.cjs +7 -0
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +1 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +2 -1
- package/dist/esm/load-matches.js +14 -9
- package/dist/esm/load-matches.js.map +1 -1
- package/dist/esm/router.d.ts +16 -10
- package/dist/esm/router.js +135 -151
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/scroll-restoration.js +5 -4
- package/dist/esm/scroll-restoration.js.map +1 -1
- package/dist/esm/ssr/createRequestHandler.js +2 -2
- package/dist/esm/ssr/createRequestHandler.js.map +1 -1
- package/dist/esm/ssr/ssr-client.js +14 -17
- package/dist/esm/ssr/ssr-client.js.map +1 -1
- package/dist/esm/ssr/ssr-server.js +1 -1
- package/dist/esm/ssr/ssr-server.js.map +1 -1
- package/dist/esm/stores.d.ts +70 -0
- package/dist/esm/stores.js +146 -0
- package/dist/esm/stores.js.map +1 -0
- package/dist/esm/utils.d.ts +1 -0
- package/dist/esm/utils.js +7 -1
- package/dist/esm/utils.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +11 -0
- package/src/load-matches.ts +23 -11
- package/src/router.ts +238 -252
- package/src/scroll-restoration.ts +6 -5
- package/src/ssr/createRequestHandler.ts +5 -4
- package/src/ssr/ssr-client.ts +17 -18
- package/src/ssr/ssr-server.ts +1 -1
- package/src/stores.ts +342 -0
- package/src/utils.ts +9 -0
- package/dist/cjs/utils/batch.cjs +0 -16
- package/dist/cjs/utils/batch.cjs.map +0 -1
- package/dist/cjs/utils/batch.d.cts +0 -1
- package/dist/esm/utils/batch.d.ts +0 -1
- package/dist/esm/utils/batch.js +0 -15
- package/dist/esm/utils/batch.js.map +0 -1
- package/src/utils/batch.ts +0 -18
package/src/router.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { createStore } from '@tanstack/store'
|
|
2
1
|
import { createBrowserHistory, parseHref } from '@tanstack/history'
|
|
3
2
|
import { isServer } from '@tanstack/router-core/isServer'
|
|
4
|
-
import { batch } from './utils/batch'
|
|
5
3
|
import {
|
|
6
4
|
DEFAULT_PROTOCOL_ALLOWLIST,
|
|
7
5
|
createControlledPromise,
|
|
@@ -43,7 +41,7 @@ import {
|
|
|
43
41
|
executeRewriteOutput,
|
|
44
42
|
rewriteBasepath,
|
|
45
43
|
} from './rewrite'
|
|
46
|
-
import
|
|
44
|
+
import { createRouterStores } from './stores'
|
|
47
45
|
import type { LRUCache } from './lru-cache'
|
|
48
46
|
import type {
|
|
49
47
|
ProcessRouteTreeResult,
|
|
@@ -105,7 +103,7 @@ import type {
|
|
|
105
103
|
AnySerializationAdapter,
|
|
106
104
|
ValidateSerializableInput,
|
|
107
105
|
} from './ssr/serializer/transformer'
|
|
108
|
-
|
|
106
|
+
import type { GetStoreConfig, RouterStores } from './stores'
|
|
109
107
|
|
|
110
108
|
export type ControllablePromise<T = any> = Promise<T> & {
|
|
111
109
|
resolve: (value: T) => void
|
|
@@ -543,8 +541,6 @@ export interface RouterState<
|
|
|
543
541
|
isLoading: boolean
|
|
544
542
|
isTransitioning: boolean
|
|
545
543
|
matches: Array<TRouteMatch>
|
|
546
|
-
pendingMatches?: Array<TRouteMatch>
|
|
547
|
-
cachedMatches: Array<TRouteMatch>
|
|
548
544
|
location: ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
549
545
|
resolvedLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
550
546
|
statusCode: number
|
|
@@ -871,27 +867,20 @@ export type TrailingSlashOption =
|
|
|
871
867
|
|
|
872
868
|
/**
|
|
873
869
|
* Compute whether path, href or hash changed between previous and current
|
|
874
|
-
* resolved locations
|
|
870
|
+
* resolved locations.
|
|
875
871
|
*/
|
|
876
|
-
export function getLocationChangeInfo(
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
const fromLocation =
|
|
881
|
-
const toLocation =
|
|
872
|
+
export function getLocationChangeInfo(
|
|
873
|
+
location: ParsedLocation,
|
|
874
|
+
resolvedLocation?: ParsedLocation,
|
|
875
|
+
) {
|
|
876
|
+
const fromLocation = resolvedLocation
|
|
877
|
+
const toLocation = location
|
|
882
878
|
const pathChanged = fromLocation?.pathname !== toLocation.pathname
|
|
883
879
|
const hrefChanged = fromLocation?.href !== toLocation.href
|
|
884
880
|
const hashChanged = fromLocation?.hash !== toLocation.hash
|
|
885
881
|
return { fromLocation, toLocation, pathChanged, hrefChanged, hashChanged }
|
|
886
882
|
}
|
|
887
883
|
|
|
888
|
-
function filterRedirectedCachedMatches<T extends { status: string }>(
|
|
889
|
-
matches: Array<T>,
|
|
890
|
-
): Array<T> {
|
|
891
|
-
const filtered = matches.filter((d) => d.status !== 'redirected')
|
|
892
|
-
return filtered.length === matches.length ? matches : filtered
|
|
893
|
-
}
|
|
894
|
-
|
|
895
884
|
export type CreateRouterFn = <
|
|
896
885
|
TRouteTree extends AnyRoute,
|
|
897
886
|
TTrailingSlashOption extends TrailingSlashOption = 'never',
|
|
@@ -936,24 +925,6 @@ declare global {
|
|
|
936
925
|
*
|
|
937
926
|
* @link https://tanstack.com/router/latest/docs/framework/react/api/router/RouterType
|
|
938
927
|
*/
|
|
939
|
-
type RouterStateStore<TState> = {
|
|
940
|
-
state: TState
|
|
941
|
-
setState: (updater: (prev: TState) => TState) => void
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
function createServerStore<TState>(
|
|
945
|
-
initialState: TState,
|
|
946
|
-
): RouterStateStore<TState> {
|
|
947
|
-
const store = {
|
|
948
|
-
state: initialState,
|
|
949
|
-
setState: (updater: (prev: TState) => TState) => {
|
|
950
|
-
store.state = updater(store.state)
|
|
951
|
-
},
|
|
952
|
-
} as RouterStateStore<TState>
|
|
953
|
-
|
|
954
|
-
return store
|
|
955
|
-
}
|
|
956
|
-
|
|
957
928
|
export class RouterCore<
|
|
958
929
|
in out TRouteTree extends AnyRoute,
|
|
959
930
|
in out TTrailingSlashOption extends TrailingSlashOption,
|
|
@@ -974,7 +945,10 @@ export class RouterCore<
|
|
|
974
945
|
isScrollRestorationSetup = false
|
|
975
946
|
|
|
976
947
|
// Must build in constructor
|
|
977
|
-
|
|
948
|
+
stores!: RouterStores<TRouteTree>
|
|
949
|
+
private getStoreConfig!: GetStoreConfig
|
|
950
|
+
batch!: (fn: () => void) => void
|
|
951
|
+
|
|
978
952
|
options!: PickAsRequired<
|
|
979
953
|
RouterOptions<
|
|
980
954
|
TRouteTree,
|
|
@@ -1011,7 +985,10 @@ export class RouterCore<
|
|
|
1011
985
|
TRouterHistory,
|
|
1012
986
|
TDehydrated
|
|
1013
987
|
>,
|
|
988
|
+
getStoreConfig: GetStoreConfig,
|
|
1014
989
|
) {
|
|
990
|
+
this.getStoreConfig = getStoreConfig
|
|
991
|
+
|
|
1015
992
|
this.update({
|
|
1016
993
|
defaultPreloadDelay: 50,
|
|
1017
994
|
defaultPendingMs: 1000,
|
|
@@ -1140,14 +1117,15 @@ export class RouterCore<
|
|
|
1140
1117
|
this.setRoutes(processRouteTreeResult)
|
|
1141
1118
|
}
|
|
1142
1119
|
|
|
1143
|
-
if (!this.
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
)
|
|
1148
|
-
|
|
1149
|
-
|
|
1120
|
+
if (!this.stores && this.latestLocation) {
|
|
1121
|
+
const config = this.getStoreConfig(this)
|
|
1122
|
+
this.batch = config.batch
|
|
1123
|
+
this.stores = createRouterStores(
|
|
1124
|
+
getInitialRouterState(this.latestLocation),
|
|
1125
|
+
config,
|
|
1126
|
+
)
|
|
1150
1127
|
|
|
1128
|
+
if (!(isServer ?? this.isServer)) {
|
|
1151
1129
|
setupScrollRestoration(this)
|
|
1152
1130
|
}
|
|
1153
1131
|
}
|
|
@@ -1188,11 +1166,8 @@ export class RouterCore<
|
|
|
1188
1166
|
needsLocationUpdate = true
|
|
1189
1167
|
}
|
|
1190
1168
|
|
|
1191
|
-
if (needsLocationUpdate && this.
|
|
1192
|
-
this.
|
|
1193
|
-
...s,
|
|
1194
|
-
location: this.latestLocation,
|
|
1195
|
-
}))
|
|
1169
|
+
if (needsLocationUpdate && this.stores) {
|
|
1170
|
+
this.stores.location.setState(() => this.latestLocation)
|
|
1196
1171
|
}
|
|
1197
1172
|
|
|
1198
1173
|
if (
|
|
@@ -1207,7 +1182,7 @@ export class RouterCore<
|
|
|
1207
1182
|
}
|
|
1208
1183
|
|
|
1209
1184
|
get state(): RouterState<TRouteTree> {
|
|
1210
|
-
return this.__store.state
|
|
1185
|
+
return this.stores.__store.state
|
|
1211
1186
|
}
|
|
1212
1187
|
|
|
1213
1188
|
updateLatestLocation = () => {
|
|
@@ -1440,10 +1415,14 @@ export class RouterCore<
|
|
|
1440
1415
|
: undefined
|
|
1441
1416
|
|
|
1442
1417
|
const matches = new Array<AnyRouteMatch>(matchedRoutes.length)
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
)
|
|
1418
|
+
// Snapshot of active match state keyed by routeId, used to stabilise
|
|
1419
|
+
// params/search across navigations.
|
|
1420
|
+
const previousActiveMatchesByRouteId = new Map<string, AnyRouteMatch>()
|
|
1421
|
+
for (const store of this.stores.activeMatchStoresById.values()) {
|
|
1422
|
+
if (store.routeId) {
|
|
1423
|
+
previousActiveMatchesByRouteId.set(store.routeId, store.state)
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1447
1426
|
|
|
1448
1427
|
for (let index = 0; index < matchedRoutes.length; index++) {
|
|
1449
1428
|
const route = matchedRoutes[index]!
|
|
@@ -1528,7 +1507,7 @@ export class RouterCore<
|
|
|
1528
1507
|
|
|
1529
1508
|
const existingMatch = this.getMatch(matchId)
|
|
1530
1509
|
|
|
1531
|
-
const previousMatch =
|
|
1510
|
+
const previousMatch = previousActiveMatchesByRouteId.get(route.id)
|
|
1532
1511
|
|
|
1533
1512
|
const strictParams = existingMatch?._strictParams ?? usedParams
|
|
1534
1513
|
|
|
@@ -1644,7 +1623,7 @@ export class RouterCore<
|
|
|
1644
1623
|
const existingMatch = this.getMatch(match.id)
|
|
1645
1624
|
|
|
1646
1625
|
// Update the match's params
|
|
1647
|
-
const previousMatch =
|
|
1626
|
+
const previousMatch = previousActiveMatchesByRouteId.get(match.routeId)
|
|
1648
1627
|
match.params = previousMatch
|
|
1649
1628
|
? nullReplaceEqualDeep(previousMatch.params, routeParams)
|
|
1650
1629
|
: routeParams
|
|
@@ -1734,11 +1713,14 @@ export class RouterCore<
|
|
|
1734
1713
|
}
|
|
1735
1714
|
|
|
1736
1715
|
// Determine params: reuse from state if possible, otherwise parse
|
|
1737
|
-
const
|
|
1716
|
+
const lastStateMatchId = last(this.stores.matchesId.state)
|
|
1717
|
+
const lastStateMatch =
|
|
1718
|
+
lastStateMatchId &&
|
|
1719
|
+
this.stores.activeMatchStoresById.get(lastStateMatchId)?.state
|
|
1738
1720
|
const canReuseParams =
|
|
1739
1721
|
lastStateMatch &&
|
|
1740
1722
|
lastStateMatch.routeId === lastRoute.id &&
|
|
1741
|
-
|
|
1723
|
+
lastStateMatch.pathname === location.pathname
|
|
1742
1724
|
|
|
1743
1725
|
let params: Record<string, unknown>
|
|
1744
1726
|
if (canReuseParams) {
|
|
@@ -1783,19 +1765,23 @@ export class RouterCore<
|
|
|
1783
1765
|
}
|
|
1784
1766
|
|
|
1785
1767
|
cancelMatches = () => {
|
|
1786
|
-
|
|
1787
|
-
(
|
|
1788
|
-
)
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1768
|
+
this.stores.pendingMatchesId.state.forEach((matchId) => {
|
|
1769
|
+
this.cancelMatch(matchId)
|
|
1770
|
+
})
|
|
1771
|
+
|
|
1772
|
+
this.stores.matchesId.state.forEach((matchId) => {
|
|
1773
|
+
if (this.stores.pendingMatchStoresById.has(matchId)) {
|
|
1774
|
+
return
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
const match = this.stores.activeMatchStoresById.get(matchId)?.state
|
|
1778
|
+
if (!match) {
|
|
1779
|
+
return
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
if (match.status === 'pending' || match.isFetching === 'loader') {
|
|
1783
|
+
this.cancelMatch(matchId)
|
|
1784
|
+
}
|
|
1799
1785
|
})
|
|
1800
1786
|
}
|
|
1801
1787
|
|
|
@@ -2368,26 +2354,28 @@ export class RouterCore<
|
|
|
2368
2354
|
// Match the routes
|
|
2369
2355
|
const pendingMatches = this.matchRoutes(this.latestLocation)
|
|
2370
2356
|
|
|
2357
|
+
const nextCachedMatches = this.stores.cachedMatchesSnapshot.state.filter(
|
|
2358
|
+
(d) => !pendingMatches.some((e) => e.id === d.id),
|
|
2359
|
+
)
|
|
2360
|
+
|
|
2371
2361
|
// Ingest the new matches
|
|
2372
|
-
this.
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
(d) => !pendingMatches.some((e) => e.id === d.id),
|
|
2382
|
-
),
|
|
2383
|
-
}))
|
|
2362
|
+
this.batch(() => {
|
|
2363
|
+
this.stores.status.setState(() => 'pending')
|
|
2364
|
+
this.stores.statusCode.setState(() => 200)
|
|
2365
|
+
this.stores.isLoading.setState(() => true)
|
|
2366
|
+
this.stores.location.setState(() => this.latestLocation)
|
|
2367
|
+
this.stores.setPendingMatches(pendingMatches)
|
|
2368
|
+
// If a cached match moved to pending matches, remove it from cached matches
|
|
2369
|
+
this.stores.setCachedMatches(nextCachedMatches)
|
|
2370
|
+
})
|
|
2384
2371
|
}
|
|
2385
2372
|
|
|
2386
2373
|
load: LoadFn = async (opts?: { sync?: boolean }): Promise<void> => {
|
|
2387
2374
|
let redirect: AnyRedirect | undefined
|
|
2388
2375
|
let notFound: NotFoundError | undefined
|
|
2389
2376
|
let loadPromise: Promise<void>
|
|
2390
|
-
const previousLocation =
|
|
2377
|
+
const previousLocation =
|
|
2378
|
+
this.stores.resolvedLocation.state ?? this.stores.location.state
|
|
2391
2379
|
|
|
2392
2380
|
// eslint-disable-next-line prefer-const
|
|
2393
2381
|
loadPromise = new Promise<void>((resolve) => {
|
|
@@ -2395,31 +2383,26 @@ export class RouterCore<
|
|
|
2395
2383
|
try {
|
|
2396
2384
|
this.beforeLoad()
|
|
2397
2385
|
const next = this.latestLocation
|
|
2398
|
-
const prevLocation = this.
|
|
2386
|
+
const prevLocation = this.stores.resolvedLocation.state
|
|
2387
|
+
const locationChangeInfo = getLocationChangeInfo(next, prevLocation)
|
|
2399
2388
|
|
|
2400
|
-
if (!this.
|
|
2389
|
+
if (!this.stores.redirect.state) {
|
|
2401
2390
|
this.emit({
|
|
2402
2391
|
type: 'onBeforeNavigate',
|
|
2403
|
-
...
|
|
2404
|
-
resolvedLocation: prevLocation,
|
|
2405
|
-
location: next,
|
|
2406
|
-
}),
|
|
2392
|
+
...locationChangeInfo,
|
|
2407
2393
|
})
|
|
2408
2394
|
}
|
|
2409
2395
|
|
|
2410
2396
|
this.emit({
|
|
2411
2397
|
type: 'onBeforeLoad',
|
|
2412
|
-
...
|
|
2413
|
-
resolvedLocation: prevLocation,
|
|
2414
|
-
location: next,
|
|
2415
|
-
}),
|
|
2398
|
+
...locationChangeInfo,
|
|
2416
2399
|
})
|
|
2417
2400
|
|
|
2418
2401
|
await loadMatches({
|
|
2419
2402
|
router: this,
|
|
2420
2403
|
sync: opts?.sync,
|
|
2421
2404
|
forceStaleReload: previousLocation.href === next.href,
|
|
2422
|
-
matches: this.state
|
|
2405
|
+
matches: this.stores.pendingMatchesSnapshot.state,
|
|
2423
2406
|
location: next,
|
|
2424
2407
|
updateMatch: this.updateMatch,
|
|
2425
2408
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
@@ -2434,80 +2417,92 @@ export class RouterCore<
|
|
|
2434
2417
|
//
|
|
2435
2418
|
// exitingMatches uses match.id (routeId + params + loaderDeps) so
|
|
2436
2419
|
// navigating /foo?page=1 → /foo?page=2 correctly caches the page=1 entry.
|
|
2437
|
-
let exitingMatches: Array<AnyRouteMatch> =
|
|
2420
|
+
let exitingMatches: Array<AnyRouteMatch> | null = null
|
|
2438
2421
|
|
|
2439
2422
|
// Lifecycle-hook identity uses routeId only so that navigating between
|
|
2440
2423
|
// different params/deps of the same route fires onStay (not onLeave+onEnter).
|
|
2441
|
-
let hookExitingMatches: Array<AnyRouteMatch> =
|
|
2442
|
-
let hookEnteringMatches: Array<AnyRouteMatch> =
|
|
2443
|
-
let hookStayingMatches: Array<AnyRouteMatch> =
|
|
2444
|
-
|
|
2445
|
-
batch(() => {
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2424
|
+
let hookExitingMatches: Array<AnyRouteMatch> | null = null
|
|
2425
|
+
let hookEnteringMatches: Array<AnyRouteMatch> | null = null
|
|
2426
|
+
let hookStayingMatches: Array<AnyRouteMatch> | null = null
|
|
2427
|
+
|
|
2428
|
+
this.batch(() => {
|
|
2429
|
+
const pendingMatches =
|
|
2430
|
+
this.stores.pendingMatchesSnapshot.state
|
|
2431
|
+
const mountPending = pendingMatches.length
|
|
2432
|
+
const currentMatches =
|
|
2433
|
+
this.stores.activeMatchesSnapshot.state
|
|
2434
|
+
|
|
2435
|
+
exitingMatches = mountPending
|
|
2436
|
+
? currentMatches.filter(
|
|
2437
|
+
(match) =>
|
|
2438
|
+
!this.stores.pendingMatchStoresById.has(match.id),
|
|
2439
|
+
)
|
|
2440
|
+
: null
|
|
2441
|
+
|
|
2442
|
+
// Lifecycle-hook identity: routeId only (route presence in tree)
|
|
2443
|
+
// Build routeId sets from pools to avoid derived stores.
|
|
2444
|
+
const pendingRouteIds = new Set<string>()
|
|
2445
|
+
for (const s of this.stores.pendingMatchStoresById.values()) {
|
|
2446
|
+
if (s.routeId) pendingRouteIds.add(s.routeId)
|
|
2447
|
+
}
|
|
2448
|
+
const activeRouteIds = new Set<string>()
|
|
2449
|
+
for (const s of this.stores.activeMatchStoresById.values()) {
|
|
2450
|
+
if (s.routeId) activeRouteIds.add(s.routeId)
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
hookExitingMatches = mountPending
|
|
2454
|
+
? currentMatches.filter(
|
|
2455
|
+
(match) => !pendingRouteIds.has(match.routeId),
|
|
2456
|
+
)
|
|
2457
|
+
: null
|
|
2458
|
+
hookEnteringMatches = mountPending
|
|
2459
|
+
? pendingMatches.filter(
|
|
2460
|
+
(match) => !activeRouteIds.has(match.routeId),
|
|
2461
|
+
)
|
|
2462
|
+
: null
|
|
2463
|
+
hookStayingMatches = mountPending
|
|
2464
|
+
? pendingMatches.filter((match) =>
|
|
2465
|
+
activeRouteIds.has(match.routeId),
|
|
2466
|
+
)
|
|
2467
|
+
: currentMatches
|
|
2468
|
+
|
|
2469
|
+
this.stores.isLoading.setState(() => false)
|
|
2470
|
+
this.stores.loadedAt.setState(() => Date.now())
|
|
2471
|
+
/**
|
|
2472
|
+
* When committing new matches, cache any exiting matches that are still usable.
|
|
2473
|
+
* Routes that resolved with `status: 'error'` or `status: 'notFound'` are
|
|
2474
|
+
* deliberately excluded from `cachedMatches` so that subsequent invalidations
|
|
2475
|
+
* or reloads re-run their loaders instead of reusing the failed/not-found data.
|
|
2476
|
+
*/
|
|
2477
|
+
if (mountPending) {
|
|
2478
|
+
this.stores.setActiveMatches(pendingMatches)
|
|
2479
|
+
this.stores.setPendingMatches([])
|
|
2480
|
+
this.stores.setCachedMatches([
|
|
2481
|
+
...this.stores.cachedMatchesSnapshot.state,
|
|
2482
|
+
...exitingMatches!.filter(
|
|
2483
|
+
(d) =>
|
|
2484
|
+
d.status !== 'error' &&
|
|
2485
|
+
d.status !== 'notFound' &&
|
|
2486
|
+
d.status !== 'redirected',
|
|
2468
2487
|
),
|
|
2469
|
-
)
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
...s,
|
|
2473
|
-
isLoading: false,
|
|
2474
|
-
loadedAt: Date.now(),
|
|
2475
|
-
matches: newMatches,
|
|
2476
|
-
pendingMatches: undefined,
|
|
2477
|
-
/**
|
|
2478
|
-
* When committing new matches, cache any exiting matches that are still usable.
|
|
2479
|
-
* Routes that resolved with `status: 'error'` or `status: 'notFound'` are
|
|
2480
|
-
* deliberately excluded from `cachedMatches` so that subsequent invalidations
|
|
2481
|
-
* or reloads re-run their loaders instead of reusing the failed/not-found data.
|
|
2482
|
-
*/
|
|
2483
|
-
cachedMatches: [
|
|
2484
|
-
...s.cachedMatches,
|
|
2485
|
-
...exitingMatches.filter(
|
|
2486
|
-
(d) =>
|
|
2487
|
-
d.status !== 'error' &&
|
|
2488
|
-
d.status !== 'notFound' &&
|
|
2489
|
-
d.status !== 'redirected',
|
|
2490
|
-
),
|
|
2491
|
-
],
|
|
2492
|
-
}
|
|
2493
|
-
})
|
|
2494
|
-
this.clearExpiredCache()
|
|
2488
|
+
])
|
|
2489
|
+
this.clearExpiredCache()
|
|
2490
|
+
}
|
|
2495
2491
|
})
|
|
2496
2492
|
|
|
2497
2493
|
//
|
|
2498
|
-
|
|
2499
|
-
[
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
matches.forEach((match) => {
|
|
2494
|
+
for (const [matches, hook] of [
|
|
2495
|
+
[hookExitingMatches, 'onLeave'],
|
|
2496
|
+
[hookEnteringMatches, 'onEnter'],
|
|
2497
|
+
[hookStayingMatches, 'onStay'],
|
|
2498
|
+
] as const) {
|
|
2499
|
+
if (!matches) continue
|
|
2500
|
+
for (const match of matches as Array<AnyRouteMatch>) {
|
|
2506
2501
|
this.looseRoutesById[match.routeId]!.options[hook]?.(
|
|
2507
2502
|
match,
|
|
2508
2503
|
)
|
|
2509
|
-
}
|
|
2510
|
-
}
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2511
2506
|
})
|
|
2512
2507
|
})
|
|
2513
2508
|
},
|
|
@@ -2526,17 +2521,20 @@ export class RouterCore<
|
|
|
2526
2521
|
notFound = err
|
|
2527
2522
|
}
|
|
2528
2523
|
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
?
|
|
2533
|
-
:
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2524
|
+
const nextStatusCode = redirect
|
|
2525
|
+
? redirect.status
|
|
2526
|
+
: notFound
|
|
2527
|
+
? 404
|
|
2528
|
+
: this.stores.activeMatchesSnapshot.state.some(
|
|
2529
|
+
(d) => d.status === 'error',
|
|
2530
|
+
)
|
|
2531
|
+
? 500
|
|
2532
|
+
: 200
|
|
2533
|
+
|
|
2534
|
+
this.batch(() => {
|
|
2535
|
+
this.stores.statusCode.setState(() => nextStatusCode)
|
|
2536
|
+
this.stores.redirect.setState(() => redirect)
|
|
2537
|
+
})
|
|
2540
2538
|
}
|
|
2541
2539
|
|
|
2542
2540
|
if (this.latestLoadPromise === loadPromise) {
|
|
@@ -2563,14 +2561,13 @@ export class RouterCore<
|
|
|
2563
2561
|
let newStatusCode: number | undefined = undefined
|
|
2564
2562
|
if (this.hasNotFoundMatch()) {
|
|
2565
2563
|
newStatusCode = 404
|
|
2566
|
-
} else if (
|
|
2564
|
+
} else if (
|
|
2565
|
+
this.stores.activeMatchesSnapshot.state.some((d) => d.status === 'error')
|
|
2566
|
+
) {
|
|
2567
2567
|
newStatusCode = 500
|
|
2568
2568
|
}
|
|
2569
2569
|
if (newStatusCode !== undefined) {
|
|
2570
|
-
this.
|
|
2571
|
-
...s,
|
|
2572
|
-
statusCode: newStatusCode,
|
|
2573
|
-
}))
|
|
2570
|
+
this.stores.statusCode.setState(() => newStatusCode)
|
|
2574
2571
|
}
|
|
2575
2572
|
}
|
|
2576
2573
|
|
|
@@ -2599,15 +2596,12 @@ export class RouterCore<
|
|
|
2599
2596
|
this.isViewTransitionTypesSupported
|
|
2600
2597
|
) {
|
|
2601
2598
|
const next = this.latestLocation
|
|
2602
|
-
const prevLocation = this.
|
|
2599
|
+
const prevLocation = this.stores.resolvedLocation.state
|
|
2603
2600
|
|
|
2604
2601
|
const resolvedViewTransitionTypes =
|
|
2605
2602
|
typeof shouldViewTransition.types === 'function'
|
|
2606
2603
|
? shouldViewTransition.types(
|
|
2607
|
-
getLocationChangeInfo(
|
|
2608
|
-
resolvedLocation: prevLocation,
|
|
2609
|
-
location: next,
|
|
2610
|
-
}),
|
|
2604
|
+
getLocationChangeInfo(next, prevLocation),
|
|
2611
2605
|
)
|
|
2612
2606
|
: shouldViewTransition.types
|
|
2613
2607
|
|
|
@@ -2632,40 +2626,40 @@ export class RouterCore<
|
|
|
2632
2626
|
|
|
2633
2627
|
updateMatch: UpdateMatchFn = (id, updater) => {
|
|
2634
2628
|
this.startTransition(() => {
|
|
2635
|
-
const
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2629
|
+
const pendingMatch = this.stores.pendingMatchStoresById.get(id)
|
|
2630
|
+
if (pendingMatch) {
|
|
2631
|
+
pendingMatch.setState(updater)
|
|
2632
|
+
return
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
const activeMatch = this.stores.activeMatchStoresById.get(id)
|
|
2636
|
+
if (activeMatch) {
|
|
2637
|
+
activeMatch.setState(updater)
|
|
2638
|
+
return
|
|
2639
|
+
}
|
|
2640
|
+
|
|
2641
|
+
const cachedMatch = this.stores.cachedMatchStoresById.get(id)
|
|
2642
|
+
if (cachedMatch) {
|
|
2643
|
+
const next = updater(cachedMatch.state)
|
|
2644
|
+
if (next.status === 'redirected') {
|
|
2645
|
+
const deleted = this.stores.cachedMatchStoresById.delete(id)
|
|
2646
|
+
if (deleted) {
|
|
2647
|
+
this.stores.cachedMatchesId.setState((prev) =>
|
|
2648
|
+
prev.filter((matchId) => matchId !== id),
|
|
2649
|
+
)
|
|
2650
|
+
}
|
|
2651
2651
|
} else {
|
|
2652
|
-
|
|
2653
|
-
...s,
|
|
2654
|
-
[matchesKey]: s[matchesKey]?.map((d) =>
|
|
2655
|
-
d.id === id ? updater(d) : d,
|
|
2656
|
-
),
|
|
2657
|
-
}))
|
|
2652
|
+
cachedMatch.setState(() => next)
|
|
2658
2653
|
}
|
|
2659
2654
|
}
|
|
2660
2655
|
})
|
|
2661
2656
|
}
|
|
2662
2657
|
|
|
2663
2658
|
getMatch: GetMatchFn = (matchId: string): AnyRouteMatch | undefined => {
|
|
2664
|
-
const findFn = (d: { id: string }) => d.id === matchId
|
|
2665
2659
|
return (
|
|
2666
|
-
this.
|
|
2667
|
-
this.
|
|
2668
|
-
this.
|
|
2660
|
+
this.stores.cachedMatchStoresById.get(matchId)?.state ??
|
|
2661
|
+
this.stores.pendingMatchStoresById.get(matchId)?.state ??
|
|
2662
|
+
this.stores.activeMatchStoresById.get(matchId)?.state
|
|
2669
2663
|
)
|
|
2670
2664
|
}
|
|
2671
2665
|
|
|
@@ -2701,12 +2695,17 @@ export class RouterCore<
|
|
|
2701
2695
|
return d
|
|
2702
2696
|
}
|
|
2703
2697
|
|
|
2704
|
-
this.
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2698
|
+
this.batch(() => {
|
|
2699
|
+
this.stores.setActiveMatches(
|
|
2700
|
+
this.stores.activeMatchesSnapshot.state.map(invalidate),
|
|
2701
|
+
)
|
|
2702
|
+
this.stores.setCachedMatches(
|
|
2703
|
+
this.stores.cachedMatchesSnapshot.state.map(invalidate),
|
|
2704
|
+
)
|
|
2705
|
+
this.stores.setPendingMatches(
|
|
2706
|
+
this.stores.pendingMatchesSnapshot.state.map(invalidate),
|
|
2707
|
+
)
|
|
2708
|
+
})
|
|
2710
2709
|
|
|
2711
2710
|
this.shouldViewTransition = false
|
|
2712
2711
|
return this.load({ sync: opts?.sync })
|
|
@@ -2763,25 +2762,18 @@ export class RouterCore<
|
|
|
2763
2762
|
clearCache: ClearCacheFn<this> = (opts) => {
|
|
2764
2763
|
const filter = opts?.filter
|
|
2765
2764
|
if (filter !== undefined) {
|
|
2766
|
-
this.
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
),
|
|
2772
|
-
}
|
|
2773
|
-
})
|
|
2765
|
+
this.stores.setCachedMatches(
|
|
2766
|
+
this.stores.cachedMatchesSnapshot.state.filter(
|
|
2767
|
+
(m) => !filter(m as MakeRouteMatchUnion<this>),
|
|
2768
|
+
),
|
|
2769
|
+
)
|
|
2774
2770
|
} else {
|
|
2775
|
-
this.
|
|
2776
|
-
return {
|
|
2777
|
-
...s,
|
|
2778
|
-
cachedMatches: [],
|
|
2779
|
-
}
|
|
2780
|
-
})
|
|
2771
|
+
this.stores.setCachedMatches([])
|
|
2781
2772
|
}
|
|
2782
2773
|
}
|
|
2783
2774
|
|
|
2784
2775
|
clearExpiredCache = () => {
|
|
2776
|
+
const now = Date.now()
|
|
2785
2777
|
// This is where all of the garbage collection magic happens
|
|
2786
2778
|
const filter = (d: MakeRouteMatch<TRouteTree>) => {
|
|
2787
2779
|
const route = this.looseRoutesById[d.routeId]!
|
|
@@ -2801,7 +2793,7 @@ export class RouterCore<
|
|
|
2801
2793
|
const isError = d.status === 'error'
|
|
2802
2794
|
if (isError) return true
|
|
2803
2795
|
|
|
2804
|
-
const gcEligible =
|
|
2796
|
+
const gcEligible = now - d.updatedAt >= gcTime
|
|
2805
2797
|
return gcEligible
|
|
2806
2798
|
}
|
|
2807
2799
|
this.clearCache({ filter })
|
|
@@ -2823,28 +2815,24 @@ export class RouterCore<
|
|
|
2823
2815
|
dest: opts,
|
|
2824
2816
|
})
|
|
2825
2817
|
|
|
2826
|
-
const activeMatchIds = new Set(
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
)
|
|
2818
|
+
const activeMatchIds = new Set([
|
|
2819
|
+
...this.stores.matchesId.state,
|
|
2820
|
+
...this.stores.pendingMatchesId.state,
|
|
2821
|
+
])
|
|
2831
2822
|
|
|
2832
2823
|
const loadedMatchIds = new Set([
|
|
2833
2824
|
...activeMatchIds,
|
|
2834
|
-
...this.
|
|
2825
|
+
...this.stores.cachedMatchesId.state,
|
|
2835
2826
|
])
|
|
2836
2827
|
|
|
2837
|
-
// If the matches are already loaded, we need to add them to the
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
}
|
|
2846
|
-
})
|
|
2847
|
-
})
|
|
2828
|
+
// If the matches are already loaded, we need to add them to the cached matches.
|
|
2829
|
+
const matchesToCache = matches.filter(
|
|
2830
|
+
(match) => !loadedMatchIds.has(match.id),
|
|
2831
|
+
)
|
|
2832
|
+
if (matchesToCache.length) {
|
|
2833
|
+
const cachedMatches = this.stores.cachedMatchesSnapshot.state
|
|
2834
|
+
this.stores.setCachedMatches([...cachedMatches, ...matchesToCache])
|
|
2835
|
+
}
|
|
2848
2836
|
|
|
2849
2837
|
try {
|
|
2850
2838
|
matches = await loadMatches({
|
|
@@ -2898,16 +2886,16 @@ export class RouterCore<
|
|
|
2898
2886
|
}
|
|
2899
2887
|
const next = this.buildLocation(matchLocation as any)
|
|
2900
2888
|
|
|
2901
|
-
if (opts?.pending && this.
|
|
2889
|
+
if (opts?.pending && this.stores.status.state !== 'pending') {
|
|
2902
2890
|
return false
|
|
2903
2891
|
}
|
|
2904
2892
|
|
|
2905
2893
|
const pending =
|
|
2906
|
-
opts?.pending === undefined ? !this.
|
|
2894
|
+
opts?.pending === undefined ? !this.stores.isLoading.state : opts.pending
|
|
2907
2895
|
|
|
2908
2896
|
const baseLocation = pending
|
|
2909
2897
|
? this.latestLocation
|
|
2910
|
-
: this.
|
|
2898
|
+
: this.stores.resolvedLocation.state || this.stores.location.state
|
|
2911
2899
|
|
|
2912
2900
|
const match = findSingleMatch(
|
|
2913
2901
|
next.pathname,
|
|
@@ -2943,7 +2931,7 @@ export class RouterCore<
|
|
|
2943
2931
|
serverSsr?: ServerSsr
|
|
2944
2932
|
|
|
2945
2933
|
hasNotFoundMatch = () => {
|
|
2946
|
-
return this.
|
|
2934
|
+
return this.stores.activeMatchesSnapshot.state.some(
|
|
2947
2935
|
(d) => d.status === 'notFound' || d.globalNotFound,
|
|
2948
2936
|
)
|
|
2949
2937
|
}
|
|
@@ -2989,8 +2977,6 @@ export function getInitialRouterState(
|
|
|
2989
2977
|
resolvedLocation: undefined,
|
|
2990
2978
|
location,
|
|
2991
2979
|
matches: [],
|
|
2992
|
-
pendingMatches: [],
|
|
2993
|
-
cachedMatches: [],
|
|
2994
2980
|
statusCode: 200,
|
|
2995
2981
|
}
|
|
2996
2982
|
}
|