@tanstack/router-core 1.120.5 → 1.121.0-alpha.3
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 +3 -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 +18 -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 +17 -14
- package/dist/cjs/redirect.cjs.map +1 -1
- package/dist/cjs/redirect.d.cts +13 -7
- package/dist/cjs/route.cjs +12 -1
- package/dist/cjs/route.cjs.map +1 -1
- package/dist/cjs/route.d.cts +19 -18
- package/dist/cjs/router.cjs +268 -195
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +46 -3
- package/dist/cjs/typePrimitives.d.cts +2 -2
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +3 -0
- package/dist/esm/fileRoute.d.ts +6 -2
- package/dist/esm/index.d.ts +6 -6
- package/dist/esm/index.js +5 -2
- package/dist/esm/link.d.ts +18 -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 +13 -7
- package/dist/esm/redirect.js +17 -14
- package/dist/esm/redirect.js.map +1 -1
- package/dist/esm/route.d.ts +19 -18
- package/dist/esm/route.js +12 -1
- package/dist/esm/route.js.map +1 -1
- package/dist/esm/router.d.ts +46 -3
- package/dist/esm/router.js +271 -198
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/typePrimitives.d.ts +2 -2
- package/dist/esm/utils.d.ts +3 -0
- package/dist/esm/utils.js.map +1 -1
- package/package.json +2 -2
- package/src/fileRoute.ts +90 -1
- package/src/index.ts +14 -6
- package/src/link.ts +97 -11
- package/src/path.ts +181 -16
- package/src/redirect.ts +37 -22
- package/src/route.ts +109 -39
- package/src/router.ts +373 -250
- package/src/typePrimitives.ts +2 -2
- package/src/utils.ts +15 -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>
|
|
@@ -836,6 +844,8 @@ export class RouterCore<
|
|
|
836
844
|
// router can be used in a non-react environment if necessary
|
|
837
845
|
startTransition: StartTransitionFn = (fn) => fn()
|
|
838
846
|
|
|
847
|
+
isShell = false
|
|
848
|
+
|
|
839
849
|
update: UpdateFn<
|
|
840
850
|
TRouteTree,
|
|
841
851
|
TTrailingSlashOption,
|
|
@@ -882,7 +892,6 @@ export class RouterCore<
|
|
|
882
892
|
}
|
|
883
893
|
|
|
884
894
|
if (
|
|
885
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
886
895
|
!this.history ||
|
|
887
896
|
(this.options.history && this.options.history !== this.history)
|
|
888
897
|
) {
|
|
@@ -901,7 +910,6 @@ export class RouterCore<
|
|
|
901
910
|
this.buildRouteTree()
|
|
902
911
|
}
|
|
903
912
|
|
|
904
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
905
913
|
if (!this.__store) {
|
|
906
914
|
this.__store = new Store(getInitialRouterState(this.latestLocation), {
|
|
907
915
|
onUpdate: () => {
|
|
@@ -920,13 +928,16 @@ export class RouterCore<
|
|
|
920
928
|
if (
|
|
921
929
|
typeof window !== 'undefined' &&
|
|
922
930
|
'CSS' in window &&
|
|
923
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
924
931
|
typeof window.CSS?.supports === 'function'
|
|
925
932
|
) {
|
|
926
933
|
this.isViewTransitionTypesSupported = window.CSS.supports(
|
|
927
934
|
'selector(:active-view-transition-type(a)',
|
|
928
935
|
)
|
|
929
936
|
}
|
|
937
|
+
|
|
938
|
+
if ((this.latestLocation.search as any).__TSS_SHELL) {
|
|
939
|
+
this.isShell = true
|
|
940
|
+
}
|
|
930
941
|
}
|
|
931
942
|
|
|
932
943
|
get state() {
|
|
@@ -934,124 +945,29 @@ export class RouterCore<
|
|
|
934
945
|
}
|
|
935
946
|
|
|
936
947
|
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({
|
|
948
|
+
const { routesById, routesByPath, flatRoutes } = processRouteTree({
|
|
949
|
+
routeTree: this.routeTree,
|
|
950
|
+
initRoute: (route, i) => {
|
|
951
|
+
route.init({
|
|
952
952
|
originalIndex: i,
|
|
953
953
|
defaultSsr: this.options.defaultSsr,
|
|
954
954
|
})
|
|
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 })
|
|
955
|
+
},
|
|
1023
956
|
})
|
|
1024
957
|
|
|
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
|
-
}
|
|
958
|
+
this.routesById = routesById as RoutesById<TRouteTree>
|
|
959
|
+
this.routesByPath = routesByPath as RoutesByPath<TRouteTree>
|
|
960
|
+
this.flatRoutes = flatRoutes as Array<AnyRoute>
|
|
1035
961
|
|
|
1036
|
-
|
|
1037
|
-
if (a.scores.length !== b.scores.length) {
|
|
1038
|
-
return b.scores.length - a.scores.length
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
// Sort by min available parsed value
|
|
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
|
-
}
|
|
962
|
+
const notFoundRoute = this.options.notFoundRoute
|
|
1047
963
|
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
d.child.rank = i
|
|
1053
|
-
return d.child
|
|
964
|
+
if (notFoundRoute) {
|
|
965
|
+
notFoundRoute.init({
|
|
966
|
+
originalIndex: 99999999999,
|
|
967
|
+
defaultSsr: this.options.defaultSsr,
|
|
1054
968
|
})
|
|
969
|
+
this.routesById[notFoundRoute.id] = notFoundRoute
|
|
970
|
+
}
|
|
1055
971
|
}
|
|
1056
972
|
|
|
1057
973
|
subscribe: SubscribeFn = (eventType, fn) => {
|
|
@@ -1165,8 +1081,8 @@ export class RouterCore<
|
|
|
1165
1081
|
opts?: MatchRoutesOpts,
|
|
1166
1082
|
): Array<AnyRouteMatch> {
|
|
1167
1083
|
const { foundRoute, matchedRoutes, routeParams } = this.getMatchedRoutes(
|
|
1168
|
-
next,
|
|
1169
|
-
opts?.dest,
|
|
1084
|
+
next.pathname,
|
|
1085
|
+
opts?.dest?.to as string,
|
|
1170
1086
|
)
|
|
1171
1087
|
let isGlobalNotFound = false
|
|
1172
1088
|
|
|
@@ -1452,47 +1368,19 @@ export class RouterCore<
|
|
|
1452
1368
|
return matches
|
|
1453
1369
|
}
|
|
1454
1370
|
|
|
1455
|
-
getMatchedRoutes: GetMatchRoutesFn = (
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
let foundRoute: AnyRoute | undefined =
|
|
1469
|
-
dest?.to !== undefined ? this.routesByPath[dest.to!] : undefined
|
|
1470
|
-
if (foundRoute) {
|
|
1471
|
-
routeParams = getMatchedParams(foundRoute)!
|
|
1472
|
-
} else {
|
|
1473
|
-
foundRoute = this.flatRoutes.find((route) => {
|
|
1474
|
-
const matchedParams = getMatchedParams(route)
|
|
1475
|
-
|
|
1476
|
-
if (matchedParams) {
|
|
1477
|
-
routeParams = matchedParams
|
|
1478
|
-
return true
|
|
1479
|
-
}
|
|
1480
|
-
|
|
1481
|
-
return false
|
|
1482
|
-
})
|
|
1483
|
-
}
|
|
1484
|
-
|
|
1485
|
-
let routeCursor: AnyRoute =
|
|
1486
|
-
foundRoute || (this.routesById as any)[rootRouteId]
|
|
1487
|
-
|
|
1488
|
-
const matchedRoutes: Array<AnyRoute> = [routeCursor]
|
|
1489
|
-
|
|
1490
|
-
while (routeCursor.parentRoute) {
|
|
1491
|
-
routeCursor = routeCursor.parentRoute
|
|
1492
|
-
matchedRoutes.unshift(routeCursor)
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
|
-
return { matchedRoutes, routeParams, foundRoute }
|
|
1371
|
+
getMatchedRoutes: GetMatchRoutesFn = (
|
|
1372
|
+
pathname: string,
|
|
1373
|
+
routePathname: string | undefined,
|
|
1374
|
+
) => {
|
|
1375
|
+
return getMatchedRoutes({
|
|
1376
|
+
pathname,
|
|
1377
|
+
routePathname,
|
|
1378
|
+
basepath: this.basepath,
|
|
1379
|
+
caseSensitive: this.options.caseSensitive,
|
|
1380
|
+
routesByPath: this.routesByPath,
|
|
1381
|
+
routesById: this.routesById,
|
|
1382
|
+
flatRoutes: this.flatRoutes,
|
|
1383
|
+
})
|
|
1496
1384
|
}
|
|
1497
1385
|
|
|
1498
1386
|
cancelMatch = (id: string) => {
|
|
@@ -1792,11 +1680,17 @@ export class RouterCore<
|
|
|
1792
1680
|
}
|
|
1793
1681
|
}
|
|
1794
1682
|
|
|
1795
|
-
const nextMatches = this.getMatchedRoutes(
|
|
1683
|
+
const nextMatches = this.getMatchedRoutes(
|
|
1684
|
+
next.pathname,
|
|
1685
|
+
dest.to as string,
|
|
1686
|
+
)
|
|
1796
1687
|
const final = build(dest, nextMatches)
|
|
1797
1688
|
|
|
1798
1689
|
if (maskedNext) {
|
|
1799
|
-
const maskedMatches = this.getMatchedRoutes(
|
|
1690
|
+
const maskedMatches = this.getMatchedRoutes(
|
|
1691
|
+
maskedNext.pathname,
|
|
1692
|
+
maskedDest?.to as string,
|
|
1693
|
+
)
|
|
1800
1694
|
const maskedFinal = build(maskedDest, maskedMatches)
|
|
1801
1695
|
final.maskedLocation = maskedFinal
|
|
1802
1696
|
}
|
|
@@ -1938,6 +1832,13 @@ export class RouterCore<
|
|
|
1938
1832
|
}
|
|
1939
1833
|
|
|
1940
1834
|
navigate: NavigateFn = ({ to, reloadDocument, href, ...rest }) => {
|
|
1835
|
+
if (!reloadDocument && href) {
|
|
1836
|
+
try {
|
|
1837
|
+
new URL(`${href}`)
|
|
1838
|
+
reloadDocument = true
|
|
1839
|
+
} catch {}
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1941
1842
|
if (reloadDocument) {
|
|
1942
1843
|
if (!href) {
|
|
1943
1844
|
const location = this.buildLocation({ to, ...rest } as any)
|
|
@@ -1960,10 +1861,30 @@ export class RouterCore<
|
|
|
1960
1861
|
|
|
1961
1862
|
latestLoadPromise: undefined | Promise<void>
|
|
1962
1863
|
|
|
1963
|
-
|
|
1864
|
+
beforeLoad = () => {
|
|
1865
|
+
// Cancel any pending matches
|
|
1866
|
+
this.cancelMatches()
|
|
1964
1867
|
this.latestLocation = this.parseLocation(this.latestLocation)
|
|
1965
1868
|
|
|
1966
|
-
|
|
1869
|
+
// Match the routes
|
|
1870
|
+
const pendingMatches = this.matchRoutes(this.latestLocation)
|
|
1871
|
+
|
|
1872
|
+
// Ingest the new matches
|
|
1873
|
+
this.__store.setState((s) => ({
|
|
1874
|
+
...s,
|
|
1875
|
+
status: 'pending',
|
|
1876
|
+
isLoading: true,
|
|
1877
|
+
location: this.latestLocation,
|
|
1878
|
+
pendingMatches,
|
|
1879
|
+
// If a cached moved to pendingMatches, remove it from cachedMatches
|
|
1880
|
+
cachedMatches: s.cachedMatches.filter((d) => {
|
|
1881
|
+
return !pendingMatches.find((e) => e.id === d.id)
|
|
1882
|
+
}),
|
|
1883
|
+
}))
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
load: LoadFn = async (opts?: { sync?: boolean }): Promise<void> => {
|
|
1887
|
+
let redirect: AnyRedirect | undefined
|
|
1967
1888
|
let notFound: NotFoundError | undefined
|
|
1968
1889
|
|
|
1969
1890
|
let loadPromise: Promise<void>
|
|
@@ -1972,36 +1893,10 @@ export class RouterCore<
|
|
|
1972
1893
|
loadPromise = new Promise<void>((resolve) => {
|
|
1973
1894
|
this.startTransition(async () => {
|
|
1974
1895
|
try {
|
|
1896
|
+
this.beforeLoad()
|
|
1975
1897
|
const next = this.latestLocation
|
|
1976
1898
|
const prevLocation = this.state.resolvedLocation
|
|
1977
1899
|
|
|
1978
|
-
// Cancel any pending matches
|
|
1979
|
-
this.cancelMatches()
|
|
1980
|
-
|
|
1981
|
-
let pendingMatches!: Array<AnyRouteMatch>
|
|
1982
|
-
|
|
1983
|
-
batch(() => {
|
|
1984
|
-
// this call breaks a route context of destination route after a redirect
|
|
1985
|
-
// we should be fine not eagerly calling this since we call it later
|
|
1986
|
-
// this.clearExpiredCache()
|
|
1987
|
-
|
|
1988
|
-
// Match the routes
|
|
1989
|
-
pendingMatches = this.matchRoutes(next)
|
|
1990
|
-
|
|
1991
|
-
// Ingest the new matches
|
|
1992
|
-
this.__store.setState((s) => ({
|
|
1993
|
-
...s,
|
|
1994
|
-
status: 'pending',
|
|
1995
|
-
isLoading: true,
|
|
1996
|
-
location: next,
|
|
1997
|
-
pendingMatches,
|
|
1998
|
-
// If a cached moved to pendingMatches, remove it from cachedMatches
|
|
1999
|
-
cachedMatches: s.cachedMatches.filter((d) => {
|
|
2000
|
-
return !pendingMatches.find((e) => e.id === d.id)
|
|
2001
|
-
}),
|
|
2002
|
-
}))
|
|
2003
|
-
})
|
|
2004
|
-
|
|
2005
1900
|
if (!this.state.redirect) {
|
|
2006
1901
|
this.emit({
|
|
2007
1902
|
type: 'onBeforeNavigate',
|
|
@@ -2022,7 +1917,7 @@ export class RouterCore<
|
|
|
2022
1917
|
|
|
2023
1918
|
await this.loadMatches({
|
|
2024
1919
|
sync: opts?.sync,
|
|
2025
|
-
matches: pendingMatches
|
|
1920
|
+
matches: this.state.pendingMatches as Array<AnyRouteMatch>,
|
|
2026
1921
|
location: next,
|
|
2027
1922
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
2028
1923
|
onReady: async () => {
|
|
@@ -2083,11 +1978,11 @@ export class RouterCore<
|
|
|
2083
1978
|
},
|
|
2084
1979
|
})
|
|
2085
1980
|
} catch (err) {
|
|
2086
|
-
if (
|
|
1981
|
+
if (isRedirect(err)) {
|
|
2087
1982
|
redirect = err
|
|
2088
1983
|
if (!this.isServer) {
|
|
2089
1984
|
this.navigate({
|
|
2090
|
-
...redirect,
|
|
1985
|
+
...redirect.options,
|
|
2091
1986
|
replace: true,
|
|
2092
1987
|
ignoreBlocker: true,
|
|
2093
1988
|
})
|
|
@@ -2099,7 +1994,7 @@ export class RouterCore<
|
|
|
2099
1994
|
this.__store.setState((s) => ({
|
|
2100
1995
|
...s,
|
|
2101
1996
|
statusCode: redirect
|
|
2102
|
-
? redirect.
|
|
1997
|
+
? redirect.status
|
|
2103
1998
|
: notFound
|
|
2104
1999
|
? 404
|
|
2105
2000
|
: s.matches.some((d) => d.status === 'error')
|
|
@@ -2255,13 +2150,15 @@ export class RouterCore<
|
|
|
2255
2150
|
}
|
|
2256
2151
|
|
|
2257
2152
|
const handleRedirectAndNotFound = (match: AnyRouteMatch, err: any) => {
|
|
2258
|
-
if (
|
|
2259
|
-
if (
|
|
2260
|
-
|
|
2153
|
+
if (isRedirect(err) || isNotFound(err)) {
|
|
2154
|
+
if (isRedirect(err)) {
|
|
2155
|
+
if (err.redirectHandled) {
|
|
2156
|
+
if (!err.options.reloadDocument) {
|
|
2157
|
+
throw err
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2261
2160
|
}
|
|
2262
|
-
}
|
|
2263
2161
|
|
|
2264
|
-
if (isRedirect(err) || isNotFound(err)) {
|
|
2265
2162
|
updateMatch(match.id, (prev) => ({
|
|
2266
2163
|
...prev,
|
|
2267
2164
|
status: isRedirect(err)
|
|
@@ -2285,7 +2182,9 @@ export class RouterCore<
|
|
|
2285
2182
|
|
|
2286
2183
|
if (isRedirect(err)) {
|
|
2287
2184
|
rendered = true
|
|
2288
|
-
err =
|
|
2185
|
+
err.options._fromLocation = location
|
|
2186
|
+
err.redirectHandled = true
|
|
2187
|
+
err = this.resolveRedirect(err)
|
|
2289
2188
|
throw err
|
|
2290
2189
|
} else if (isNotFound(err)) {
|
|
2291
2190
|
this._handleNotFound(matches, err, {
|
|
@@ -2589,7 +2488,7 @@ export class RouterCore<
|
|
|
2589
2488
|
!this.state.matches.find((d) => d.id === matchId),
|
|
2590
2489
|
}))
|
|
2591
2490
|
|
|
2592
|
-
const executeHead = () => {
|
|
2491
|
+
const executeHead = async () => {
|
|
2593
2492
|
const match = this.getMatch(matchId)
|
|
2594
2493
|
// in case of a redirecting match during preload, the match does not exist
|
|
2595
2494
|
if (!match) {
|
|
@@ -2601,21 +2500,17 @@ export class RouterCore<
|
|
|
2601
2500
|
params: match.params,
|
|
2602
2501
|
loaderData: match.loaderData,
|
|
2603
2502
|
}
|
|
2604
|
-
const headFnContent =
|
|
2503
|
+
const headFnContent =
|
|
2504
|
+
await route.options.head?.(assetContext)
|
|
2605
2505
|
const meta = headFnContent?.meta
|
|
2606
2506
|
const links = headFnContent?.links
|
|
2607
2507
|
const headScripts = headFnContent?.scripts
|
|
2608
2508
|
|
|
2609
|
-
const scripts =
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
links,
|
|
2615
|
-
headScripts,
|
|
2616
|
-
headers,
|
|
2617
|
-
scripts,
|
|
2618
|
-
}))
|
|
2509
|
+
const scripts =
|
|
2510
|
+
await route.options.scripts?.(assetContext)
|
|
2511
|
+
const headers =
|
|
2512
|
+
await route.options.headers?.(assetContext)
|
|
2513
|
+
return { meta, links, headScripts, headers, scripts }
|
|
2619
2514
|
}
|
|
2620
2515
|
|
|
2621
2516
|
const runLoader = async () => {
|
|
@@ -2662,17 +2557,19 @@ export class RouterCore<
|
|
|
2662
2557
|
// to be preloaded before we resolve the match
|
|
2663
2558
|
await route._componentsPromise
|
|
2664
2559
|
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2560
|
+
updateMatch(matchId, (prev) => ({
|
|
2561
|
+
...prev,
|
|
2562
|
+
error: undefined,
|
|
2563
|
+
status: 'success',
|
|
2564
|
+
isFetching: false,
|
|
2565
|
+
updatedAt: Date.now(),
|
|
2566
|
+
loaderData,
|
|
2567
|
+
}))
|
|
2568
|
+
const head = await executeHead()
|
|
2569
|
+
updateMatch(matchId, (prev) => ({
|
|
2570
|
+
...prev,
|
|
2571
|
+
...head,
|
|
2572
|
+
}))
|
|
2676
2573
|
} catch (e) {
|
|
2677
2574
|
let error = e
|
|
2678
2575
|
|
|
@@ -2689,16 +2586,14 @@ export class RouterCore<
|
|
|
2689
2586
|
onErrorError,
|
|
2690
2587
|
)
|
|
2691
2588
|
}
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
executeHead()
|
|
2701
|
-
})
|
|
2589
|
+
const head = await executeHead()
|
|
2590
|
+
updateMatch(matchId, (prev) => ({
|
|
2591
|
+
...prev,
|
|
2592
|
+
error,
|
|
2593
|
+
status: 'error',
|
|
2594
|
+
isFetching: false,
|
|
2595
|
+
...head,
|
|
2596
|
+
}))
|
|
2702
2597
|
}
|
|
2703
2598
|
|
|
2704
2599
|
this.serverSsr?.onMatchSettled({
|
|
@@ -2706,13 +2601,13 @@ export class RouterCore<
|
|
|
2706
2601
|
match: this.getMatch(matchId)!,
|
|
2707
2602
|
})
|
|
2708
2603
|
} catch (err) {
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
})
|
|
2604
|
+
const head = await executeHead()
|
|
2605
|
+
|
|
2606
|
+
updateMatch(matchId, (prev) => ({
|
|
2607
|
+
...prev,
|
|
2608
|
+
loaderPromise: undefined,
|
|
2609
|
+
...head,
|
|
2610
|
+
}))
|
|
2716
2611
|
handleRedirectAndNotFound(this.getMatch(matchId)!, err)
|
|
2717
2612
|
}
|
|
2718
2613
|
}
|
|
@@ -2738,8 +2633,8 @@ export class RouterCore<
|
|
|
2738
2633
|
loaderPromise: undefined,
|
|
2739
2634
|
}))
|
|
2740
2635
|
} catch (err) {
|
|
2741
|
-
if (
|
|
2742
|
-
await this.navigate(err)
|
|
2636
|
+
if (isRedirect(err)) {
|
|
2637
|
+
await this.navigate(err.options)
|
|
2743
2638
|
}
|
|
2744
2639
|
}
|
|
2745
2640
|
})()
|
|
@@ -2752,7 +2647,11 @@ export class RouterCore<
|
|
|
2752
2647
|
// if the loader did not run, still update head.
|
|
2753
2648
|
// reason: parent's beforeLoad may have changed the route context
|
|
2754
2649
|
// and only now do we know the route context (and that the loader would not run)
|
|
2755
|
-
executeHead()
|
|
2650
|
+
const head = await executeHead()
|
|
2651
|
+
updateMatch(matchId, (prev) => ({
|
|
2652
|
+
...prev,
|
|
2653
|
+
...head,
|
|
2654
|
+
}))
|
|
2756
2655
|
}
|
|
2757
2656
|
}
|
|
2758
2657
|
if (!loaderIsRunningAsync) {
|
|
@@ -2829,11 +2728,14 @@ export class RouterCore<
|
|
|
2829
2728
|
return this.load({ sync: opts?.sync })
|
|
2830
2729
|
}
|
|
2831
2730
|
|
|
2832
|
-
resolveRedirect = (
|
|
2833
|
-
|
|
2731
|
+
resolveRedirect = (redirect: AnyRedirect): AnyRedirect => {
|
|
2732
|
+
if (!redirect.options.href) {
|
|
2733
|
+
redirect.options.href = this.buildLocation(redirect.options).href
|
|
2734
|
+
redirect.headers.set('Location', redirect.options.href)
|
|
2735
|
+
}
|
|
2834
2736
|
|
|
2835
|
-
if (!redirect.
|
|
2836
|
-
redirect.
|
|
2737
|
+
if (!redirect.headers.get('Location')) {
|
|
2738
|
+
redirect.headers.set('Location', redirect.options.href)
|
|
2837
2739
|
}
|
|
2838
2740
|
|
|
2839
2741
|
return redirect
|
|
@@ -2968,11 +2870,11 @@ export class RouterCore<
|
|
|
2968
2870
|
return matches
|
|
2969
2871
|
} catch (err) {
|
|
2970
2872
|
if (isRedirect(err)) {
|
|
2971
|
-
if (err.reloadDocument) {
|
|
2873
|
+
if (err.options.reloadDocument) {
|
|
2972
2874
|
return undefined
|
|
2973
2875
|
}
|
|
2974
2876
|
return await this.preloadRoute({
|
|
2975
|
-
...
|
|
2877
|
+
...err.options,
|
|
2976
2878
|
_fromLocation: next,
|
|
2977
2879
|
})
|
|
2978
2880
|
}
|
|
@@ -3206,3 +3108,224 @@ function routeNeedsPreload(route: AnyRoute) {
|
|
|
3206
3108
|
}
|
|
3207
3109
|
return false
|
|
3208
3110
|
}
|
|
3111
|
+
|
|
3112
|
+
interface RouteLike {
|
|
3113
|
+
id: string
|
|
3114
|
+
isRoot?: boolean
|
|
3115
|
+
path?: string
|
|
3116
|
+
fullPath: string
|
|
3117
|
+
rank?: number
|
|
3118
|
+
parentRoute?: RouteLike
|
|
3119
|
+
children?: Array<RouteLike>
|
|
3120
|
+
options?: {
|
|
3121
|
+
caseSensitive?: boolean
|
|
3122
|
+
}
|
|
3123
|
+
}
|
|
3124
|
+
|
|
3125
|
+
export function processRouteTree<TRouteLike extends RouteLike>({
|
|
3126
|
+
routeTree,
|
|
3127
|
+
initRoute,
|
|
3128
|
+
}: {
|
|
3129
|
+
routeTree: TRouteLike
|
|
3130
|
+
initRoute?: (route: TRouteLike, index: number) => void
|
|
3131
|
+
}) {
|
|
3132
|
+
const routesById = {} as Record<string, TRouteLike>
|
|
3133
|
+
const routesByPath = {} as Record<string, TRouteLike>
|
|
3134
|
+
|
|
3135
|
+
const recurseRoutes = (childRoutes: Array<TRouteLike>) => {
|
|
3136
|
+
childRoutes.forEach((childRoute, i) => {
|
|
3137
|
+
initRoute?.(childRoute, i)
|
|
3138
|
+
|
|
3139
|
+
const existingRoute = routesById[childRoute.id]
|
|
3140
|
+
|
|
3141
|
+
invariant(
|
|
3142
|
+
!existingRoute,
|
|
3143
|
+
`Duplicate routes found with id: ${String(childRoute.id)}`,
|
|
3144
|
+
)
|
|
3145
|
+
|
|
3146
|
+
routesById[childRoute.id] = childRoute
|
|
3147
|
+
|
|
3148
|
+
if (!childRoute.isRoot && childRoute.path) {
|
|
3149
|
+
const trimmedFullPath = trimPathRight(childRoute.fullPath)
|
|
3150
|
+
if (
|
|
3151
|
+
!routesByPath[trimmedFullPath] ||
|
|
3152
|
+
childRoute.fullPath.endsWith('/')
|
|
3153
|
+
) {
|
|
3154
|
+
routesByPath[trimmedFullPath] = childRoute
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
|
|
3158
|
+
const children = childRoute.children as Array<TRouteLike>
|
|
3159
|
+
|
|
3160
|
+
if (children?.length) {
|
|
3161
|
+
recurseRoutes(children)
|
|
3162
|
+
}
|
|
3163
|
+
})
|
|
3164
|
+
}
|
|
3165
|
+
|
|
3166
|
+
recurseRoutes([routeTree])
|
|
3167
|
+
|
|
3168
|
+
const scoredRoutes: Array<{
|
|
3169
|
+
child: TRouteLike
|
|
3170
|
+
trimmed: string
|
|
3171
|
+
parsed: ReturnType<typeof parsePathname>
|
|
3172
|
+
index: number
|
|
3173
|
+
scores: Array<number>
|
|
3174
|
+
}> = []
|
|
3175
|
+
|
|
3176
|
+
const routes: Array<TRouteLike> = Object.values(routesById)
|
|
3177
|
+
|
|
3178
|
+
routes.forEach((d, i) => {
|
|
3179
|
+
if (d.isRoot || !d.path) {
|
|
3180
|
+
return
|
|
3181
|
+
}
|
|
3182
|
+
|
|
3183
|
+
const trimmed = trimPathLeft(d.fullPath)
|
|
3184
|
+
const parsed = parsePathname(trimmed)
|
|
3185
|
+
|
|
3186
|
+
// Removes the leading slash if it is not the only remaining segment
|
|
3187
|
+
while (parsed.length > 1 && parsed[0]?.value === '/') {
|
|
3188
|
+
parsed.shift()
|
|
3189
|
+
}
|
|
3190
|
+
|
|
3191
|
+
const scores = parsed.map((segment) => {
|
|
3192
|
+
if (segment.value === '/') {
|
|
3193
|
+
return 0.75
|
|
3194
|
+
}
|
|
3195
|
+
|
|
3196
|
+
if (
|
|
3197
|
+
segment.type === 'param' &&
|
|
3198
|
+
segment.prefixSegment &&
|
|
3199
|
+
segment.suffixSegment
|
|
3200
|
+
) {
|
|
3201
|
+
return 0.55
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
if (segment.type === 'param' && segment.prefixSegment) {
|
|
3205
|
+
return 0.52
|
|
3206
|
+
}
|
|
3207
|
+
|
|
3208
|
+
if (segment.type === 'param' && segment.suffixSegment) {
|
|
3209
|
+
return 0.51
|
|
3210
|
+
}
|
|
3211
|
+
|
|
3212
|
+
if (segment.type === 'param') {
|
|
3213
|
+
return 0.5
|
|
3214
|
+
}
|
|
3215
|
+
|
|
3216
|
+
if (
|
|
3217
|
+
segment.type === 'wildcard' &&
|
|
3218
|
+
segment.prefixSegment &&
|
|
3219
|
+
segment.suffixSegment
|
|
3220
|
+
) {
|
|
3221
|
+
return 0.3
|
|
3222
|
+
}
|
|
3223
|
+
|
|
3224
|
+
if (segment.type === 'wildcard' && segment.prefixSegment) {
|
|
3225
|
+
return 0.27
|
|
3226
|
+
}
|
|
3227
|
+
|
|
3228
|
+
if (segment.type === 'wildcard' && segment.suffixSegment) {
|
|
3229
|
+
return 0.26
|
|
3230
|
+
}
|
|
3231
|
+
|
|
3232
|
+
if (segment.type === 'wildcard') {
|
|
3233
|
+
return 0.25
|
|
3234
|
+
}
|
|
3235
|
+
|
|
3236
|
+
return 1
|
|
3237
|
+
})
|
|
3238
|
+
|
|
3239
|
+
scoredRoutes.push({ child: d, trimmed, parsed, index: i, scores })
|
|
3240
|
+
})
|
|
3241
|
+
|
|
3242
|
+
const flatRoutes = scoredRoutes
|
|
3243
|
+
.sort((a, b) => {
|
|
3244
|
+
const minLength = Math.min(a.scores.length, b.scores.length)
|
|
3245
|
+
|
|
3246
|
+
// Sort by min available score
|
|
3247
|
+
for (let i = 0; i < minLength; i++) {
|
|
3248
|
+
if (a.scores[i] !== b.scores[i]) {
|
|
3249
|
+
return b.scores[i]! - a.scores[i]!
|
|
3250
|
+
}
|
|
3251
|
+
}
|
|
3252
|
+
|
|
3253
|
+
// Sort by length of score
|
|
3254
|
+
if (a.scores.length !== b.scores.length) {
|
|
3255
|
+
return b.scores.length - a.scores.length
|
|
3256
|
+
}
|
|
3257
|
+
|
|
3258
|
+
// Sort by min available parsed value
|
|
3259
|
+
for (let i = 0; i < minLength; i++) {
|
|
3260
|
+
if (a.parsed[i]!.value !== b.parsed[i]!.value) {
|
|
3261
|
+
return a.parsed[i]!.value > b.parsed[i]!.value ? 1 : -1
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
|
|
3265
|
+
// Sort by original index
|
|
3266
|
+
return a.index - b.index
|
|
3267
|
+
})
|
|
3268
|
+
.map((d, i) => {
|
|
3269
|
+
d.child.rank = i
|
|
3270
|
+
return d.child
|
|
3271
|
+
})
|
|
3272
|
+
|
|
3273
|
+
return { routesById, routesByPath, flatRoutes }
|
|
3274
|
+
}
|
|
3275
|
+
|
|
3276
|
+
export function getMatchedRoutes<TRouteLike extends RouteLike>({
|
|
3277
|
+
pathname,
|
|
3278
|
+
routePathname,
|
|
3279
|
+
basepath,
|
|
3280
|
+
caseSensitive,
|
|
3281
|
+
routesByPath,
|
|
3282
|
+
routesById,
|
|
3283
|
+
flatRoutes,
|
|
3284
|
+
}: {
|
|
3285
|
+
pathname: string
|
|
3286
|
+
routePathname?: string
|
|
3287
|
+
basepath: string
|
|
3288
|
+
caseSensitive?: boolean
|
|
3289
|
+
routesByPath: Record<string, TRouteLike>
|
|
3290
|
+
routesById: Record<string, TRouteLike>
|
|
3291
|
+
flatRoutes: Array<TRouteLike>
|
|
3292
|
+
}) {
|
|
3293
|
+
let routeParams: Record<string, string> = {}
|
|
3294
|
+
const trimmedPath = trimPathRight(pathname)
|
|
3295
|
+
const getMatchedParams = (route: TRouteLike) => {
|
|
3296
|
+
const result = matchPathname(basepath, trimmedPath, {
|
|
3297
|
+
to: route.fullPath,
|
|
3298
|
+
caseSensitive: route.options?.caseSensitive ?? caseSensitive,
|
|
3299
|
+
fuzzy: true,
|
|
3300
|
+
})
|
|
3301
|
+
return result
|
|
3302
|
+
}
|
|
3303
|
+
|
|
3304
|
+
let foundRoute: TRouteLike | undefined =
|
|
3305
|
+
routePathname !== undefined ? routesByPath[routePathname] : undefined
|
|
3306
|
+
if (foundRoute) {
|
|
3307
|
+
routeParams = getMatchedParams(foundRoute)!
|
|
3308
|
+
} else {
|
|
3309
|
+
foundRoute = flatRoutes.find((route) => {
|
|
3310
|
+
const matchedParams = getMatchedParams(route)
|
|
3311
|
+
|
|
3312
|
+
if (matchedParams) {
|
|
3313
|
+
routeParams = matchedParams
|
|
3314
|
+
return true
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3317
|
+
return false
|
|
3318
|
+
})
|
|
3319
|
+
}
|
|
3320
|
+
|
|
3321
|
+
let routeCursor: TRouteLike = foundRoute || routesById[rootRouteId]!
|
|
3322
|
+
|
|
3323
|
+
const matchedRoutes: Array<TRouteLike> = [routeCursor]
|
|
3324
|
+
|
|
3325
|
+
while (routeCursor.parentRoute) {
|
|
3326
|
+
routeCursor = routeCursor.parentRoute as TRouteLike
|
|
3327
|
+
matchedRoutes.unshift(routeCursor)
|
|
3328
|
+
}
|
|
3329
|
+
|
|
3330
|
+
return { matchedRoutes, routeParams, foundRoute }
|
|
3331
|
+
}
|