@tanstack/router-core 1.120.4-alpha.19 → 1.120.4

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 (58) hide show
  1. package/dist/cjs/fileRoute.d.cts +2 -6
  2. package/dist/cjs/index.cjs +0 -3
  3. package/dist/cjs/index.cjs.map +1 -1
  4. package/dist/cjs/index.d.cts +6 -6
  5. package/dist/cjs/link.cjs.map +1 -1
  6. package/dist/cjs/link.d.cts +1 -18
  7. package/dist/cjs/path.cjs +16 -130
  8. package/dist/cjs/path.cjs.map +1 -1
  9. package/dist/cjs/path.d.cts +0 -17
  10. package/dist/cjs/redirect.cjs +14 -17
  11. package/dist/cjs/redirect.cjs.map +1 -1
  12. package/dist/cjs/redirect.d.cts +7 -13
  13. package/dist/cjs/route.cjs +1 -12
  14. package/dist/cjs/route.cjs.map +1 -1
  15. package/dist/cjs/route.d.cts +14 -15
  16. package/dist/cjs/router.cjs +155 -231
  17. package/dist/cjs/router.cjs.map +1 -1
  18. package/dist/cjs/router.d.cts +3 -46
  19. package/dist/cjs/scroll-restoration.cjs +23 -12
  20. package/dist/cjs/scroll-restoration.cjs.map +1 -1
  21. package/dist/cjs/scroll-restoration.d.cts +1 -1
  22. package/dist/cjs/typePrimitives.d.cts +2 -2
  23. package/dist/cjs/utils.cjs.map +1 -1
  24. package/dist/cjs/utils.d.cts +0 -2
  25. package/dist/esm/fileRoute.d.ts +2 -6
  26. package/dist/esm/index.d.ts +6 -6
  27. package/dist/esm/index.js +2 -5
  28. package/dist/esm/link.d.ts +1 -18
  29. package/dist/esm/link.js.map +1 -1
  30. package/dist/esm/path.d.ts +0 -17
  31. package/dist/esm/path.js +16 -130
  32. package/dist/esm/path.js.map +1 -1
  33. package/dist/esm/redirect.d.ts +7 -13
  34. package/dist/esm/redirect.js +14 -17
  35. package/dist/esm/redirect.js.map +1 -1
  36. package/dist/esm/route.d.ts +14 -15
  37. package/dist/esm/route.js +1 -12
  38. package/dist/esm/route.js.map +1 -1
  39. package/dist/esm/router.d.ts +3 -46
  40. package/dist/esm/router.js +158 -234
  41. package/dist/esm/router.js.map +1 -1
  42. package/dist/esm/scroll-restoration.d.ts +1 -1
  43. package/dist/esm/scroll-restoration.js +23 -12
  44. package/dist/esm/scroll-restoration.js.map +1 -1
  45. package/dist/esm/typePrimitives.d.ts +2 -2
  46. package/dist/esm/utils.d.ts +0 -2
  47. package/dist/esm/utils.js.map +1 -1
  48. package/package.json +2 -2
  49. package/src/fileRoute.ts +1 -90
  50. package/src/index.ts +6 -14
  51. package/src/link.ts +11 -97
  52. package/src/path.ts +16 -181
  53. package/src/redirect.ts +22 -37
  54. package/src/route.ts +35 -104
  55. package/src/router.ts +209 -332
  56. package/src/scroll-restoration.ts +44 -27
  57. package/src/typePrimitives.ts +2 -2
  58. 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?: AnyRedirect
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
- pathname: string,
605
- routePathname: string | undefined,
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
- const { routesById, routesByPath, flatRoutes } = processRouteTree({
949
- routeTree: this.routeTree,
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
970
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
+ })
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.pathname,
1085
- opts?.dest?.to as string,
1168
+ next,
1169
+ opts?.dest,
1086
1170
  )
1087
1171
  let isGlobalNotFound = false
1088
1172
 
@@ -1388,19 +1472,47 @@ export class RouterCore<
1388
1472
  return matches
1389
1473
  }
