@tanstack/router-core 1.120.4 → 1.121.0-alpha.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/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 +17 -18
- package/dist/cjs/router.cjs +290 -211
- 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 +2 -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 +17 -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 +293 -214
- 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 +14 -6
- package/src/link.ts +97 -11
- package/src/path.ts +181 -16
- package/src/redirect.ts +37 -22
- package/src/route.ts +119 -39
- package/src/router.ts +393 -269
- 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>
|
|
@@ -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
|
|
|
@@ -1447,72 +1363,24 @@ export class RouterCore<
|
|
|
1447
1363
|
...match.__beforeLoadContext,
|
|
1448
1364
|
}
|
|
1449
1365
|
}
|
|
1450
|
-
|
|
1451
|
-
// If it's already a success, update headers and head content
|
|
1452
|
-
// These may get updated again if the match is refreshed
|
|
1453
|
-
// due to being stale
|
|
1454
|
-
if (match.status === 'success') {
|
|
1455
|
-
match.headers = route.options.headers?.({
|
|
1456
|
-
loaderData: match.loaderData,
|
|
1457
|
-
})
|
|
1458
|
-
const assetContext = {
|
|
1459
|
-
matches,
|
|
1460
|
-
match,
|
|
1461
|
-
params: match.params,
|
|
1462
|
-
loaderData: match.loaderData,
|
|
1463
|
-
}
|
|
1464
|
-
const headFnContent = route.options.head?.(assetContext)
|
|
1465
|
-
match.links = headFnContent?.links
|
|
1466
|
-
match.headScripts = headFnContent?.scripts
|
|
1467
|
-
match.meta = headFnContent?.meta
|
|
1468
|
-
match.scripts = route.options.scripts?.(assetContext)
|
|
1469
|
-
}
|
|
1470
1366
|
})
|
|
1471
1367
|
|
|
1472
1368
|
return matches
|
|
1473
1369
|
}
|
|
1474
1370
|
|
|
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 }
|
|
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
|
+
})
|
|
1516
1384
|
}
|
|
1517
1385
|
|
|
1518
1386
|
cancelMatch = (id: string) => {
|
|
@@ -1812,11 +1680,17 @@ export class RouterCore<
|
|
|
1812
1680
|
}
|
|
1813
1681
|
}
|
|
1814
1682
|
|
|
1815
|
-
const nextMatches = this.getMatchedRoutes(
|
|
1683
|
+
const nextMatches = this.getMatchedRoutes(
|
|
1684
|
+
next.pathname,
|
|
1685
|
+
dest.to as string,
|
|
1686
|
+
)
|
|
1816
1687
|
const final = build(dest, nextMatches)
|
|
1817
1688
|
|
|
1818
1689
|
if (maskedNext) {
|
|
1819
|
-
const maskedMatches = this.getMatchedRoutes(
|
|
1690
|
+
const maskedMatches = this.getMatchedRoutes(
|
|
1691
|
+
maskedNext.pathname,
|
|
1692
|
+
maskedDest?.to as string,
|
|
1693
|
+
)
|
|
1820
1694
|
const maskedFinal = build(maskedDest, maskedMatches)
|
|
1821
1695
|
final.maskedLocation = maskedFinal
|
|
1822
1696
|
}
|
|
@@ -1958,6 +1832,13 @@ export class RouterCore<
|
|
|
1958
1832
|
}
|
|
1959
1833
|
|
|
1960
1834
|
navigate: NavigateFn = ({ to, reloadDocument, href, ...rest }) => {
|
|
1835
|
+
if (!reloadDocument && href) {
|
|
1836
|
+
try {
|
|
1837
|
+
new URL(`${href}`)
|
|
1838
|
+
reloadDocument = true
|
|
1839
|
+
} catch {}
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1961
1842
|
if (reloadDocument) {
|
|
1962
1843
|
if (!href) {
|
|
1963
1844
|
const location = this.buildLocation({ to, ...rest } as any)
|
|
@@ -1980,10 +1861,30 @@ export class RouterCore<
|
|
|
1980
1861
|
|
|
1981
1862
|
latestLoadPromise: undefined | Promise<void>
|
|
1982
1863
|
|
|
1983
|
-
|
|
1864
|
+
beforeLoad = () => {
|
|
1865
|
+
// Cancel any pending matches
|
|
1866
|
+
this.cancelMatches()
|
|
1984
1867
|
this.latestLocation = this.parseLocation(this.latestLocation)
|
|
1985
1868
|
|
|
1986
|
-
|
|
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
|
|
1987
1888
|
let notFound: NotFoundError | undefined
|
|
1988
1889
|
|
|
1989
1890
|
let loadPromise: Promise<void>
|
|
@@ -1992,36 +1893,10 @@ export class RouterCore<
|
|
|
1992
1893
|
loadPromise = new Promise<void>((resolve) => {
|
|
1993
1894
|
this.startTransition(async () => {
|
|
1994
1895
|
try {
|
|
1896
|
+
this.beforeLoad()
|
|
1995
1897
|
const next = this.latestLocation
|
|
1996
1898
|
const prevLocation = this.state.resolvedLocation
|
|
1997
1899
|
|
|
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
1900
|
if (!this.state.redirect) {
|
|
2026
1901
|
this.emit({
|
|
2027
1902
|
type: 'onBeforeNavigate',
|
|
@@ -2042,7 +1917,7 @@ export class RouterCore<
|
|
|
2042
1917
|
|
|
2043
1918
|
await this.loadMatches({
|
|
2044
1919
|
sync: opts?.sync,
|
|
2045
|
-
matches: pendingMatches
|
|
1920
|
+
matches: this.state.pendingMatches as Array<AnyRouteMatch>,
|
|
2046
1921
|
location: next,
|
|
2047
1922
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
2048
1923
|
onReady: async () => {
|
|
@@ -2103,11 +1978,11 @@ export class RouterCore<
|
|
|
2103
1978
|
},
|
|
2104
1979
|
})
|
|
2105
1980
|
} catch (err) {
|
|
2106
|
-
if (
|
|
1981
|
+
if (isRedirect(err)) {
|
|
2107
1982
|
redirect = err
|
|
2108
1983
|
if (!this.isServer) {
|
|
2109
1984
|
this.navigate({
|
|
2110
|
-
...redirect,
|
|
1985
|
+
...redirect.options,
|
|
2111
1986
|
replace: true,
|
|
2112
1987
|
ignoreBlocker: true,
|
|
2113
1988
|
})
|
|
@@ -2119,7 +1994,7 @@ export class RouterCore<
|
|
|
2119
1994
|
this.__store.setState((s) => ({
|
|
2120
1995
|
...s,
|
|
2121
1996
|
statusCode: redirect
|
|
2122
|
-
? redirect.
|
|
1997
|
+
? redirect.status
|
|
2123
1998
|
: notFound
|
|
2124
1999
|
? 404
|
|
2125
2000
|
: s.matches.some((d) => d.status === 'error')
|
|
@@ -2275,13 +2150,15 @@ export class RouterCore<
|
|
|
2275
2150
|
}
|
|
2276
2151
|
|
|
2277
2152
|
const handleRedirectAndNotFound = (match: AnyRouteMatch, err: any) => {
|
|
2278
|
-
if (
|
|
2279
|
-
if (
|
|
2280
|
-
|
|
2153
|
+
if (isRedirect(err) || isNotFound(err)) {
|
|
2154
|
+
if (isRedirect(err)) {
|
|
2155
|
+
if (err.redirectHandled) {
|
|
2156
|
+
if (!err.options.reloadDocument) {
|
|
2157
|
+
throw err
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2281
2160
|
}
|
|
2282
|
-
}
|
|
2283
2161
|
|
|
2284
|
-
if (isRedirect(err) || isNotFound(err)) {
|
|
2285
2162
|
updateMatch(match.id, (prev) => ({
|
|
2286
2163
|
...prev,
|
|
2287
2164
|
status: isRedirect(err)
|
|
@@ -2305,7 +2182,9 @@ export class RouterCore<
|
|
|
2305
2182
|
|
|
2306
2183
|
if (isRedirect(err)) {
|
|
2307
2184
|
rendered = true
|
|
2308
|
-
err =
|
|
2185
|
+
err.options._fromLocation = location
|
|
2186
|
+
err.redirectHandled = true
|
|
2187
|
+
err = this.resolveRedirect(err)
|
|
2309
2188
|
throw err
|
|
2310
2189
|
} else if (isNotFound(err)) {
|
|
2311
2190
|
this._handleNotFound(matches, err, {
|
|
@@ -2609,6 +2488,35 @@ export class RouterCore<
|
|
|
2609
2488
|
!this.state.matches.find((d) => d.id === matchId),
|
|
2610
2489
|
}))
|
|
2611
2490
|
|
|
2491
|
+
const executeHead = () => {
|
|
2492
|
+
const match = this.getMatch(matchId)
|
|
2493
|
+
// in case of a redirecting match during preload, the match does not exist
|
|
2494
|
+
if (!match) {
|
|
2495
|
+
return
|
|
2496
|
+
}
|
|
2497
|
+
const assetContext = {
|
|
2498
|
+
matches,
|
|
2499
|
+
match,
|
|
2500
|
+
params: match.params,
|
|
2501
|
+
loaderData: match.loaderData,
|
|
2502
|
+
}
|
|
2503
|
+
const headFnContent = route.options.head?.(assetContext)
|
|
2504
|
+
const meta = headFnContent?.meta
|
|
2505
|
+
const links = headFnContent?.links
|
|
2506
|
+
const headScripts = headFnContent?.scripts
|
|
2507
|
+
|
|
2508
|
+
const scripts = route.options.scripts?.(assetContext)
|
|
2509
|
+
const headers = route.options.headers?.(assetContext)
|
|
2510
|
+
updateMatch(matchId, (prev) => ({
|
|
2511
|
+
...prev,
|
|
2512
|
+
meta,
|
|
2513
|
+
links,
|
|
2514
|
+
headScripts,
|
|
2515
|
+
headers,
|
|
2516
|
+
scripts,
|
|
2517
|
+
}))
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2612
2520
|
const runLoader = async () => {
|
|
2613
2521
|
try {
|
|
2614
2522
|
// If the Matches component rendered
|
|
@@ -2649,40 +2557,21 @@ export class RouterCore<
|
|
|
2649
2557
|
|
|
2650
2558
|
await potentialPendingMinPromise()
|
|
2651
2559
|
|
|
2652
|
-
const assetContext = {
|
|
2653
|
-
matches,
|
|
2654
|
-
match: this.getMatch(matchId)!,
|
|
2655
|
-
params: this.getMatch(matchId)!.params,
|
|
2656
|
-
loaderData,
|
|
2657
|
-
}
|
|
2658
|
-
const headFnContent =
|
|
2659
|
-
route.options.head?.(assetContext)
|
|
2660
|
-
const meta = headFnContent?.meta
|
|
2661
|
-
const links = headFnContent?.links
|
|
2662
|
-
const headScripts = headFnContent?.scripts
|
|
2663
|
-
|
|
2664
|
-
const scripts = route.options.scripts?.(assetContext)
|
|
2665
|
-
const headers = route.options.headers?.({
|
|
2666
|
-
loaderData,
|
|
2667
|
-
})
|
|
2668
|
-
|
|
2669
2560
|
// Last but not least, wait for the the components
|
|
2670
2561
|
// to be preloaded before we resolve the match
|
|
2671
2562
|
await route._componentsPromise
|
|
2672
2563
|
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
scripts,
|
|
2685
|
-
}))
|
|
2564
|
+
batch(() => {
|
|
2565
|
+
updateMatch(matchId, (prev) => ({
|
|
2566
|
+
...prev,
|
|
2567
|
+
error: undefined,
|
|
2568
|
+
status: 'success',
|
|
2569
|
+
isFetching: false,
|
|
2570
|
+
updatedAt: Date.now(),
|
|
2571
|
+
loaderData,
|
|
2572
|
+
}))
|
|
2573
|
+
executeHead()
|
|
2574
|
+
})
|
|
2686
2575
|
} catch (e) {
|
|
2687
2576
|
let error = e
|
|
2688
2577
|
|
|
@@ -2700,12 +2589,15 @@ export class RouterCore<
|
|
|
2700
2589
|
)
|
|
2701
2590
|
}
|
|
2702
2591
|
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2592
|
+
batch(() => {
|
|
2593
|
+
updateMatch(matchId, (prev) => ({
|
|
2594
|
+
...prev,
|
|
2595
|
+
error,
|
|
2596
|
+
status: 'error',
|
|
2597
|
+
isFetching: false,
|
|
2598
|
+
}))
|
|
2599
|
+
executeHead()
|
|
2600
|
+
})
|
|
2709
2601
|
}
|
|
2710
2602
|
|
|
2711
2603
|
this.serverSsr?.onMatchSettled({
|
|
@@ -2713,10 +2605,13 @@ export class RouterCore<
|
|
|
2713
2605
|
match: this.getMatch(matchId)!,
|
|
2714
2606
|
})
|
|
2715
2607
|
} catch (err) {
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2608
|
+
batch(() => {
|
|
2609
|
+
updateMatch(matchId, (prev) => ({
|
|
2610
|
+
...prev,
|
|
2611
|
+
loaderPromise: undefined,
|
|
2612
|
+
}))
|
|
2613
|
+
executeHead()
|
|
2614
|
+
})
|
|
2720
2615
|
handleRedirectAndNotFound(this.getMatch(matchId)!, err)
|
|
2721
2616
|
}
|
|
2722
2617
|
}
|
|
@@ -2742,8 +2637,8 @@ export class RouterCore<
|
|
|
2742
2637
|
loaderPromise: undefined,
|
|
2743
2638
|
}))
|
|
2744
2639
|
} catch (err) {
|
|
2745
|
-
if (
|
|
2746
|
-
await this.navigate(err)
|
|
2640
|
+
if (isRedirect(err)) {
|
|
2641
|
+
await this.navigate(err.options)
|
|
2747
2642
|
}
|
|
2748
2643
|
}
|
|
2749
2644
|
})()
|
|
@@ -2752,6 +2647,11 @@ export class RouterCore<
|
|
|
2752
2647
|
(loaderShouldRunAsync && sync)
|
|
2753
2648
|
) {
|
|
2754
2649
|
await runLoader()
|
|
2650
|
+
} else {
|
|
2651
|
+
// if the loader did not run, still update head.
|
|
2652
|
+
// reason: parent's beforeLoad may have changed the route context
|
|
2653
|
+
// and only now do we know the route context (and that the loader would not run)
|
|
2654
|
+
executeHead()
|
|
2755
2655
|
}
|
|
2756
2656
|
}
|
|
2757
2657
|
if (!loaderIsRunningAsync) {
|
|
@@ -2828,11 +2728,14 @@ export class RouterCore<
|
|
|
2828
2728
|
return this.load({ sync: opts?.sync })
|
|
2829
2729
|
}
|
|
2830
2730
|
|
|
2831
|
-
resolveRedirect = (
|
|
2832
|
-
|
|
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
|
+
}
|
|
2833
2736
|
|
|
2834
|
-
if (!redirect.
|
|
2835
|
-
redirect.
|
|
2737
|
+
if (!redirect.headers.get('Location')) {
|
|
2738
|
+
redirect.headers.set('Location', redirect.options.href)
|
|
2836
2739
|
}
|
|
2837
2740
|
|
|
2838
2741
|
return redirect
|
|
@@ -2967,11 +2870,11 @@ export class RouterCore<
|
|
|
2967
2870
|
return matches
|
|
2968
2871
|
} catch (err) {
|
|
2969
2872
|
if (isRedirect(err)) {
|
|
2970
|
-
if (err.reloadDocument) {
|
|
2873
|
+
if (err.options.reloadDocument) {
|
|
2971
2874
|
return undefined
|
|
2972
2875
|
}
|
|
2973
2876
|
return await this.preloadRoute({
|
|
2974
|
-
...
|
|
2877
|
+
...err.options,
|
|
2975
2878
|
_fromLocation: next,
|
|
2976
2879
|
})
|
|
2977
2880
|
}
|
|
@@ -3205,3 +3108,224 @@ function routeNeedsPreload(route: AnyRoute) {
|
|
|
3205
3108
|
}
|
|
3206
3109
|
return false
|
|
3207
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
|
+
}
|