@tanstack/router-core 1.132.0-alpha.2 → 1.132.0-alpha.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 (102) 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 +18 -39
  15. package/dist/cjs/scroll-restoration.cjs +32 -29
  16. package/dist/cjs/scroll-restoration.cjs.map +1 -1
  17. package/dist/cjs/scroll-restoration.d.cts +1 -1
  18. package/dist/cjs/searchParams.cjs +7 -15
  19. package/dist/cjs/searchParams.cjs.map +1 -1
  20. package/dist/cjs/ssr/constants.cjs +5 -0
  21. package/dist/cjs/ssr/constants.cjs.map +1 -0
  22. package/dist/cjs/ssr/constants.d.cts +1 -0
  23. package/dist/cjs/ssr/{seroval-plugins.cjs → serializer/ShallowErrorPlugin.cjs} +2 -2
  24. package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs.map +1 -0
  25. package/dist/cjs/ssr/{seroval-plugins.d.cts → serializer/ShallowErrorPlugin.d.cts} +1 -2
  26. package/dist/cjs/ssr/serializer/seroval-plugins.cjs +11 -0
  27. package/dist/cjs/ssr/serializer/seroval-plugins.cjs.map +1 -0
  28. package/dist/cjs/ssr/serializer/seroval-plugins.d.cts +2 -0
  29. package/dist/cjs/ssr/serializer/transformer.cjs +50 -0
  30. package/dist/cjs/ssr/serializer/transformer.cjs.map +1 -0
  31. package/dist/cjs/ssr/serializer/transformer.d.cts +18 -0
  32. package/dist/cjs/ssr/ssr-client.cjs +15 -1
  33. package/dist/cjs/ssr/ssr-client.cjs.map +1 -1
  34. package/dist/cjs/ssr/ssr-client.d.cts +5 -1
  35. package/dist/cjs/ssr/ssr-server.cjs +12 -10
  36. package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
  37. package/dist/cjs/ssr/ssr-server.d.cts +0 -1
  38. package/dist/cjs/ssr/tsrScript.cjs +1 -1
  39. package/dist/cjs/ssr/tsrScript.cjs.map +1 -1
  40. package/dist/cjs/utils.cjs +8 -7
  41. package/dist/cjs/utils.cjs.map +1 -1
  42. package/dist/cjs/utils.d.cts +1 -1
  43. package/dist/esm/index.d.ts +6 -2
  44. package/dist/esm/index.js +9 -3
  45. package/dist/esm/index.js.map +1 -1
  46. package/dist/esm/load-matches.d.ts +16 -0
  47. package/dist/esm/load-matches.js +636 -0
  48. package/dist/esm/load-matches.js.map +1 -0
  49. package/dist/esm/qss.d.ts +6 -4
  50. package/dist/esm/qss.js +19 -19
  51. package/dist/esm/qss.js.map +1 -1
  52. package/dist/esm/redirect.js +3 -3
  53. package/dist/esm/redirect.js.map +1 -1
  54. package/dist/esm/router.d.ts +18 -39
  55. package/dist/esm/router.js +33 -702
  56. package/dist/esm/router.js.map +1 -1
  57. package/dist/esm/scroll-restoration.d.ts +1 -1
  58. package/dist/esm/scroll-restoration.js +32 -29
  59. package/dist/esm/scroll-restoration.js.map +1 -1
  60. package/dist/esm/searchParams.js +7 -15
  61. package/dist/esm/searchParams.js.map +1 -1
  62. package/dist/esm/ssr/constants.d.ts +1 -0
  63. package/dist/esm/ssr/constants.js +5 -0
  64. package/dist/esm/ssr/constants.js.map +1 -0
  65. package/dist/esm/ssr/{seroval-plugins.d.ts → serializer/ShallowErrorPlugin.d.ts} +1 -2
  66. package/dist/esm/ssr/{seroval-plugins.js → serializer/ShallowErrorPlugin.js} +2 -2
  67. package/dist/esm/ssr/serializer/ShallowErrorPlugin.js.map +1 -0
  68. package/dist/esm/ssr/serializer/seroval-plugins.d.ts +2 -0
  69. package/dist/esm/ssr/serializer/seroval-plugins.js +11 -0
  70. package/dist/esm/ssr/serializer/seroval-plugins.js.map +1 -0
  71. package/dist/esm/ssr/serializer/transformer.d.ts +18 -0
  72. package/dist/esm/ssr/serializer/transformer.js +50 -0
  73. package/dist/esm/ssr/serializer/transformer.js.map +1 -0
  74. package/dist/esm/ssr/ssr-client.d.ts +5 -1
  75. package/dist/esm/ssr/ssr-client.js +15 -1
  76. package/dist/esm/ssr/ssr-client.js.map +1 -1
  77. package/dist/esm/ssr/ssr-server.d.ts +0 -1
  78. package/dist/esm/ssr/ssr-server.js +12 -10
  79. package/dist/esm/ssr/ssr-server.js.map +1 -1
  80. package/dist/esm/ssr/tsrScript.js +1 -1
  81. package/dist/esm/ssr/tsrScript.js.map +1 -1
  82. package/dist/esm/utils.d.ts +1 -1
  83. package/dist/esm/utils.js +8 -7
  84. package/dist/esm/utils.js.map +1 -1
  85. package/package.json +1 -1
  86. package/src/index.ts +12 -2
  87. package/src/load-matches.ts +955 -0
  88. package/src/qss.ts +27 -24
  89. package/src/redirect.ts +3 -3
  90. package/src/router.ts +66 -1050
  91. package/src/scroll-restoration.ts +42 -37
  92. package/src/searchParams.ts +8 -19
  93. package/src/ssr/constants.ts +1 -0
  94. package/src/ssr/{seroval-plugins.ts → serializer/ShallowErrorPlugin.ts} +2 -2
  95. package/src/ssr/serializer/seroval-plugins.ts +9 -0
  96. package/src/ssr/serializer/transformer.ts +78 -0
  97. package/src/ssr/ssr-client.ts +30 -3
  98. package/src/ssr/ssr-server.ts +18 -10
  99. package/src/ssr/tsrScript.ts +5 -1
  100. package/src/utils.ts +11 -10
  101. package/dist/cjs/ssr/seroval-plugins.cjs.map +0 -1
  102. 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.
