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