1390
1474
 
1391
- getMatchedRoutes: GetMatchRoutesFn = (
1392
- pathname: string,
1393
- routePathname: string | undefined,
1394
- ) => {
1395
- return getMatchedRoutes({
1396
- pathname,
1397
- routePathname,
1398
- basepath: this.basepath,
1399
- caseSensitive: this.options.caseSensitive,
1400
- routesByPath: this.routesByPath,
1401
- routesById: this.routesById,
1402
- flatRoutes: this.flatRoutes,
1403
- })
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 }
1404
1516
  }
1405
1517
 
1406
1518
  cancelMatch = (id: string) => {
@@ -1700,17 +1812,11 @@ export class RouterCore<
1700
1812
  }
1701
1813
  }
1702
1814
 
1703
- const nextMatches = this.getMatchedRoutes(
1704
- next.pathname,
1705
- dest.to as string,
1706
- )
1815
+ const nextMatches = this.getMatchedRoutes(next, dest)
1707
1816
  const final = build(dest, nextMatches)
1708
1817
 
1709
1818
  if (maskedNext) {
1710
- const maskedMatches = this.getMatchedRoutes(
1711
- maskedNext.pathname,
1712
- maskedDest?.to as string,
1713
- )
1819
+ const maskedMatches = this.getMatchedRoutes(maskedNext, maskedDest)
1714
1820
  const maskedFinal = build(maskedDest, maskedMatches)
1715
1821
  final.maskedLocation = maskedFinal
1716
1822
  }
@@ -1852,13 +1958,6 @@ export class RouterCore<
1852
1958
  }
1853
1959
 