@@ -393,7 +391,9 @@ export interface RouterOptions<
393
391
  *
394
392
  * @default false
395
393
  */
396
- scrollRestoration?: boolean
394
+ scrollRestoration?:
395
+ | boolean
396
+ | ((opts: { location: ParsedLocation }) => boolean)
397
397
 
398
398
  /**
399
399
  * A function that will be called to get the key for the scroll restoration cache.
@@ -424,6 +424,8 @@ export interface RouterOptions<
424
424
  * @default false
425
425
  */
426
426
  disableGlobalCatchBoundary?: boolean
427
+
428
+ serializationAdapters?: TTransformerConfig
427
429
  }
428
430
 
429
431
  export interface RouterState<
@@ -532,13 +534,15 @@ export type RouterConstructorOptions<
532
534
  TDefaultStructuralSharingOption extends boolean,
533
535
  TRouterHistory extends RouterHistory,
534
536
  TDehydrated extends Record<string, any>,
537
+ TTransformerConfig,
535
538
  > = Omit<
536
539
  RouterOptions<
537
540
  TRouteTree,
538
541
  TTrailingSlashOption,
539
542
  TDefaultStructuralSharingOption,
540
543
  TRouterHistory,
541
- TDehydrated
544
+ TDehydrated,
545
+ TTransformerConfig
542
546
  >,
543
547
  'context'
544
548
  > &
@@ -598,13 +602,15 @@ export type UpdateFn<
598
602
  TDefaultStructuralSharingOption extends boolean,
599
603
  TRouterHistory extends RouterHistory,
