@tanstack/react-router 1.48.4 → 1.49.1
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 +3 -1
- package/dist/cjs/Matches.cjs.map +1 -1
- package/dist/cjs/Matches.d.cts +5 -4
- package/dist/cjs/fileRoute.cjs.map +1 -1
- package/dist/cjs/fileRoute.d.cts +4 -5
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +3 -1
- package/dist/cjs/link.cjs.map +1 -1
- package/dist/cjs/link.d.cts +1 -1
- package/dist/cjs/path.cjs +6 -9
- package/dist/cjs/path.cjs.map +1 -1
- package/dist/cjs/route.cjs +1 -1
- package/dist/cjs/route.cjs.map +1 -1
- package/dist/cjs/route.d.cts +80 -61
- package/dist/cjs/router.cjs +90 -57
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +22 -9
- package/dist/cjs/transformer.cjs +39 -0
- package/dist/cjs/transformer.cjs.map +1 -0
- package/dist/cjs/transformer.d.cts +5 -0
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +1 -0
- package/dist/esm/Matches.d.ts +5 -4
- package/dist/esm/Matches.js +3 -1
- package/dist/esm/Matches.js.map +1 -1
- package/dist/esm/fileRoute.d.ts +4 -5
- package/dist/esm/fileRoute.js.map +1 -1
- package/dist/esm/index.d.ts +3 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/link.d.ts +1 -1
- package/dist/esm/link.js.map +1 -1
- package/dist/esm/path.js +6 -9
- package/dist/esm/path.js.map +1 -1
- package/dist/esm/route.d.ts +80 -61
- package/dist/esm/route.js +1 -1
- package/dist/esm/route.js.map +1 -1
- package/dist/esm/router.d.ts +22 -9
- package/dist/esm/router.js +91 -58
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/transformer.d.ts +5 -0
- package/dist/esm/transformer.js +39 -0
- package/dist/esm/transformer.js.map +1 -0
- package/dist/esm/utils.d.ts +1 -0
- package/dist/esm/utils.js.map +1 -1
- package/package.json +2 -2
- package/src/Matches.tsx +11 -7
- package/src/fileRoute.ts +28 -22
- package/src/index.tsx +5 -1
- package/src/link.tsx +10 -6
- package/src/path.ts +7 -10
- package/src/route.ts +375 -190
- package/src/router.ts +144 -75
- package/src/transformer.ts +49 -0
- package/src/utils.ts +5 -0
package/src/router.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createBrowserHistory,
|
|
3
|
+
createMemoryHistory,
|
|
4
|
+
parseHref,
|
|
5
|
+
} from '@tanstack/history'
|
|
2
6
|
import { Store } from '@tanstack/react-store'
|
|
3
7
|
import invariant from 'tiny-invariant'
|
|
4
8
|
import warning from 'tiny-warning'
|
|
@@ -25,6 +29,7 @@ import {
|
|
|
25
29
|
} from './path'
|
|
26
30
|
import { isRedirect, isResolvedRedirect } from './redirects'
|
|
27
31
|
import { isNotFound } from './not-found'
|
|
32
|
+
import { defaultTransformer } from './transformer'
|
|
28
33
|
import type * as React from 'react'
|
|
29
34
|
import type {
|
|
30
35
|
HistoryLocation,
|
|
@@ -37,12 +42,13 @@ import type {
|
|
|
37
42
|
AnyContext,
|
|
38
43
|
AnyRoute,
|
|
39
44
|
AnyRouteWithContext,
|
|
40
|
-
|
|
45
|
+
BeforeLoadContextOptions,
|
|
41
46
|
ErrorRouteComponent,
|
|
42
47
|
LoaderFnContext,
|
|
43
48
|
NotFoundRouteComponent,
|
|
44
49
|
RootRoute,
|
|
45
50
|
RouteComponent,
|
|
51
|
+
RouteContextOptions,
|
|
46
52
|
RouteMask,
|
|
47
53
|
} from './route'
|
|
48
54
|
import type {
|
|
@@ -73,13 +79,18 @@ import type {
|
|
|
73
79
|
import type { AnyRedirect, ResolvedRedirect } from './redirects'
|
|
74
80
|
import type { NotFoundError } from './not-found'
|
|
75
81
|
import type { NavigateOptions, ResolveRelativePath, ToOptions } from './link'
|
|
82
|
+
import type { RouterTransformer } from './transformer'
|
|
76
83
|
|
|
77
84
|
//
|
|
78
85
|
|
|
79
86
|
declare global {
|
|
80
87
|
interface Window {
|
|
81
88
|
__TSR__?: {
|
|
82
|
-
matches: Array<
|
|
89
|
+
matches: Array<{
|
|
90
|
+
__beforeLoadContext?: string
|
|
91
|
+
loaderData?: string
|
|
92
|
+
extracted?: Array<ExtractedEntry>
|
|
93
|
+
}>
|
|
83
94
|
streamedValues: Record<
|
|
84
95
|
string,
|
|
85
96
|
{
|
|
@@ -121,9 +132,9 @@ export type HydrationCtx = {
|
|
|
121
132
|
export type InferRouterContext<TRouteTree extends AnyRoute> =
|
|
122
133
|
TRouteTree extends RootRoute<
|
|
123
134
|
any,
|
|
135
|
+
infer TRouterContext extends AnyContext,
|
|
124
136
|
any,
|
|
125
137
|
any,
|
|
126
|
-
infer TRouterContext extends AnyContext,
|
|
127
138
|
any,
|
|
128
139
|
any,
|
|
129
140
|
any,
|
|
@@ -132,6 +143,20 @@ export type InferRouterContext<TRouteTree extends AnyRoute> =
|
|
|
132
143
|
? TRouterContext
|
|
133
144
|
: AnyContext
|
|
134
145
|
|
|
146
|
+
export type ExtractedEntry = {
|
|
147
|
+
dataType: '__beforeLoadContext' | 'loaderData'
|
|
148
|
+
type: 'promise' | 'stream'
|
|
149
|
+
path: Array<string>
|
|
150
|
+
value: any
|
|
151
|
+
id: number
|
|
152
|
+
streamState?: StreamState
|
|
153
|
+
matchIndex: number
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export type StreamState = {
|
|
157
|
+
promises: Array<ControlledPromise<string | null>>
|
|
158
|
+
}
|
|
159
|
+
|
|
135
160
|
export type RouterContextOptions<TRouteTree extends AnyRoute> =
|
|
136
161
|
AnyContext extends InferRouterContext<TRouteTree>
|
|
137
162
|
? {
|
|
@@ -428,10 +453,6 @@ export interface RouterOptions<
|
|
|
428
453
|
isServer?: boolean
|
|
429
454
|
}
|
|
430
455
|
|
|
431
|
-
export interface RouterTransformer {
|
|
432
|
-
stringify: (obj: unknown) => string
|
|
433
|
-
parse: (str: string) => unknown
|
|
434
|
-
}
|
|
435
456
|
export interface RouterErrorSerializer<TSerializedError> {
|
|
436
457
|
serialize: (err: unknown) => TSerializedError
|
|
437
458
|
deserialize: (err: TSerializedError) => unknown
|
|
@@ -591,7 +612,8 @@ export class Router<
|
|
|
591
612
|
matchIndex: number
|
|
592
613
|
}) => any
|
|
593
614
|
serializeLoaderData?: (
|
|
594
|
-
|
|
615
|
+
type: '__beforeLoadContext' | 'loaderData',
|
|
616
|
+
loaderData: any,
|
|
595
617
|
ctx: {
|
|
596
618
|
router: AnyRouter
|
|
597
619
|
match: AnyRouteMatch
|
|
@@ -644,10 +666,7 @@ export class Router<
|
|
|
644
666
|
notFoundMode: options.notFoundMode ?? 'fuzzy',
|
|
645
667
|
stringifySearch: options.stringifySearch ?? defaultStringifySearch,
|
|
646
668
|
parseSearch: options.parseSearch ?? defaultParseSearch,
|
|
647
|
-
transformer: options.transformer ??
|
|
648
|
-
parse: JSON.parse,
|
|
649
|
-
stringify: JSON.stringify,
|
|
650
|
-
},
|
|
669
|
+
transformer: options.transformer ?? defaultTransformer,
|
|
651
670
|
})
|
|
652
671
|
|
|
653
672
|
if (typeof document !== 'undefined') {
|
|
@@ -932,8 +951,7 @@ export class Router<
|
|
|
932
951
|
}
|
|
933
952
|
|
|
934
953
|
matchRoutes = (
|
|
935
|
-
|
|
936
|
-
locationSearch: AnySearchSchema,
|
|
954
|
+
next: ParsedLocation,
|
|
937
955
|
opts?: { preload?: boolean; throwOnError?: boolean },
|
|
938
956
|
): Array<AnyRouteMatch> => {
|
|
939
957
|
let routeParams: Record<string, string> = {}
|
|
@@ -941,7 +959,7 @@ export class Router<
|
|
|
941
959
|
const foundRoute = this.flatRoutes.find((route) => {
|
|
942
960
|
const matchedParams = matchPathname(
|
|
943
961
|
this.basepath,
|
|
944
|
-
trimPathRight(pathname),
|
|
962
|
+
trimPathRight(next.pathname),
|
|
945
963
|
{
|
|
946
964
|
to: route.fullPath,
|
|
947
965
|
caseSensitive:
|
|
@@ -971,7 +989,7 @@ export class Router<
|
|
|
971
989
|
foundRoute
|
|
972
990
|
? foundRoute.path !== '/' && routeParams['**']
|
|
973
991
|
: // Or if we didn't find a route and we have left over path
|
|
974
|
-
trimPathRight(pathname)
|
|
992
|
+
trimPathRight(next.pathname)
|
|
975
993
|
) {
|
|
976
994
|
// If the user has defined an (old) 404 route, use it
|
|
977
995
|
if (this.options.notFoundRoute) {
|
|
@@ -1048,7 +1066,7 @@ export class Router<
|
|
|
1048
1066
|
|
|
1049
1067
|
const [preMatchSearch, searchError]: [Record<string, any>, any] = (() => {
|
|
1050
1068
|
// Validate the search params and stabilize them
|
|
1051
|
-
const parentSearch = parentMatch?.search ??
|
|
1069
|
+
const parentSearch = parentMatch?.search ?? next.search
|
|
1052
1070
|
|
|
1053
1071
|
try {
|
|
1054
1072
|
const validator =
|
|
@@ -1138,8 +1156,9 @@ export class Router<
|
|
|
1138
1156
|
isFetching: false,
|
|
1139
1157
|
error: undefined,
|
|
1140
1158
|
paramsError: parseErrors[index],
|
|
1141
|
-
|
|
1142
|
-
|
|
1159
|
+
__routeContext: {},
|
|
1160
|
+
__beforeLoadContext: {},
|
|
1161
|
+
context: {},
|
|
1143
1162
|
abortController: new AbortController(),
|
|
1144
1163
|
fetchCount: 0,
|
|
1145
1164
|
cause,
|
|
@@ -1180,6 +1199,41 @@ export class Router<
|
|
|
1180
1199
|
// And also update the searchError if there is one
|
|
1181
1200
|
match.searchError = searchError
|
|
1182
1201
|
|
|
1202
|
+
const parentMatchId = parentMatch?.id
|
|
1203
|
+
|
|
1204
|
+
const parentContext = !parentMatchId
|
|
1205
|
+
? ((this.options.context as any) ?? {})
|
|
1206
|
+
: (parentMatch.context ?? this.options.context ?? {})
|
|
1207
|
+
|
|
1208
|
+
match.context = {
|
|
1209
|
+
...parentContext,
|
|
1210
|
+
...match.__routeContext,
|
|
1211
|
+
...match.__beforeLoadContext,
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
// Update the match's context
|
|
1215
|
+
const contextFnContext: RouteContextOptions<any, any, any, any> = {
|
|
1216
|
+
search: match.search,
|
|
1217
|
+
params: match.params,
|
|
1218
|
+
context: match.context,
|
|
1219
|
+
location: next,
|
|
1220
|
+
navigate: (opts: any) =>
|
|
1221
|
+
this.navigate({ ...opts, _fromLocation: next }),
|
|
1222
|
+
buildLocation: this.buildLocation,
|
|
1223
|
+
cause: match.cause,
|
|
1224
|
+
abortController: match.abortController,
|
|
1225
|
+
preload: !!match.preload,
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
// Get the route context
|
|
1229
|
+
match.__routeContext = route.options.context?.(contextFnContext) ?? {}
|
|
1230
|
+
|
|
1231
|
+
match.context = {
|
|
1232
|
+
...parentContext,
|
|
1233
|
+
...match.__routeContext,
|
|
1234
|
+
...match.__beforeLoadContext,
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1183
1237
|
matches.push(match)
|
|
1184
1238
|
})
|
|
1185
1239
|
|
|
@@ -1210,10 +1264,10 @@ export class Router<
|
|
|
1210
1264
|
): ParsedLocation => {
|
|
1211
1265
|
const fromMatches =
|
|
1212
1266
|
dest._fromLocation != null
|
|
1213
|
-
? this.matchRoutes(
|
|
1214
|
-
dest._fromLocation
|
|
1215
|
-
dest.fromSearch || dest._fromLocation.search,
|
|
1216
|
-
)
|
|
1267
|
+
? this.matchRoutes({
|
|
1268
|
+
...dest._fromLocation,
|
|
1269
|
+
search: dest.fromSearch || dest._fromLocation.search,
|
|
1270
|
+
})
|
|
1217
1271
|
: this.state.matches
|
|
1218
1272
|
|
|
1219
1273
|
const fromMatch =
|
|
@@ -1389,9 +1443,9 @@ export class Router<
|
|
|
1389
1443
|
}
|
|
1390
1444
|
}
|
|
1391
1445
|
|
|
1392
|
-
const nextMatches = this.matchRoutes(next
|
|
1446
|
+
const nextMatches = this.matchRoutes(next)
|
|
1393
1447
|
const maskedMatches = maskedNext
|
|
1394
|
-
? this.matchRoutes(maskedNext
|
|
1448
|
+
? this.matchRoutes(maskedNext)
|
|
1395
1449
|
: undefined
|
|
1396
1450
|
const maskedFinal = maskedNext
|
|
1397
1451
|
? build(maskedDest, maskedMatches)
|
|
@@ -1501,6 +1555,14 @@ export class Router<
|
|
|
1501
1555
|
ignoreBlocker,
|
|
1502
1556
|
...rest
|
|
1503
1557
|
}: BuildNextOptions & CommitLocationOptions = {}) => {
|
|
1558
|
+
const href = (rest as any).href
|
|
1559
|
+
if (href) {
|
|
1560
|
+
const parsed = parseHref(href, {})
|
|
1561
|
+
rest.to = parsed.pathname
|
|
1562
|
+
rest.search = this.options.parseSearch(parsed.search)
|
|
1563
|
+
rest.hash = parsed.hash
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1504
1566
|
const location = this.buildLocation(rest as any)
|
|
1505
1567
|
return this.commitLocation({
|
|
1506
1568
|
...location,
|
|
@@ -1511,14 +1573,13 @@ export class Router<
|
|
|
1511
1573
|
})
|
|
1512
1574
|
}
|
|
1513
1575
|
|
|
1514
|
-
navigate: NavigateFn = ({
|
|
1576
|
+
navigate: NavigateFn = ({ to, __isRedirect, ...rest }) => {
|
|
1515
1577
|
// If this link simply reloads the current route,
|
|
1516
1578
|
// make sure it has a new key so it will trigger a data refresh
|
|
1517
1579
|
|
|
1518
1580
|
// If this `to` is a valid external URL, return
|
|
1519
1581
|
// null for LinkUtils
|
|
1520
1582
|
const toString = String(to)
|
|
1521
|
-
// const fromString = from !== undefined ? String(from) : from
|
|
1522
1583
|
let isExternal
|
|
1523
1584
|
|
|
1524
1585
|
try {
|
|
@@ -1533,7 +1594,6 @@ export class Router<
|
|
|
1533
1594
|
|
|
1534
1595
|
return this.buildAndCommitLocation({
|
|
1535
1596
|
...rest,
|
|
1536
|
-
from,
|
|
1537
1597
|
to,
|
|
1538
1598
|
// to: toString,
|
|
1539
1599
|
})
|
|
@@ -1552,7 +1612,10 @@ export class Router<
|
|
|
1552
1612
|
let redirect: ResolvedRedirect | undefined
|
|
1553
1613
|
let notFound: NotFoundError | undefined
|
|
1554
1614
|
|
|
1555
|
-
|
|
1615
|
+
let loadPromise: Promise<void>
|
|
1616
|
+
|
|
1617
|
+
// eslint-disable-next-line prefer-const
|
|
1618
|
+
loadPromise = new Promise<void>((resolve) => {
|
|
1556
1619
|
this.startReactTransition(async () => {
|
|
1557
1620
|
try {
|
|
1558
1621
|
const next = this.latestLocation
|
|
@@ -1570,7 +1633,7 @@ export class Router<
|
|
|
1570
1633
|
// this.cleanCache()
|
|
1571
1634
|
|
|
1572
1635
|
// Match the routes
|
|
1573
|
-
pendingMatches = this.matchRoutes(next
|
|
1636
|
+
pendingMatches = this.matchRoutes(next)
|
|
1574
1637
|
|
|
1575
1638
|
// Ingest the new matches
|
|
1576
1639
|
this.__store.setState((s) => ({
|
|
@@ -1871,6 +1934,7 @@ export class Router<
|
|
|
1871
1934
|
|
|
1872
1935
|
for (const [index, { id: matchId, routeId }] of matches.entries()) {
|
|
1873
1936
|
const existingMatch = this.getMatch(matchId)!
|
|
1937
|
+
const parentMatchId = matches[index - 1]?.id
|
|
1874
1938
|
|
|
1875
1939
|
if (
|
|
1876
1940
|
// If we are in the middle of a load, either of these will be present
|
|
@@ -1894,20 +1958,6 @@ export class Router<
|
|
|
1894
1958
|
const route = this.looseRoutesById[routeId]!
|
|
1895
1959
|
const abortController = new AbortController()
|
|
1896
1960
|
|
|
1897
|
-
const parentMatchId = matches[index - 1]?.id
|
|
1898
|
-
|
|
1899
|
-
const getParentContext = () => {
|
|
1900
|
-
if (!parentMatchId) {
|
|
1901
|
-
return (this.options.context as any) ?? {}
|
|
1902
|
-
}
|
|
1903
|
-
|
|
1904
|
-
return (
|
|
1905
|
-
this.getMatch(parentMatchId)!.context ??
|
|
1906
|
-
this.options.context ??
|
|
1907
|
-
{}
|
|
1908
|
-
)
|
|
1909
|
-
}
|
|
1910
|
-
|
|
1911
1961
|
const pendingMs =
|
|
1912
1962
|
route.options.pendingMs ?? this.options.defaultPendingMs
|
|
1913
1963
|
|
|
@@ -1946,30 +1996,39 @@ export class Router<
|
|
|
1946
1996
|
handleSerialError(index, searchError, 'VALIDATE_SEARCH')
|
|
1947
1997
|
}
|
|
1948
1998
|
|
|
1949
|
-
const
|
|
1999
|
+
const getParentMatchContext = () =>
|
|
2000
|
+
parentMatchId
|
|
2001
|
+
? this.getMatch(parentMatchId)!.context
|
|
2002
|
+
: (this.options.context ?? {})
|
|
1950
2003
|
|
|
1951
2004
|
updateMatch(matchId, (prev) => ({
|
|
1952
2005
|
...prev,
|
|
1953
2006
|
isFetching: 'beforeLoad',
|
|
1954
2007
|
fetchCount: prev.fetchCount + 1,
|
|
1955
|
-
routeContext: replaceEqualDeep(
|
|
1956
|
-
prev.routeContext,
|
|
1957
|
-
parentContext,
|
|
1958
|
-
),
|
|
1959
|
-
context: replaceEqualDeep(prev.context, parentContext),
|
|
1960
2008
|
abortController,
|
|
1961
2009
|
pendingTimeout,
|
|
2010
|
+
context: {
|
|
2011
|
+
...getParentMatchContext(),
|
|
2012
|
+
...prev.__routeContext,
|
|
2013
|
+
...prev.__beforeLoadContext,
|
|
2014
|
+
},
|
|
1962
2015
|
}))
|
|
1963
2016
|
|
|
1964
|
-
const { search, params,
|
|
2017
|
+
const { search, params, context, cause } =
|
|
1965
2018
|
this.getMatch(matchId)!
|
|
1966
2019
|
|
|
1967
|
-
const beforeLoadFnContext
|
|
2020
|
+
const beforeLoadFnContext: BeforeLoadContextOptions<
|
|
2021
|
+
any,
|
|
2022
|
+
any,
|
|
2023
|
+
any,
|
|
2024
|
+
any,
|
|
2025
|
+
any
|
|
2026
|
+
> = {
|
|
1968
2027
|
search,
|
|
1969
2028
|
abortController,
|
|
1970
2029
|
params,
|
|
1971
2030
|
preload: !!preload,
|
|
1972
|
-
context
|
|
2031
|
+
context,
|
|
1973
2032
|
location,
|
|
1974
2033
|
navigate: (opts: any) =>
|
|
1975
2034
|
this.navigate({ ...opts, _fromLocation: location }),
|
|
@@ -1977,10 +2036,21 @@ export class Router<
|
|
|
1977
2036
|
cause: preload ? 'preload' : cause,
|
|
1978
2037
|
}
|
|
1979
2038
|
|
|
1980
|
-
|
|
2039
|
+
let beforeLoadContext =
|
|
1981
2040
|
(await route.options.beforeLoad?.(beforeLoadFnContext)) ??
|
|
1982
2041
|
{}
|
|
1983
2042
|
|
|
2043
|
+
if (this.serializeLoaderData) {
|
|
2044
|
+
beforeLoadContext = this.serializeLoaderData(
|
|
2045
|
+
'__beforeLoadContext',
|
|
2046
|
+
beforeLoadContext,
|
|
2047
|
+
{
|
|
2048
|
+
router: this,
|
|
2049
|
+
match: this.getMatch(matchId)!,
|
|
2050
|
+
},
|
|
2051
|
+
)
|
|
2052
|
+
}
|
|
2053
|
+
|
|
1984
2054
|
if (
|
|
1985
2055
|
isRedirect(beforeLoadContext) ||
|
|
1986
2056
|
isNotFound(beforeLoadContext)
|
|
@@ -1989,18 +2059,14 @@ export class Router<
|
|
|
1989
2059
|
}
|
|
1990
2060
|
|
|
1991
2061
|
updateMatch(matchId, (prev) => {
|
|
1992
|
-
const routeContext = {
|
|
1993
|
-
...prev.routeContext,
|
|
1994
|
-
...beforeLoadContext,
|
|
1995
|
-
}
|
|
1996
|
-
|
|
1997
2062
|
return {
|
|
1998
2063
|
...prev,
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2064
|
+
__beforeLoadContext: beforeLoadContext,
|
|
2065
|
+
context: {
|
|
2066
|
+
...getParentMatchContext(),
|
|
2067
|
+
...prev.__routeContext,
|
|
2068
|
+
...beforeLoadContext,
|
|
2069
|
+
},
|
|
2004
2070
|
abortController,
|
|
2005
2071
|
}
|
|
2006
2072
|
})
|
|
@@ -2150,10 +2216,14 @@ export class Router<
|
|
|
2150
2216
|
await route.options.loader?.(getLoaderContext())
|
|
2151
2217
|
|
|
2152
2218
|
if (this.serializeLoaderData) {
|
|
2153
|
-
loaderData = this.serializeLoaderData(
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2219
|
+
loaderData = this.serializeLoaderData(
|
|
2220
|
+
'loaderData',
|
|
2221
|
+
loaderData,
|
|
2222
|
+
{
|
|
2223
|
+
router: this,
|
|
2224
|
+
match: this.getMatch(matchId)!,
|
|
2225
|
+
},
|
|
2226
|
+
)
|
|
2157
2227
|
}
|
|
2158
2228
|
|
|
2159
2229
|
handleRedirectAndNotFound(
|
|
@@ -2220,7 +2290,9 @@ export class Router<
|
|
|
2220
2290
|
// If the route is successful and still fresh, just resolve
|
|
2221
2291
|
const { status, invalid } = this.getMatch(matchId)!
|
|
2222
2292
|
|
|
2223
|
-
if (
|
|
2293
|
+
if (preload && route.options.preload === false) {
|
|
2294
|
+
// Do nothing
|
|
2295
|
+
} else if (
|
|
2224
2296
|
status === 'success' &&
|
|
2225
2297
|
(invalid || (shouldReload ?? age > staleAge))
|
|
2226
2298
|
) {
|
|
@@ -2342,7 +2414,7 @@ export class Router<
|
|
|
2342
2414
|
): Promise<Array<AnyRouteMatch> | undefined> => {
|
|
2343
2415
|
const next = this.buildLocation(opts as any)
|
|
2344
2416
|
|
|
2345
|
-
let matches = this.matchRoutes(next
|
|
2417
|
+
let matches = this.matchRoutes(next, {
|
|
2346
2418
|
throwOnError: true,
|
|
2347
2419
|
preload: true,
|
|
2348
2420
|
})
|
|
@@ -2499,10 +2571,7 @@ export class Router<
|
|
|
2499
2571
|
this.options.hydrate?.(ctx.payload as any)
|
|
2500
2572
|
const dehydratedState = ctx.router.state
|
|
2501
2573
|
|
|
2502
|
-
const matches = this.matchRoutes(
|
|
2503
|
-
this.state.location.pathname,
|
|
2504
|
-
this.state.location.search,
|
|
2505
|
-
).map((match) => {
|
|
2574
|
+
const matches = this.matchRoutes(this.state.location).map((match) => {
|
|
2506
2575
|
const dehydratedMatch = dehydratedState.dehydratedMatches.find(
|
|
2507
2576
|
(d) => d.id === match.id,
|
|
2508
2577
|
)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { isPlainObject } from './utils'
|
|
2
|
+
|
|
3
|
+
export interface RouterTransformer {
|
|
4
|
+
stringify: (obj: unknown) => string
|
|
5
|
+
parse: (str: string) => unknown
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const defaultTransformer: RouterTransformer = {
|
|
9
|
+
stringify: (value: any) =>
|
|
10
|
+
JSON.stringify(value, function replacer(key, value) {
|
|
11
|
+
const keyVal = this[key]
|
|
12
|
+
const transformer = transformers.find((t) => t.stringifyCondition(keyVal))
|
|
13
|
+
|
|
14
|
+
if (transformer) {
|
|
15
|
+
return transformer.stringify(keyVal)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return value
|
|
19
|
+
}),
|
|
20
|
+
parse: (value: string) =>
|
|
21
|
+
JSON.parse(value, function parser(key, value) {
|
|
22
|
+
const keyVal = this[key]
|
|
23
|
+
const transformer = transformers.find((t) => t.parseCondition(keyVal))
|
|
24
|
+
|
|
25
|
+
if (transformer) {
|
|
26
|
+
return transformer.parse(keyVal)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return value
|
|
30
|
+
}),
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const transformers = [
|
|
34
|
+
{
|
|
35
|
+
// Dates
|
|
36
|
+
stringifyCondition: (value: any) => value instanceof Date,
|
|
37
|
+
stringify: (value: any) => ({ $date: value.toISOString() }),
|
|
38
|
+
parseCondition: (value: any) => isPlainObject(value) && value.$date,
|
|
39
|
+
parse: (value: any) => new Date(value.$date),
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
// undefined
|
|
43
|
+
stringifyCondition: (value: any) => value === undefined,
|
|
44
|
+
stringify: () => ({ $undefined: '' }),
|
|
45
|
+
parseCondition: (value: any) =>
|
|
46
|
+
isPlainObject(value) && value.$undefined === '',
|
|
47
|
+
parse: () => undefined,
|
|
48
|
+
},
|
|
49
|
+
] as const
|
package/src/utils.ts
CHANGED
|
@@ -4,6 +4,7 @@ export type NoInfer<T> = [T][T extends any ? 0 : never]
|
|
|
4
4
|
export type IsAny<TValue, TYesResult, TNoResult = TValue> = 1 extends 0 & TValue
|
|
5
5
|
? TYesResult
|
|
6
6
|
: TNoResult
|
|
7
|
+
|
|
7
8
|
export type PickAsRequired<TValue, TKey extends keyof TValue> = Omit<
|
|
8
9
|
TValue,
|
|
9
10
|
TKey
|
|
@@ -98,6 +99,10 @@ export type MergeUnion<TUnion> =
|
|
|
98
99
|
| MergeUnionPrimitives<TUnion>
|
|
99
100
|
| MergeUnionObject<TUnion>
|
|
100
101
|
|
|
102
|
+
export type Constrain<T, TConstaint> =
|
|
103
|
+
| (T extends TConstaint ? T : never)
|
|
104
|
+
| TConstaint
|
|
105
|
+
|
|
101
106
|
export function last<T>(arr: Array<T>) {
|
|
102
107
|
return arr[arr.length - 1]
|
|
103
108
|
}
|