@tanstack/router-core 1.132.0-alpha.2 → 1.132.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/dist/cjs/index.cjs +8 -2
  2. package/dist/cjs/index.cjs.map +1 -1
  3. package/dist/cjs/index.d.cts +6 -2
  4. package/dist/cjs/load-matches.cjs +636 -0
  5. package/dist/cjs/load-matches.cjs.map +1 -0
  6. package/dist/cjs/load-matches.d.cts +16 -0
  7. package/dist/cjs/qss.cjs +19 -19
  8. package/dist/cjs/qss.cjs.map +1 -1
  9. package/dist/cjs/qss.d.cts +6 -4
  10. package/dist/cjs/redirect.cjs +3 -3
  11. package/dist/cjs/redirect.cjs.map +1 -1
  12. package/dist/cjs/router.cjs +33 -702
  13. package/dist/cjs/router.cjs.map +1 -1
  14. package/dist/cjs/router.d.cts +14 -37
  15. package/dist/cjs/scroll-restoration.cjs +20 -25
  16. package/dist/cjs/scroll-restoration.cjs.map +1 -1
  17. package/dist/cjs/searchParams.cjs +7 -15
  18. package/dist/cjs/searchParams.cjs.map +1 -1
  19. package/dist/cjs/ssr/constants.cjs +5 -0
  20. package/dist/cjs/ssr/constants.cjs.map +1 -0
  21. package/dist/cjs/ssr/constants.d.cts +1 -0
  22. package/dist/cjs/ssr/{seroval-plugins.cjs → serializer/ShallowErrorPlugin.cjs} +2 -2
  23. package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs.map +1 -0
  24. package/dist/cjs/ssr/{seroval-plugins.d.cts → serializer/ShallowErrorPlugin.d.cts} +1 -2
  25. package/dist/cjs/ssr/serializer/seroval-plugins.cjs +11 -0
  26. package/dist/cjs/ssr/serializer/seroval-plugins.cjs.map +1 -0
  27. package/dist/cjs/ssr/serializer/seroval-plugins.d.cts +2 -0
  28. package/dist/cjs/ssr/serializer/transformer.cjs +50 -0
  29. package/dist/cjs/ssr/serializer/transformer.cjs.map +1 -0
  30. package/dist/cjs/ssr/serializer/transformer.d.cts +18 -0
  31. package/dist/cjs/ssr/ssr-client.cjs +15 -1
  32. package/dist/cjs/ssr/ssr-client.cjs.map +1 -1
  33. package/dist/cjs/ssr/ssr-client.d.cts +5 -1
  34. package/dist/cjs/ssr/ssr-server.cjs +12 -10
  35. package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
  36. package/dist/cjs/ssr/ssr-server.d.cts +0 -1
  37. package/dist/cjs/ssr/tsrScript.cjs +1 -1
  38. package/dist/cjs/ssr/tsrScript.cjs.map +1 -1
  39. package/dist/cjs/utils.cjs +8 -7
  40. package/dist/cjs/utils.cjs.map +1 -1
  41. package/dist/cjs/utils.d.cts +1 -1
  42. package/dist/esm/index.d.ts +6 -2
  43. package/dist/esm/index.js +9 -3
  44. package/dist/esm/index.js.map +1 -1
  45. package/dist/esm/load-matches.d.ts +16 -0
  46. package/dist/esm/load-matches.js +636 -0
  47. package/dist/esm/load-matches.js.map +1 -0
  48. package/dist/esm/qss.d.ts +6 -4
  49. package/dist/esm/qss.js +19 -19
  50. package/dist/esm/qss.js.map +1 -1
  51. package/dist/esm/redirect.js +3 -3
  52. package/dist/esm/redirect.js.map +1 -1
  53. package/dist/esm/router.d.ts +14 -37
  54. package/dist/esm/router.js +33 -702
  55. package/dist/esm/router.js.map +1 -1
  56. package/dist/esm/scroll-restoration.js +20 -25
  57. package/dist/esm/scroll-restoration.js.map +1 -1
  58. package/dist/esm/searchParams.js +7 -15
  59. package/dist/esm/searchParams.js.map +1 -1
  60. package/dist/esm/ssr/constants.d.ts +1 -0
  61. package/dist/esm/ssr/constants.js +5 -0
  62. package/dist/esm/ssr/constants.js.map +1 -0
  63. package/dist/esm/ssr/{seroval-plugins.d.ts → serializer/ShallowErrorPlugin.d.ts} +1 -2
  64. package/dist/esm/ssr/{seroval-plugins.js → serializer/ShallowErrorPlugin.js} +2 -2
  65. package/dist/esm/ssr/serializer/ShallowErrorPlugin.js.map +1 -0
  66. package/dist/esm/ssr/serializer/seroval-plugins.d.ts +2 -0
  67. package/dist/esm/ssr/serializer/seroval-plugins.js +11 -0
  68. package/dist/esm/ssr/serializer/seroval-plugins.js.map +1 -0
  69. package/dist/esm/ssr/serializer/transformer.d.ts +18 -0
  70. package/dist/esm/ssr/serializer/transformer.js +50 -0
  71. package/dist/esm/ssr/serializer/transformer.js.map +1 -0
  72. package/dist/esm/ssr/ssr-client.d.ts +5 -1
  73. package/dist/esm/ssr/ssr-client.js +15 -1
  74. package/dist/esm/ssr/ssr-client.js.map +1 -1
  75. package/dist/esm/ssr/ssr-server.d.ts +0 -1
  76. package/dist/esm/ssr/ssr-server.js +12 -10
  77. package/dist/esm/ssr/ssr-server.js.map +1 -1
  78. package/dist/esm/ssr/tsrScript.js +1 -1
  79. package/dist/esm/ssr/tsrScript.js.map +1 -1
  80. package/dist/esm/utils.d.ts +1 -1
  81. package/dist/esm/utils.js +8 -7
  82. package/dist/esm/utils.js.map +1 -1
  83. package/package.json +1 -1
  84. package/src/index.ts +12 -2
  85. package/src/load-matches.ts +955 -0
  86. package/src/qss.ts +27 -24
  87. package/src/redirect.ts +3 -3
  88. package/src/router.ts +62 -1048
  89. package/src/scroll-restoration.ts +25 -32
  90. package/src/searchParams.ts +8 -19
  91. package/src/ssr/constants.ts +1 -0
  92. package/src/ssr/{seroval-plugins.ts → serializer/ShallowErrorPlugin.ts} +2 -2
  93. package/src/ssr/serializer/seroval-plugins.ts +9 -0
  94. package/src/ssr/serializer/transformer.ts +78 -0
  95. package/src/ssr/ssr-client.ts +30 -3
  96. package/src/ssr/ssr-server.ts +18 -10
  97. package/src/ssr/tsrScript.ts +5 -1
  98. package/src/utils.ts +11 -10
  99. package/dist/cjs/ssr/seroval-plugins.cjs.map +0 -1
  100. package/dist/esm/ssr/seroval-plugins.js.map +0 -1
package/src/router.ts CHANGED
@@ -8,10 +8,9 @@ import invariant from 'tiny-invariant'
8
8
  import {
9
9
  createControlledPromise,
10
10
  deepEqual,
11
+ findLast,
11
12
  functionalUpdate,
12
- isPromise,
13
13
  last,
14
- pick,
15
14
  replaceEqualDeep,
16
15
  } from './utils'