600
604
  TDehydrated extends Record<string, any>,
605
+ TTransformerConfig extends any,
601
606
  > = (
602
607
  newOptions: RouterConstructorOptions<
603
608
  TRouteTree,
604
609
  TTrailingSlashOption,
605
610
  TDefaultStructuralSharingOption,
606
611
  TRouterHistory,
607
- TDehydrated
612
+ TDehydrated,
613
+ TTransformerConfig
608
614
  >,
609
615
  ) => void
610
616
 
@@ -689,10 +695,11 @@ export type AnyRouterWithContext<TContext> = RouterCore<
689
695
  any,
690
696
  any,
691
697
  any,
698
+ any,
692
699
  any
693
700
  >
694
701
 
695
- export type AnyRouter = RouterCore<any, any, any, any, any>
702
+ export type AnyRouter = RouterCore<any, any, any, any, any, any>
696
703
 
697
704
  export interface ViewTransitionOptions {
698
705
  types:
@@ -706,6 +713,7 @@ export interface ViewTransitionOptions {
706
713
  }) => Array<string>)
707
714
  }
708
715
 
716
+ // TODO where is this used? can we remove this?
709
717
  export function defaultSerializeError(err: unknown) {
710
718
  if (err instanceof Error) {
711
719
  const obj = {
@@ -745,6 +753,7 @@ export type CreateRouterFn = <
745
753
  TDefaultStructuralSharingOption extends boolean = false,
746
754
  TRouterHistory extends RouterHistory = RouterHistory,
747
755
  TDehydrated extends Record<string, any> = Record<string, any>,
756
+ TTransformerConfig = any,
748
757
  >(
749
758
  options: undefined extends number
750
759
  ? 'strictNullChecks must be enabled in tsconfig.json'
@@ -753,34 +762,25 @@ export type CreateRouterFn = <
753
762
  TTrailingSlashOption,
754
763
  TDefaultStructuralSharingOption,
755
764
  TRouterHistory,
756
- TDehydrated
765
+ TDehydrated,
766
+ TTransformerConfig
757
767
  >,
758
768
  ) => RouterCore<
759
769
  TRouteTree,
760
770
  TTrailingSlashOption,
761
771
  TDefaultStructuralSharingOption,
762
772
  TRouterHistory,
763
- TDehydrated
773
+ TDehydrated,
774
+ TTransformerConfig
764
775
  >
765
776
 
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
777
  export class RouterCore<
779
778
  in out TRouteTree extends AnyRoute,
780
779
  in out TTrailingSlashOption extends TrailingSlashOption,
781
780
  in out TDefaultStructuralSharingOption extends boolean,
782
781
  in out TRouterHistory extends RouterHistory = RouterHistory,
783
782
  in out TDehydrated extends Record<string, any> = Record<string, any>,
783
+ in out TTransformerConfig = any,
784
784
  > {
785
785
  // Option-independent properties
786
786
  tempLocationKey: string | undefined = `${Math.round(
@@ -802,7 +802,8 @@ export class RouterCore<
802
802
  TTrailingSlashOption,
803
803
  TDefaultStructuralSharingOption,
804
804
  TRouterHistory,
805
- TDehydrated
805
+ TDehydrated,
806
+ TTransformerConfig
806
807
  >,
807
808
  'stringifySearch' | 'parseSearch' | 'context'
808
809
  >
@@ -825,7 +826,8 @@ export class RouterCore<
825
826
  TTrailingSlashOption,
826
827
  TDefaultStructuralSharingOption,
827
828
  TRouterHistory,
828
- TDehydrated
829
+ TDehydrated,
830
+ TTransformerConfig
829
831
  >,
830
832
  ) {
831
833
  this.update({
@@ -863,7 +865,8 @@ export class RouterCore<
863
865
  TTrailingSlashOption,
864
866
  TDefaultStructuralSharingOption,
865
867
  TRouterHistory,
866
- TDehydrated
868
+ TDehydrated,
869
+ TTransformerConfig
867
870
  > = (newOptions) => {
868
871
  if (newOptions.notFoundRoute) {
869
872
  console.warn(
@@ -948,7 +951,7 @@ export class RouterCore<
948
951
  }
949
952
  }
950
953
 
951
- get state() {
954
+ get state(): RouterState<TRouteTree> {
952
955
  return this.__store.state
953
956
  }
954
957
 
@@ -1449,13 +1452,11 @@ export class RouterCore<
1449
1452
  undefined,
1450
1453
  ).matchedRoutes
1451
1454
 
1452
- const matchedFrom = [...allCurrentLocationMatches]
1453
- .reverse()
1454
- .find((d) => {
1455
- return comparePaths(d.fullPath, fromPath)
1456
- })
1455
+ const matchedFrom = findLast(allCurrentLocationMatches, (d) => {
1456
+ return comparePaths(d.fullPath, fromPath)
1457
+ })
1457
1458
 
1458
- const matchedCurrent = [...allFromMatches].reverse().find((d) => {
1459
+ const matchedCurrent = findLast(allFromMatches, (d) => {
1459
1460
  return comparePaths(d.fullPath, currentLocation.pathname)
1460
1461
  })
1461
1462
 
@@ -1480,20 +1481,20 @@ export class RouterCore<
1480
1481
  : this.resolvePathWithBase(fromPath, '.')
1481
1482
 
1482
1483
  // Resolve the next params
1483
- let nextParams =
1484
+ const nextParams =
1484
1485
  dest.params === false || dest.params === null
1485
1486
  ? {}
1486
1487
  : (dest.params ?? true) === true
1487
1488
  ? fromParams
1488
- : {
1489
- ...fromParams,
1490
- ...functionalUpdate(dest.params as any, fromParams),
1491
- }
1489
+ : Object.assign(
1490
+ fromParams,
1491
+ functionalUpdate(dest.params as any, fromParams),
1492
+ )
1492
1493
 
1493
1494
  // Interpolate the path first to get the actual resolved path, then match against that
1494
1495
  const interpolatedNextTo = interpolatePath({
1495
1496
  path: nextTo,
1496
- params: nextParams ?? {},
1497
+ params: nextParams,
1497
1498
  parseCache: this.parsePathnameCache,
1498
1499
  }).interpolatedPath
1499
1500
 
@@ -1503,23 +1504,20 @@ export class RouterCore<
1503
1504
 
1504
1505
  // If there are any params, we need to stringify them
1505
1506
  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
- })
1507
+ for (const route of destRoutes) {
1508
+ const fn =
1509
+ route.options.params?.stringify ?? route.options.stringifyParams
1510
+ if (fn) {
1511
+ Object.assign(nextParams, fn(nextParams))
1512
+ }
1513
+ }
1516
1514
  }
1517
1515
 
1518
1516
  const nextPathname = interpolatePath({
1519
1517
  // Use the original template path for interpolation
1520
1518
  // This preserves the original parameter syntax including optional parameters
1521
1519
  path: nextTo,
1522
- params: nextParams ?? {},
1520
+ params: nextParams,
1523
1521
  leaveWildcards: false,
1524
1522
  leaveParams: opts.leaveParams,
1525
1523
  decodeCharMap: this.pathParamsDecodeCharMap,
@@ -1529,20 +1527,20 @@ export class RouterCore<
1529
1527
  // Resolve the next search
1530
1528
  let nextSearch = fromSearch
1531
1529
  if (opts._includeValidateSearch && this.options.search?.strict) {
1532
- let validatedSearch = {}
1530
+ const validatedSearch = {}
1533
1531
  destRoutes.forEach((route) => {
1534
- try {
1535
- if (route.options.validateSearch) {
1536
- validatedSearch = {
1537
- ...validatedSearch,
1538
- ...(validateSearch(route.options.validateSearch, {
1532
+ if (route.options.validateSearch) {
1533
+ try {
1534
+ Object.assign(
1535
+ validatedSearch,
1536
+ validateSearch(route.options.validateSearch, {
1539
1537
  ...validatedSearch,
1540
1538
  ...nextSearch,
1541
- }) ?? {}),
1542
- }
1539
+ }),
1540
+ )
1541
+ } catch {
1542
+ // ignore errors here because they are already handled in matchRoutes
1543
1543
  }
1544
- } catch {
1545
- // ignore errors here because they are already handled in matchRoutes
1546
1544
  }
1547
1545
  })
1548
1546
  nextSearch = validatedSearch
@@ -1629,7 +1627,7 @@ export class RouterCore<
1629
1627
  if (foundMask) {
1630
1628
  const { from: _from, ...maskProps } = foundMask
1631
1629
  maskedDest = {
1632
- ...pick(opts, ['from']),
1630
+ from: opts.from,
1633
1631
  ...maskProps,
1634
1632
  params,
1635
1633
  }
@@ -1647,7 +1645,7 @@ export class RouterCore<
1647
1645
 
1648
1646
  if (opts.mask) {
1649
1647
  return buildWithMatches(opts, {
1650
- ...pick(opts, ['from']),
1648
+ from: opts.from,
1651
1649
  ...opts.mask,
1652
1650
  })
1653
1651
  }
@@ -1895,10 +1893,12 @@ export class RouterCore<
1895
1893
  }),
1896
1894
  })
1897
1895
 
1898
- await this.loadMatches({
1896
+ await loadMatches({
1897
+ router: this,
1899
1898
  sync: opts?.sync,
1900
1899
  matches: this.state.pendingMatches as Array<AnyRouteMatch>,
1901
1900
  location: next,
1901
+ updateMatch: this.updateMatch,
1902
1902
  // eslint-disable-next-line @typescript-eslint/require-await
1903
1903
  onReady: async () => {
1904
1904
  // eslint-disable-next-line @typescript-eslint/require-await
@@ -2088,873 +2088,6 @@ export class RouterCore<
2088
2088
  )
2089
2089
  }
2090
2090
 
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
2091
  invalidate: InvalidateFn<
2959
2092
  RouterCore<
2960
2093
  TRouteTree,
@@ -3048,47 +2181,7 @@ export class RouterCore<
3048
2181
  this.clearCache({ filter })
3049
2182
  }
3050
2183
 
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
- }
2184
+ loadRouteChunk = loadRouteChunk
3092
2185
 
3093
2186
  preloadRoute: PreloadRouteFn<
3094
2187
  TRouteTree,
@@ -3128,7 +2221,8 @@ export class RouterCore<
3128
2221
  })
3129
2222
 
3130
2223
  try {
3131
- matches = await this.loadMatches({
2224
+ matches = await loadMatches({
2225
+ router: this,
3132
2226
  matches,
3133
2227
  location: next,
3134
2228
  preload: true,
@@ -3226,58 +2320,6 @@ export class RouterCore<
3226
2320
 
3227
2321
  serverSsr?: ServerSsr
3228
2322
 
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
2323
  hasNotFoundMatch = () => {
3282
2324
  return this.__store.state.matches.some(
3283
2325
  (d) => d.status === 'notFound' || d.globalNotFound,
@@ -3289,16 +2331,6 @@ export class SearchParamError extends Error {}
3289
2331
 
3290
2332
  export class PathParamError extends Error {}
3291
2333
 
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
2334
  const normalize = (str: string) =>
3303
2335
  str.endsWith('/') && str.length > 1 ? str.slice(0, -1) : str
3304
2336
  function comparePaths(a: string, b: string) {
@@ -3365,22 +2397,6 @@ function validateSearch(validateSearch: AnyValidator, input: unknown): unknown {
3365
2397
  return {}
3366
2398
  }
3367
2399
 
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
2400
  interface RouteLike {
3385
2401
  id: string
3386
2402
  isRoot?: boolean