@tanstack/router-core 1.132.0-alpha.12 → 1.132.0-alpha.16

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.
@@ -178,6 +178,18 @@ export interface RouterOptions<TRouteTree extends AnyRoute, TTrailingSlashOption
178
178
  /**
179
179
  * The basepath for then entire router. This is useful for mounting a router instance at a subpath.
180
180
  *
181
+ * @deprecated - use `rewrite.input` with the new `rewriteBasepath` utility instead:
182
+ * ```ts
183
+ * const router = createRouter({
184
+ * routeTree,
185
+ * rewrite: rewriteBasepath('/basepath')
186
+ * // Or wrap existing rewrite functionality
187
+ * rewrite: rewriteBasepath('/basepath', {
188
+ * output: ({ url }) => {...},
189
+ * input: ({ url }) => {...},
190
+ * })
191
+ * })
192
+ * ```
181
193
  * @default '/'
182
194
  * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#basepath-property)
183
195
  */
@@ -328,7 +340,41 @@ export interface RouterOptions<TRouteTree extends AnyRoute, TTrailingSlashOption
328
340
  */
329
341
  disableGlobalCatchBoundary?: boolean;
330
342
  serializationAdapters?: ReadonlyArray<AnySerializationAdapter>;
343
+ /**
344
+ * Configures how the router will rewrite the location between the actual href and the internal href of the router.
345
+ *
346
+ * @default undefined
347
+ * @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!
348
+ * This is useful for basepath rewriting, shifting data from the origin to the path (for things like )
349
+ */
350
+ rewrite?: LocationRewrite;
351
+ origin?: string;
331
352
  }