17
16
  import {
@@ -35,6 +34,7 @@ import { defaultParseSearch, defaultStringifySearch } from './searchParams'
35
34
  import { rootRouteId } from './root'
36
35
  import { isRedirect, redirect } from './redirect'
37
36
  import { createLRUCache } from './lru-cache'
37
+ import { loadMatches, loadRouteChunk, routeNeedsPreload } from './load-matches'
38
38
  import type { ParsePathnameCache, Segment } from './path'
39
39
  import type { SearchParser, SearchSerializer } from './searchParams'
40
40
  import type { AnyRedirect, ResolvedRedirect } from './redirect'
@@ -57,13 +57,10 @@ import type {
57
57
  AnyContext,
58
58
  AnyRoute,
59
59
  AnyRouteWithContext,
60
- BeforeLoadContextOptions,
61
- LoaderFnContext,
62
60
  MakeRemountDepsOptionsUnion,
63
61
  RouteContextOptions,
64
62
  RouteMask,
65
63
  SearchMiddleware,
66
- SsrContextOptions,
67
64
  } from './route'
68
65
  import type {
69
66
  FullSearchSchema,
@@ -120,6 +117,7 @@ export interface RouterOptions<
120
117
  TDefaultStructuralSharingOption extends boolean = false,
121
118
  TRouterHistory extends RouterHistory = RouterHistory,
122
119
  TDehydrated extends Record<string, any> = Record<string, any>,
120
+ TTransformerConfig = any,
123
121
  > extends RouterOptionsExtensions {
124
122
  /**
125
123
  * The history object that will be used to manage the browser history.
@@ -424,6 +422,8 @@ export interface RouterOptions<
424
422
  * @default false
425
423
  */
426
424
  disableGlobalCatchBoundary?: boolean
425
+
426
+ serializationAdapters?: TTransformerConfig
427
427
  }
428
428
 
429
429
  export interface RouterState<
@@ -532,13 +532,15 @@ export type RouterConstructorOptions<
532
532
  TDefaultStructuralSharingOption extends boolean,
533
533
  TRouterHistory extends RouterHistory,
534
534
  TDehydrated extends Record<string, any>,
535
+ TTransformerConfig,
535
536
  > = Omit<
536
537
  RouterOptions<
537
538
  TRouteTree,
538
539
  TTrailingSlashOption,
539
540
  TDefaultStructuralSharingOption,
540
541
  TRouterHistory,
541
- TDehydrated
542
+ TDehydrated,
543
+ TTransformerConfig
542
544
  >,
543
545
  'context'
544
546
  > &
@@ -598,13 +600,15 @@ export type UpdateFn<
598
600
  TDefaultStructuralSharingOption extends boolean,
599
601
  TRouterHistory extends RouterHistory,
600
602
  TDehydrated extends Record<string, any>,
603
+ TTransformerConfig extends any,
601
604
  > = (
602
605
  newOptions: RouterConstructorOptions<
603
606
  TRouteTree,
604
607
  TTrailingSlashOption,
605
608
  TDefaultStructuralSharingOption,
606
609
  TRouterHistory,
607
- TDehydrated
610
+ TDehydrated,
611
+ TTransformerConfig
608
612
  >,
609
613
  ) => void
610
614
 
@@ -689,10 +693,11 @@ export type AnyRouterWithContext<TContext> = RouterCore<
689
693
  any,
690
694
  any,
691
695
  any,
696
+ any,
692
697
  any
693
698
  >
694
699
 
695
- export type AnyRouter = RouterCore<any, any, any, any, any>
700
+ export type AnyRouter = RouterCore<any, any, any, any, any, any>
696
701
 
697
702
  export interface ViewTransitionOptions {
698
703
  types:
@@ -706,6 +711,7 @@ export interface ViewTransitionOptions {
706
711
  }) => Array<string>)
707
712
  }
708
713
 
