@tanstack/router-core 1.132.0-alpha.8 → 1.132.0

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 (92) hide show
  1. package/dist/cjs/Matches.cjs +2 -1
  2. package/dist/cjs/Matches.cjs.map +1 -1
  3. package/dist/cjs/fileRoute.d.cts +3 -3
  4. package/dist/cjs/index.cjs +7 -2
  5. package/dist/cjs/index.cjs.map +1 -1
  6. package/dist/cjs/index.d.cts +8 -4
  7. package/dist/cjs/load-matches.cjs +5 -3
  8. package/dist/cjs/load-matches.cjs.map +1 -1
  9. package/dist/cjs/location.d.cts +38 -0
  10. package/dist/cjs/path.cjs +27 -64
  11. package/dist/cjs/path.cjs.map +1 -1
  12. package/dist/cjs/path.d.cts +6 -7
  13. package/dist/cjs/process-route-tree.cjs +144 -0
  14. package/dist/cjs/process-route-tree.cjs.map +1 -0
  15. package/dist/cjs/process-route-tree.d.cts +10 -0
  16. package/dist/cjs/redirect.cjs +1 -1
  17. package/dist/cjs/redirect.cjs.map +1 -1
  18. package/dist/cjs/rewrite.cjs +63 -0
  19. package/dist/cjs/rewrite.cjs.map +1 -0
  20. package/dist/cjs/rewrite.d.cts +22 -0
  21. package/dist/cjs/route.cjs.map +1 -1
  22. package/dist/cjs/route.d.cts +55 -42
  23. package/dist/cjs/router.cjs +77 -184
  24. package/dist/cjs/router.cjs.map +1 -1
  25. package/dist/cjs/router.d.cts +68 -37
  26. package/dist/cjs/scroll-restoration.cjs.map +1 -1
  27. package/dist/cjs/scroll-restoration.d.cts +9 -0
  28. package/dist/cjs/ssr/createRequestHandler.cjs +4 -1
  29. package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -1
  30. package/dist/cjs/ssr/serializer/transformer.cjs.map +1 -1
  31. package/dist/cjs/ssr/serializer/transformer.d.cts +10 -8
  32. package/dist/cjs/ssr/server.d.cts +0 -5
  33. package/dist/cjs/ssr/ssr-server.cjs +5 -2
  34. package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
  35. package/dist/cjs/ssr/ssr-server.d.cts +4 -1
  36. package/dist/cjs/utils.cjs +68 -46
  37. package/dist/cjs/utils.cjs.map +1 -1
  38. package/dist/esm/Matches.js +2 -1
  39. package/dist/esm/Matches.js.map +1 -1
  40. package/dist/esm/fileRoute.d.ts +3 -3
  41. package/dist/esm/index.d.ts +8 -4
  42. package/dist/esm/index.js +8 -3
  43. package/dist/esm/index.js.map +1 -1
  44. package/dist/esm/load-matches.js +5 -3
  45. package/dist/esm/load-matches.js.map +1 -1
  46. package/dist/esm/location.d.ts +38 -0
  47. package/dist/esm/path.d.ts +6 -7
  48. package/dist/esm/path.js +27 -64
  49. package/dist/esm/path.js.map +1 -1
  50. package/dist/esm/process-route-tree.d.ts +10 -0
  51. package/dist/esm/process-route-tree.js +144 -0
  52. package/dist/esm/process-route-tree.js.map +1 -0
  53. package/dist/esm/redirect.js +1 -1
  54. package/dist/esm/redirect.js.map +1 -1
  55. package/dist/esm/rewrite.d.ts +22 -0
  56. package/dist/esm/rewrite.js +63 -0
  57. package/dist/esm/rewrite.js.map +1 -0
  58. package/dist/esm/route.d.ts +55 -42
  59. package/dist/esm/route.js.map +1 -1
  60. package/dist/esm/router.d.ts +68 -37
  61. package/dist/esm/router.js +79 -186
  62. package/dist/esm/router.js.map +1 -1
  63. package/dist/esm/scroll-restoration.d.ts +9 -0
  64. package/dist/esm/scroll-restoration.js.map +1 -1
  65. package/dist/esm/ssr/createRequestHandler.js +4 -1
  66. package/dist/esm/ssr/createRequestHandler.js.map +1 -1
  67. package/dist/esm/ssr/serializer/transformer.d.ts +10 -8
  68. package/dist/esm/ssr/serializer/transformer.js.map +1 -1
  69. package/dist/esm/ssr/server.d.ts +0 -5
  70. package/dist/esm/ssr/ssr-server.d.ts +4 -1
  71. package/dist/esm/ssr/ssr-server.js +5 -2
  72. package/dist/esm/ssr/ssr-server.js.map +1 -1
  73. package/dist/esm/utils.js +68 -46
  74. package/dist/esm/utils.js.map +1 -1
  75. package/package.json +2 -2
  76. package/src/Matches.ts +2 -1
  77. package/src/fileRoute.ts +16 -6
  78. package/src/index.ts +11 -6
  79. package/src/load-matches.ts +29 -19
  80. package/src/location.ts +38 -0
  81. package/src/path.ts +44 -82
  82. package/src/process-route-tree.ts +233 -0
  83. package/src/redirect.ts +1 -1
  84. package/src/rewrite.ts +70 -0
  85. package/src/route.ts +214 -80
  86. package/src/router.ts +208 -329
  87. package/src/scroll-restoration.ts +1 -1
  88. package/src/ssr/createRequestHandler.ts +4 -1
  89. package/src/ssr/serializer/transformer.ts +17 -17
  90. package/src/ssr/server.ts +5 -5
  91. package/src/ssr/ssr-server.ts +8 -5
  92. package/src/utils.ts +83 -61
