@tanstack/router-core 1.120.4-alpha.19 → 1.120.5
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 +2 -6
- package/dist/cjs/index.cjs +0 -3
- 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 +1 -18
- package/dist/cjs/path.cjs +16 -130
- package/dist/cjs/path.cjs.map +1 -1
- package/dist/cjs/path.d.cts +0 -17
- package/dist/cjs/redirect.cjs +14 -17
- package/dist/cjs/redirect.cjs.map +1 -1
- package/dist/cjs/redirect.d.cts +7 -13
- package/dist/cjs/route.cjs +1 -12
- package/dist/cjs/route.cjs.map +1 -1
- package/dist/cjs/route.d.cts +16 -19
- package/dist/cjs/router.cjs +214 -287
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +3 -46
- package/dist/cjs/scroll-restoration.cjs +23 -12
- package/dist/cjs/scroll-restoration.cjs.map +1 -1
- package/dist/cjs/scroll-restoration.d.cts +1 -1
- package/dist/cjs/typePrimitives.d.cts +2 -2
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +0 -2
- package/dist/esm/fileRoute.d.ts +2 -6
- package/dist/esm/index.d.ts +6 -6
- package/dist/esm/index.js +2 -5
- package/dist/esm/link.d.ts +1 -18
- package/dist/esm/link.js.map +1 -1
- package/dist/esm/path.d.ts +0 -17
- package/dist/esm/path.js +16 -130
- package/dist/esm/path.js.map +1 -1
- package/dist/esm/redirect.d.ts +7 -13
- package/dist/esm/redirect.js +14 -17
- package/dist/esm/redirect.js.map +1 -1
- package/dist/esm/route.d.ts +16 -19
- package/dist/esm/route.js +1 -12
- package/dist/esm/route.js.map +1 -1
- package/dist/esm/router.d.ts +3 -46
- package/dist/esm/router.js +217 -290
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/scroll-restoration.d.ts +1 -1
- package/dist/esm/scroll-restoration.js +23 -12
- package/dist/esm/scroll-restoration.js.map +1 -1
- package/dist/esm/typePrimitives.d.ts +2 -2
- package/dist/esm/utils.d.ts +0 -2
- package/dist/esm/utils.js.map +1 -1
- package/package.json +2 -2
- package/src/fileRoute.ts +1 -90
- package/src/index.ts +6 -14
- package/src/link.ts +11 -97
- package/src/path.ts +16 -181
- package/src/redirect.ts +22 -37
- package/src/route.ts +50 -108
- package/src/router.ts +270 -392
- package/src/scroll-restoration.ts +44 -27
- package/src/typePrimitives.ts +2 -2
- package/src/utils.ts +0 -14
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 } from './redirect'
|
|
31
|
+
import { isRedirect, isResolvedRedirect } from './redirect'
|
|
32
32
|
import type { SearchParser, SearchSerializer } from './searchParams'
|
|
33
33
|
import type { AnyRedirect, ResolvedRedirect } from './redirect'
|
|
34
34
|
import type {
|
|
@@ -165,14 +165,6 @@ 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
|
|
176
168
|
/**
|
|
177
169
|
* The default `pendingMs` a route should use if no pendingMs is provided.
|
|
178
170
|
*
|
|
@@ -415,7 +407,7 @@ export interface RouterState<
|
|
|
415
407
|
location: ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
416
408
|
resolvedLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
417
409
|
statusCode: number
|
|
418
|
-
redirect?:
|
|
410
|
+
redirect?: ResolvedRedirect
|
|
419
411
|
}
|
|
420
412
|
|
|
421
413
|
export interface BuildNextOptions {
|
|
@@ -601,8 +593,8 @@ export type ParseLocationFn<TRouteTree extends AnyRoute> = (
|
|
|
601
593
|
) => ParsedLocation<FullSearchSchema<TRouteTree>>
|
|
602
594
|
|
|
603
595
|
export type GetMatchRoutesFn = (
|
|
604
|
-
|
|
605
|
-
|
|
596
|
+
next: ParsedLocation,
|
|
597
|
+
dest?: BuildNextOptions,
|
|
606
598
|
) => {
|
|
607
599
|
matchedRoutes: Array<AnyRoute>
|
|
608
600
|
routeParams: Record<string, string>
|
|
@@ -844,8 +836,6 @@ export class RouterCore<
|
|
|
844
836
|
// router can be used in a non-react environment if necessary
|
|
845
837
|
startTransition: StartTransitionFn = (fn) => fn()
|
|
846
838
|
|
|
847
|
-
isShell = false
|
|
848
|
-
|
|
849
839
|
update: UpdateFn<
|
|
850
840
|
TRouteTree,
|
|
851
841
|
TTrailingSlashOption,
|
|
@@ -892,6 +882,7 @@ export class RouterCore<
|
|
|
892
882
|
}
|
|
893
883
|
|
|
894
884
|
if (
|
|
885
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
895
886
|
!this.history ||
|
|
896
887
|
(this.options.history && this.options.history !== this.history)
|
|
897
888
|
) {
|
|
@@ -910,6 +901,7 @@ export class RouterCore<
|
|
|
910
901
|
this.buildRouteTree()
|
|
911
902
|
}
|
|
912
903
|
|
|
904
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
913
905
|
if (!this.__store) {
|
|
914
906
|
this.__store = new Store(getInitialRouterState(this.latestLocation), {
|
|
915
907
|
onUpdate: () => {
|
|
@@ -928,16 +920,13 @@ export class RouterCore<
|
|
|
928
920
|
if (
|
|
929
921
|
typeof window !== 'undefined' &&
|
|
930
922
|
'CSS' in window &&
|
|
923
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
931
924
|
typeof window.CSS?.supports === 'function'
|
|
932
925
|
) {
|
|
933
926
|
this.isViewTransitionTypesSupported = window.CSS.supports(
|
|
934
927
|
'selector(:active-view-transition-type(a)',
|
|
935
928
|
)
|
|
936
929
|
}
|
|
937
|
-
|
|
938
|
-
if ((this.latestLocation.search as any).__TSS_SHELL) {
|
|
939
|
-
this.isShell = true
|
|
940
|
-
}
|
|
941
930
|
}
|
|
942
931
|
|
|
943
932
|
get state() {
|
|
@@ -945,29 +934,124 @@ export class RouterCore<
|
|
|
945
934
|
}
|
|
946
935
|
|
|
947
936
|
buildRouteTree = () => {
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
initRoute: (route, i) => {
|
|
951
|
-
route.init({
|
|
952
|
-
originalIndex: i,
|
|
953
|
-
defaultSsr: this.options.defaultSsr,
|
|
954
|
-
})
|
|
955
|
-
},
|
|
956
|
-
})
|
|
957
|
-
|
|
958
|
-
this.routesById = routesById as RoutesById<TRouteTree>
|
|
959
|
-
this.routesByPath = routesByPath as RoutesByPath<TRouteTree>
|
|
960
|
-
this.flatRoutes = flatRoutes as Array<AnyRoute>
|
|
937
|
+
this.routesById = {} as RoutesById<TRouteTree>
|
|
938
|
+
this.routesByPath = {} as RoutesByPath<TRouteTree>
|
|
961
939
|
|
|
962
940
|
const notFoundRoute = this.options.notFoundRoute
|
|
963
|
-
|
|
964
941
|
if (notFoundRoute) {
|
|
965
942
|
notFoundRoute.init({
|
|
966
943
|
originalIndex: 99999999999,
|
|
967
944
|
defaultSsr: this.options.defaultSsr,
|
|
968
945
|
})
|
|
969
|
-
this.routesById[notFoundRoute.id] = notFoundRoute
|
|
946
|
+
;(this.routesById as any)[notFoundRoute.id] = notFoundRoute
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
const recurseRoutes = (childRoutes: Array<AnyRoute>) => {
|
|
950
|
+
childRoutes.forEach((childRoute, i) => {
|
|
951
|
+
childRoute.init({
|
|
952
|
+
originalIndex: i,
|
|
953
|
+
defaultSsr: this.options.defaultSsr,
|
|
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
|
+
})
|
|
970
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 })
|
|
1023
|
+
})
|
|
1024
|
+
|
|
1025
|
+
this.flatRoutes = scoredRoutes
|
|
1026
|
+
.sort((a, b) => {
|
|
1027
|
+
const minLength = Math.min(a.scores.length, b.scores.length)
|
|
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
|
+
}
|
|
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
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// Sort by original index
|
|
1049
|
+
return a.index - b.index
|
|
1050
|
+
})
|
|
1051
|
+
.map((d, i) => {
|
|
1052
|
+
d.child.rank = i
|
|
1053
|
+
return d.child
|
|
1054
|
+
})
|
|
971
1055
|
}
|
|
972
1056
|
|
|
973
1057
|
subscribe: SubscribeFn = (eventType, fn) => {
|
|
@@ -1081,8 +1165,8 @@ export class RouterCore<
|
|
|
1081
1165
|
opts?: MatchRoutesOpts,
|
|
1082
1166
|
): Array<AnyRouteMatch> {
|
|
1083
1167
|
const { foundRoute, matchedRoutes, routeParams } = this.getMatchedRoutes(
|
|
1084
|
-
next
|
|
1085
|
-
opts?.dest
|
|
1168
|
+
next,
|
|
1169
|
+
opts?.dest,
|
|
1086
1170
|
)
|
|
1087
1171
|
let isGlobalNotFound = false
|
|
1088
1172
|
|
|
@@ -1363,44 +1447,52 @@ export class RouterCore<
|
|
|
1363
1447
|
...match.__beforeLoadContext,
|
|
1364
1448
|
}
|
|
1365
1449
|
}
|
|
1366
|
-
|
|
1367
|
-
// If it's already a success, update headers and head content
|
|
1368
|
-
// These may get updated again if the match is refreshed
|
|
1369
|
-
// due to being stale
|
|
1370
|
-
if (match.status === 'success') {
|
|
1371
|
-
match.headers = route.options.headers?.({
|
|
1372
|
-
loaderData: match.loaderData,
|
|
1373
|
-
})
|
|
1374
|
-
const assetContext = {
|
|
1375
|
-
matches,
|
|
1376
|
-
match,
|
|
1377
|
-
params: match.params,
|
|
1378
|
-
loaderData: match.loaderData,
|
|
1379
|
-
}
|
|
1380
|
-
const headFnContent = route.options.head?.(assetContext)
|
|
1381
|
-
match.links = headFnContent?.links
|
|
1382
|
-
match.headScripts = headFnContent?.scripts
|
|
1383
|
-
match.meta = headFnContent?.meta
|
|
1384
|
-
match.scripts = route.options.scripts?.(assetContext)
|
|
1385
|
-
}
|
|
1386
1450
|
})
|
|
1387
1451
|
|
|
1388
1452
|
return matches
|
|
1389
1453
|
}
|
|
1390
1454
|
|
|
1391
|
-
getMatchedRoutes: GetMatchRoutesFn = (
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1455
|
+
getMatchedRoutes: GetMatchRoutesFn = (next, dest) => {
|
|
1456
|
+
let routeParams: Record<string, string> = {}
|
|
1457
|
+
const trimmedPath = trimPathRight(next.pathname)
|
|
1458
|
+
const getMatchedParams = (route: AnyRoute) => {
|
|
1459
|
+
const result = matchPathname(this.basepath, trimmedPath, {
|
|
1460
|
+
to: route.fullPath,
|
|
1461
|
+
caseSensitive:
|
|
1462
|
+
route.options.caseSensitive ?? this.options.caseSensitive,
|
|
1463
|
+
fuzzy: true,
|
|
1464
|
+
})
|
|
1465
|
+
return result
|
|
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 }
|
|
1404
1496
|
}
|
|
1405
1497
|
|
|
1406
1498
|
cancelMatch = (id: string) => {
|
|
@@ -1700,17 +1792,11 @@ export class RouterCore<
|
|
|
1700
1792
|
}
|
|
1701
1793
|
}
|
|
1702
1794
|
|
|
1703
|
-
const nextMatches = this.getMatchedRoutes(
|
|
1704
|
-
next.pathname,
|
|
1705
|
-
dest.to as string,
|
|
1706
|
-
)
|
|
1795
|
+
const nextMatches = this.getMatchedRoutes(next, dest)
|
|
1707
1796
|
const final = build(dest, nextMatches)
|
|
1708
1797
|
|
|
1709
1798
|
if (maskedNext) {
|
|
1710
|
-
const maskedMatches = this.getMatchedRoutes(
|
|
1711
|
-
maskedNext.pathname,
|
|
1712
|
-
maskedDest?.to as string,
|
|
1713
|
-
)
|
|
1799
|
+
const maskedMatches = this.getMatchedRoutes(maskedNext, maskedDest)
|
|
1714
1800
|
const maskedFinal = build(maskedDest, maskedMatches)
|
|
1715
1801
|
final.maskedLocation = maskedFinal
|
|
1716
1802
|
}
|
|
@@ -1852,13 +1938,6 @@ export class RouterCore<
|
|
|
1852
1938
|
}
|
|
1853
1939
|
|
|
1854
1940
|
navigate: NavigateFn = ({ to, reloadDocument, href, ...rest }) => {
|
|
1855
|
-
if (!reloadDocument && href) {
|
|
1856
|
-
try {
|
|
1857
|
-
new URL(`${href}`)
|
|
1858
|
-
reloadDocument = true
|
|
1859
|
-
} catch {}
|
|
1860
|
-
}
|
|
1861
|
-
|
|
1862
1941
|
if (reloadDocument) {
|
|
1863
1942
|
if (!href) {
|
|
1864
1943
|
const location = this.buildLocation({ to, ...rest } as any)
|
|
@@ -1881,30 +1960,10 @@ export class RouterCore<
|
|
|
1881
1960
|
|
|
1882
1961
|
latestLoadPromise: undefined | Promise<void>
|
|
1883
1962
|
|
|
1884
|
-
|
|
1885
|
-
// Cancel any pending matches
|
|
1886
|
-
this.cancelMatches()
|
|
1963
|
+
load: LoadFn = async (opts?: { sync?: boolean }): Promise<void> => {
|
|
1887
1964
|
this.latestLocation = this.parseLocation(this.latestLocation)
|
|
1888
1965
|
|
|
1889
|
-
|
|
1890
|
-
const pendingMatches = this.matchRoutes(this.latestLocation)
|
|
1891
|
-
|
|
1892
|
-
// Ingest the new matches
|
|
1893
|
-
this.__store.setState((s) => ({
|
|
1894
|
-
...s,
|
|
1895
|
-
status: 'pending',
|
|
1896
|
-
isLoading: true,
|
|
1897
|
-
location: this.latestLocation,
|
|
1898
|
-
pendingMatches,
|
|
1899
|
-
// If a cached moved to pendingMatches, remove it from cachedMatches
|
|
1900
|
-
cachedMatches: s.cachedMatches.filter((d) => {
|
|
1901
|
-
return !pendingMatches.find((e) => e.id === d.id)
|
|
1902
|
-
}),
|
|
1903
|
-
}))
|
|
1904
|
-
}
|
|
1905
|
-
|
|
1906
|
-
load: LoadFn = async (opts?: { sync?: boolean }): Promise<void> => {
|
|
1907
|
-
let redirect: AnyRedirect | undefined
|
|
1966
|
+
let redirect: ResolvedRedirect | undefined
|
|
1908
1967
|
let notFound: NotFoundError | undefined
|
|
1909
1968
|
|
|
1910
1969
|
let loadPromise: Promise<void>
|
|
@@ -1913,10 +1972,36 @@ export class RouterCore<
|
|
|
1913
1972
|
loadPromise = new Promise<void>((resolve) => {
|
|
1914
1973
|
this.startTransition(async () => {
|
|
1915
1974
|
try {
|
|
1916
|
-
this.beforeLoad()
|
|
1917
1975
|
const next = this.latestLocation
|
|
1918
1976
|
const prevLocation = this.state.resolvedLocation
|
|
1919
1977
|
|
|
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
|
+
|
|
1920
2005
|
if (!this.state.redirect) {
|
|
1921
2006
|
this.emit({
|
|
1922
2007
|
type: 'onBeforeNavigate',
|
|
@@ -1937,7 +2022,7 @@ export class RouterCore<
|
|
|
1937
2022
|
|
|
1938
2023
|
await this.loadMatches({
|
|
1939
2024
|
sync: opts?.sync,
|
|
1940
|
-
matches:
|
|
2025
|
+
matches: pendingMatches,
|
|
1941
2026
|
location: next,
|
|
1942
2027
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
1943
2028
|
onReady: async () => {
|
|
@@ -1998,11 +2083,11 @@ export class RouterCore<
|
|
|
1998
2083
|
},
|
|
1999
2084
|
})
|
|
2000
2085
|
} catch (err) {
|
|
2001
|
-
if (
|
|
2086
|
+
if (isResolvedRedirect(err)) {
|
|
2002
2087
|
redirect = err
|
|
2003
2088
|
if (!this.isServer) {
|
|
2004
2089
|
this.navigate({
|
|
2005
|
-
...redirect
|
|
2090
|
+
...redirect,
|
|
2006
2091
|
replace: true,
|
|
2007
2092
|
ignoreBlocker: true,
|
|
2008
2093
|
})
|
|
@@ -2014,7 +2099,7 @@ export class RouterCore<
|
|
|
2014
2099
|
this.__store.setState((s) => ({
|
|
2015
2100
|
...s,
|
|
2016
2101
|
statusCode: redirect
|
|
2017
|
-
? redirect.
|
|
2102
|
+
? redirect.statusCode
|
|
2018
2103
|
: notFound
|
|
2019
2104
|
? 404
|
|
2020
2105
|
: s.matches.some((d) => d.status === 'error')
|
|
@@ -2170,15 +2255,13 @@ export class RouterCore<
|
|
|
2170
2255
|
}
|
|
2171
2256
|
|
|
2172
2257
|
const handleRedirectAndNotFound = (match: AnyRouteMatch, err: any) => {
|
|
2173
|
-
if (
|
|
2174
|
-
if (
|
|
2175
|
-
|
|
2176
|
-
if (!err.options.reloadDocument) {
|
|
2177
|
-
throw err
|
|
2178
|
-
}
|
|
2179
|
-
}
|
|
2258
|
+
if (isResolvedRedirect(err)) {
|
|
2259
|
+
if (!err.reloadDocument) {
|
|
2260
|
+
throw err
|
|
2180
2261
|
}
|
|
2262
|
+
}
|
|
2181
2263
|
|
|
2264
|
+
if (isRedirect(err) || isNotFound(err)) {
|
|
2182
2265
|
updateMatch(match.id, (prev) => ({
|
|
2183
2266
|
...prev,
|
|
2184
2267
|
status: isRedirect(err)
|
|
@@ -2202,9 +2285,7 @@ export class RouterCore<
|
|
|
2202
2285
|
|
|
2203
2286
|
if (isRedirect(err)) {
|
|
2204
2287
|
rendered = true
|
|
2205
|
-
err.
|
|
2206
|
-
err.redirectHandled = true
|
|
2207
|
-
err = this.resolveRedirect(err)
|
|
2288
|
+
err = this.resolveRedirect({ ...err, _fromLocation: location })
|
|
2208
2289
|
throw err
|
|
2209
2290
|
} else if (isNotFound(err)) {
|
|
2210
2291
|
this._handleNotFound(matches, err, {
|
|
@@ -2508,6 +2589,35 @@ export class RouterCore<
|
|
|
2508
2589
|
!this.state.matches.find((d) => d.id === matchId),
|
|
2509
2590
|
}))
|
|
2510
2591
|
|
|
2592
|
+
const executeHead = () => {
|
|
2593
|
+
const match = this.getMatch(matchId)
|
|
2594
|
+
// in case of a redirecting match during preload, the match does not exist
|
|
2595
|
+
if (!match) {
|
|
2596
|
+
return
|
|
2597
|
+
}
|
|
2598
|
+
const assetContext = {
|
|
2599
|
+
matches,
|
|
2600
|
+
match,
|
|
2601
|
+
params: match.params,
|
|
2602
|
+
loaderData: match.loaderData,
|
|
2603
|
+
}
|
|
2604
|
+
const headFnContent = route.options.head?.(assetContext)
|
|
2605
|
+
const meta = headFnContent?.meta
|
|
2606
|
+
const links = headFnContent?.links
|
|
2607
|
+
const headScripts = headFnContent?.scripts
|
|
2608
|
+
|
|
2609
|
+
const scripts = route.options.scripts?.(assetContext)
|
|
2610
|
+
const headers = route.options.headers?.(assetContext)
|
|
2611
|
+
updateMatch(matchId, (prev) => ({
|
|
2612
|
+
...prev,
|
|
2613
|
+
meta,
|
|
2614
|
+
links,
|
|
2615
|
+
headScripts,
|
|
2616
|
+
headers,
|
|
2617
|
+
scripts,
|
|
2618
|
+
}))
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2511
2621
|
const runLoader = async () => {
|
|
2512
2622
|
try {
|
|
2513
2623
|
// If the Matches component rendered
|
|
@@ -2548,40 +2658,21 @@ export class RouterCore<
|
|
|
2548
2658
|
|
|
2549
2659
|
await potentialPendingMinPromise()
|
|
2550
2660
|
|
|
2551
|
-
const assetContext = {
|
|
2552
|
-
matches,
|
|
2553
|
-
match: this.getMatch(matchId)!,
|
|
2554
|
-
params: this.getMatch(matchId)!.params,
|
|
2555
|
-
loaderData,
|
|
2556
|
-
}
|
|
2557
|
-
const headFnContent =
|
|
2558
|
-
route.options.head?.(assetContext)
|
|
2559
|
-
const meta = headFnContent?.meta
|
|
2560
|
-
const links = headFnContent?.links
|
|
2561
|
-
const headScripts = headFnContent?.scripts
|
|
2562
|
-
|
|
2563
|
-
const scripts = route.options.scripts?.(assetContext)
|
|
2564
|
-
const headers = route.options.headers?.({
|
|
2565
|
-
loaderData,
|
|
2566
|
-
})
|
|
2567
|
-
|
|
2568
2661
|
// Last but not least, wait for the the components
|
|
2569
2662
|
// to be preloaded before we resolve the match
|
|
2570
2663
|
await route._componentsPromise
|
|
2571
2664
|
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
scripts,
|
|
2584
|
-
}))
|
|
2665
|
+
batch(() => {
|
|
2666
|
+
updateMatch(matchId, (prev) => ({
|
|
2667
|
+
...prev,
|
|
2668
|
+
error: undefined,
|
|
2669
|
+
status: 'success',
|
|
2670
|
+
isFetching: false,
|
|
2671
|
+
updatedAt: Date.now(),
|
|
2672
|
+
loaderData,
|
|
2673
|
+
}))
|
|
2674
|
+
executeHead()
|
|
2675
|
+
})
|
|
2585
2676
|
} catch (e) {
|
|
2586
2677
|
let error = e
|
|
2587
2678
|
|
|
@@ -2599,12 +2690,15 @@ export class RouterCore<
|
|
|
2599
2690
|
)
|
|
2600
2691
|
}
|
|
2601
2692
|
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2693
|
+
batch(() => {
|
|
2694
|
+
updateMatch(matchId, (prev) => ({
|
|
2695
|
+
...prev,
|
|
2696
|
+
error,
|
|
2697
|
+
status: 'error',
|
|
2698
|
+
isFetching: false,
|
|
2699
|
+
}))
|
|
2700
|
+
executeHead()
|
|
2701
|
+
})
|
|
2608
2702
|
}
|
|
2609
2703
|
|
|
2610
2704
|
this.serverSsr?.onMatchSettled({
|
|
@@ -2612,10 +2706,13 @@ export class RouterCore<
|
|
|
2612
2706
|
match: this.getMatch(matchId)!,
|
|
2613
2707
|
})
|
|
2614
2708
|
} catch (err) {
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2709
|
+
batch(() => {
|
|
2710
|
+
updateMatch(matchId, (prev) => ({
|
|
2711
|
+
...prev,
|
|
2712
|
+
loaderPromise: undefined,
|
|
2713
|
+
}))
|
|
2714
|
+
executeHead()
|
|
2715
|
+
})
|
|
2619
2716
|
handleRedirectAndNotFound(this.getMatch(matchId)!, err)
|
|
2620
2717
|
}
|
|
2621
2718
|
}
|
|
@@ -2641,8 +2738,8 @@ export class RouterCore<
|
|
|
2641
2738
|
loaderPromise: undefined,
|
|
2642
2739
|
}))
|
|
2643
2740
|
} catch (err) {
|
|
2644
|
-
if (
|
|
2645
|
-
await this.navigate(err
|
|
2741
|
+
if (isResolvedRedirect(err)) {
|
|
2742
|
+
await this.navigate(err)
|
|
2646
2743
|
}
|
|
2647
2744
|
}
|
|
2648
2745
|
})()
|
|
@@ -2651,6 +2748,11 @@ export class RouterCore<
|
|
|
2651
2748
|
(loaderShouldRunAsync && sync)
|
|
2652
2749
|
) {
|
|
2653
2750
|
await runLoader()
|
|
2751
|
+
} else {
|
|
2752
|
+
// if the loader did not run, still update head.
|
|
2753
|
+
// reason: parent's beforeLoad may have changed the route context
|
|
2754
|
+
// and only now do we know the route context (and that the loader would not run)
|
|
2755
|
+
executeHead()
|
|
2654
2756
|
}
|
|
2655
2757
|
}
|
|
2656
2758
|
if (!loaderIsRunningAsync) {
|
|
@@ -2727,14 +2829,11 @@ export class RouterCore<
|
|
|
2727
2829
|
return this.load({ sync: opts?.sync })
|
|
2728
2830
|
}
|
|
2729
2831
|
|
|
2730
|
-
resolveRedirect = (
|
|
2731
|
-
|
|
2732
|
-
redirect.options.href = this.buildLocation(redirect.options).href
|
|
2733
|
-
redirect.headers.set('Location', redirect.options.href)
|
|
2734
|
-
}
|
|
2832
|
+
resolveRedirect = (err: AnyRedirect): ResolvedRedirect => {
|
|
2833
|
+
const redirect = err as ResolvedRedirect
|
|
2735
2834
|
|
|
2736
|
-
if (!redirect.
|
|
2737
|
-
redirect.
|
|
2835
|
+
if (!redirect.href) {
|
|
2836
|
+
redirect.href = this.buildLocation(redirect as any).href
|
|
2738
2837
|
}
|
|
2739
2838
|
|
|
2740
2839
|
return redirect
|
|
@@ -2869,11 +2968,11 @@ export class RouterCore<
|
|
|
2869
2968
|
return matches
|
|
2870
2969
|
} catch (err) {
|
|
2871
2970
|
if (isRedirect(err)) {
|
|
2872
|
-
if (err.
|
|
2971
|
+
if (err.reloadDocument) {
|
|
2873
2972
|
return undefined
|
|
2874
2973
|
}
|
|
2875
2974
|
return await this.preloadRoute({
|
|
2876
|
-
...err
|
|
2975
|
+
...(err as any),
|
|
2877
2976
|
_fromLocation: next,
|
|
2878
2977
|
})
|
|
2879
2978
|
}
|
|
@@ -3107,224 +3206,3 @@ function routeNeedsPreload(route: AnyRoute) {
|
|
|
3107
3206
|
}
|
|
3108
3207
|
return false
|
|
3109
3208
|
}
|
|
3110
|
-
|
|
3111
|
-
interface RouteLike {
|
|
3112
|
-
id: string
|
|
3113
|
-
isRoot?: boolean
|
|
3114
|
-
path?: string
|
|
3115
|
-
fullPath: string
|
|
3116
|
-
rank?: number
|
|
3117
|
-
parentRoute?: RouteLike
|
|
3118
|
-
children?: Array<RouteLike>
|
|
3119
|
-
options?: {
|
|
3120
|
-
caseSensitive?: boolean
|
|
3121
|
-
}
|
|
3122
|
-
}
|
|
3123
|
-
|
|
3124
|
-
export function processRouteTree<TRouteLike extends RouteLike>({
|
|
3125
|
-
routeTree,
|
|
3126
|
-
initRoute,
|
|
3127
|
-
}: {
|
|
3128
|
-
routeTree: TRouteLike
|
|
3129
|
-
initRoute?: (route: TRouteLike, index: number) => void
|
|
3130
|
-
}) {
|
|
3131
|
-
const routesById = {} as Record<string, TRouteLike>
|
|
3132
|
-
const routesByPath = {} as Record<string, TRouteLike>
|
|
3133
|
-
|
|
3134
|
-
const recurseRoutes = (childRoutes: Array<TRouteLike>) => {
|
|
3135
|
-
childRoutes.forEach((childRoute, i) => {
|
|
3136
|
-
initRoute?.(childRoute, i)
|
|
3137
|
-
|
|
3138
|
-
const existingRoute = routesById[childRoute.id]
|
|
3139
|
-
|
|
3140
|
-
invariant(
|
|
3141
|
-
!existingRoute,
|
|
3142
|
-
`Duplicate routes found with id: ${String(childRoute.id)}`,
|
|
3143
|
-
)
|
|
3144
|
-
|
|
3145
|
-
routesById[childRoute.id] = childRoute
|
|
3146
|
-
|
|
3147
|
-
if (!childRoute.isRoot && childRoute.path) {
|
|
3148
|
-
const trimmedFullPath = trimPathRight(childRoute.fullPath)
|
|
3149
|
-
if (
|
|
3150
|
-
!routesByPath[trimmedFullPath] ||
|
|
3151
|
-
childRoute.fullPath.endsWith('/')
|
|
3152
|
-
) {
|
|
3153
|
-
routesByPath[trimmedFullPath] = childRoute
|
|
3154
|
-
}
|
|
3155
|
-
}
|
|
3156
|
-
|
|
3157
|
-
const children = childRoute.children as Array<TRouteLike>
|
|
3158
|
-
|
|
3159
|
-
if (children?.length) {
|
|
3160
|
-
recurseRoutes(children)
|
|
3161
|
-
}
|
|
3162
|
-
})
|
|
3163
|
-
}
|
|
3164
|
-
|
|
3165
|
-
recurseRoutes([routeTree])
|
|
3166
|
-
|
|
3167
|
-
const scoredRoutes: Array<{
|
|
3168
|
-
child: TRouteLike
|
|
3169
|
-
trimmed: string
|
|
3170
|
-
parsed: ReturnType<typeof parsePathname>
|
|
3171
|
-
index: number
|
|
3172
|
-
scores: Array<number>
|
|
3173
|
-
}> = []
|
|
3174
|
-
|
|
3175
|
-
const routes: Array<TRouteLike> = Object.values(routesById)
|
|
3176
|
-
|
|
3177
|
-
routes.forEach((d, i) => {
|
|
3178
|
-
if (d.isRoot || !d.path) {
|
|
3179
|
-
return
|
|
3180
|
-
}
|
|
3181
|
-
|
|
3182
|
-
const trimmed = trimPathLeft(d.fullPath)
|
|
3183
|
-
const parsed = parsePathname(trimmed)
|
|
3184
|
-
|
|
3185
|
-
// Removes the leading slash if it is not the only remaining segment
|
|
3186
|
-
while (parsed.length > 1 && parsed[0]?.value === '/') {
|
|
3187
|
-
parsed.shift()
|
|
3188
|
-
}
|
|
3189
|
-
|
|
3190
|
-
const scores = parsed.map((segment) => {
|
|
3191
|
-
if (segment.value === '/') {
|
|
3192
|
-
return 0.75
|
|
3193
|
-
}
|
|
3194
|
-
|
|
3195
|
-
if (
|
|
3196
|
-
segment.type === 'param' &&
|
|
3197
|
-
segment.prefixSegment &&
|
|
3198
|
-
segment.suffixSegment
|
|
3199
|
-
) {
|
|
3200
|
-
return 0.55
|
|
3201
|
-
}
|
|
3202
|
-
|
|
3203
|
-
if (segment.type === 'param' && segment.prefixSegment) {
|
|
3204
|
-
return 0.52
|
|
3205
|
-
}
|
|
3206
|
-
|
|
3207
|
-
if (segment.type === 'param' && segment.suffixSegment) {
|
|
3208
|
-
return 0.51
|
|
3209
|
-
}
|
|
3210
|
-
|
|
3211
|
-
if (segment.type === 'param') {
|
|
3212
|
-
return 0.5
|
|
3213
|
-
}
|
|
3214
|
-
|
|
3215
|
-
if (
|
|
3216
|
-
segment.type === 'wildcard' &&
|
|
3217
|
-
segment.prefixSegment &&
|
|
3218
|
-
segment.suffixSegment
|
|
3219
|
-
) {
|
|
3220
|
-
return 0.3
|
|
3221
|
-
}
|
|
3222
|
-
|
|
3223
|
-
if (segment.type === 'wildcard' && segment.prefixSegment) {
|
|
3224
|
-
return 0.27
|
|
3225
|
-
}
|
|
3226
|
-
|
|
3227
|
-
if (segment.type === 'wildcard' && segment.suffixSegment) {
|
|
3228
|
-
return 0.26
|
|
3229
|
-
}
|
|
3230
|
-
|
|
3231
|
-
if (segment.type === 'wildcard') {
|
|
3232
|
-
return 0.25
|
|
3233
|
-
}
|
|
3234
|
-
|
|
3235
|
-
return 1
|
|
3236
|
-
})
|
|
3237
|
-
|
|
3238
|
-
scoredRoutes.push({ child: d, trimmed, parsed, index: i, scores })
|
|
3239
|
-
})
|
|
3240
|
-
|
|
3241
|
-
const flatRoutes = scoredRoutes
|
|
3242
|
-
.sort((a, b) => {
|
|
3243
|
-
const minLength = Math.min(a.scores.length, b.scores.length)
|
|
3244
|
-
|
|
3245
|
-
// Sort by min available score
|
|
3246
|
-
for (let i = 0; i < minLength; i++) {
|
|
3247
|
-
if (a.scores[i] !== b.scores[i]) {
|
|
3248
|
-
return b.scores[i]! - a.scores[i]!
|
|
3249
|
-
}
|
|
3250
|
-
}
|
|
3251
|
-
|
|
3252
|
-
// Sort by length of score
|
|
3253
|
-
if (a.scores.length !== b.scores.length) {
|
|
3254
|
-
return b.scores.length - a.scores.length
|
|
3255
|
-
}
|
|
3256
|
-
|
|
3257
|
-
// Sort by min available parsed value
|
|
3258
|
-
for (let i = 0; i < minLength; i++) {
|
|
3259
|
-
if (a.parsed[i]!.value !== b.parsed[i]!.value) {
|
|
3260
|
-
return a.parsed[i]!.value > b.parsed[i]!.value ? 1 : -1
|
|
3261
|
-
}
|
|
3262
|
-
}
|
|
3263
|
-
|
|
3264
|
-
// Sort by original index
|
|
3265
|
-
return a.index - b.index
|
|
3266
|
-
})
|
|
3267
|
-
.map((d, i) => {
|
|
3268
|
-
d.child.rank = i
|
|
3269
|
-
return d.child
|
|
3270
|
-
})
|
|
3271
|
-
|
|
3272
|
-
return { routesById, routesByPath, flatRoutes }
|
|
3273
|
-
}
|
|
3274
|
-
|
|
3275
|
-
export function getMatchedRoutes<TRouteLike extends RouteLike>({
|
|
3276
|
-
pathname,
|
|
3277
|
-
routePathname,
|
|
3278
|
-
basepath,
|
|
3279
|
-
caseSensitive,
|
|
3280
|
-
routesByPath,
|
|
3281
|
-
routesById,
|
|
3282
|
-
flatRoutes,
|
|
3283
|
-
}: {
|
|
3284
|
-
pathname: string
|
|
3285
|
-
routePathname?: string
|
|
3286
|
-
basepath: string
|
|
3287
|
-
caseSensitive?: boolean
|
|
3288
|
-
routesByPath: Record<string, TRouteLike>
|
|
3289
|
-
routesById: Record<string, TRouteLike>
|
|
3290
|
-
flatRoutes: Array<TRouteLike>
|
|
3291
|
-
}) {
|
|
3292
|
-
let routeParams: Record<string, string> = {}
|
|
3293
|
-
const trimmedPath = trimPathRight(pathname)
|
|
3294
|
-
const getMatchedParams = (route: TRouteLike) => {
|
|
3295
|
-
const result = matchPathname(basepath, trimmedPath, {
|
|
3296
|
-
to: route.fullPath,
|
|
3297
|
-
caseSensitive: route.options?.caseSensitive ?? caseSensitive,
|
|
3298
|
-
fuzzy: true,
|
|
3299
|
-
})
|
|
3300
|
-
return result
|
|
3301
|
-
}
|
|
3302
|
-
|
|
3303
|
-
let foundRoute: TRouteLike | undefined =
|
|
3304
|
-
routePathname !== undefined ? routesByPath[routePathname] : undefined
|
|
3305
|
-
if (foundRoute) {
|
|
3306
|
-
routeParams = getMatchedParams(foundRoute)!
|
|
3307
|
-
} else {
|
|
3308
|
-
foundRoute = flatRoutes.find((route) => {
|
|
3309
|
-
const matchedParams = getMatchedParams(route)
|
|
3310
|
-
|
|
3311
|
-
if (matchedParams) {
|
|
3312
|
-
routeParams = matchedParams
|
|
3313
|
-
return true
|
|
3314
|
-
}
|
|
3315
|
-
|
|
3316
|
-
return false
|
|
3317
|
-
})
|
|
3318
|
-
}
|
|
3319
|
-
|
|
3320
|
-
let routeCursor: TRouteLike = foundRoute || routesById[rootRouteId]!
|
|
3321
|
-
|
|
3322
|
-
const matchedRoutes: Array<TRouteLike> = [routeCursor]
|
|
3323
|
-
|
|
3324
|
-
while (routeCursor.parentRoute) {
|
|
3325
|
-
routeCursor = routeCursor.parentRoute as TRouteLike
|
|
3326
|
-
matchedRoutes.unshift(routeCursor)
|
|
3327
|
-
}
|
|
3328
|
-
|
|
3329
|
-
return { matchedRoutes, routeParams, foundRoute }
|
|
3330
|
-
}
|