714
+ // TODO where is this used? can we remove this?
709
715
  export function defaultSerializeError(err: unknown) {
710
716
  if (err instanceof Error) {
711
717
  const obj = {
@@ -745,6 +751,7 @@ export type CreateRouterFn = <
745
751
  TDefaultStructuralSharingOption extends boolean = false,
746
752
  TRouterHistory extends RouterHistory = RouterHistory,
747
753
  TDehydrated extends Record<string, any> = Record<string, any>,
754
+ TTransformerConfig = any,
748
755
  >(
749
756
  options: undefined extends number
750
757
  ? 'strictNullChecks must be enabled in tsconfig.json'
@@ -753,34 +760,25 @@ export type CreateRouterFn = <
753
760
  TTrailingSlashOption,
754
761
  TDefaultStructuralSharingOption,
755
762
  TRouterHistory,
756
- TDehydrated
763
+ TDehydrated,
764
+ TTransformerConfig
757
765
  >,
758
766
  ) => RouterCore<
759
767
  TRouteTree,
760
768
  TTrailingSlashOption,
761
769
  TDefaultStructuralSharingOption,
762
770
  TRouterHistory,
763
- TDehydrated
771
+ TDehydrated,
772
+ TTransformerConfig
764
773
  >
765
774
 
766
- type InnerLoadContext = {
767
- location: ParsedLocation
768
- firstBadMatchIndex?: number
769
- rendered?: boolean
770
- updateMatch: UpdateMatchFn
771
- matches: Array<AnyRouteMatch>
772
- preload?: boolean
773
- onReady?: () => Promise<void>
774
- sync?: boolean
775
- matchPromises: Array<Promise<AnyRouteMatch>>
776
- }
777
-
778
775
  export class RouterCore<
779
776
  in out TRouteTree extends AnyRoute,
780
777
  in out TTrailingSlashOption extends TrailingSlashOption,
781
778
  in out TDefaultStructuralSharingOption extends boolean,
782
779
  in out TRouterHistory extends RouterHistory = RouterHistory,
783
780
  in out TDehydrated extends Record<string, any> = Record<string, any>,
781
+ in out TTransformerConfig = any,
784
782
  > {
785
783
  // Option-independent properties
786
784
  tempLocationKey: string | undefined = `${Math.round(
@@ -802,7 +800,8 @@ export class RouterCore<
802
800
  TTrailingSlashOption,
803
801
  TDefaultStructuralSharingOption,
804
802
  TRouterHistory,
805
- TDehydrated
803
+ TDehydrated,
804
+ TTransformerConfig
806
805
  >,
807
806
  'stringifySearch' | 'parseSearch' | 'context'
808
807
  >
@@ -825,7 +824,8 @@ export class RouterCore<
825
824
  TTrailingSlashOption,
826
825
  TDefaultStructuralSharingOption,
827
826
  TRouterHistory,
828
- TDehydrated
827
+ TDehydrated,
828
+ TTransformerConfig
829
829
  >,
830
830
  ) {
831
831
  this.update({
@@ -863,7 +863,8 @@ export class RouterCore<
863
863
  TTrailingSlashOption,
864
864
  TDefaultStructuralSharingOption,
865
865
  TRouterHistory,
866
- TDehydrated
866
+ TDehydrated,
867
+ TTransformerConfig
867
868
  > = (newOptions) => {
868
869
  if (newOptions.notFoundRoute) {
869
870
  console.warn(
@@ -1449,13 +1450,11 @@ export class RouterCore<
1449
1450
  undefined,
1450
1451
  ).matchedRoutes
1451
1452
 
1452
- const matchedFrom = [...allCurrentLocationMatches]
1453
- .reverse()
1454
- .find((d) => {
1455
- return comparePaths(d.fullPath, fromPath)
1456
- })
1453
+ const matchedFrom = findLast(allCurrentLocationMatches, (d) => {
1454
+ return comparePaths(d.fullPath, fromPath)
1455
+ })
1457
1456
 
1458
- const matchedCurrent = [...allFromMatches].reverse().find((d) => {
1457
+ const matchedCurrent = findLast(allFromMatches, (d) => {
1459
1458
  return comparePaths(d.fullPath, currentLocation.pathname)
1460
1459
  })
1461
1460
 
@@ -1480,20 +1479,20 @@ export class RouterCore<
1480
1479
  : this.resolvePathWithBase(fromPath, '.')
1481
1480
 
1482
1481
  // Resolve the next params
1483
- let nextParams =
1482
+ const nextParams =
1484
1483
  dest.params === false || dest.params === null
1485
1484
  ? {}
1486
1485
  : (dest.params ?? true) === true
1487
1486
  ? fromParams
1488
- : {
1489
- ...fromParams,
1490
- ...functionalUpdate(dest.params as any, fromParams),
1491
- }
1487
+ : Object.assign(
1488
+ fromParams,
1489
+ functionalUpdate(dest.params as any, fromParams),
1490
+ )
1492
1491
 
1493
1492
  // Interpolate the path first to get the actual resolved path, then match against that
1494
1493
  const interpolatedNextTo = interpolatePath({
1495
1494
  path: nextTo,
1496
- params: nextParams ?? {},
1495
+ params: nextParams,
1497
1496
  parseCache: this.parsePathnameCache,
1498
1497
  }).interpolatedPath
1499
1498
 
@@ -1503,23 +1502,20 @@ export class RouterCore<
1503
1502
 
1504
1503
  // If there are any params, we need to stringify them
1505
1504
  if (Object.keys(nextParams).length > 0) {
1506
- destRoutes
1507
- .map((route) => {
1508
- return (
1509
- route.options.params?.stringify ?? route.options.stringifyParams
1510
- )
1511
- })
1512
- .filter(Boolean)
1513
- .forEach((fn) => {
1514
- nextParams = { ...nextParams!, ...fn!(nextParams) }
1515
- })
1505
+ for (const route of destRoutes) {
1506
+ const fn =
1507
+ route.options.params?.stringify ?? route.options.stringifyParams
1508
+ if (fn) {
1509
+ Object.assign(nextParams, fn(nextParams))
1510
+ }
1511
+ }
1516
1512
  }
1517
1513
 
1518
1514
  const nextPathname = interpolatePath({
1519
1515
  // Use the original template path for interpolation
1520
1516
  // This preserves the original parameter syntax including optional parameters
1521
1517
  path: nextTo,
1522
- params: nextParams ?? {},
1518
+ params: nextParams,
1523
1519
  leaveWildcards: false,
1524
1520
  leaveParams: opts.leaveParams,
1525
1521
  decodeCharMap: this.pathParamsDecodeCharMap,
@@ -1529,20 +1525,20 @@ export class RouterCore<
1529
1525
  // Resolve the next search
1530
1526
  let nextSearch = fromSearch
1531
1527
  if (opts._includeValidateSearch && this.options.search?.strict) {
1532
- let validatedSearch = {}
1528
+ const validatedSearch = {}
1533
1529
  destRoutes.forEach((route) => {
1534
- try {
1535
- if (route.options.validateSearch) {
1536
- validatedSearch = {
1537
- ...validatedSearch,
1538
- ...(validateSearch(route.options.validateSearch, {
1530
+ if (route.options.validateSearch) {
1531
+ try {
1532
+ Object.assign(
1533
+ validatedSearch,
1534
+ validateSearch(route.options.validateSearch, {
1539
1535
  ...validatedSearch,
1540
1536
  ...nextSearch,
1541
- }) ?? {}),
1542
- }
1537
+ }),
1538
+ )
1539
+ } catch {
1540
+ // ignore errors here because they are already handled in matchRoutes
1543
1541
  }
1544
- } catch {
1545
- // ignore errors here because they are already handled in matchRoutes
1546
1542
  }
1547
1543
  })
1548
1544
  nextSearch = validatedSearch
@@ -1629,7 +1625,7 @@ export class RouterCore<
1629
1625
  if (foundMask) {
1630
1626
  const { from: _from, ...maskProps } = foundMask
1631
1627
  maskedDest = {
1632
- ...pick(opts, ['from']),
1628
+ from: opts.from,
1633
1629
  ...maskProps,
1634
1630
  params,
1635
1631
  }
@@ -1647,7 +1643,7 @@ export class RouterCore<
1647
1643
 
1648
1644
  if (opts.mask) {
1649
1645
  return buildWithMatches(opts, {
1650
- ...pick(opts, ['from']),
1646
+ from: opts.from,
1651
1647
  ...opts.mask,
1652
1648
  })
1653
1649
  }
@@ -1895,10 +1891,12 @@ export class RouterCore<
1895
1891
  }),
1896
1892
  })
1897
1893
 
1898
- await this.loadMatches({
1894
+ await loadMatches({
1895
+ router: this,
1899
1896
  sync: opts?.sync,
1900
1897
  matches: this.state.pendingMatches as Array<AnyRouteMatch>,
1901
1898
  location: next,
1899
+ updateMatch: this.updateMatch,
1902
1900
  // eslint-disable-next-line @typescript-eslint/require-await
1903
1901
  onReady: async () => {
1904
1902
  // eslint-disable-next-line @typescript-eslint/require-await
@@ -2088,873 +2086,6 @@ export class RouterCore<
2088
2086
  )
2089
2087
  }
2090
2088
 
2091
- private triggerOnReady = (
2092
- innerLoadContext: InnerLoadContext,
2093
- ): void | Promise<void> => {
2094
- if (!innerLoadContext.rendered) {
2095
- innerLoadContext.rendered = true
2096
- return innerLoadContext.onReady?.()
2097
- }
2098
- }
2099
-
2100
- private resolvePreload = (
2101
- innerLoadContext: InnerLoadContext,
2102
- matchId: string,
2103
- ): boolean => {
2104
- return !!(
2105
- innerLoadContext.preload &&
2106
- !this.state.matches.some((d) => d.id === matchId)
2107
- )
2108
- }
2109
-
2110
- private handleRedirectAndNotFound = (
2111
- innerLoadContext: InnerLoadContext,
2112
- match: AnyRouteMatch | undefined,
2113
- err: unknown,
2114
- ): void => {
2115
- if (!isRedirect(err) && !isNotFound(err)) return
2116
-
2117
- if (isRedirect(err) && err.redirectHandled && !err.options.reloadDocument) {
2118
- throw err
2119
- }
2120
-
2121
- // in case of a redirecting match during preload, the match does not exist
2122
- if (match) {
2123
- match._nonReactive.beforeLoadPromise?.resolve()
2124
- match._nonReactive.loaderPromise?.resolve()
2125
- match._nonReactive.beforeLoadPromise = undefined
2126
- match._nonReactive.loaderPromise = undefined
2127
-
2128
- const status = isRedirect(err) ? 'redirected' : 'notFound'
2129
-
2130
- innerLoadContext.updateMatch(match.id, (prev) => ({
2131
- ...prev,
2132
- status,
2133
- isFetching: false,
2134
- error: err,
2135
- }))
2136
-
2137
- if (isNotFound(err) && !err.routeId) {
2138
- err.routeId = match.routeId
2139
- }
2140
-
2141
- match._nonReactive.loadPromise?.resolve()
2142
- }
2143
-
2144
- if (isRedirect(err)) {
2145
- innerLoadContext.rendered = true
2146
- err.options._fromLocation = innerLoadContext.location
2147
- err.redirectHandled = true
2148
- err = this.resolveRedirect(err)
2149
- throw err
2150
- } else {
2151
- this._handleNotFound(innerLoadContext, err)
2152
- throw err
2153
- }
2154
- }
2155
-
2156
- private shouldSkipLoader = (matchId: string): boolean => {
2157
- const match = this.getMatch(matchId)!
2158
- // upon hydration, we skip the loader if the match has been dehydrated on the server
2159
- if (!this.isServer && match._nonReactive.dehydrated) {
2160
- return true
2161
- }
2162
-
2163
- if (this.isServer) {
2164
- if (match.ssr === false) {
2165
- return true
2166
- }
2167
- }
2168
- return false
2169
- }
2170
-
2171
- private handleSerialError = (
2172
- innerLoadContext: InnerLoadContext,
2173
- index: number,
2174
- err: any,
2175
- routerCode: string,
2176
- ): void => {
2177
- const { id: matchId, routeId } = innerLoadContext.matches[index]!
2178
- const route = this.looseRoutesById[routeId]!
2179
-
2180
- // Much like suspense, we use a promise here to know if
2181
- // we've been outdated by a new loadMatches call and
2182
- // should abort the current async operation
2183
- if (err instanceof Promise) {
2184
- throw err
2185
- }
2186
-
2187
- err.routerCode = routerCode
2188
- innerLoadContext.firstBadMatchIndex ??= index
2189
- this.handleRedirectAndNotFound(
2190
- innerLoadContext,
2191
- this.getMatch(matchId),
2192
- err,
2193
- )
2194
-
2195
- try {
2196
- route.options.onError?.(err)
2197
- } catch (errorHandlerErr) {
2198
- err = errorHandlerErr
2199
- this.handleRedirectAndNotFound(
2200
- innerLoadContext,
2201
- this.getMatch(matchId),
2202
- err,
2203
- )
2204
- }
2205
-
2206
- innerLoadContext.updateMatch(matchId, (prev) => {
2207
- prev._nonReactive.beforeLoadPromise?.resolve()
2208
- prev._nonReactive.beforeLoadPromise = undefined
2209
- prev._nonReactive.loadPromise?.resolve()
2210
-
2211
- return {
2212
- ...prev,
2213
- error: err,
2214
- status: 'error',
2215
- isFetching: false,
2216
- updatedAt: Date.now(),
2217
- abortController: new AbortController(),
2218
- }
2219
- })
2220
- }
2221
-
2222
- private isBeforeLoadSsr = (
2223
- innerLoadContext: InnerLoadContext,
2224
- matchId: string,
2225
- index: number,
2226
- route: AnyRoute,
2227
- ): void | Promise<void> => {
2228
- const existingMatch = this.getMatch(matchId)!
2229
- const parentMatchId = innerLoadContext.matches[index - 1]?.id
2230
- const parentMatch = parentMatchId
2231
- ? this.getMatch(parentMatchId)!
2232
- : undefined
2233
-
2234
- // in SPA mode, only SSR the root route
2235
- if (this.isShell()) {
2236
- existingMatch.ssr = matchId === rootRouteId
2237
- return
2238
- }
2239
-
2240
- if (parentMatch?.ssr === false) {
2241
- existingMatch.ssr = false
2242
- return
2243
- }
2244
-
2245
- const parentOverride = (tempSsr: boolean | 'data-only') => {
2246
- if (tempSsr === true && parentMatch?.ssr === 'data-only') {
2247
- return 'data-only'
2248
- }
2249
- return tempSsr
2250
- }
2251
-
2252
- const defaultSsr = this.options.defaultSsr ?? true
2253
-
2254
- if (route.options.ssr === undefined) {
2255
- existingMatch.ssr = parentOverride(defaultSsr)
2256
- return
2257
- }
2258
-
2259
- if (typeof route.options.ssr !== 'function') {
2260
- existingMatch.ssr = parentOverride(route.options.ssr)
2261
- return
2262
- }
2263
- const { search, params } = this.getMatch(matchId)!
2264
-
2265
- const ssrFnContext: SsrContextOptions<any, any, any> = {
2266
- search: makeMaybe(search, existingMatch.searchError),
2267
- params: makeMaybe(params, existingMatch.paramsError),
2268
- location: innerLoadContext.location,
2269
- matches: innerLoadContext.matches.map((match) => ({
2270
- index: match.index,
2271
- pathname: match.pathname,
2272
- fullPath: match.fullPath,
2273
- staticData: match.staticData,
2274
- id: match.id,
2275
- routeId: match.routeId,
2276
- search: makeMaybe(match.search, match.searchError),
2277
- params: makeMaybe(match.params, match.paramsError),
2278
- ssr: match.ssr,
2279
- })),
2280
- }
2281
-
2282
- const tempSsr = route.options.ssr(ssrFnContext)
2283
- if (isPromise(tempSsr)) {
2284
- return tempSsr.then((ssr) => {
2285
- existingMatch.ssr = parentOverride(ssr ?? defaultSsr)
2286
- })
2287
- }
2288
-
2289
- existingMatch.ssr = parentOverride(tempSsr ?? defaultSsr)
2290
- return
2291
- }
2292
-
2293
- private setupPendingTimeout = (
2294
- innerLoadContext: InnerLoadContext,
2295
- matchId: string,
2296
- route: AnyRoute,
2297
- ): void => {
2298
- const pendingMs = route.options.pendingMs ?? this.options.defaultPendingMs
2299
- const shouldPending = !!(
2300
- innerLoadContext.onReady &&
2301
- !this.isServer &&
2302
- !this.resolvePreload(innerLoadContext, matchId) &&
2303
- (route.options.loader ||
2304
- route.options.beforeLoad ||
2305
- routeNeedsPreload(route)) &&
2306
- typeof pendingMs === 'number' &&
2307
- pendingMs !== Infinity &&
2308
- (route.options.pendingComponent ??
2309
- (this.options as any)?.defaultPendingComponent)
2310
- )
2311
- const match = this.getMatch(matchId)!
2312
- if (shouldPending && match._nonReactive.pendingTimeout === undefined) {
2313
- const pendingTimeout = setTimeout(() => {
2314
- // Update the match and prematurely resolve the loadMatches promise so that
2315
- // the pending component can start rendering
2316
- this.triggerOnReady(innerLoadContext)
2317
- }, pendingMs)
2318
- match._nonReactive.pendingTimeout = pendingTimeout
2319
- }
2320
- }
2321
-
2322
- private shouldExecuteBeforeLoad = (
2323
- innerLoadContext: InnerLoadContext,
2324
- matchId: string,
2325
- route: AnyRoute,
2326
- ): boolean | Promise<boolean> => {
2327
- const existingMatch = this.getMatch(matchId)!
2328
-
2329
- // If we are in the middle of a load, either of these will be present
2330
- // (not to be confused with `loadPromise`, which is always defined)
2331
- if (
2332
- !existingMatch._nonReactive.beforeLoadPromise &&
2333
- !existingMatch._nonReactive.loaderPromise
2334
- )
2335
- return true
2336
-
2337
- this.setupPendingTimeout(innerLoadContext, matchId, route)
2338
-
2339
- const then = () => {
2340
- let shouldExecuteBeforeLoad = true
2341
- const match = this.getMatch(matchId)!
2342
- if (match.status === 'error') {
2343
- shouldExecuteBeforeLoad = true
2344
- } else if (
2345
- match.preload &&
2346
- (match.status === 'redirected' || match.status === 'notFound')
2347
- ) {
2348
- this.handleRedirectAndNotFound(innerLoadContext, match, match.error)
2349
- }
2350
- return shouldExecuteBeforeLoad
2351
- }
2352
-
2353
- // Wait for the beforeLoad to resolve before we continue
2354
- return existingMatch._nonReactive.beforeLoadPromise
2355
- ? existingMatch._nonReactive.beforeLoadPromise.then(then)
2356
- : then()
2357
- }
2358
-
2359
- private executeBeforeLoad = (
2360
- innerLoadContext: InnerLoadContext,
2361
- matchId: string,
2362
- index: number,
2363
- route: AnyRoute,
2364
- ): void | Promise<void> => {
2365
- const match = this.getMatch(matchId)!
2366
-
2367
- match._nonReactive.beforeLoadPromise = createControlledPromise<void>()
2368
- // explicitly capture the previous loadPromise
2369
- const prevLoadPromise = match._nonReactive.loadPromise
2370
- match._nonReactive.loadPromise = createControlledPromise<void>(() => {
2371
- prevLoadPromise?.resolve()
2372
- })
2373
-
2374
- const { paramsError, searchError } = match
2375
-
2376
- if (paramsError) {
2377
- this.handleSerialError(
2378
- innerLoadContext,
2379
- index,
2380
- paramsError,
2381
- 'PARSE_PARAMS',
2382
- )
2383
- }
2384
-
2385
- if (searchError) {
2386
- this.handleSerialError(
2387
- innerLoadContext,
2388
- index,
2389
- searchError,
2390
- 'VALIDATE_SEARCH',
2391
- )
2392
- }
2393
-
2394
- this.setupPendingTimeout(innerLoadContext, matchId, route)
2395
-
2396
- const abortController = new AbortController()
2397
-
2398
- const parentMatchId = innerLoadContext.matches[index - 1]?.id
2399
- const parentMatch = parentMatchId
2400
- ? this.getMatch(parentMatchId)!
2401
- : undefined
2402
- const parentMatchContext =
2403
- parentMatch?.context ?? this.options.context ?? undefined
2404
-
2405
- const context = { ...parentMatchContext, ...match.__routeContext }
2406
-
2407
- let isPending = false
2408
- const pending = () => {
2409
- if (isPending) return
2410
- isPending = true
2411
- innerLoadContext.updateMatch(matchId, (prev) => ({
2412
- ...prev,
2413
- isFetching: 'beforeLoad',
2414
- fetchCount: prev.fetchCount + 1,
2415
- abortController,
2416
- context,
2417
- }))
2418
- }
2419
-
2420
- const resolve = () => {
2421
- match._nonReactive.beforeLoadPromise?.resolve()
2422
- match._nonReactive.beforeLoadPromise = undefined
2423
- innerLoadContext.updateMatch(matchId, (prev) => ({
2424
- ...prev,
2425
- isFetching: false,
2426
- }))
2427
- }
2428
-
2429
- // if there is no `beforeLoad` option, skip everything, batch update the store, return early
2430
- if (!route.options.beforeLoad) {
2431
- batch(() => {
2432
- pending()
2433
- resolve()
2434
- })
2435
- return
2436
- }
2437
-
2438
- const { search, params, cause } = match
2439
- const preload = this.resolvePreload(innerLoadContext, matchId)
2440
- const beforeLoadFnContext: BeforeLoadContextOptions<
2441
- any,
2442
- any,
2443
- any,
2444
- any,
2445
- any
2446
- > = {
2447
- search,
2448
- abortController,
2449
- params,
2450
- preload,
2451
- context,
2452
- location: innerLoadContext.location,
2453
- navigate: (opts: any) =>
2454
- this.navigate({ ...opts, _fromLocation: innerLoadContext.location }),
2455
- buildLocation: this.buildLocation,
2456
- cause: preload ? 'preload' : cause,
2457
- matches: innerLoadContext.matches,
2458
- }
2459
-
2460
- const updateContext = (beforeLoadContext: any) => {
2461
- if (beforeLoadContext === undefined) {
2462
- batch(() => {
2463
- pending()
2464
- resolve()
2465
- })
2466
- return
2467
- }
2468
- if (isRedirect(beforeLoadContext) || isNotFound(beforeLoadContext)) {
2469
- pending()
2470
- this.handleSerialError(
2471
- innerLoadContext,
2472
- index,
2473
- beforeLoadContext,
2474
- 'BEFORE_LOAD',
2475
- )
2476
- }
2477
-
2478
- batch(() => {
2479
- pending()
2480
- innerLoadContext.updateMatch(matchId, (prev) => ({
2481
- ...prev,
2482
- __beforeLoadContext: beforeLoadContext,
2483
- context: {
2484
- ...prev.context,
2485
- ...beforeLoadContext,
2486
- },
2487
- }))
2488
- resolve()
2489
- })
2490
- }
2491
-
2492
- let beforeLoadContext
2493
- try {
2494
- beforeLoadContext = route.options.beforeLoad(beforeLoadFnContext)
2495
- if (isPromise(beforeLoadContext)) {
2496
- pending()
2497
- return beforeLoadContext
2498
- .catch((err) => {
2499
- this.handleSerialError(innerLoadContext, index, err, 'BEFORE_LOAD')
2500
- })
2501
- .then(updateContext)
2502
- }
2503
- } catch (err) {
2504
- pending()
2505
- this.handleSerialError(innerLoadContext, index, err, 'BEFORE_LOAD')
2506
- }
2507
-
2508
- updateContext(beforeLoadContext)
2509
- return
2510
- }
2511
-
2512
- private handleBeforeLoad = (
2513
- innerLoadContext: InnerLoadContext,
2514
- index: number,
2515
- ): void | Promise<void> => {
2516
- const { id: matchId, routeId } = innerLoadContext.matches[index]!
2517
- const route = this.looseRoutesById[routeId]!
2518
-
2519
- const serverSsr = () => {
2520
- // on the server, determine whether SSR the current match or not
2521
- if (this.isServer) {
2522
- const maybePromise = this.isBeforeLoadSsr(
2523
- innerLoadContext,
2524
- matchId,
2525
- index,
2526
- route,
2527
- )
2528
- if (isPromise(maybePromise)) return maybePromise.then(queueExecution)
2529
- }
2530
- return queueExecution()
2531
- }
2532
-
2533
- const queueExecution = () => {
2534
- if (this.shouldSkipLoader(matchId)) return
2535
- const shouldExecuteBeforeLoadResult = this.shouldExecuteBeforeLoad(
2536
- innerLoadContext,
2537
- matchId,
2538
- route,
2539
- )
2540
- return isPromise(shouldExecuteBeforeLoadResult)
2541
- ? shouldExecuteBeforeLoadResult.then(execute)
2542
- : execute(shouldExecuteBeforeLoadResult)
2543
- }
2544
-
2545
- const execute = (shouldExecuteBeforeLoad: boolean) => {
2546
- if (shouldExecuteBeforeLoad) {
2547
- // If we are not in the middle of a load OR the previous load failed, start it
2548
- return this.executeBeforeLoad(innerLoadContext, matchId, index, route)
2549
- }
2550
- return
2551
- }
2552
-
2553
- return serverSsr()
2554
- }
2555
-
2556
- private executeHead = (
2557
- innerLoadContext: InnerLoadContext,
2558
- matchId: string,
2559
- route: AnyRoute,
2560
- ): void | Promise<
2561
- Pick<
2562
- AnyRouteMatch,
2563
- 'meta' | 'links' | 'headScripts' | 'headers' | 'scripts' | 'styles'
2564
- >
2565
- > => {
2566
- const match = this.getMatch(matchId)
2567
- // in case of a redirecting match during preload, the match does not exist
2568
- if (!match) {
2569
- return
2570
- }
2571
- if (
2572
- !route.options.head &&
2573
- !route.options.scripts &&
2574
- !route.options.headers
2575
- ) {
2576
- return
2577
- }
2578
- const assetContext = {
2579
- matches: innerLoadContext.matches,
2580
- match,
2581
- params: match.params,
2582
- loaderData: match.loaderData,
2583
- }
2584
-
2585
- return Promise.all([
2586
- route.options.head?.(assetContext),
2587
- route.options.scripts?.(assetContext),
2588
- route.options.headers?.(assetContext),
2589
- ]).then(([headFnContent, scripts, headers]) => {
2590
- const meta = headFnContent?.meta
2591
- const links = headFnContent?.links
2592
- const headScripts = headFnContent?.scripts
2593
- const styles = headFnContent?.styles
2594
-
2595
- return {
2596
- meta,
2597
- links,
2598
- headScripts,
2599
- headers,
2600
- scripts,
2601
- styles,
2602
- }
2603
- })
2604
- }
2605
-
2606
- private potentialPendingMinPromise = (
2607
- matchId: string,
2608
- ): void | ControlledPromise<void> => {
2609
- const latestMatch = this.getMatch(matchId)!
2610
- return latestMatch._nonReactive.minPendingPromise
2611
- }
2612
-
2613
- private getLoaderContext = (
2614
- innerLoadContext: InnerLoadContext,
2615
- matchId: string,
2616
- index: number,
2617
- route: AnyRoute,
2618
- ): LoaderFnContext => {
2619
- const parentMatchPromise = innerLoadContext.matchPromises[index - 1] as any
2620
- const { params, loaderDeps, abortController, context, cause } =
2621
- this.getMatch(matchId)!
2622
-
2623
- const preload = this.resolvePreload(innerLoadContext, matchId)
2624
-
2625
- return {
2626
- params,
2627
- deps: loaderDeps,
2628
- preload: !!preload,
2629
- parentMatchPromise,
2630
- abortController: abortController,
2631
- context,
2632
- location: innerLoadContext.location,
2633
- navigate: (opts) =>
2634
- this.navigate({ ...opts, _fromLocation: innerLoadContext.location }),
2635
- cause: preload ? 'preload' : cause,
2636
- route,
2637
- }
2638
- }
2639
-
2640
- private runLoader = async (
2641
- innerLoadContext: InnerLoadContext,
2642
- matchId: string,
2643
- index: number,
2644
- route: AnyRoute,
2645
- ): Promise<void> => {
2646
- try {
2647
- // If the Matches component rendered
2648
- // the pending component and needs to show it for
2649
- // a minimum duration, we''ll wait for it to resolve
2650
- // before committing to the match and resolving
2651
- // the loadPromise
2652
-
2653
- // Actually run the loader and handle the result
2654
- try {
2655
- if (!this.isServer || this.getMatch(matchId)!.ssr === true) {
2656
- this.loadRouteChunk(route)
2657
- }
2658
-
2659
- // Kick off the loader!
2660
- const loaderResult = route.options.loader?.(
2661
- this.getLoaderContext(innerLoadContext, matchId, index, route),
2662
- )
2663
- const loaderResultIsPromise =
2664
- route.options.loader && isPromise(loaderResult)
2665
-
2666
- const willLoadSomething = !!(
2667
- loaderResultIsPromise ||
2668
- route._lazyPromise ||
2669
- route._componentsPromise ||
2670
- route.options.head ||
2671
- route.options.scripts ||
2672
- route.options.headers ||
2673
- this.getMatch(matchId)!._nonReactive.minPendingPromise
2674
- )
2675
-
2676
- if (willLoadSomething) {
2677
- innerLoadContext.updateMatch(matchId, (prev) => ({
2678
- ...prev,
2679
- isFetching: 'loader',
2680
- }))
2681
- }
2682
-
2683
- if (route.options.loader) {
2684
- const loaderData = loaderResultIsPromise
2685
- ? await loaderResult
2686
- : loaderResult
2687
-
2688
- this.handleRedirectAndNotFound(
2689
- innerLoadContext,
2690
- this.getMatch(matchId),
2691
- loaderData,
2692
- )
2693
- if (loaderData !== undefined) {
2694
- innerLoadContext.updateMatch(matchId, (prev) => ({
2695
- ...prev,
2696
- loaderData,
2697
- }))
2698
- }
2699
- }
2700
-
2701
- // Lazy option can modify the route options,
2702
- // so we need to wait for it to resolve before
2703
- // we can use the options
2704
- if (route._lazyPromise) await route._lazyPromise
2705
- const headResult = this.executeHead(innerLoadContext, matchId, route)
2706
- const head = headResult ? await headResult : undefined
2707
- const pendingPromise = this.potentialPendingMinPromise(matchId)
2708
- if (pendingPromise) await pendingPromise
2709
-
2710
- // Last but not least, wait for the the components
2711
- // to be preloaded before we resolve the match
2712
- if (route._componentsPromise) await route._componentsPromise
2713
- innerLoadContext.updateMatch(matchId, (prev) => ({
2714
- ...prev,
2715
- error: undefined,
2716
- status: 'success',
2717
- isFetching: false,
2718
- updatedAt: Date.now(),
2719
- ...head,
2720
- }))
2721
- } catch (e) {
2722
- let error = e
2723
-
2724
- const pendingPromise = this.potentialPendingMinPromise(matchId)
2725
- if (pendingPromise) await pendingPromise
2726
-
2727
- this.handleRedirectAndNotFound(
2728
- innerLoadContext,
2729
- this.getMatch(matchId),
2730
- e,
2731
- )
2732
-
2733
- try {
2734
- route.options.onError?.(e)
2735
- } catch (onErrorError) {
2736
- error = onErrorError
2737
- this.handleRedirectAndNotFound(
2738
- innerLoadContext,
2739
- this.getMatch(matchId),
2740
- onErrorError,
2741
- )
2742
- }
2743
- const headResult = this.executeHead(innerLoadContext, matchId, route)
2744
- const head = headResult ? await headResult : undefined
2745
- innerLoadContext.updateMatch(matchId, (prev) => ({
2746
- ...prev,
2747
- error,
2748
- status: 'error',
2749
- isFetching: false,
2750
- ...head,
2751
- }))
2752
- }
2753
- } catch (err) {
2754
- const match = this.getMatch(matchId)
2755
- // in case of a redirecting match during preload, the match does not exist
2756
- if (match) {
2757
- const headResult = this.executeHead(innerLoadContext, matchId, route)
2758
- if (headResult) {
2759
- const head = await headResult
2760
- innerLoadContext.updateMatch(matchId, (prev) => ({
2761
- ...prev,
2762
- ...head,
2763
- }))
2764
- }
2765
- match._nonReactive.loaderPromise = undefined
2766
- }
2767
- this.handleRedirectAndNotFound(innerLoadContext, match, err)
2768
- }
2769
- }
2770
-
2771
- private loadRouteMatch = async (
2772
- innerLoadContext: InnerLoadContext,
2773
- index: number,
2774
- ): Promise<AnyRouteMatch> => {
2775
- const { id: matchId, routeId } = innerLoadContext.matches[index]!
2776
- let loaderShouldRunAsync = false
2777
- let loaderIsRunningAsync = false
2778
- const route = this.looseRoutesById[routeId]!
2779
-
2780
- const prevMatch = this.getMatch(matchId)!
2781
- if (this.shouldSkipLoader(matchId)) {
2782
- if (this.isServer) {
2783
- const headResult = this.executeHead(innerLoadContext, matchId, route)
2784
- if (headResult) {
2785
- const head = await headResult
2786
- innerLoadContext.updateMatch(matchId, (prev) => ({
2787
- ...prev,
2788
- ...head,
2789
- }))
2790
- }
2791
- return this.getMatch(matchId)!
2792
- }
2793
- }
2794
- // there is a loaderPromise, so we are in the middle of a load
2795
- else if (prevMatch._nonReactive.loaderPromise) {
2796
- // do not block if we already have stale data we can show
2797
- // but only if the ongoing load is not a preload since error handling is different for preloads
2798
- // and we don't want to swallow errors
2799
- if (
2800
- prevMatch.status === 'success' &&
2801
- !innerLoadContext.sync &&
2802
- !prevMatch.preload
2803
- ) {
2804
- return this.getMatch(matchId)!
2805
- }
2806
- await prevMatch._nonReactive.loaderPromise
2807
- const match = this.getMatch(matchId)!
2808
- if (match.error) {
2809
- this.handleRedirectAndNotFound(innerLoadContext, match, match.error)
2810
- }
2811
- } else {
2812
- // This is where all of the stale-while-revalidate magic happens
2813
- const age = Date.now() - this.getMatch(matchId)!.updatedAt
2814
-
2815
- const preload = this.resolvePreload(innerLoadContext, matchId)
2816
-
2817
- const staleAge = preload
2818
- ? (route.options.preloadStaleTime ??
2819
- this.options.defaultPreloadStaleTime ??
2820
- 30_000) // 30 seconds for preloads by default
2821
- : (route.options.staleTime ?? this.options.defaultStaleTime ?? 0)
2822
-
2823
- const shouldReloadOption = route.options.shouldReload
2824
-
2825
- // Default to reloading the route all the time
2826
- // Allow shouldReload to get the last say,
2827
- // if provided.
2828
- const shouldReload =
2829
- typeof shouldReloadOption === 'function'
2830
- ? shouldReloadOption(
2831
- this.getLoaderContext(innerLoadContext, matchId, index, route),
2832
- )
2833
- : shouldReloadOption
2834
-
2835
- const nextPreload =
2836
- !!preload && !this.state.matches.some((d) => d.id === matchId)
2837
- const match = this.getMatch(matchId)!
2838
- match._nonReactive.loaderPromise = createControlledPromise<void>()
2839
- if (nextPreload !== match.preload) {
2840
- innerLoadContext.updateMatch(matchId, (prev) => ({
2841
- ...prev,
2842
- preload: nextPreload,
2843
- }))
2844
- }
2845
-
2846
- // If the route is successful and still fresh, just resolve
2847
- const { status, invalid } = this.getMatch(matchId)!
2848
- loaderShouldRunAsync =
2849
- status === 'success' && (invalid || (shouldReload ?? age > staleAge))
2850
- if (preload && route.options.preload === false) {
2851
- // Do nothing
2852
- } else if (loaderShouldRunAsync && !innerLoadContext.sync) {
2853
- loaderIsRunningAsync = true
2854
- ;(async () => {
2855
- try {
2856
- await this.runLoader(innerLoadContext, matchId, index, route)
2857
- const match = this.getMatch(matchId)!
2858
- match._nonReactive.loaderPromise?.resolve()
2859
- match._nonReactive.loadPromise?.resolve()
2860
- match._nonReactive.loaderPromise = undefined
2861
- } catch (err) {
2862
- if (isRedirect(err)) {
2863
- await this.navigate(err.options)
2864
- }
2865
- }
2866
- })()
2867
- } else if (
2868
- status !== 'success' ||
2869
- (loaderShouldRunAsync && innerLoadContext.sync)
2870
- ) {
2871
- await this.runLoader(innerLoadContext, matchId, index, route)
2872
- } else {
2873
- // if the loader did not run, still update head.
2874
- // reason: parent's beforeLoad may have changed the route context
2875
- // and only now do we know the route context (and that the loader would not run)
2876
- const headResult = this.executeHead(innerLoadContext, matchId, route)
2877
- if (headResult) {
2878
- const head = await headResult
2879
- innerLoadContext.updateMatch(matchId, (prev) => ({
2880
- ...prev,
2881
- ...head,
2882
- }))
2883
- }
2884
- }
2885
- }
2886
- const match = this.getMatch(matchId)!
2887
- if (!loaderIsRunningAsync) {
2888
- match._nonReactive.loaderPromise?.resolve()
2889
- match._nonReactive.loadPromise?.resolve()
2890
- }
2891
-
2892
- clearTimeout(match._nonReactive.pendingTimeout)
2893
- match._nonReactive.pendingTimeout = undefined
2894
- if (!loaderIsRunningAsync) match._nonReactive.loaderPromise = undefined
2895
- match._nonReactive.dehydrated = undefined
2896
- const nextIsFetching = loaderIsRunningAsync ? match.isFetching : false
2897
- if (nextIsFetching !== match.isFetching || match.invalid !== false) {
2898
- innerLoadContext.updateMatch(matchId, (prev) => ({
2899
- ...prev,
2900
- isFetching: nextIsFetching,
2901
- invalid: false,
2902
- }))
2903
- }
2904
- return this.getMatch(matchId)!
2905
- }
2906
-
2907
- loadMatches = async (baseContext: {
2908
- location: ParsedLocation
2909
- matches: Array<AnyRouteMatch>
2910
- preload?: boolean
2911
- onReady?: () => Promise<void>
2912
- updateMatch?: UpdateMatchFn
2913
- sync?: boolean
2914
- }): Promise<Array<MakeRouteMatch>> => {
2915
- const innerLoadContext = baseContext as InnerLoadContext
2916
- innerLoadContext.updateMatch ??= this.updateMatch
2917
- innerLoadContext.matchPromises = []
2918
-
2919
- // make sure the pending component is immediately rendered when hydrating a match that is not SSRed
2920
- // the pending component was already rendered on the server and we want to keep it shown on the client until minPendingMs is reached
2921
- if (!this.isServer && this.state.matches.some((d) => d._forcePending)) {
2922
- this.triggerOnReady(innerLoadContext)
2923
- }
2924
-
2925
- try {
2926
- // Execute all beforeLoads one by one
2927
- for (let i = 0; i < innerLoadContext.matches.length; i++) {
2928
- const beforeLoad = this.handleBeforeLoad(innerLoadContext, i)
2929
- if (isPromise(beforeLoad)) await beforeLoad
2930
- }
2931
-
2932
- // Execute all loaders in parallel
2933
- const max =
2934
- innerLoadContext.firstBadMatchIndex ?? innerLoadContext.matches.length
2935
- for (let i = 0; i < max; i++) {
2936
- innerLoadContext.matchPromises.push(
2937
- this.loadRouteMatch(innerLoadContext, i),
2938
- )
2939
- }
2940
- await Promise.all(innerLoadContext.matchPromises)
2941
-
2942
- const readyPromise = this.triggerOnReady(innerLoadContext)
2943
- if (isPromise(readyPromise)) await readyPromise
2944
- } catch (err) {
2945
- if (isNotFound(err) && !innerLoadContext.preload) {
2946
- const readyPromise = this.triggerOnReady(innerLoadContext)
2947
- if (isPromise(readyPromise)) await readyPromise
2948
- throw err
2949
- }
2950
- if (isRedirect(err)) {
2951
- throw err
2952
- }
2953
- }
2954
-
2955
- return innerLoadContext.matches
2956
- }
2957
-
2958
2089
  invalidate: InvalidateFn<
2959
2090
  RouterCore<
2960
2091
  TRouteTree,
@@ -3048,47 +2179,7 @@ export class RouterCore<
3048
2179
  this.clearCache({ filter })
3049
2180
  }
3050
2181
 
3051
- loadRouteChunk = (route: AnyRoute) => {
3052
- if (!route._lazyLoaded && route._lazyPromise === undefined) {
3053
- if (route.lazyFn) {
3054
- route._lazyPromise = route.lazyFn().then((lazyRoute) => {
3055
- // explicitly don't copy over the lazy route's id
3056
- const { id: _id, ...options } = lazyRoute.options
3057
- Object.assign(route.options, options)
3058
- route._lazyLoaded = true
3059
- route._lazyPromise = undefined // gc promise, we won't need it anymore
3060
- })
3061
- } else {
3062
- route._lazyLoaded = true
3063
- }
3064
- }
3065
-
3066
- // If for some reason lazy resolves more lazy components...
3067
- // We'll wait for that before we attempt to preload the
3068
- // components themselves.
3069
- if (!route._componentsLoaded && route._componentsPromise === undefined) {
3070
- const loadComponents = () => {
3071
- const preloads = []
3072
- for (const type of componentTypes) {
3073
- const preload = (route.options[type] as any)?.preload
3074
- if (preload) preloads.push(preload())
3075
- }
3076
- if (preloads.length)
3077
- return Promise.all(preloads).then(() => {
3078
- route._componentsLoaded = true
3079
- route._componentsPromise = undefined // gc promise, we won't need it anymore
3080
- })
3081
- route._componentsLoaded = true
3082
- route._componentsPromise = undefined // gc promise, we won't need it anymore
3083
- return
3084
- }
3085
- route._componentsPromise = route._lazyPromise
3086
- ? route._lazyPromise.then(loadComponents)
3087
- : loadComponents()
3088
- }
3089
-
3090
- return route._componentsPromise
3091
- }
2182
+ loadRouteChunk = loadRouteChunk
3092
2183
 
3093
2184
  preloadRoute: PreloadRouteFn<
3094
2185
  TRouteTree,
@@ -3128,7 +2219,8 @@ export class RouterCore<
3128
2219
  })
3129
2220
 
3130
2221
  try {
3131
- matches = await this.loadMatches({
2222
+ matches = await loadMatches({
2223
+ router: this,
3132
2224
  matches,
3133
2225
  location: next,
3134
2226
  preload: true,
@@ -3226,58 +2318,6 @@ export class RouterCore<
3226
2318
 
3227
2319
  serverSsr?: ServerSsr
3228
2320
 
3229
- private _handleNotFound = (
3230
- innerLoadContext: InnerLoadContext,
3231
- err: NotFoundError,
3232
- ) => {
3233
- // Find the route that should handle the not found error
3234
- // First check if a specific route is requested to show the error
3235
- const routeCursor = this.routesById[err.routeId ?? ''] ?? this.routeTree
3236
- const matchesByRouteId: Record<string, AnyRouteMatch> = {}
3237
-
3238
- // Setup routesByRouteId object for quick access
3239
- for (const match of innerLoadContext.matches) {
3240
- matchesByRouteId[match.routeId] = match
3241
- }
3242
-
3243
- // Ensure a NotFoundComponent exists on the route
3244
- if (
3245
- !routeCursor.options.notFoundComponent &&
3246
- (this.options as any)?.defaultNotFoundComponent
3247
- ) {
3248
- routeCursor.options.notFoundComponent = (
3249
- this.options as any
3250
- ).defaultNotFoundComponent
3251
- }
3252
-
3253
- // Ensure we have a notFoundComponent
3254
- invariant(
3255
- routeCursor.options.notFoundComponent,
3256
- 'No notFoundComponent found. Please set a notFoundComponent on your route or provide a defaultNotFoundComponent to the router.',
3257
- )
3258
-
3259
- // Find the match for this route
3260
- const matchForRoute = matchesByRouteId[routeCursor.id]
3261
-
3262
- invariant(
3263
- matchForRoute,
3264
- 'Could not find match for route: ' + routeCursor.id,
3265
- )
3266
-
3267
- // Assign the error to the match - using non-null assertion since we've checked with invariant
3268
- innerLoadContext.updateMatch(matchForRoute.id, (prev) => ({
3269
- ...prev,
3270
- status: 'notFound',
3271
- error: err,
3272
- isFetching: false,
3273
- }))
3274
-
3275
- if ((err as any).routerCode === 'BEFORE_LOAD' && routeCursor.parentRoute) {
3276
- err.routeId = routeCursor.parentRoute.id
3277
- this._handleNotFound(innerLoadContext, err)
3278
- }
3279
- }
3280
-
3281
2321
  hasNotFoundMatch = () => {
3282
2322
  return this.__store.state.matches.some(
3283
2323
  (d) => d.status === 'notFound' || d.globalNotFound,
@@ -3289,16 +2329,6 @@ export class SearchParamError extends Error {}
3289
2329
 
3290
2330
  export class PathParamError extends Error {}
3291
2331
 
3292
- function makeMaybe<TValue, TError>(
3293
- value: TValue,
3294
- error: TError,
3295
- ): { status: 'success'; value: TValue } | { status: 'error'; error: TError } {
3296
- if (error) {
3297
- return { status: 'error' as const, error }
3298
- }
3299
- return { status: 'success' as const, value }
3300
- }
3301
-
3302
2332
  const normalize = (str: string) =>
3303
2333
  str.endsWith('/') && str.length > 1 ? str.slice(0, -1) : str
3304
2334
  function comparePaths(a: string, b: string) {
@@ -3365,22 +2395,6 @@ function validateSearch(validateSearch: AnyValidator, input: unknown): unknown {
3365
2395
  return {}
3366
2396
  }
3367
2397
 
3368
- export const componentTypes = [
3369
- 'component',
3370
- 'errorComponent',
3371
- 'pendingComponent',
3372
- 'notFoundComponent',
3373
- ] as const
3374
-
3375
- function routeNeedsPreload(route: AnyRoute) {
3376
- for (const componentType of componentTypes) {
3377
- if ((route.options[componentType] as any)?.preload) {
3378
- return true
3379
- }
3380
- }
3381
- return false
3382
- }
3383
-
3384
2398
  interface RouteLike {
3385
2399
  id: string
3386
2400
  isRoot?: boolean