1854
1960
  navigate: NavigateFn = ({ to, reloadDocument, href, ...rest }) => {
1855
- if (!reloadDocument && href) {
1856
- try {
1857
- new URL(`${href}`)
1858
- reloadDocument = true
1859
- } catch {}
1860
- }
1861
-
1862
1961
  if (reloadDocument) {
1863
1962
  if (!href) {
1864
1963
  const location = this.buildLocation({ to, ...rest } as any)
@@ -1881,30 +1980,10 @@ export class RouterCore<
1881
1980
 
1882
1981
  latestLoadPromise: undefined | Promise<void>
1883
1982
 
1884
- beforeLoad = () => {
1885
- // Cancel any pending matches
1886
- this.cancelMatches()
1983
+ load: LoadFn = async (opts?: { sync?: boolean }): Promise<void> => {
1887
1984
  this.latestLocation = this.parseLocation(this.latestLocation)
1888
1985
 
1889
- // Match the routes
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
1986
+ let redirect: ResolvedRedirect | undefined
1908
1987
  let notFound: NotFoundError | undefined
1909
1988
 
1910
1989
  let loadPromise: Promise<void>
@@ -1913,10 +1992,36 @@ export class RouterCore<
1913
1992
  loadPromise = new Promise<void>((resolve) => {
1914
1993
  this.startTransition(async () => {
1915
1994
  try {
1916
- this.beforeLoad()
1917
1995
  const next = this.latestLocation
1918
1996
  const prevLocation = this.state.resolvedLocation
1919
1997
 
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
+
1920
2025
  if (!this.state.redirect) {
1921
2026
  this.emit({
1922
2027
  type: 'onBeforeNavigate',
@@ -1937,7 +2042,7 @@ export class RouterCore<
1937
2042
 
1938
2043
  await this.loadMatches({
1939
2044
  sync: opts?.sync,
1940
- matches: this.state.pendingMatches as Array<AnyRouteMatch>,
2045
+ matches: pendingMatches,
1941
2046
  location: next,
1942
2047
  // eslint-disable-next-line @typescript-eslint/require-await
1943
2048
  onReady: async () => {
@@ -1998,11 +2103,11 @@ export class RouterCore<
1998
2103
  },
1999
2104
  })
2000
2105
  } catch (err) {
2001
- if (isRedirect(err)) {
2106
+ if (isResolvedRedirect(err)) {
2002
2107
  redirect = err
2003
2108
  if (!this.isServer) {
2004
2109
  this.navigate({
2005
- ...redirect.options,
2110
+ ...redirect,
2006
2111
  replace: true,
2007
2112
  ignoreBlocker: true,
2008
2113
  })
@@ -2014,7 +2119,7 @@ export class RouterCore<
2014
2119
  this.__store.setState((s) => ({
2015
2120
  ...s,
2016
2121
  statusCode: redirect
2017
- ? redirect.status
2122
+ ? redirect.statusCode
2018
2123
  : notFound
2019
2124
  ? 404
2020
2125
  : s.matches.some((d) => d.status === 'error')
@@ -2170,15 +2275,13 @@ export class RouterCore<
2170
2275
  }
2171
2276
 
2172
2277
  const handleRedirectAndNotFound = (match: AnyRouteMatch, err: any) => {
2173
- if (isRedirect(err) || isNotFound(err)) {
2174
- if (isRedirect(err)) {
2175
- if (err.redirectHandled) {
2176
- if (!err.options.reloadDocument) {
2177
- throw err
2178
- }
2179
- }
2278
+ if (isResolvedRedirect(err)) {
2279
+ if (!err.reloadDocument) {
2280
+ throw err
2180
2281
  }
2282
+ }
2181
2283
 
2284
+ if (isRedirect(err) || isNotFound(err)) {
2182
2285
  updateMatch(match.id, (prev) => ({
2183
2286
  ...prev,
2184
2287
  status: isRedirect(err)
@@ -2202,9 +2305,7 @@ export class RouterCore<
2202
2305
 
2203
2306
  if (isRedirect(err)) {
2204
2307
  rendered = true
2205
- err.options._fromLocation = location
2206
- err.redirectHandled = true
2207
- err = this.resolveRedirect(err)
2308
+ err = this.resolveRedirect({ ...err, _fromLocation: location })
2208
2309
  throw err
2209
2310
  } else if (isNotFound(err)) {
2210
2311
  this._handleNotFound(matches, err, {
@@ -2641,8 +2742,8 @@ export class RouterCore<
2641
2742
  loaderPromise: undefined,
2642
2743
  }))
2643
2744
  } catch (err) {
2644
- if (isRedirect(err)) {
2645
- await this.navigate(err.options)
2745
+ if (isResolvedRedirect(err)) {
2746
+ await this.navigate(err)
2646
2747
  }
2647
2748
  }
2648
2749
  })()
@@ -2727,14 +2828,11 @@ export class RouterCore<
2727
2828
  return this.load({ sync: opts?.sync })
2728
2829
  }
2729
2830
 
2730
- resolveRedirect = (redirect: AnyRedirect): AnyRedirect => {
2731
- if (!redirect.options.href) {
2732
- redirect.options.href = this.buildLocation(redirect.options).href
2733
- redirect.headers.set('Location', redirect.options.href)
2734
- }
2831
+ resolveRedirect = (err: AnyRedirect): ResolvedRedirect => {
2832
+ const redirect = err as ResolvedRedirect
2735
2833
 
2736
- if (!redirect.headers.get('Location')) {
2737
- redirect.headers.set('Location', redirect.options.href)
2834
+ if (!redirect.href) {
2835
+ redirect.href = this.buildLocation(redirect as any).href
2738
2836
  }
2739
2837
 
2740
2838
  return redirect
@@ -2869,11 +2967,11 @@ export class RouterCore<
2869
2967
  return matches
2870
2968
  } catch (err) {
2871
2969
  if (isRedirect(err)) {
2872
- if (err.options.reloadDocument) {
2970
+ if (err.reloadDocument) {
2873
2971
  return undefined
2874
2972
  }
2875
2973
  return await this.preloadRoute({
2876
- ...err.options,
2974
+ ...(err as any),
2877
2975
  _fromLocation: next,
2878
2976
  })
2879
2977
  }
@@ -3107,224 +3205,3 @@ function routeNeedsPreload(route: AnyRoute) {
3107
3205
  }
3108
3206
  return false
3109
3207
  }
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
- }