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

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,
@@ -391,7 +416,7 @@ class RouterCore {
391
416
  });
392
417
  return isEqual;
393
418
  };
394
- const isSameUrl = this.latestLocation.href === next.href;
419
+ const isSameUrl = trimPathRight(this.latestLocation.href) === trimPathRight(next.href);
395
420
  const previousCommitPromise = this.commitLocationPromise;
396
421
  this.commitLocationPromise = createControlledPromise(() => {
397
422
  previousCommitPromise?.resolve();
@@ -427,7 +452,7 @@ class RouterCore {
427
452
  nextHistory.state.__hashScrollIntoViewOptions = hashScrollIntoView ?? this.options.defaultHashScrollIntoView ?? true;
428
453
  this.shouldViewTransition = viewTransition;
429
454
  this.history[next.replace ? "replace" : "push"](
430
- nextHistory.href,
455
+ nextHistory.publicHref,
431
456
  nextHistory.state,
432
457
  { ignoreBlocker }
433
458
  );
@@ -480,7 +505,7 @@ class RouterCore {
480
505
  if (reloadDocument) {
481
506
  if (!href) {
482
507
  const location = this.buildLocation({ to, ...rest });
483
- href = this.history.createHref(location.href);
508
+ href = location.href;
484
509
  }
485
510
  if (rest.replace) {
486
511
  window.location.replace(href);
@@ -827,7 +852,6 @@ class RouterCore {
827
852
  const pending = opts?.pending === void 0 ? !this.state.isLoading : opts.pending;
828
853
  const baseLocation = pending ? this.latestLocation : this.state.resolvedLocation || this.state.location;
829
854
  const match = matchPathname(
830
- this.basepath,
831
855
  baseLocation.pathname,
832
856
  {
833
857
  ...opts,
@@ -1005,7 +1029,7 @@ class RouterCore {
1005
1029
  routeId: route.id,
1006
1030
  params: previousMatch ? replaceEqualDeep(previousMatch.params, routeParams) : routeParams,
1007
1031
  _strictParams: usedParams,
1008
- pathname: joinPaths([this.basepath, interpolatedPath]),
1032
+ pathname: interpolatedPath,
1009
1033
  updatedAt: Date.now(),
1010
1034
  search: previousMatch ? replaceEqualDeep(previousMatch.search, preMatchSearch) : preMatchSearch,
1011
1035
  _strictSearch: strictMatchSearch,
@@ -1257,7 +1281,6 @@ function processRouteTree({
1257
1281
  function getMatchedRoutes({
1258
1282
  pathname,
1259
1283
  routePathname,
1260
- basepath,
1261
1284
  caseSensitive,
1262
1285
  routesByPath,
1263
1286
  routesById,
@@ -1268,7 +1291,6 @@ function getMatchedRoutes({
1268
1291
  const trimmedPath = trimPathRight(pathname);
1269
1292
  const getMatchedParams = (route) => {
1270
1293
  const result = matchPathname(
1271
- basepath,
1272
1294
  trimmedPath,
1273
1295
  {
1274
1296
  to: route.fullPath,