353
+ export type LocationRewrite = {
354
+ /**
355
+ * A function that will be called to rewrite the URL before it is interpreted by the router from the history instance.
356
+ * Utilities like `rewriteBasepath` are provided as a convenience for common use cases.
357
+ *
358
+ * @default undefined
359
+ */
360
+ input?: LocationRewriteFunction;
361
+ /**
362
+ * A function that will be called to rewrite the URL before it is committed to the actual history instance from the router.
363
+ * Utilities like `rewriteBasepath` are provided as a convenience for common use cases.
364
+ *
365
+ * @default undefined
366
+ */
367
+ output?: LocationRewriteFunction;
368
+ };
369
+ /**
370
+ * A function that will be called to rewrite the URL.
371
+ *
372
+ * @param url The URL to rewrite.
373
+ * @returns The rewritten URL (as a URL instance or full href string) or undefined if no rewrite is needed.
374
+ */
375
+ export type LocationRewriteFunction = ({ url, }: {
376
+ url: URL;
377
+ }) => undefined | string | URL;
332
378
  export interface RouterState<in out TRouteTree extends AnyRoute = AnyRoute, in out TRouteMatch = MakeRouteMatchUnion> {
333
379
  status: 'pending' | 'idle';
334
380
  loadedAt: number;
@@ -495,6 +541,8 @@ export declare class RouterCore<in out TRouteTree extends AnyRoute, in out TTrai
495
541
  __store: Store<RouterState<TRouteTree>>;
496
542
  options: PickAsRequired<RouterOptions<TRouteTree, TTrailingSlashOption, TDefaultStructuralSharingOption, TRouterHistory, TDehydrated>, 'stringifySearch' | 'parseSearch' | 'context'>;
497
543
  history: TRouterHistory;
544
+ rewrite?: LocationRewrite;
545
+ origin?: string;
498
546
  latestLocation: ParsedLocation<FullSearchSchema<TRouteTree>>;
499
547
  basepath: string;
500
548
  routeTree: TRouteTree;
@@ -577,10 +625,9 @@ export declare function processRouteTree<TRouteLike extends RouteLike>({ routeTr
577
625
  routeTree: TRouteLike;
578
626
  initRoute?: (route: TRouteLike, index: number) => void;
579
627
  }): ProcessRouteTreeResult<TRouteLike>;
580
- export declare function getMatchedRoutes<TRouteLike extends RouteLike>({ pathname, routePathname, basepath, caseSensitive, routesByPath, routesById, flatRoutes, parseCache, }: {
628
+ export declare function getMatchedRoutes<TRouteLike extends RouteLike>({ pathname, routePathname, caseSensitive, routesByPath, routesById, flatRoutes, parseCache, }: {
581
629
  pathname: string;
582
630
  routePathname?: string;
583
- basepath: string;
584
631
  caseSensitive?: boolean;
585
632
  routesByPath: Record<string, TRouteLike>;
586
633
  routesById: Record<string, TRouteLike>;
@@ -1,8 +1,8 @@
1
1
  import { Store, batch } from "@tanstack/store";
2
- import { createMemoryHistory, createBrowserHistory, parseHref } from "@tanstack/history";
2
+ import { createBrowserHistory, parseHref } from "@tanstack/history";
3
3
  import invariant from "tiny-invariant";
4
4
  import { createControlledPromise, deepEqual, replaceEqualDeep, last, findLast, functionalUpdate } from "./utils.js";
5
- import { trimPath, resolvePath, cleanPath, matchPathname, trimPathRight, interpolatePath, joinPaths, trimPathLeft, parsePathname, SEGMENT_TYPE_PARAM, SEGMENT_TYPE_OPTIONAL_PARAM, SEGMENT_TYPE_WILDCARD, SEGMENT_TYPE_PATHNAME } from "./path.js";
5
+ import { resolvePath, cleanPath, trimPathRight, trimPath, matchPathname, interpolatePath, trimPathLeft, parsePathname, SEGMENT_TYPE_PARAM, SEGMENT_TYPE_OPTIONAL_PARAM, SEGMENT_TYPE_WILDCARD, SEGMENT_TYPE_PATHNAME } from "./path.js";
6
6
  import { isNotFound } from "./not-found.js";
7
7
  import { setupScrollRestoration } from "./scroll-restoration.js";
8
8
  import { defaultParseSearch, defaultStringifySearch } from "./searchParams.js";
@@ -10,6 +10,7 @@ import { rootRouteId } from "./root.js";
10
10
  import { redirect, isRedirect } from "./redirect.js";
11
11
  import { createLRUCache } from "./lru-cache.js";
12
12
  import { loadMatches, loadRouteChunk, routeNeedsPreload } from "./load-matches.js";
13
+ import { rewriteBasepath, composeRewrites, executeRewriteInput, executeRewriteOutput } from "./rewrite.js";
13
14
  function defaultSerializeError(err) {
14
15
  if (err instanceof Error) {
15
16
  const obj = {
@@ -54,7 +55,6 @@ class RouterCore {
54
55
  "The notFoundRoute API is deprecated and will be removed in the next major version. See https://tanstack.com/router/v1/docs/framework/react/guide/not-found-errors#migrating-from-notfoundroute for more info."
55
56
  );
56
57
  }
57
- const previousOptions = this.options;
58
58
  this.options = {
59
59
  ...this.options,
60
60
  ...newOptions
@@ -66,24 +66,43 @@ class RouterCore {
66
66
  char
67
67
  ])
68
68
  ) : void 0;
69
- if (!this.basepath || newOptions.basepath && newOptions.basepath !== previousOptions.basepath) {
70
- if (newOptions.basepath === void 0 || newOptions.basepath === "" || newOptions.basepath === "/") {
71
- this.basepath = "/";
69
+ if (!this.history || this.options.history && this.options.history !== this.history) {
70
+ if (!this.options.history) {
71
+ if (!this.isServer) {
72
+ this.history = createBrowserHistory();
73
+ }
72
74
  } else {
73
- this.basepath = `/${trimPath(newOptions.basepath)}`;
75
+ this.history = this.options.history;
74
76
  }
75
77
  }
76
- if (!this.history || this.options.history && this.options.history !== this.history) {
77
- this.history = this.options.history ?? (this.isServer ? createMemoryHistory({
78
- initialEntries: [this.basepath || "/"]
79
- }) : createBrowserHistory());
78
+ if (this.options.basepath) {
79
+ const basepathRewrite = rewriteBasepath({
80
+ basepath: this.options.basepath
81
+ });
82
+ if (this.options.rewrite) {
83
+ this.rewrite = composeRewrites([basepathRewrite, this.options.rewrite]);
84
+ } else {
85
+ this.rewrite = basepathRewrite;
86
+ }
87
+ } else {
88
+ this.rewrite = this.options.rewrite;
89
+ }
90
+ this.origin = this.options.origin;
91
+ if (!this.origin) {
92
+ if (!this.isServer) {
93
+ this.origin = window.origin;
94
+ } else {
95
+ this.origin = "http://localhost";
96
+ }
97
+ }
98
+ if (this.history) {
80
99
  this.updateLatestLocation();
81
100
  }
82
101
  if (this.options.routeTree !== this.routeTree) {
83
102
  this.routeTree = this.options.routeTree;
84
103
  this.buildRouteTree();
85
104
  }
86
- if (!this.__store) {
105
+ if (!this.__store && this.latestLocation) {
87
106
  this.__store = new Store(getInitialRouterState(this.latestLocation), {
88
107
  onUpdate: () => {
89
108
  this.__store.state = {
@@ -147,19 +166,24 @@ class RouterCore {
147
166
  };
148
167
  this.parseLocation = (locationToParse, previousLocation) => {
149
168
  const parse = ({
150
- pathname,
151
- search,
152
- hash,
169
+ href,
153
170
  state
154
171
  }) => {
155
- const parsedSearch = this.options.parseSearch(search);
172
+ const fullUrl = new URL(href, this.origin);
173
+ const url = executeRewriteInput(this.rewrite, fullUrl);
174
+ const parsedSearch = this.options.parseSearch(url.search);
156
175
  const searchStr = this.options.stringifySearch(parsedSearch);
176
+ url.search = searchStr;
177
+ const fullPath = url.href.replace(url.origin, "");
178
+ const { pathname, hash } = url;
157
179
  return {
180
+ href: fullPath,
181
+ publicHref: href,
182
+ url: url.href,
158
183
  pathname,
159
184
  searchStr,
160
185
  search: replaceEqualDeep(previousLocation?.search, parsedSearch),
161
186
  hash: hash.split("#").reverse()[0] ?? "",
162
- href: `${pathname}${searchStr}${hash}`,
163
187
  state: replaceEqualDeep(previousLocation?.state, state)
164
188
  };
165
189
  };
@@ -179,11 +203,9 @@ class RouterCore {
179
203
  };
180
204
  this.resolvePathWithBase = (from, path) => {
181
205
  const resolvedPath = resolvePath({
182
- basepath: this.basepath,
183
206
  base: from,
184
207
  to: cleanPath(path),
185
208
  trailingSlash: this.options.trailingSlash,
186
- caseSensitive: this.options.caseSensitive,
187
209
  parseCache: this.parsePathnameCache
188
210
  });
189
211
  return resolvedPath;
@@ -205,7 +227,6 @@ class RouterCore {
205
227
  return getMatchedRoutes({
206
228
  pathname,
207
229
  routePathname,
208
- basepath: this.basepath,
209
230
  caseSensitive: this.options.caseSensitive,
210
231
  routesByPath: this.routesByPath,
211
232
  routesById: this.routesById,
@@ -313,13 +334,18 @@ class RouterCore {
313
334
  const hashStr = hash ? `#${hash}` : "";
314
335
  let nextState = dest.state === true ? currentLocation.state : dest.state ? functionalUpdate(dest.state, currentLocation.state) : {};
315
336
  nextState = replaceEqualDeep(currentLocation.state, nextState);
337
+ const fullPath = `${nextPathname}${searchStr}${hashStr}`;
338
+ const url = new URL(fullPath, this.origin);
339
+ const rewrittenUrl = executeRewriteOutput(this.rewrite, url);
316
340
  return {
341
+ publicHref: rewrittenUrl.pathname + rewrittenUrl.search + rewrittenUrl.hash,
342
+ href: fullPath,
343
+ url: rewrittenUrl.href,
317
344
  pathname: nextPathname,
318
345
  search: nextSearch,
319
346
  searchStr,
320
347
  state: nextState,
321
348
  hash: hash ?? "",
322
- href: `${nextPathname}${searchStr}${hashStr}`,
323
349
  unmaskOnReload: dest.unmaskOnReload
324
350
  };
325
351
  };
@@ -330,7 +356,6 @@ class RouterCore {
330
356
  let params = {};
331
357
  const foundMask = this.options.routeMasks?.find((d) => {
332
358
  const match = matchPathname(
333
- this.basepath,
334
359
  next.pathname,
335
360
  {
336
361
  to: d.from,
@@ -356,8 +381,7 @@ class RouterCore {
356
381
  }
357
382
  }
358
383
  if (maskedNext) {
359
- const maskedFinal = build(maskedDest);
360
- next.maskedLocation = maskedFinal;
384
+ next.maskedLocation = maskedNext;
361
385
  }
362
386
  return next;
363
387
  };
@@ -391,7 +415,7 @@ class RouterCore {
391
415
  });
392
416
  return isEqual;
393
417
  };
394
- const isSameUrl = this.latestLocation.href === next.href;
418
+ const isSameUrl = trimPathRight(this.latestLocation.href) === trimPathRight(next.href);
395
419
  const previousCommitPromise = this.commitLocationPromise;
396
420
  this.commitLocationPromise = createControlledPromise(() => {
397
421
  previousCommitPromise?.resolve();
@@ -427,7 +451,7 @@ class RouterCore {
427
451
  nextHistory.state.__hashScrollIntoViewOptions = hashScrollIntoView ?? this.options.defaultHashScrollIntoView ?? true;
428
452
  this.shouldViewTransition = viewTransition;
429
453
  this.history[next.replace ? "replace" : "push"](
430
- nextHistory.href,
454
+ nextHistory.publicHref,
431
455
  nextHistory.state,
432
456
  { ignoreBlocker }
433
457
  );
@@ -480,7 +504,7 @@ class RouterCore {
480
504
  if (reloadDocument) {
481
505
  if (!href) {
482
506
  const location = this.buildLocation({ to, ...rest });
483
- href = this.history.createHref(location.href);
507
+ href = location.href;
484
508
  }
485
509
  if (rest.replace) {
486
510
  window.location.replace(href);
@@ -827,7 +851,6 @@ class RouterCore {
827
851
  const pending = opts?.pending === void 0 ? !this.state.isLoading : opts.pending;
828
852
  const baseLocation = pending ? this.latestLocation : this.state.resolvedLocation || this.state.location;
829
853
  const match = matchPathname(
830
- this.basepath,
831
854
  baseLocation.pathname,
832
855
  {
833
856
  ...opts,
@@ -1005,7 +1028,7 @@ class RouterCore {
1005
1028
  routeId: route.id,
1006
1029
  params: previousMatch ? replaceEqualDeep(previousMatch.params, routeParams) : routeParams,
1007
1030
  _strictParams: usedParams,
1008
- pathname: joinPaths([this.basepath, interpolatedPath]),
1031
+ pathname: interpolatedPath,
1009
1032
  updatedAt: Date.now(),
1010
1033
  search: previousMatch ? replaceEqualDeep(previousMatch.search, preMatchSearch) : preMatchSearch,
1011
1034
  _strictSearch: strictMatchSearch,
@@ -1257,7 +1280,6 @@ function processRouteTree({
1257
1280
  function getMatchedRoutes({
1258
1281
  pathname,
1259
1282
  routePathname,
1260
- basepath,
1261
1283
  caseSensitive,
1262
1284
  routesByPath,
1263
1285
  routesById,
@@ -1268,7 +1290,6 @@ function getMatchedRoutes({
1268
1290
  const trimmedPath = trimPathRight(pathname);
1269
1291
  const getMatchedParams = (route) => {
1270
1292
  const result = matchPathname(
1271
- basepath,
1272
1293
  trimmedPath,
1273
1294
  {
1274
1295
  to: route.fullPath,