@tanstack/router-core 1.120.3 → 1.120.4-alpha.10
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/fileRoute.d.cts +6 -2
- package/dist/cjs/index.cjs +4 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +6 -6
- package/dist/cjs/link.cjs.map +1 -1
- package/dist/cjs/link.d.cts +17 -1
- package/dist/cjs/path.cjs +130 -16
- package/dist/cjs/path.cjs.map +1 -1
- package/dist/cjs/path.d.cts +17 -0
- package/dist/cjs/redirect.cjs +19 -14
- package/dist/cjs/redirect.cjs.map +1 -1
- package/dist/cjs/redirect.d.cts +10 -7
- package/dist/cjs/route.cjs +12 -1
- package/dist/cjs/route.cjs.map +1 -1
- package/dist/cjs/route.d.cts +15 -14
- package/dist/cjs/router.cjs +227 -155
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +45 -3
- package/dist/cjs/typePrimitives.d.cts +2 -2
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +2 -0
- package/dist/esm/fileRoute.d.ts +6 -2
- package/dist/esm/index.d.ts +6 -6
- package/dist/esm/index.js +7 -3
- package/dist/esm/link.d.ts +17 -1
- package/dist/esm/link.js.map +1 -1
- package/dist/esm/path.d.ts +17 -0
- package/dist/esm/path.js +130 -16
- package/dist/esm/path.js.map +1 -1
- package/dist/esm/redirect.d.ts +10 -7
- package/dist/esm/redirect.js +20 -15
- package/dist/esm/redirect.js.map +1 -1
- package/dist/esm/route.d.ts +15 -14
- package/dist/esm/route.js +12 -1
- package/dist/esm/route.js.map +1 -1
- package/dist/esm/router.d.ts +45 -3
- package/dist/esm/router.js +230 -158
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/typePrimitives.d.ts +2 -2
- package/dist/esm/utils.d.ts +2 -0
- package/dist/esm/utils.js.map +1 -1
- package/package.json +2 -2
- package/src/fileRoute.ts +90 -1
- package/src/index.ts +15 -6
- package/src/link.ts +96 -11
- package/src/path.ts +181 -16
- package/src/redirect.ts +37 -22
- package/src/route.ts +104 -35
- package/src/router.ts +326 -209
- package/src/typePrimitives.ts +2 -2
- package/src/utils.ts +14 -0
package/src/router.ts
CHANGED
|
@@ -28,7 +28,7 @@ import { isNotFound } from './not-found'
|
|
|
28
28
|
import { setupScrollRestoration } from './scroll-restoration'
|
|
29
29
|
import { defaultParseSearch, defaultStringifySearch } from './searchParams'
|
|
30
30
|
import { rootRouteId } from './root'
|
|
31
|
-
import { isRedirect
|
|
31
|
+
import { isRedirect } from './redirect'
|
|
32
32
|
import type { SearchParser, SearchSerializer } from './searchParams'
|
|
33
33
|
import type { AnyRedirect, ResolvedRedirect } from './redirect'
|
|
34
34
|
import type {
|
|
@@ -165,6 +165,14 @@ export interface RouterOptions<
|
|
|
165
165
|
* @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/preloading#preload-delay)
|
|
166
166
|
*/
|
|
167
167
|
defaultPreloadDelay?: number
|
|
168
|
+
/**
|
|
169
|
+
* The default `preloadIntentProximity` a route should use if no preloadIntentProximity is provided.
|
|
170
|
+
*
|
|
171
|
+
* @default 0
|
|
172
|
+
* @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#defaultpreloadintentproximity-property)
|
|
173
|
+
* @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/preloading#preload-intent-proximity)
|
|
174
|
+
*/
|
|
175
|
+
defaultPreloadIntentProximity?: number
|
|
168
176
|
/**
|
|
169
177
|
* The default `pendingMs` a route should use if no pendingMs is provided.
|
|
170
178
|
*
|
|
@@ -407,7 +415,7 @@ export interface RouterState<
|
|
|
407
415
|
location: ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
408
416
|
resolvedLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
409
417
|
statusCode: number
|
|
410
|
-
redirect?:
|
|
418
|
+
redirect?: AnyRedirect
|
|
411
419
|
}
|
|
412
420
|
|
|
413
421
|
export interface BuildNextOptions {
|
|
@@ -593,8 +601,8 @@ export type ParseLocationFn<TRouteTree extends AnyRoute> = (
|
|
|
593
601
|
) => ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
594
602
|
|
|
595
603
|
export type GetMatchRoutesFn = (
|
|
596
|
-
|
|
597
|
-
|
|
604
|
+
pathname: string,
|
|
605
|
+
routePathname: string | undefined,
|
|
598
606
|
) => {
|
|
599
607
|
matchedRoutes: Array<AnyRoute>
|
|
600
608
|
routeParams: Record<string, string>
|
|
@@ -882,7 +890,6 @@ export class RouterCore<
|
|
|
882
890
|
}
|
|
883
891
|
|
|
884
892
|
if (
|
|
885
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
886
893
|
!this.history ||
|
|
887
894
|
(this.options.history && this.options.history !== this.history)
|
|
888
895
|
) {
|
|
@@ -901,7 +908,6 @@ export class RouterCore<
|
|
|
901
908
|
this.buildRouteTree()
|
|
902
909
|
}
|
|
903
910
|
|
|
904
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
905
911
|
if (!this.__store) {
|
|
906
912
|
this.__store = new Store(getInitialRouterState(this.latestLocation), {
|
|
907
913
|
onUpdate: () => {
|
|
@@ -920,7 +926,6 @@ export class RouterCore<
|
|
|
920
926
|
if (
|
|
921
927
|
typeof window !== 'undefined' &&
|
|
922
928
|
'CSS' in window &&
|
|
923
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
924
929
|
typeof window.CSS?.supports === 'function'
|
|
925
930
|
) {
|
|
926
931
|
this.isViewTransitionTypesSupported = window.CSS.supports(
|
|
@@ -934,124 +939,29 @@ export class RouterCore<
|
|
|
934
939
|
}
|
|
935
940
|
|
|
936
941
|
buildRouteTree = () => {
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
if (notFoundRoute) {
|
|
942
|
-
notFoundRoute.init({
|
|
943
|
-
originalIndex: 99999999999,
|
|
944
|
-
defaultSsr: this.options.defaultSsr,
|
|
945
|
-
})
|
|
946
|
-
;(this.routesById as any)[notFoundRoute.id] = notFoundRoute
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
const recurseRoutes = (childRoutes: Array<AnyRoute>) => {
|
|
950
|
-
childRoutes.forEach((childRoute, i) => {
|
|
951
|
-
childRoute.init({
|
|
942
|
+
const { routesById, routesByPath, flatRoutes } = processRouteTree({
|
|
943
|
+
routeTree: this.routeTree,
|
|
944
|
+
initRoute: (route, i) => {
|
|
945
|
+
route.init({
|
|
952
946
|
originalIndex: i,
|
|
953
947
|
defaultSsr: this.options.defaultSsr,
|
|
954
948
|
})
|
|
955
|
-
|
|
956
|
-
const existingRoute = (this.routesById as any)[childRoute.id]
|
|
957
|
-
|
|
958
|
-
invariant(
|
|
959
|
-
!existingRoute,
|
|
960
|
-
`Duplicate routes found with id: ${String(childRoute.id)}`,
|
|
961
|
-
)
|
|
962
|
-
;(this.routesById as any)[childRoute.id] = childRoute
|
|
963
|
-
|
|
964
|
-
if (!childRoute.isRoot && childRoute.path) {
|
|
965
|
-
const trimmedFullPath = trimPathRight(childRoute.fullPath)
|
|
966
|
-
if (
|
|
967
|
-
!(this.routesByPath as any)[trimmedFullPath] ||
|
|
968
|
-
childRoute.fullPath.endsWith('/')
|
|
969
|
-
) {
|
|
970
|
-
;(this.routesByPath as any)[trimmedFullPath] = childRoute
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
const children = childRoute.children
|
|
975
|
-
|
|
976
|
-
if (children?.length) {
|
|
977
|
-
recurseRoutes(children)
|
|
978
|
-
}
|
|
979
|
-
})
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
recurseRoutes([this.routeTree])
|
|
983
|
-
|
|
984
|
-
const scoredRoutes: Array<{
|
|
985
|
-
child: AnyRoute
|
|
986
|
-
trimmed: string
|
|
987
|
-
parsed: ReturnType<typeof parsePathname>
|
|
988
|
-
index: number
|
|
989
|
-
scores: Array<number>
|
|
990
|
-
}> = []
|
|
991
|
-
|
|
992
|
-
const routes: Array<AnyRoute> = Object.values(this.routesById)
|
|
993
|
-
|
|
994
|
-
routes.forEach((d, i) => {
|
|
995
|
-
if (d.isRoot || !d.path) {
|
|
996
|
-
return
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
const trimmed = trimPathLeft(d.fullPath)
|
|
1000
|
-
const parsed = parsePathname(trimmed)
|
|
1001
|
-
|
|
1002
|
-
while (parsed.length > 1 && parsed[0]?.value === '/') {
|
|
1003
|
-
parsed.shift()
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
const scores = parsed.map((segment) => {
|
|
1007
|
-
if (segment.value === '/') {
|
|
1008
|
-
return 0.75
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
if (segment.type === 'param') {
|
|
1012
|
-
return 0.5
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
if (segment.type === 'wildcard') {
|
|
1016
|
-
return 0.25
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
return 1
|
|
1020
|
-
})
|
|
1021
|
-
|
|
1022
|
-
scoredRoutes.push({ child: d, trimmed, parsed, index: i, scores })
|
|
949
|
+
},
|
|
1023
950
|
})
|
|
1024
951
|
|
|
1025
|
-
this.
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
// Sort by min available score
|
|
1030
|
-
for (let i = 0; i < minLength; i++) {
|
|
1031
|
-
if (a.scores[i] !== b.scores[i]) {
|
|
1032
|
-
return b.scores[i]! - a.scores[i]!
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
// Sort by length of score
|
|
1037
|
-
if (a.scores.length !== b.scores.length) {
|
|
1038
|
-
return b.scores.length - a.scores.length
|
|
1039
|
-
}
|
|
952
|
+
this.routesById = routesById as RoutesById<TRouteTree>
|
|
953
|
+
this.routesByPath = routesByPath as RoutesByPath<TRouteTree>
|
|
954
|
+
this.flatRoutes = flatRoutes as Array<AnyRoute>
|
|
1040
955
|
|
|
1041
|
-
|
|
1042
|
-
for (let i = 0; i < minLength; i++) {
|
|
1043
|
-
if (a.parsed[i]!.value !== b.parsed[i]!.value) {
|
|
1044
|
-
return a.parsed[i]!.value > b.parsed[i]!.value ? 1 : -1
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
956
|
+
const notFoundRoute = this.options.notFoundRoute
|
|
1047
957
|
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
d.child.rank = i
|
|
1053
|
-
return d.child
|
|
958
|
+
if (notFoundRoute) {
|
|
959
|
+
notFoundRoute.init({
|
|
960
|
+
originalIndex: 99999999999,
|
|
961
|
+
defaultSsr: this.options.defaultSsr,
|
|
1054
962
|
})
|
|
963
|
+
this.routesById[notFoundRoute.id] = notFoundRoute
|
|
964
|
+
}
|
|
1055
965
|
}
|
|
1056
966
|
|
|
1057
967
|
subscribe: SubscribeFn = (eventType, fn) => {
|
|
@@ -1165,8 +1075,8 @@ export class RouterCore<
|
|
|
1165
1075
|
opts?: MatchRoutesOpts,
|
|
1166
1076
|
): Array<AnyRouteMatch> {
|
|
1167
1077
|
const { foundRoute, matchedRoutes, routeParams } = this.getMatchedRoutes(
|
|
1168
|
-
next,
|
|
1169
|
-
opts?.dest,
|
|
1078
|
+
next.pathname,
|
|
1079
|
+
opts?.dest?.to as string,
|
|
1170
1080
|
)
|
|
1171
1081
|
let isGlobalNotFound = false
|
|
1172
1082
|
|
|
@@ -1472,47 +1382,19 @@ export class RouterCore<
|
|
|
1472
1382
|
return matches
|
|
1473
1383
|
}
|
|
1474
1384
|
|
|
1475
|
-
getMatchedRoutes: GetMatchRoutesFn = (
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
let foundRoute: AnyRoute | undefined =
|
|
1489
|
-
dest?.to !== undefined ? this.routesByPath[dest.to!] : undefined
|
|
1490
|
-
if (foundRoute) {
|
|
1491
|
-
routeParams = getMatchedParams(foundRoute)!
|
|
1492
|
-
} else {
|
|
1493
|
-
foundRoute = this.flatRoutes.find((route) => {
|
|
1494
|
-
const matchedParams = getMatchedParams(route)
|
|
1495
|
-
|
|
1496
|
-
if (matchedParams) {
|
|
1497
|
-
routeParams = matchedParams
|
|
1498
|
-
return true
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
|
-
return false
|
|
1502
|
-
})
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
|
-
let routeCursor: AnyRoute =
|
|
1506
|
-
foundRoute || (this.routesById as any)[rootRouteId]
|
|
1507
|
-
|
|
1508
|
-
const matchedRoutes: Array<AnyRoute> = [routeCursor]
|
|
1509
|
-
|
|
1510
|
-
while (routeCursor.parentRoute) {
|
|
1511
|
-
routeCursor = routeCursor.parentRoute
|
|
1512
|
-
matchedRoutes.unshift(routeCursor)
|
|
1513
|
-
}
|
|
1514
|
-
|
|
1515
|
-
return { matchedRoutes, routeParams, foundRoute }
|
|
1385
|
+
getMatchedRoutes: GetMatchRoutesFn = (
|
|
1386
|
+
pathname: string,
|
|
1387
|
+
routePathname: string | undefined,
|
|
1388
|
+
) => {
|
|
1389
|
+
return getMatchedRoutes({
|
|
1390
|
+
pathname,
|
|
1391
|
+
routePathname,
|
|
1392
|
+
basepath: this.basepath,
|
|
1393
|
+
caseSensitive: this.options.caseSensitive,
|
|
1394
|
+
routesByPath: this.routesByPath,
|
|
1395
|
+
routesById: this.routesById,
|
|
1396
|
+
flatRoutes: this.flatRoutes,
|
|
1397
|
+
})
|
|
1516
1398
|
}
|
|
1517
1399
|
|
|
1518
1400
|
cancelMatch = (id: string) => {
|
|
@@ -1812,11 +1694,17 @@ export class RouterCore<
|
|
|
1812
1694
|
}
|
|
1813
1695
|
}
|
|
1814
1696
|
|
|
1815
|
-
const nextMatches = this.getMatchedRoutes(
|
|
1697
|
+
const nextMatches = this.getMatchedRoutes(
|
|
1698
|
+
next.pathname,
|
|
1699
|
+
dest.to as string,
|
|
1700
|
+
)
|
|
1816
1701
|
const final = build(dest, nextMatches)
|
|
1817
1702
|
|
|
1818
1703
|
if (maskedNext) {
|
|
1819
|
-
const maskedMatches = this.getMatchedRoutes(
|
|
1704
|
+
const maskedMatches = this.getMatchedRoutes(
|
|
1705
|
+
maskedNext.pathname,
|
|
1706
|
+
maskedDest?.to as string,
|
|
1707
|
+
)
|
|
1820
1708
|
const maskedFinal = build(maskedDest, maskedMatches)
|
|
1821
1709
|
final.maskedLocation = maskedFinal
|
|
1822
1710
|
}
|
|
@@ -1958,6 +1846,13 @@ export class RouterCore<
|
|
|
1958
1846
|
}
|
|
1959
1847
|
|
|
1960
1848
|
navigate: NavigateFn = ({ to, reloadDocument, href, ...rest }) => {
|
|
1849
|
+
if (!reloadDocument && href) {
|
|
1850
|
+
try {
|
|
1851
|
+
new URL(`${href}`)
|
|
1852
|
+
reloadDocument = true
|
|
1853
|
+
} catch {}
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1961
1856
|
if (reloadDocument) {
|
|
1962
1857
|
if (!href) {
|
|
1963
1858
|
const location = this.buildLocation({ to, ...rest } as any)
|
|
@@ -1980,10 +1875,30 @@ export class RouterCore<
|
|
|
1980
1875
|
|
|
1981
1876
|
latestLoadPromise: undefined | Promise<void>
|
|
1982
1877
|
|
|
1983
|
-
|
|
1878
|
+
beforeLoad = () => {
|
|
1879
|
+
// Cancel any pending matches
|
|
1880
|
+
this.cancelMatches()
|
|
1984
1881
|
this.latestLocation = this.parseLocation(this.latestLocation)
|
|
1985
1882
|
|
|
1986
|
-
|
|
1883
|
+
// Match the routes
|
|
1884
|
+
const pendingMatches = this.matchRoutes(this.latestLocation)
|
|
1885
|
+
|
|
1886
|
+
// Ingest the new matches
|
|
1887
|
+
this.__store.setState((s) => ({
|
|
1888
|
+
...s,
|
|
1889
|
+
status: 'pending',
|
|
1890
|
+
isLoading: true,
|
|
1891
|
+
location: this.latestLocation,
|
|
1892
|
+
pendingMatches,
|
|
1893
|
+
// If a cached moved to pendingMatches, remove it from cachedMatches
|
|
1894
|
+
cachedMatches: s.cachedMatches.filter((d) => {
|
|
1895
|
+
return !pendingMatches.find((e) => e.id === d.id)
|
|
1896
|
+
}),
|
|
1897
|
+
}))
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
load: LoadFn = async (opts?: { sync?: boolean }): Promise<void> => {
|
|
1901
|
+
let redirect: AnyRedirect | undefined
|
|
1987
1902
|
let notFound: NotFoundError | undefined
|
|
1988
1903
|
|
|
1989
1904
|
let loadPromise: Promise<void>
|
|
@@ -1992,36 +1907,10 @@ export class RouterCore<
|
|
|
1992
1907
|
loadPromise = new Promise<void>((resolve) => {
|
|
1993
1908
|
this.startTransition(async () => {
|
|
1994
1909
|
try {
|
|
1910
|
+
this.beforeLoad()
|
|
1995
1911
|
const next = this.latestLocation
|
|
1996
1912
|
const prevLocation = this.state.resolvedLocation
|
|
1997
1913
|
|
|
1998
|
-
// Cancel any pending matches
|
|
1999
|
-
this.cancelMatches()
|
|
2000
|
-
|
|
2001
|
-
let pendingMatches!: Array<AnyRouteMatch>
|
|
2002
|
-
|
|
2003
|
-
batch(() => {
|
|
2004
|
-
// this call breaks a route context of destination route after a redirect
|
|
2005
|
-
// we should be fine not eagerly calling this since we call it later
|
|
2006
|
-
// this.clearExpiredCache()
|
|
2007
|
-
|
|
2008
|
-
// Match the routes
|
|
2009
|
-
pendingMatches = this.matchRoutes(next)
|
|
2010
|
-
|
|
2011
|
-
// Ingest the new matches
|
|
2012
|
-
this.__store.setState((s) => ({
|
|
2013
|
-
...s,
|
|
2014
|
-
status: 'pending',
|
|
2015
|
-
isLoading: true,
|
|
2016
|
-
location: next,
|
|
2017
|
-
pendingMatches,
|
|
2018
|
-
// If a cached moved to pendingMatches, remove it from cachedMatches
|
|
2019
|
-
cachedMatches: s.cachedMatches.filter((d) => {
|
|
2020
|
-
return !pendingMatches.find((e) => e.id === d.id)
|
|
2021
|
-
}),
|
|
2022
|
-
}))
|
|
2023
|
-
})
|
|
2024
|
-
|
|
2025
1914
|
if (!this.state.redirect) {
|
|
2026
1915
|
this.emit({
|
|
2027
1916
|
type: 'onBeforeNavigate',
|
|
@@ -2042,7 +1931,7 @@ export class RouterCore<
|
|
|
2042
1931
|
|
|
2043
1932
|
await this.loadMatches({
|
|
2044
1933
|
sync: opts?.sync,
|
|
2045
|
-
matches: pendingMatches
|
|
1934
|
+
matches: this.state.pendingMatches as Array<AnyRouteMatch>,
|
|
2046
1935
|
location: next,
|
|
2047
1936
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
2048
1937
|
onReady: async () => {
|
|
@@ -2103,11 +1992,11 @@ export class RouterCore<
|
|
|
2103
1992
|
},
|
|
2104
1993
|
})
|
|
2105
1994
|
} catch (err) {
|
|
2106
|
-
if (
|
|
1995
|
+
if (isRedirect(err)) {
|
|
2107
1996
|
redirect = err
|
|
2108
1997
|
if (!this.isServer) {
|
|
2109
1998
|
this.navigate({
|
|
2110
|
-
...redirect,
|
|
1999
|
+
...redirect.options,
|
|
2111
2000
|
replace: true,
|
|
2112
2001
|
ignoreBlocker: true,
|
|
2113
2002
|
})
|
|
@@ -2119,7 +2008,7 @@ export class RouterCore<
|
|
|
2119
2008
|
this.__store.setState((s) => ({
|
|
2120
2009
|
...s,
|
|
2121
2010
|
statusCode: redirect
|
|
2122
|
-
? redirect.
|
|
2011
|
+
? redirect.status
|
|
2123
2012
|
: notFound
|
|
2124
2013
|
? 404
|
|
2125
2014
|
: s.matches.some((d) => d.status === 'error')
|
|
@@ -2275,13 +2164,15 @@ export class RouterCore<
|
|
|
2275
2164
|
}
|
|
2276
2165
|
|
|
2277
2166
|
const handleRedirectAndNotFound = (match: AnyRouteMatch, err: any) => {
|
|
2278
|
-
if (
|
|
2279
|
-
if (
|
|
2280
|
-
|
|
2167
|
+
if (isRedirect(err) || isNotFound(err)) {
|
|
2168
|
+
if (isRedirect(err)) {
|
|
2169
|
+
if (err.redirectHandled) {
|
|
2170
|
+
if (!err.options.reloadDocument) {
|
|
2171
|
+
throw err
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2281
2174
|
}
|
|
2282
|
-
}
|
|
2283
2175
|
|
|
2284
|
-
if (isRedirect(err) || isNotFound(err)) {
|
|
2285
2176
|
updateMatch(match.id, (prev) => ({
|
|
2286
2177
|
...prev,
|
|
2287
2178
|
status: isRedirect(err)
|
|
@@ -2305,7 +2196,9 @@ export class RouterCore<
|
|
|
2305
2196
|
|
|
2306
2197
|
if (isRedirect(err)) {
|
|
2307
2198
|
rendered = true
|
|
2308
|
-
err =
|
|
2199
|
+
err.options._fromLocation = location
|
|
2200
|
+
err.redirectHandled = true
|
|
2201
|
+
err = this.resolveRedirect(err)
|
|
2309
2202
|
throw err
|
|
2310
2203
|
} else if (isNotFound(err)) {
|
|
2311
2204
|
this._handleNotFound(matches, err, {
|
|
@@ -2742,8 +2635,8 @@ export class RouterCore<
|
|
|
2742
2635
|
loaderPromise: undefined,
|
|
2743
2636
|
}))
|
|
2744
2637
|
} catch (err) {
|
|
2745
|
-
if (
|
|
2746
|
-
await this.navigate(err)
|
|
2638
|
+
if (isRedirect(err)) {
|
|
2639
|
+
await this.navigate(err.options)
|
|
2747
2640
|
}
|
|
2748
2641
|
}
|
|
2749
2642
|
})()
|
|
@@ -2828,11 +2721,14 @@ export class RouterCore<
|
|
|
2828
2721
|
return this.load({ sync: opts?.sync })
|
|
2829
2722
|
}
|
|
2830
2723
|
|
|
2831
|
-
resolveRedirect = (
|
|
2832
|
-
|
|
2724
|
+
resolveRedirect = (redirect: AnyRedirect): AnyRedirect => {
|
|
2725
|
+
if (!redirect.options.href) {
|
|
2726
|
+
redirect.options.href = this.buildLocation(redirect.options).href
|
|
2727
|
+
redirect.headers.set('Location', redirect.options.href)
|
|
2728
|
+
}
|
|
2833
2729
|
|
|
2834
|
-
if (!redirect.
|
|
2835
|
-
redirect.
|
|
2730
|
+
if (!redirect.headers.get('Location')) {
|
|
2731
|
+
redirect.headers.set('Location', redirect.options.href)
|
|
2836
2732
|
}
|
|
2837
2733
|
|
|
2838
2734
|
return redirect
|
|
@@ -2967,11 +2863,11 @@ export class RouterCore<
|
|
|
2967
2863
|
return matches
|
|
2968
2864
|
} catch (err) {
|
|
2969
2865
|
if (isRedirect(err)) {
|
|
2970
|
-
if (err.reloadDocument) {
|
|
2866
|
+
if (err.options.reloadDocument) {
|
|
2971
2867
|
return undefined
|
|
2972
2868
|
}
|
|
2973
2869
|
return await this.preloadRoute({
|
|
2974
|
-
...
|
|
2870
|
+
...err.options,
|
|
2975
2871
|
_fromLocation: next,
|
|
2976
2872
|
})
|
|
2977
2873
|
}
|
|
@@ -3205,3 +3101,224 @@ function routeNeedsPreload(route: AnyRoute) {
|
|
|
3205
3101
|
}
|
|
3206
3102
|
return false
|
|
3207
3103
|
}
|
|
3104
|
+
|
|
3105
|
+
interface RouteLike {
|
|
3106
|
+
id: string
|
|
3107
|
+
isRoot?: boolean
|
|
3108
|
+
path?: string
|
|
3109
|
+
fullPath: string
|
|
3110
|
+
rank?: number
|
|
3111
|
+
parentRoute?: RouteLike
|
|
3112
|
+
children?: Array<RouteLike>
|
|
3113
|
+
options?: {
|
|
3114
|
+
caseSensitive?: boolean
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
|
|
3118
|
+
export function processRouteTree<TRouteLike extends RouteLike>({
|
|
3119
|
+
routeTree,
|
|
3120
|
+
initRoute,
|
|
3121
|
+
}: {
|
|
3122
|
+
routeTree: TRouteLike
|
|
3123
|
+
initRoute?: (route: TRouteLike, index: number) => void
|
|
3124
|
+
}) {
|
|
3125
|
+
const routesById = {} as Record<string, TRouteLike>
|
|
3126
|
+
const routesByPath = {} as Record<string, TRouteLike>
|
|
3127
|
+
|
|
3128
|
+
const recurseRoutes = (childRoutes: Array<TRouteLike>) => {
|
|
3129
|
+
childRoutes.forEach((childRoute, i) => {
|
|
3130
|
+
initRoute?.(childRoute, i)
|
|
3131
|
+
|
|
3132
|
+
const existingRoute = routesById[childRoute.id]
|
|
3133
|
+
|
|
3134
|
+
invariant(
|
|
3135
|
+
!existingRoute,
|
|
3136
|
+
`Duplicate routes found with id: ${String(childRoute.id)}`,
|
|
3137
|
+
)
|
|
3138
|
+
|
|
3139
|
+
routesById[childRoute.id] = childRoute
|
|
3140
|
+
|
|
3141
|
+
if (!childRoute.isRoot && childRoute.path) {
|
|
3142
|
+
const trimmedFullPath = trimPathRight(childRoute.fullPath)
|
|
3143
|
+
if (
|
|
3144
|
+
!routesByPath[trimmedFullPath] ||
|
|
3145
|
+
childRoute.fullPath.endsWith('/')
|
|
3146
|
+
) {
|
|
3147
|
+
routesByPath[trimmedFullPath] = childRoute
|
|
3148
|
+
}
|
|
3149
|
+
}
|
|
3150
|
+
|
|
3151
|
+
const children = childRoute.children as Array<TRouteLike>
|
|
3152
|
+
|
|
3153
|
+
if (children?.length) {
|
|
3154
|
+
recurseRoutes(children)
|
|
3155
|
+
}
|
|
3156
|
+
})
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
recurseRoutes([routeTree])
|
|
3160
|
+
|
|
3161
|
+
const scoredRoutes: Array<{
|
|
3162
|
+
child: TRouteLike
|
|
3163
|
+
trimmed: string
|
|
3164
|
+
parsed: ReturnType<typeof parsePathname>
|
|
3165
|
+
index: number
|
|
3166
|
+
scores: Array<number>
|
|
3167
|
+
}> = []
|
|
3168
|
+
|
|
3169
|
+
const routes: Array<TRouteLike> = Object.values(routesById)
|
|
3170
|
+
|
|
3171
|
+
routes.forEach((d, i) => {
|
|
3172
|
+
if (d.isRoot || !d.path) {
|
|
3173
|
+
return
|
|
3174
|
+
}
|
|
3175
|
+
|
|
3176
|
+
const trimmed = trimPathLeft(d.fullPath)
|
|
3177
|
+
const parsed = parsePathname(trimmed)
|
|
3178
|
+
|
|
3179
|
+
// Removes the leading slash if it is not the only remaining segment
|
|
3180
|
+
while (parsed.length > 1 && parsed[0]?.value === '/') {
|
|
3181
|
+
parsed.shift()
|
|
3182
|
+
}
|
|
3183
|
+
|
|
3184
|
+
const scores = parsed.map((segment) => {
|
|
3185
|
+
if (segment.value === '/') {
|
|
3186
|
+
return 0.75
|
|
3187
|
+
}
|
|
3188
|
+
|
|
3189
|
+
if (
|
|
3190
|
+
segment.type === 'param' &&
|
|
3191
|
+
segment.prefixSegment &&
|
|
3192
|
+
segment.suffixSegment
|
|
3193
|
+
) {
|
|
3194
|
+
return 0.55
|
|
3195
|
+
}
|
|
3196
|
+
|
|
3197
|
+
if (segment.type === 'param' && segment.prefixSegment) {
|
|
3198
|
+
return 0.52
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3201
|
+
if (segment.type === 'param' && segment.suffixSegment) {
|
|
3202
|
+
return 0.51
|
|
3203
|
+
}
|
|
3204
|
+
|
|
3205
|
+
if (segment.type === 'param') {
|
|
3206
|
+
return 0.5
|
|
3207
|
+
}
|
|
3208
|
+
|
|
3209
|
+
if (
|
|
3210
|
+
segment.type === 'wildcard' &&
|
|
3211
|
+
segment.prefixSegment &&
|
|
3212
|
+
segment.suffixSegment
|
|
3213
|
+
) {
|
|
3214
|
+
return 0.3
|
|
3215
|
+
}
|
|
3216
|
+
|
|
3217
|
+
if (segment.type === 'wildcard' && segment.prefixSegment) {
|
|
3218
|
+
return 0.27
|
|
3219
|
+
}
|
|
3220
|
+
|
|
3221
|
+
if (segment.type === 'wildcard' && segment.suffixSegment) {
|
|
3222
|
+
return 0.26
|
|
3223
|
+
}
|
|
3224
|
+
|
|
3225
|
+
if (segment.type === 'wildcard') {
|
|
3226
|
+
return 0.25
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3229
|
+
return 1
|
|
3230
|
+
})
|
|
3231
|
+
|
|
3232
|
+
scoredRoutes.push({ child: d, trimmed, parsed, index: i, scores })
|
|
3233
|
+
})
|
|
3234
|
+
|
|
3235
|
+
const flatRoutes = scoredRoutes
|
|
3236
|
+
.sort((a, b) => {
|
|
3237
|
+
const minLength = Math.min(a.scores.length, b.scores.length)
|
|
3238
|
+
|
|
3239
|
+
// Sort by min available score
|
|
3240
|
+
for (let i = 0; i < minLength; i++) {
|
|
3241
|
+
if (a.scores[i] !== b.scores[i]) {
|
|
3242
|
+
return b.scores[i]! - a.scores[i]!
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
|
|
3246
|
+
// Sort by length of score
|
|
3247
|
+
if (a.scores.length !== b.scores.length) {
|
|
3248
|
+
return b.scores.length - a.scores.length
|
|
3249
|
+
}
|
|
3250
|
+
|
|
3251
|
+
// Sort by min available parsed value
|
|
3252
|
+
for (let i = 0; i < minLength; i++) {
|
|
3253
|
+
if (a.parsed[i]!.value !== b.parsed[i]!.value) {
|
|
3254
|
+
return a.parsed[i]!.value > b.parsed[i]!.value ? 1 : -1
|
|
3255
|
+
}
|
|
3256
|
+
}
|
|
3257
|
+
|
|
3258
|
+
// Sort by original index
|
|
3259
|
+
return a.index - b.index
|
|
3260
|
+
})
|
|
3261
|
+
.map((d, i) => {
|
|
3262
|
+
d.child.rank = i
|
|
3263
|
+
return d.child
|
|
3264
|
+
})
|
|
3265
|
+
|
|
3266
|
+
return { routesById, routesByPath, flatRoutes }
|
|
3267
|
+
}
|
|
3268
|
+
|
|
3269
|
+
export function getMatchedRoutes<TRouteLike extends RouteLike>({
|
|
3270
|
+
pathname,
|
|
3271
|
+
routePathname,
|
|
3272
|
+
basepath,
|
|
3273
|
+
caseSensitive,
|
|
3274
|
+
routesByPath,
|
|
3275
|
+
routesById,
|
|
3276
|
+
flatRoutes,
|
|
3277
|
+
}: {
|
|
3278
|
+
pathname: string
|
|
3279
|
+
routePathname?: string
|
|
3280
|
+
basepath: string
|
|
3281
|
+
caseSensitive?: boolean
|
|
3282
|
+
routesByPath: Record<string, TRouteLike>
|
|
3283
|
+
routesById: Record<string, TRouteLike>
|
|
3284
|
+
flatRoutes: Array<TRouteLike>
|
|
3285
|
+
}) {
|
|
3286
|
+
let routeParams: Record<string, string> = {}
|
|
3287
|
+
const trimmedPath = trimPathRight(pathname)
|
|
3288
|
+
const getMatchedParams = (route: TRouteLike) => {
|
|
3289
|
+
const result = matchPathname(basepath, trimmedPath, {
|
|
3290
|
+
to: route.fullPath,
|
|
3291
|
+
caseSensitive: route.options?.caseSensitive ?? caseSensitive,
|
|
3292
|
+
fuzzy: true,
|
|
3293
|
+
})
|
|
3294
|
+
return result
|
|
3295
|
+
}
|
|
3296
|
+
|
|
3297
|
+
let foundRoute: TRouteLike | undefined =
|
|
3298
|
+
routePathname !== undefined ? routesByPath[routePathname] : undefined
|
|
3299
|
+
if (foundRoute) {
|
|
3300
|
+
routeParams = getMatchedParams(foundRoute)!
|
|
3301
|
+
} else {
|
|
3302
|
+
foundRoute = flatRoutes.find((route) => {
|
|
3303
|
+
const matchedParams = getMatchedParams(route)
|
|
3304
|
+
|
|
3305
|
+
if (matchedParams) {
|
|
3306
|
+
routeParams = matchedParams
|
|
3307
|
+
return true
|
|
3308
|
+
}
|
|
3309
|
+
|
|
3310
|
+
return false
|
|
3311
|
+
})
|
|
3312
|
+
}
|
|
3313
|
+
|
|
3314
|
+
let routeCursor: TRouteLike = foundRoute || routesById[rootRouteId]!
|
|
3315
|
+
|
|
3316
|
+
const matchedRoutes: Array<TRouteLike> = [routeCursor]
|
|
3317
|
+
|
|
3318
|
+
while (routeCursor.parentRoute) {
|
|
3319
|
+
routeCursor = routeCursor.parentRoute as TRouteLike
|
|
3320
|
+
matchedRoutes.unshift(routeCursor)
|
|
3321
|
+
}
|
|
3322
|
+
|
|
3323
|
+
return { matchedRoutes, routeParams, foundRoute }
|
|
3324
|
+
}
|