package/src/router.ts CHANGED
@@ -1,10 +1,5 @@
1
1
  import { Store, batch } from '@tanstack/store'
2
- import {
3
- createBrowserHistory,
4
- createMemoryHistory,
5
- parseHref,
6
- } from '@tanstack/history'
7
- import invariant from 'tiny-invariant'
2
+ import { createBrowserHistory, parseHref } from '@tanstack/history'
8
3
  import {
9
4
  createControlledPromise,
10
5
  deepEqual,
@@ -13,19 +8,13 @@ import {
13
8
  last,
14
9
  replaceEqualDeep,
15
10
  } from './utils'
11
+ import { processRouteTree } from './process-route-tree'
16
12
  import {
17
- SEGMENT_TYPE_OPTIONAL_PARAM,
18
- SEGMENT_TYPE_PARAM,
19
- SEGMENT_TYPE_PATHNAME,
20
- SEGMENT_TYPE_WILDCARD,
21
13
  cleanPath,
22
14
  interpolatePath,
23
- joinPaths,
24
15
  matchPathname,
25
- parsePathname,
26
16
  resolvePath,
27
17
  trimPath,
28
- trimPathLeft,
29
18
  trimPathRight,
30
19
  } from './path'
31
20
  import { isNotFound } from './not-found'
@@ -35,7 +24,13 @@ import { rootRouteId } from './root'
35
24
  import { isRedirect, redirect } from './redirect'
36
25
  import { createLRUCache } from './lru-cache'
37
26
  import { loadMatches, loadRouteChunk, routeNeedsPreload } from './load-matches'
38
- import type { ParsePathnameCache, Segment } from './path'
27
+ import {
28
+ composeRewrites,
29
+ executeRewriteInput,
30
+ executeRewriteOutput,
31
+ rewriteBasepath,
32
+ } from './rewrite'
33
+ import type { ParsePathnameCache } from './path'
39
34
  import type { SearchParser, SearchSerializer } from './searchParams'
40
35
  import type { AnyRedirect, ResolvedRedirect } from './redirect'
41
36
  import type {
@@ -46,6 +41,7 @@ import type {
46
41
  } from '@tanstack/history'
47
42
  import type {
48
43
  Awaitable,
44
+ Constrain,
49
45
  ControlledPromise,
50
46
  NoInfer,
51
47
  NonNullableUpdater,
@@ -59,6 +55,7 @@ import type {
59
55
  AnyRouteWithContext,
60
56
  MakeRemountDepsOptionsUnion,
61
57
  RouteContextOptions,
58
+ RouteLike,
62
59
  RouteMask,
63
60
  SearchMiddleware,
64
61
  } from './route'
@@ -84,8 +81,11 @@ import type { Manifest } from './manifest'
84
81
  import type { AnySchema, AnyValidator } from './validators'
85
82
  import type { NavigateOptions, ResolveRelativePath, ToOptions } from './link'
86
83
  import type { NotFoundError } from './not-found'
87
- import type { AnySerializationAdapter } from './ssr/serializer/transformer'
88
- import type { AnyRouterConfig } from './config'
84
+ import type {
85
+ AnySerializationAdapter,
86
+ ValidateSerializableInput,
87
+ } from './ssr/serializer/transformer'
88
+ // import type { AnyRouterConfig } from './config'
89
89
 
90
90
  export type ControllablePromise<T = any> = Promise<T> & {
91
91
  resolve: (value: T) => void
@@ -94,17 +94,30 @@ export type ControllablePromise<T = any> = Promise<T> & {
94
94
 
95
95
  export type InjectedHtmlEntry = Promise<string>
96
96
 
97
- export interface DefaultRegister {
98
- router: AnyRouter
99
- config: AnyRouterConfig
100
- ssr: SSROption
97
+ export interface Register {
98
+ // Lots of things on here like...
99
+ // router
100
+ // config
101
+ // ssr
101
102
  }
102
103
 
103
- export interface Register extends DefaultRegister {
104
- // router: Router
104
+ export type RegisteredRouter<TRegister = Register> = TRegister extends {
105
+ router: infer TRouter
105
106
  }
107
+ ? TRouter
108
+ : AnyRouter
106
109
 
107
- export type RegisteredRouter = Register['router']
110
+ export type RegisteredConfigType<TRegister, TKey> = TRegister extends {
111
+ config: infer TConfig
112
+ }
113
+ ? TConfig extends {
114
+ '~types': infer TTypes
115
+ }
116
+ ? TKey extends keyof TTypes
117
+ ? TTypes[TKey]
118
+ : unknown
119
+ : unknown
120
+ : unknown
108
121
 
109
122
  export type DefaultRemountDepsFn<TRouteTree extends AnyRoute> = (
110
123
  opts: MakeRemountDepsOptionsUnion<TRouteTree>,
@@ -122,7 +135,7 @@ export interface RouterOptions<
122
135
  TTrailingSlashOption extends TrailingSlashOption,
123
136
  TDefaultStructuralSharingOption extends boolean = false,
124
137
  TRouterHistory extends RouterHistory = RouterHistory,
125
- TDehydrated extends Record<string, any> = Record<string, any>,
138
+ TDehydrated = undefined,
126
139
  > extends RouterOptionsExtensions {
127
140
  /**
128
141
  * The history object that will be used to manage the browser history.
@@ -267,6 +280,18 @@ export interface RouterOptions<
267
280
  /**
268
281
  * The basepath for then entire router. This is useful for mounting a router instance at a subpath.
269
282
  *
283
+ * @deprecated - use `rewrite.input` with the new `rewriteBasepath` utility instead:
284
+ * ```ts
285
+ * const router = createRouter({
286
+ * routeTree,
287
+ * rewrite: rewriteBasepath('/basepath')
288
+ * // Or wrap existing rewrite functionality
289
+ * rewrite: rewriteBasepath('/basepath', {
290
+ * output: ({ url }) => {...},
291
+ * input: ({ url }) => {...},
292
+ * })
293
+ * })
294
+ * ```
270
295
  * @default '/'
271
296
  * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#basepath-property)
272
297
  */
@@ -282,6 +307,9 @@ export interface RouterOptions<
282
307
  * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/router-context)
283
308
  */
284
309
  context?: InferRouterContext<TRouteTree>
310
+
311
+ additionalContext?: any
312
+
285
313
  /**
286
314
  * A function that will be called when the router is dehydrated.
287
315
  *
@@ -290,7 +318,10 @@ export interface RouterOptions<
290
318
  * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#dehydrate-method)
291
319
  * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/external-data-loading#critical-dehydrationhydration)
292
320
  */
293
- dehydrate?: () => Awaitable<TDehydrated>
321
+ dehydrate?: () => Constrain<
322
+ TDehydrated,
323
+ ValidateSerializableInput<Register, TDehydrated>
324
+ >
294
325
  /**
295
326
  * A function that will be called when the router is hydrated.
296
327
  *
@@ -431,8 +462,49 @@ export interface RouterOptions<
431
462
  disableGlobalCatchBoundary?: boolean
432
463
 
433
464
  serializationAdapters?: ReadonlyArray<AnySerializationAdapter>
465
+ /**
466
+ * Configures how the router will rewrite the location between the actual href and the internal href of the router.
467
+ *
468
+ * @default undefined
469
+ * @description You can provide a custom rewrite pair (in/out) or use the utilities like `rewriteBasepath` as a convenience for common use cases, or even do both!
470
+ * This is useful for basepath rewriting, shifting data from the origin to the path (for things like )
471
+ */
472
+ rewrite?: LocationRewrite
473
+ origin?: string
474
+ ssr?: {
475
+ nonce?: string
476
+ }
434
477
  }
435
478
 
479
+ export type LocationRewrite = {
480
+ /**
481
+ * A function that will be called to rewrite the URL before it is interpreted by the router from the history instance.
482
+ * Utilities like `rewriteBasepath` are provided as a convenience for common use cases.
483
+ *
484
+ * @default undefined
485
+ */
486
+ input?: LocationRewriteFunction
487
+ /**
488
+ * A function that will be called to rewrite the URL before it is committed to the actual history instance from the router.
489
+ * Utilities like `rewriteBasepath` are provided as a convenience for common use cases.
490
+ *
491
+ * @default undefined
492
+ */
493
+ output?: LocationRewriteFunction
494
+ }
495
+
496
+ /**
497
+ * A function that will be called to rewrite the URL.
498
+ *
499
+ * @param url The URL to rewrite.
500
+ * @returns The rewritten URL (as a URL instance or full href string) or undefined if no rewrite is needed.
501
+ */
502
+ export type LocationRewriteFunction = ({
503
+ url,
504
+ }: {
505
+ url: URL
506
+ }) => undefined | string | URL
507
+
436
508
  export interface RouterState<
437
509
  in out TRouteTree extends AnyRoute = AnyRoute,
438
510
  in out TRouteMatch = MakeRouteMatchUnion,
@@ -547,7 +619,7 @@ export type RouterConstructorOptions<
547
619
  TRouterHistory,
548
620
  TDehydrated
549
621
  >,
550
- 'context'
622
+ 'context' | 'serializationAdapters' | 'defaultSsr'
551
623
  > &
552
624
  RouterContextOptions<TRouteTree>
553
625
 
@@ -803,7 +875,10 @@ export class RouterCore<
803
875
  'stringifySearch' | 'parseSearch' | 'context'
804
876
  >
805
877
  history!: TRouterHistory
878
+ rewrite?: LocationRewrite
879
+ origin?: string
806
880
  latestLocation!: ParsedLocation<FullSearchSchema<TRouteTree>>
881
+ // @deprecated - basepath functionality is now implemented via the `rewrite` option
807
882
  basepath!: string
808
883
  routeTree!: TRouteTree
809
884
  routesById!: RoutesById<TRouteTree>
@@ -867,7 +942,6 @@ export class RouterCore<
867
942
  )
868
943
  }
869
944
 
870
- const previousOptions = this.options
871
945
  this.options = {
872
946
  ...this.options,
873
947
  ...newOptions,
@@ -885,31 +959,41 @@ export class RouterCore<
885
959
  : undefined
886
960
 
887
961
  if (
888
- !this.basepath ||
889
- (newOptions.basepath && newOptions.basepath !== previousOptions.basepath)
962
+ !this.history ||
963
+ (this.options.history && this.options.history !== this.history)
890
964
  ) {
891
- if (
892
- newOptions.basepath === undefined ||
893
- newOptions.basepath === '' ||
894
- newOptions.basepath === '/'
895
- ) {
896
- this.basepath = '/'
965
+ if (!this.options.history) {
966
+ if (!this.isServer) {
967
+ this.history = createBrowserHistory() as TRouterHistory
968
+ }
897
969
  } else {
898
- this.basepath = `/${trimPath(newOptions.basepath)}`
970
+ this.history = this.options.history
899
971
  }
900
972
  }
973
+ // For backwards compatibility, we support a basepath option, which we now implement as a rewrite
974
+ if (this.options.basepath) {
975
+ const basepathRewrite = rewriteBasepath({
976
+ basepath: this.options.basepath,
977
+ })
978
+ if (this.options.rewrite) {
979
+ this.rewrite = composeRewrites([basepathRewrite, this.options.rewrite])
980
+ } else {
981
+ this.rewrite = basepathRewrite
982
+ }
983
+ } else {
984
+ this.rewrite = this.options.rewrite
985
+ }
901
986
 
902
- if (
903
- !this.history ||
904
- (this.options.history && this.options.history !== this.history)
905
- ) {
906
- this.history =
907
- this.options.history ??
908
- ((this.isServer
909
- ? createMemoryHistory({
910
- initialEntries: [this.basepath || '/'],
911
- })
912
- : createBrowserHistory()) as TRouterHistory)
987
+ this.origin = this.options.origin
988
+ if (!this.origin) {
989
+ if (!this.isServer) {
990
+ this.origin = window.origin
991
+ } else {
992
+ // fallback for the server, can be overridden by calling router.update({origin}) on the server
993
+ this.origin = 'http://localhost'
994
+ }
995
+ }
996
+ if (this.history) {
913
997
  this.updateLatestLocation()
914
998
  }
915
999
 
@@ -918,7 +1002,7 @@ export class RouterCore<
918
1002
  this.buildRouteTree()
919
1003
  }
920
1004
 
921
- if (!this.__store) {
1005
+ if (!this.__store && this.latestLocation) {
922
1006
  this.__store = new Store(getInitialRouterState(this.latestLocation), {
923
1007
  onUpdate: () => {
924
1008
  this.__store.state = {
@@ -1005,20 +1089,32 @@ export class RouterCore<
1005
1089
  previousLocation,
1006
1090
  ) => {
1007
1091
  const parse = ({
1008
- pathname,
1009
- search,
1010
- hash,
1092
+ href,
1011
1093
  state,
1012
1094
  }: HistoryLocation): ParsedLocation<FullSearchSchema<TRouteTree>> => {
1013
- const parsedSearch = this.options.parseSearch(search)
1095
+ // Before we do any processing, we need to allow rewrites to modify the URL
1096
+ // build up the full URL by combining the href from history with the router's origin
1097
+ const fullUrl = new URL(href, this.origin)
1098
+ const url = executeRewriteInput(this.rewrite, fullUrl)
1099
+
1100
+ const parsedSearch = this.options.parseSearch(url.search)
1014
1101
  const searchStr = this.options.stringifySearch(parsedSearch)
1102
+ // Make sure our final url uses the re-stringified pathname, search, and has for consistency
1103
+ // (We were already doing this, so just keeping it for now)
1104
+ url.search = searchStr
1105
+
1106
+ const fullPath = url.href.replace(url.origin, '')
1107
+
1108
+ const { pathname, hash } = url
1015
1109
 
1016
1110
  return {
1111
+ href: fullPath,
1112
+ publicHref: href,
1113
+ url: url.href,
1017
1114
  pathname,
1018
1115
  searchStr,
1019
1116
  search: replaceEqualDeep(previousLocation?.search, parsedSearch) as any,
1020
1117
  hash: hash.split('#').reverse()[0] ?? '',
1021
- href: `${pathname}${searchStr}${hash}`,
1022
1118
  state: replaceEqualDeep(previousLocation?.state, state),
1023
1119
  }
1024
1120
  }
@@ -1046,11 +1142,9 @@ export class RouterCore<
1046
1142
 
1047
1143
  resolvePathWithBase = (from: string, path: string) => {
1048
1144
  const resolvedPath = resolvePath({
1049
- basepath: this.basepath,
1050
1145
  base: from,
1051
1146
  to: cleanPath(path),
1052
1147
  trailingSlash: this.options.trailingSlash,
1053
- caseSensitive: this.options.caseSensitive,
1054
1148
  parseCache: this.parsePathnameCache,
1055
1149
  })
1056
1150
  return resolvedPath
@@ -1122,33 +1216,6 @@ export class RouterCore<
1122
1216
  return rootRouteId
1123
1217
  })()
1124
1218
 
1125
- const parseErrors = matchedRoutes.map((route) => {
1126
- let parsedParamsError
1127
-
1128
- const parseParams =
1129
- route.options.params?.parse ?? route.options.parseParams
1130
-
1131
- if (parseParams) {
1132
- try {
1133
- const parsedParams = parseParams(routeParams)
1134
- // Add the parsed params to the accumulated params bag
1135
- Object.assign(routeParams, parsedParams)
1136
- } catch (err: any) {
1137
- parsedParamsError = new PathParamError(err.message, {
1138
- cause: err,
1139
- })
1140
-
1141
- if (opts?.throwOnError) {
1142
- throw parsedParamsError
1143
- }
1144
-
1145
- return parsedParamsError
1146
- }
1147
- }
1148
-
1149
- return
1150
- })
1151
-
1152
1219
  const matches: Array<AnyRouteMatch> = []
1153
1220
 
1154
1221
  const getParentContext = (parentMatch?: AnyRouteMatch) => {
@@ -1221,12 +1288,18 @@ export class RouterCore<
1221
1288
 
1222
1289
  const loaderDepsHash = loaderDeps ? JSON.stringify(loaderDeps) : ''
1223
1290
 
1224
- const { usedParams, interpolatedPath } = interpolatePath({
1291
+ const { interpolatedPath, usedParams } = interpolatePath({
1225
1292
  path: route.fullPath,
1226
1293
  params: routeParams,
1227
1294
  decodeCharMap: this.pathParamsDecodeCharMap,
1228
1295
  })
1229
1296
 
1297
+ // Waste not, want not. If we already have a match for this route,
1298
+ // reuse it. This is important for layout routes, which might stick
1299
+ // around between navigation actions that only change leaf routes.
1300
+
1301
+ // Existing matches are matches that are already loaded along with
1302
+ // pending matches that are still loading
1230
1303
  const matchId =
1231
1304
  interpolatePath({
1232
1305
  path: route.id,
@@ -1236,18 +1309,40 @@ export class RouterCore<
1236
1309
  parseCache: this.parsePathnameCache,
1237
1310
  }).interpolatedPath + loaderDepsHash
1238
1311
 
1239
- // Waste not, want not. If we already have a match for this route,
1240
- // reuse it. This is important for layout routes, which might stick
1241
- // around between navigation actions that only change leaf routes.
1242
-
1243
- // Existing matches are matches that are already loaded along with
1244
- // pending matches that are still loading
1245
1312
  const existingMatch = this.getMatch(matchId)
1246
1313
 
1247
1314
  const previousMatch = this.state.matches.find(
1248
1315
  (d) => d.routeId === route.id,
1249
1316
  )
1250
1317
 
1318
+ const strictParams = existingMatch?._strictParams ?? usedParams
1319
+
1320
+ let paramsError: PathParamError | undefined = undefined
1321
+
1322
+ if (!existingMatch) {
1323
+ const strictParseParams =
1324
+ route.options.params?.parse ?? route.options.parseParams
1325
+
1326
+ if (strictParseParams) {
1327
+ try {
1328
+ Object.assign(
1329
+ strictParams,
1330
+ strictParseParams(strictParams as Record<string, string>),
1331
+ )
1332
+ } catch (err: any) {
1333
+ paramsError = new PathParamError(err.message, {
1334
+ cause: err,
1335
+ })
1336
+
1337
+ if (opts?.throwOnError) {
1338
+ throw paramsError
1339
+ }
1340
+ }
1341
+ }
1342
+ }
1343
+
1344
+ Object.assign(routeParams, strictParams)
1345
+
1251
1346
  const cause = previousMatch ? 'stay' : 'enter'
1252
1347
 
1253
1348
  let match: AnyRouteMatch
@@ -1259,7 +1354,7 @@ export class RouterCore<
1259
1354
  params: previousMatch
1260
1355
  ? replaceEqualDeep(previousMatch.params, routeParams)
1261
1356
  : routeParams,
1262
- _strictParams: usedParams,
1357
+ _strictParams: strictParams,
1263
1358
  search: previousMatch
1264
1359
  ? replaceEqualDeep(previousMatch.search, preMatchSearch)
1265
1360
  : replaceEqualDeep(existingMatch.search, preMatchSearch),
@@ -1281,8 +1376,8 @@ export class RouterCore<
1281
1376
  params: previousMatch
1282
1377
  ? replaceEqualDeep(previousMatch.params, routeParams)
1283
1378
  : routeParams,
1284
- _strictParams: usedParams,
1285
- pathname: joinPaths([this.basepath, interpolatedPath]),
1379
+ _strictParams: strictParams,
1380
+ pathname: interpolatedPath,
1286
1381
  updatedAt: Date.now(),
1287
1382
  search: previousMatch
1288
1383
  ? replaceEqualDeep(previousMatch.search, preMatchSearch)
@@ -1292,7 +1387,7 @@ export class RouterCore<
1292
1387
  status,
1293
1388
  isFetching: false,
1294
1389
  error: undefined,
1295
- paramsError: parseErrors[index],
1390
+ paramsError,
1296
1391
  __routeContext: undefined,
1297
1392
  _nonReactive: {
1298
1393
  loadPromise: createControlledPromise(),
@@ -1387,7 +1482,6 @@ export class RouterCore<
1387
1482
  return getMatchedRoutes({
1388
1483
  pathname,
1389
1484
  routePathname,
1390
- basepath: this.basepath,
1391
1485
  caseSensitive: this.options.caseSensitive,
1392
1486
  routesByPath: this.routesByPath,
1393
1487
  routesById: this.routesById,
@@ -1576,14 +1670,25 @@ export class RouterCore<
1576
1670
  // Replace the equal deep
1577
1671
  nextState = replaceEqualDeep(currentLocation.state, nextState)
1578
1672
 
1579
- // Return the next location
1673
+ // Create the full path of the location
1674
+ const fullPath = `${nextPathname}${searchStr}${hashStr}`
1675
+
1676
+ // Create the new href with full origin
1677
+ const url = new URL(fullPath, this.origin)
1678
+
1679
+ // If a rewrite function is provided, use it to rewrite the URL
1680
+ const rewrittenUrl = executeRewriteOutput(this.rewrite, url)
1681
+
1580
1682
  return {
1683
+ publicHref:
1684
+ rewrittenUrl.pathname + rewrittenUrl.search + rewrittenUrl.hash,
1685
+ href: fullPath,
1686
+ url: rewrittenUrl.href,
1581
1687
  pathname: nextPathname,
1582
1688
  search: nextSearch,
1583
1689
  searchStr,
1584
1690
  state: nextState as any,
1585
1691
  hash: hash ?? '',
1586
- href: `${nextPathname}${searchStr}${hashStr}`,
1587
1692
  unmaskOnReload: dest.unmaskOnReload,
1588
1693
  }
1589
1694
  }
@@ -1601,7 +1706,6 @@ export class RouterCore<
1601
1706
 
1602
1707
  const foundMask = this.options.routeMasks?.find((d) => {
1603
1708
  const match = matchPathname(
1604
- this.basepath,
1605
1709
  next.pathname,
1606
1710
  {
1607
1711
  to: d.from,
@@ -1631,8 +1735,7 @@ export class RouterCore<
1631
1735
  }
1632
1736
 
1633
1737
  if (maskedNext) {
1634
- const maskedFinal = build(maskedDest)
1635
- next.maskedLocation = maskedFinal
1738
+ next.maskedLocation = maskedNext
1636
1739
  }
1637
1740
 
1638
1741
  return next
@@ -1675,7 +1778,8 @@ export class RouterCore<
1675
1778
  return isEqual
1676
1779
  }
1677
1780
 
1678
- const isSameUrl = this.latestLocation.href === next.href
1781
+ const isSameUrl =
1782
+ trimPathRight(this.latestLocation.href) === trimPathRight(next.href)
1679
1783
 
1680
1784
  const previousCommitPromise = this.commitLocationPromise
1681
1785
  this.commitLocationPromise = createControlledPromise<void>(() => {
@@ -1724,7 +1828,7 @@ export class RouterCore<
1724
1828
  this.shouldViewTransition = viewTransition
1725
1829
 
1726
1830
  this.history[next.replace ? 'replace' : 'push'](
1727
- nextHistory.href,
1831
+ nextHistory.publicHref,
1728
1832
  nextHistory.state,
1729
1833
  { ignoreBlocker },
1730
1834
  )
@@ -1786,7 +1890,7 @@ export class RouterCore<
1786
1890
  if (reloadDocument) {
1787
1891
  if (!href) {
1788
1892
  const location = this.buildLocation({ to, ...rest } as any)
1789
- href = this.history.createHref(location.href)
1893
+ href = location.href
1790
1894
  }
1791
1895
  if (rest.replace) {
1792
1896
  window.location.replace(href)
@@ -1839,6 +1943,7 @@ export class RouterCore<
1839
1943
  throw redirect({ href: nextLocation.href })
1840
1944
  }
1841
1945
  }
1946
+
1842
1947
  // Match the routes
1843
1948
  const pendingMatches = this.matchRoutes(this.latestLocation)
1844
1949
 
@@ -1984,6 +2089,7 @@ export class RouterCore<
1984
2089
  this.latestLoadPromise = undefined
1985
2090
  this.commitLocationPromise = undefined
1986
2091
  }
2092
+
1987
2093
  resolve()
1988
2094
  })
1989
2095
  })
@@ -2282,7 +2388,6 @@ export class RouterCore<
2282
2388
  : this.state.resolvedLocation || this.state.location
2283
2389
 
2284
2390
  const match = matchPathname(
2285
- this.basepath,
2286
2391
  baseLocation.pathname,
2287
2392
  {
2288
2393
  ...opts,
@@ -2392,233 +2497,9 @@ function validateSearch(validateSearch: AnyValidator, input: unknown): unknown {
2392
2497
  return {}
2393
2498
  }
2394
2499
 
2395
- interface RouteLike {
2396
- id: string
2397
- isRoot?: boolean
2398
- path?: string
2399
- fullPath: string
2400
- rank?: number
2401
- parentRoute?: RouteLike
2402
- children?: Array<RouteLike>
2403
- options?: {
2404
- caseSensitive?: boolean
2405
- }
2406
- }
2407
-
2408
- export type ProcessRouteTreeResult<TRouteLike extends RouteLike> = {
2409
- routesById: Record<string, TRouteLike>
2410
- routesByPath: Record<string, TRouteLike>
2411
- flatRoutes: Array<TRouteLike>
2412
- }
2413
-
2414
- const REQUIRED_PARAM_BASE_SCORE = 0.5
2415
- const OPTIONAL_PARAM_BASE_SCORE = 0.4
2416
- const WILDCARD_PARAM_BASE_SCORE = 0.25
2417
- const BOTH_PRESENCE_BASE_SCORE = 0.05
2418
- const PREFIX_PRESENCE_BASE_SCORE = 0.02
2419
- const SUFFIX_PRESENCE_BASE_SCORE = 0.01
2420
- const PREFIX_LENGTH_SCORE_MULTIPLIER = 0.0002
2421
- const SUFFIX_LENGTH_SCORE_MULTIPLIER = 0.0001
2422
-
2423
- function handleParam(segment: Segment, baseScore: number) {
2424
- if (segment.prefixSegment && segment.suffixSegment) {
2425
- return (
2426
- baseScore +
2427
- BOTH_PRESENCE_BASE_SCORE +
2428
- PREFIX_LENGTH_SCORE_MULTIPLIER * segment.prefixSegment.length +
2429
- SUFFIX_LENGTH_SCORE_MULTIPLIER * segment.suffixSegment.length
2430
- )
2431
- }
2432
-
2433
- if (segment.prefixSegment) {
2434
- return (
2435
- baseScore +
2436
- PREFIX_PRESENCE_BASE_SCORE +
2437
- PREFIX_LENGTH_SCORE_MULTIPLIER * segment.prefixSegment.length
2438
- )
2439
- }
2440
-
2441
- if (segment.suffixSegment) {
2442
- return (
2443
- baseScore +
2444
- SUFFIX_PRESENCE_BASE_SCORE +
2445
- SUFFIX_LENGTH_SCORE_MULTIPLIER * segment.suffixSegment.length
2446
- )
2447
- }
2448
-
2449
- return baseScore
2450
- }
2451
-
2452
- export function processRouteTree<TRouteLike extends RouteLike>({
2453
- routeTree,
2454
- initRoute,
2455
- }: {
2456
- routeTree: TRouteLike
2457
- initRoute?: (route: TRouteLike, index: number) => void
2458
- }): ProcessRouteTreeResult<TRouteLike> {
2459
- const routesById = {} as Record<string, TRouteLike>
2460
- const routesByPath = {} as Record<string, TRouteLike>
2461
-
2462
- const recurseRoutes = (childRoutes: Array<TRouteLike>) => {
2463
- childRoutes.forEach((childRoute, i) => {
2464
- initRoute?.(childRoute, i)
2465
-
2466
- const existingRoute = routesById[childRoute.id]
2467
-
2468
- invariant(
2469
- !existingRoute,
2470
- `Duplicate routes found with id: ${String(childRoute.id)}`,
2471
- )
2472
-
2473
- routesById[childRoute.id] = childRoute
2474
-
2475
- if (!childRoute.isRoot && childRoute.path) {
2476
- const trimmedFullPath = trimPathRight(childRoute.fullPath)
2477
- if (
2478
- !routesByPath[trimmedFullPath] ||
2479
- childRoute.fullPath.endsWith('/')
2480
- ) {
2481
- routesByPath[trimmedFullPath] = childRoute
2482
- }
2483
- }
2484
-
2485
- const children = childRoute.children as Array<TRouteLike>
2486
-
2487
- if (children?.length) {
2488
- recurseRoutes(children)
2489
- }
2490
- })
2491
- }
2492
-
2493
- recurseRoutes([routeTree])
2494
-
2495
- const scoredRoutes: Array<{
2496
- child: TRouteLike
2497
- trimmed: string
2498
- parsed: ReadonlyArray<Segment>
2499
- index: number
2500
- scores: Array<number>
2501
- hasStaticAfter: boolean
2502
- optionalParamCount: number
2503
- }> = []
2504
-
2505
- const routes: Array<TRouteLike> = Object.values(routesById)
2506
-
2507
- routes.forEach((d, i) => {
2508
- if (d.isRoot || !d.path) {
2509
- return
2510
- }
2511
-
2512
- const trimmed = trimPathLeft(d.fullPath)
2513
- let parsed = parsePathname(trimmed)
2514
-
2515
- // Removes the leading slash if it is not the only remaining segment
2516
- let skip = 0
2517
- while (parsed.length > skip + 1 && parsed[skip]?.value === '/') {
2518
- skip++
2519
- }
2520
- if (skip > 0) parsed = parsed.slice(skip)
2521
-
2522
- let optionalParamCount = 0
2523
- let hasStaticAfter = false
2524
- const scores = parsed.map((segment, index) => {
2525
- if (segment.value === '/') {
2526
- return 0.75
2527
- }
2528
-
2529
- let baseScore: number | undefined = undefined
2530
- if (segment.type === SEGMENT_TYPE_PARAM) {
2531
- baseScore = REQUIRED_PARAM_BASE_SCORE
2532
- } else if (segment.type === SEGMENT_TYPE_OPTIONAL_PARAM) {
2533
- baseScore = OPTIONAL_PARAM_BASE_SCORE
2534
- optionalParamCount++
2535
- } else if (segment.type === SEGMENT_TYPE_WILDCARD) {
2536
- baseScore = WILDCARD_PARAM_BASE_SCORE
2537
- }
2538
-
2539
- if (baseScore) {
2540
- // if there is any static segment (that is not an index) after a required / optional param,
2541
- // we will boost this param so it ranks higher than a required/optional param without a static segment after it
2542
- // JUST FOR SORTING, NOT FOR MATCHING
2543
- for (let i = index + 1; i < parsed.length; i++) {
2544
- const nextSegment = parsed[i]!
2545
- if (
2546
- nextSegment.type === SEGMENT_TYPE_PATHNAME &&
2547
- nextSegment.value !== '/'
2548
- ) {
2549
- hasStaticAfter = true
2550
- return handleParam(segment, baseScore + 0.2)
2551
- }
2552
- }
2553
-
2554
- return handleParam(segment, baseScore)
2555
- }
2556
-
2557
- return 1
2558
- })
2559
-
2560
- scoredRoutes.push({
2561
- child: d,
2562
- trimmed,
2563
- parsed,
2564
- index: i,
2565
- scores,
2566
- optionalParamCount,
2567
- hasStaticAfter,
2568
- })
2569
- })
2570
-
2571
- const flatRoutes = scoredRoutes
2572
- .sort((a, b) => {
2573
- const minLength = Math.min(a.scores.length, b.scores.length)
2574
-
2575
- // Sort by segment-by-segment score comparison ONLY for the common prefix
2576
- for (let i = 0; i < minLength; i++) {
2577
- if (a.scores[i] !== b.scores[i]) {
2578
- return b.scores[i]! - a.scores[i]!
2579
- }
2580
- }
2581
-
2582
- // If all common segments have equal scores, then consider length and specificity
2583
- if (a.scores.length !== b.scores.length) {
2584
- // If different number of optional parameters, fewer optional parameters wins (more specific)
2585
- // only if both or none of the routes has static segments after the params
2586
- if (a.optionalParamCount !== b.optionalParamCount) {
2587
- if (a.hasStaticAfter === b.hasStaticAfter) {
2588
- return a.optionalParamCount - b.optionalParamCount
2589
- } else if (a.hasStaticAfter && !b.hasStaticAfter) {
2590
- return -1
2591
- } else if (!a.hasStaticAfter && b.hasStaticAfter) {
2592
- return 1
2593
- }
2594
- }
2595
-
2596
- // If same number of optional parameters, longer path wins (for static segments)
2597
- return b.scores.length - a.scores.length
2598
- }
2599
-
2600
- // Sort by min available parsed value for alphabetical ordering
2601
- for (let i = 0; i < minLength; i++) {
2602
- if (a.parsed[i]!.value !== b.parsed[i]!.value) {
2603
- return a.parsed[i]!.value > b.parsed[i]!.value ? 1 : -1
2604
- }
2605
- }
2606
-
2607
- // Sort by original index
2608
- return a.index - b.index
2609
- })
2610
- .map((d, i) => {
2611
- d.child.rank = i
2612
- return d.child
2613
- })
2614
-
2615
- return { routesById, routesByPath, flatRoutes }
2616
- }
2617
-
2618
2500
  export function getMatchedRoutes<TRouteLike extends RouteLike>({
2619
2501
  pathname,
2620
2502
  routePathname,
2621
- basepath,
2622
2503
  caseSensitive,
2623
2504
  routesByPath,
2624
2505
  routesById,
@@ -2627,7 +2508,6 @@ export function getMatchedRoutes<TRouteLike extends RouteLike>({
2627
2508
  }: {
2628
2509
  pathname: string
2629
2510
  routePathname?: string
2630
- basepath: string
2631
2511
  caseSensitive?: boolean
2632
2512
  routesByPath: Record<string, TRouteLike>
2633
2513
  routesById: Record<string, TRouteLike>
@@ -2638,7 +2518,6 @@ export function getMatchedRoutes<TRouteLike extends RouteLike>({
2638
2518
  const trimmedPath = trimPathRight(pathname)
2639
2519
  const getMatchedParams = (route: TRouteLike) => {
2640
2520
  const result = matchPathname(
2641
- basepath,
2642
2521
  trimmedPath,
2643
2522
  {
2644
2523
  to: route.fullPath,