@tanstack/react-router 1.48.4 → 1.49.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 (46) hide show
  1. package/dist/cjs/Matches.cjs +3 -1
  2. package/dist/cjs/Matches.cjs.map +1 -1
  3. package/dist/cjs/Matches.d.cts +5 -4
  4. package/dist/cjs/fileRoute.cjs.map +1 -1
  5. package/dist/cjs/fileRoute.d.cts +4 -4
  6. package/dist/cjs/index.cjs +2 -0
  7. package/dist/cjs/index.cjs.map +1 -1
  8. package/dist/cjs/index.d.cts +3 -1
  9. package/dist/cjs/route.cjs +1 -1
  10. package/dist/cjs/route.cjs.map +1 -1
  11. package/dist/cjs/route.d.cts +73 -59
  12. package/dist/cjs/router.cjs +90 -57
  13. package/dist/cjs/router.cjs.map +1 -1
  14. package/dist/cjs/router.d.cts +22 -9
  15. package/dist/cjs/transformer.cjs +39 -0
  16. package/dist/cjs/transformer.cjs.map +1 -0
  17. package/dist/cjs/transformer.d.cts +5 -0
  18. package/dist/cjs/utils.cjs.map +1 -1
  19. package/dist/cjs/utils.d.cts +1 -0
  20. package/dist/esm/Matches.d.ts +5 -4
  21. package/dist/esm/Matches.js +3 -1
  22. package/dist/esm/Matches.js.map +1 -1
  23. package/dist/esm/fileRoute.d.ts +4 -4
  24. package/dist/esm/fileRoute.js.map +1 -1
  25. package/dist/esm/index.d.ts +3 -1
  26. package/dist/esm/index.js +2 -0
  27. package/dist/esm/index.js.map +1 -1
  28. package/dist/esm/route.d.ts +73 -59
  29. package/dist/esm/route.js +1 -1
  30. package/dist/esm/route.js.map +1 -1
  31. package/dist/esm/router.d.ts +22 -9
  32. package/dist/esm/router.js +91 -58
  33. package/dist/esm/router.js.map +1 -1
  34. package/dist/esm/transformer.d.ts +5 -0
  35. package/dist/esm/transformer.js +39 -0
  36. package/dist/esm/transformer.js.map +1 -0
  37. package/dist/esm/utils.d.ts +1 -0
  38. package/dist/esm/utils.js.map +1 -1
  39. package/package.json +2 -2
  40. package/src/Matches.tsx +11 -7
  41. package/src/fileRoute.ts +26 -21
  42. package/src/index.tsx +5 -1
  43. package/src/route.ts +356 -186
  44. package/src/router.ts +144 -75
  45. package/src/transformer.ts +49 -0
  46. package/src/utils.ts +5 -0
@@ -1,7 +1,7 @@
1
1
  import { Store, NoInfer } from '@tanstack/react-store';
2
2
  import { HistoryState, RouterHistory } from '@tanstack/history';
3
3
  import { Manifest } from './manifest.js';
4
- import { AnyContext, AnyRoute, AnyRouteWithContext, AnySearchSchema, ErrorRouteComponent, NotFoundRouteComponent, RootRoute, RouteComponent, RouteMask } from './route.js';
4
+ import { AnyContext, AnyRoute, AnyRouteWithContext, ErrorRouteComponent, NotFoundRouteComponent, RootRoute, RouteComponent, RouteMask } from './route.js';
5
5
  import { FullSearchSchema, RouteById, RoutePaths, RoutesById, RoutesByPath } from './routeInfo.js';
6
6
  import { ControlledPromise, NonNullableUpdater, PickAsRequired, Updater } from './utils.js';
7
7
  import { AnyRouteMatch, MakeRouteMatch, MatchRouteOptions } from './Matches.js';
@@ -11,12 +11,17 @@ import { BuildLocationFn, CommitLocationOptions, NavigateFn } from './RouterProv
11
11
  import { AnyRedirect, ResolvedRedirect } from './redirects.js';
12
12
  import { NotFoundError } from './not-found.js';
13
13
  import { NavigateOptions, ResolveRelativePath, ToOptions } from './link.js';
14
+ import { RouterTransformer } from './transformer.js';
14
15
 
15
16
  import type * as React from 'react';
16
17
  declare global {
17
18
  interface Window {
18
19
  __TSR__?: {
19
- matches: Array<any>;
20
+ matches: Array<{
21
+ __beforeLoadContext?: string;
22
+ loaderData?: string;
23
+ extracted?: Array<ExtractedEntry>;
24
+ }>;
20
25
  streamedValues: Record<string, {
21
26
  value: any;
22
27
  parsed: any;
@@ -38,7 +43,19 @@ export type HydrationCtx = {
38
43
  router: DehydratedRouter;
39
44
  payload: Record<string, any>;
40
45
  };
41
- export type InferRouterContext<TRouteTree extends AnyRoute> = TRouteTree extends RootRoute<any, any, any, infer TRouterContext extends AnyContext, any, any, any, any> ? TRouterContext : AnyContext;
46
+ export type InferRouterContext<TRouteTree extends AnyRoute> = TRouteTree extends RootRoute<any, infer TRouterContext extends AnyContext, any, any, any, any, any, any> ? TRouterContext : AnyContext;
47
+ export type ExtractedEntry = {
48
+ dataType: '__beforeLoadContext' | 'loaderData';
49
+ type: 'promise' | 'stream';
50
+ path: Array<string>;
51
+ value: any;
52
+ id: number;
53
+ streamState?: StreamState;
54
+ matchIndex: number;
55
+ };
56
+ export type StreamState = {
57
+ promises: Array<ControlledPromise<string | null>>;
58
+ };
42
59
  export type RouterContextOptions<TRouteTree extends AnyRoute> = AnyContext extends InferRouterContext<TRouteTree> ? {
43
60
  context?: InferRouterContext<TRouteTree>;
44
61
  } : {
@@ -328,10 +345,6 @@ export interface RouterOptions<TRouteTree extends AnyRoute, TTrailingSlashOption
328
345
  */
329
346
  isServer?: boolean;
330
347
  }
331
- export interface RouterTransformer {
332
- stringify: (obj: unknown) => string;
333
- parse: (str: string) => unknown;
334
- }
335
348
  export interface RouterErrorSerializer<TSerializedError> {
336
349
  serialize: (err: unknown) => TSerializedError;
337
350
  deserialize: (err: TSerializedError) => unknown;
@@ -422,7 +435,7 @@ export declare class Router<in out TRouteTree extends AnyRoute, in out TTrailing
422
435
  match: Pick<AnyRouteMatch, 'id' | 'status' | 'error' | 'loadPromise' | 'minPendingPromise'>;
423
436
  matchIndex: number;
424
437
  }) => any;
425
- serializeLoaderData?: (data: any, ctx: {
438
+ serializeLoaderData?: (type: '__beforeLoadContext' | 'loaderData', loaderData: any, ctx: {
426
439
  router: AnyRouter;
427
440
  match: AnyRouteMatch;
428
441
  }) => any;
@@ -452,7 +465,7 @@ export declare class Router<in out TRouteTree extends AnyRoute, in out TTrailing
452
465
  parseLocation: (previousLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>) => ParsedLocation<FullSearchSchema<TRouteTree>>;
453
466
  resolvePathWithBase: (from: string, path: string) => string;
454
467
  get looseRoutesById(): Record<string, AnyRoute>;
455
- matchRoutes: (pathname: string, locationSearch: AnySearchSchema, opts?: {
468
+ matchRoutes: (next: ParsedLocation, opts?: {
456
469
  preload?: boolean;
457
470
  throwOnError?: boolean;
458
471
  }) => Array<AnyRouteMatch>;
@@ -1,4 +1,4 @@
1
- import { createMemoryHistory, createBrowserHistory } from "@tanstack/history";
1
+ import { createMemoryHistory, createBrowserHistory, parseHref } from "@tanstack/history";
2
2
  import { Store } from "@tanstack/react-store";
3
3
  import invariant from "tiny-invariant";
4
4
  import warning from "tiny-warning";
@@ -8,6 +8,7 @@ import { createControlledPromise, replaceEqualDeep, pick, deepEqual, last, funct
8
8
  import { trimPath, trimPathLeft, parsePathname, resolvePath, cleanPath, matchPathname, trimPathRight, interpolatePath, joinPaths } from "./path.js";
9
9
  import { isResolvedRedirect, isRedirect } from "./redirects.js";
10
10
  import { isNotFound } from "./not-found.js";
11
+ import { defaultTransformer } from "./transformer.js";
11
12
  const componentTypes = [
12
13
  "component",
13
14
  "errorComponent",
@@ -208,12 +209,12 @@ class Router {
208
209
  });
209
210
  return resolvedPath;
210
211
  };
211
- this.matchRoutes = (pathname, locationSearch, opts) => {
212
+ this.matchRoutes = (next, opts) => {
212
213
  let routeParams = {};
213
214
  const foundRoute = this.flatRoutes.find((route) => {
214
215
  const matchedParams = matchPathname(
215
216
  this.basepath,
216
- trimPathRight(pathname),
217
+ trimPathRight(next.pathname),
217
218
  {
218
219
  to: route.fullPath,
219
220
  caseSensitive: route.options.caseSensitive ?? this.options.caseSensitive,
@@ -233,7 +234,7 @@ class Router {
233
234
  // If we found a route, and it's not an index route and we have left over path
234
235
  foundRoute ? foundRoute.path !== "/" && routeParams["**"] : (
235
236
  // Or if we didn't find a route and we have left over path
236
- trimPathRight(pathname)
237
+ trimPathRight(next.pathname)
237
238
  )
238
239
  ) {
239
240
  if (this.options.notFoundRoute) {
@@ -282,10 +283,10 @@ class Router {
282
283
  });
283
284
  const matches = [];
284
285
  matchedRoutes.forEach((route, index) => {
285
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
286
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
286
287
  const parentMatch = matches[index - 1];
287
288
  const [preMatchSearch, searchError] = (() => {
288
- const parentSearch = (parentMatch == null ? void 0 : parentMatch.search) ?? locationSearch;
289
+ const parentSearch = (parentMatch == null ? void 0 : parentMatch.search) ?? next.search;
289
290
  try {
290
291
  const validator = typeof route.options.validateSearch === "object" ? route.options.validateSearch.parse : route.options.validateSearch;
291
292
  const search = (validator == null ? void 0 : validator(parentSearch)) ?? {};
@@ -343,8 +344,9 @@ class Router {
343
344
  isFetching: false,
344
345
  error: void 0,
345
346
  paramsError: parseErrors[index],
346
- routeContext: void 0,
347
- context: void 0,
347
+ __routeContext: {},
348
+ __beforeLoadContext: {},
349
+ context: {},
348
350
  abortController: new AbortController(),
349
351
  fetchCount: 0,
350
352
  cause,
@@ -373,6 +375,30 @@ class Router {
373
375
  }
374
376
  match.search = replaceEqualDeep(match.search, preMatchSearch);
375
377
  match.searchError = searchError;
378
+ const parentMatchId = parentMatch == null ? void 0 : parentMatch.id;
379
+ const parentContext = !parentMatchId ? this.options.context ?? {} : parentMatch.context ?? this.options.context ?? {};
380
+ match.context = {
381
+ ...parentContext,
382
+ ...match.__routeContext,
383
+ ...match.__beforeLoadContext
384
+ };
385
+ const contextFnContext = {
386
+ search: match.search,
387
+ params: match.params,
388
+ context: match.context,
389
+ location: next,
390
+ navigate: (opts2) => this.navigate({ ...opts2, _fromLocation: next }),
391
+ buildLocation: this.buildLocation,
392
+ cause: match.cause,
393
+ abortController: match.abortController,
394
+ preload: !!match.preload
395
+ };
396
+ match.__routeContext = ((_l = (_k = route.options).context) == null ? void 0 : _l.call(_k, contextFnContext)) ?? {};
397
+ match.context = {
398
+ ...parentContext,
399
+ ...match.__routeContext,
400
+ ...match.__beforeLoadContext
401
+ };
376
402
  matches.push(match);
377
403
  });
378
404
  return matches;
@@ -392,10 +418,10 @@ class Router {
392
418
  this.buildLocation = (opts) => {
393
419
  const build = (dest = {}, matches) => {
394
420
  var _a, _b, _c;
395
- const fromMatches = dest._fromLocation != null ? this.matchRoutes(
396
- dest._fromLocation.pathname,
397
- dest.fromSearch || dest._fromLocation.search
398
- ) : this.state.matches;
421
+ const fromMatches = dest._fromLocation != null ? this.matchRoutes({
422
+ ...dest._fromLocation,
423
+ search: dest.fromSearch || dest._fromLocation.search
424
+ }) : this.state.matches;
399
425
  const fromMatch = dest.from != null ? fromMatches.find(
400
426
  (d) => matchPathname(this.basepath, trimPathRight(d.pathname), {
401
427
  to: dest.from,
@@ -487,8 +513,8 @@ class Router {
487
513
  maskedNext = build(maskedDest);
488
514
  }
489
515
  }
490
- const nextMatches = this.matchRoutes(next.pathname, next.search);
491
- const maskedMatches = maskedNext ? this.matchRoutes(maskedNext.pathname, maskedNext.search) : void 0;
516
+ const nextMatches = this.matchRoutes(next);
517
+ const maskedMatches = maskedNext ? this.matchRoutes(maskedNext) : void 0;
492
518
  const maskedFinal = maskedNext ? build(maskedDest, maskedMatches) : void 0;
493
519
  const final = build(dest, nextMatches);
494
520
  if (maskedFinal) {
@@ -566,6 +592,13 @@ class Router {
566
592
  ignoreBlocker,
567
593
  ...rest
568
594
  } = {}) => {
595
+ const href = rest.href;
596
+ if (href) {
597
+ const parsed = parseHref(href, {});
598
+ rest.to = parsed.pathname;
599
+ rest.search = this.options.parseSearch(parsed.search);
600
+ rest.hash = parsed.hash;
601
+ }
569
602
  const location = this.buildLocation(rest);
570
603
  return this.commitLocation({
571
604
  ...location,
@@ -575,7 +608,7 @@ class Router {
575
608
  ignoreBlocker
576
609
  });
577
610
  };
578
- this.navigate = ({ from, to, __isRedirect, ...rest }) => {
611
+ this.navigate = ({ to, __isRedirect, ...rest }) => {
579
612
  const toString = String(to);
580
613
  let isExternal;
581
614
  try {
@@ -589,7 +622,6 @@ class Router {
589
622
  );
590
623
  return this.buildAndCommitLocation({
591
624
  ...rest,
592
- from,
593
625
  to
594
626
  // to: toString,
595
627
  });
@@ -602,7 +634,8 @@ class Router {
602
634
  }));
603
635
  let redirect;
604
636
  let notFound;
605
- const loadPromise = new Promise((resolve) => {
637
+ let loadPromise;
638
+ loadPromise = new Promise((resolve) => {
606
639
  this.startReactTransition(async () => {
607
640
  var _a;
608
641
  try {
@@ -612,7 +645,7 @@ class Router {
612
645
  this.cancelMatches();
613
646
  let pendingMatches;
614
647
  this.__store.batch(() => {
615
- pendingMatches = this.matchRoutes(next.pathname, next.search);
648
+ pendingMatches = this.matchRoutes(next);
616
649
  this.__store.setState((s) => ({
617
650
  ...s,
618
651
  status: "pending",
@@ -832,6 +865,7 @@ class Router {
832
865
  };
833
866
  for (const [index, { id: matchId, routeId }] of matches.entries()) {
834
867
  const existingMatch = this.getMatch(matchId);
868
+ const parentMatchId = (_a = matches[index - 1]) == null ? void 0 : _a.id;
835
869
  if (
836
870
  // If we are in the middle of a load, either of these will be present
837
871
  // (not to be confused with `loadPromise`, which is always defined)
@@ -850,13 +884,6 @@ class Router {
850
884
  }));
851
885
  const route = this.looseRoutesById[routeId];
852
886
  const abortController = new AbortController();
853
- const parentMatchId = (_a = matches[index - 1]) == null ? void 0 : _a.id;
854
- const getParentContext = () => {
855
- if (!parentMatchId) {
856
- return this.options.context ?? {};
857
- }
858
- return this.getMatch(parentMatchId).context ?? this.options.context ?? {};
859
- };
860
887
  const pendingMs = route.options.pendingMs ?? this.options.defaultPendingMs;
861
888
  const shouldPending = !!(onReady && !this.isServer && !preload && (route.options.loader || route.options.beforeLoad) && typeof pendingMs === "number" && pendingMs !== Infinity && (route.options.pendingComponent ?? this.options.defaultPendingComponent));
862
889
  let pendingTimeout;
@@ -875,47 +902,54 @@ class Router {
875
902
  if (searchError) {
876
903
  handleSerialError(index, searchError, "VALIDATE_SEARCH");
877
904
  }
878
- const parentContext = getParentContext();
905
+ const getParentMatchContext = () => parentMatchId ? this.getMatch(parentMatchId).context : this.options.context ?? {};
879
906
  updateMatch(matchId, (prev) => ({
880
907
  ...prev,
881
908
  isFetching: "beforeLoad",
882
909
  fetchCount: prev.fetchCount + 1,
883
- routeContext: replaceEqualDeep(
884
- prev.routeContext,
885
- parentContext
886
- ),
887
- context: replaceEqualDeep(prev.context, parentContext),
888
910
  abortController,
889
- pendingTimeout
911
+ pendingTimeout,
912
+ context: {
913
+ ...getParentMatchContext(),
914
+ ...prev.__routeContext,
915
+ ...prev.__beforeLoadContext
916
+ }
890
917
  }));
891
- const { search, params, routeContext, cause } = this.getMatch(matchId);
918
+ const { search, params, context, cause } = this.getMatch(matchId);
892
919
  const beforeLoadFnContext = {
893
920
  search,
894
921
  abortController,
895
922
  params,
896
923
  preload: !!preload,
897
- context: routeContext,
924
+ context,
898
925
  location,
899
926
  navigate: (opts) => this.navigate({ ...opts, _fromLocation: location }),
900
927
  buildLocation: this.buildLocation,
901
928
  cause: preload ? "preload" : cause
902
929
  };
903
- const beforeLoadContext = await ((_c = (_b = route.options).beforeLoad) == null ? void 0 : _c.call(_b, beforeLoadFnContext)) ?? {};
930
+ let beforeLoadContext = await ((_c = (_b = route.options).beforeLoad) == null ? void 0 : _c.call(_b, beforeLoadFnContext)) ?? {};
931
+ if (this.serializeLoaderData) {
932
+ beforeLoadContext = this.serializeLoaderData(
933
+ "__beforeLoadContext",
934
+ beforeLoadContext,
935
+ {
936
+ router: this,
937
+ match: this.getMatch(matchId)
938
+ }
939
+ );
940
+ }
904
941
  if (isRedirect(beforeLoadContext) || isNotFound(beforeLoadContext)) {
905
942
  handleSerialError(index, beforeLoadContext, "BEFORE_LOAD");
906
943
  }
907
944
  updateMatch(matchId, (prev) => {
908
- const routeContext2 = {
909
- ...prev.routeContext,
910
- ...beforeLoadContext
911
- };
912
945
  return {
913
946
  ...prev,
914
- routeContext: replaceEqualDeep(
915
- prev.routeContext,
916
- routeContext2
917
- ),
918
- context: replaceEqualDeep(prev.context, routeContext2),
947
+ __beforeLoadContext: beforeLoadContext,
948
+ context: {
949
+ ...getParentMatchContext(),
950
+ ...prev.__routeContext,
951
+ ...beforeLoadContext
952
+ },
919
953
  abortController
920
954
  };
921
955
  });
@@ -1008,10 +1042,14 @@ class Router {
1008
1042
  await route._lazyPromise;
1009
1043
  let loaderData = await ((_b2 = (_a2 = route.options).loader) == null ? void 0 : _b2.call(_a2, getLoaderContext()));
1010
1044
  if (this.serializeLoaderData) {
1011
- loaderData = this.serializeLoaderData(loaderData, {
1012
- router: this,
1013
- match: this.getMatch(matchId)
1014
- });
1045
+ loaderData = this.serializeLoaderData(
1046
+ "loaderData",
1047
+ loaderData,
1048
+ {
1049
+ router: this,
1050
+ match: this.getMatch(matchId)
1051
+ }
1052
+ );
1015
1053
  }
1016
1054
  handleRedirectAndNotFound(
1017
1055
  this.getMatch(matchId),
@@ -1063,7 +1101,8 @@ class Router {
1063
1101
  }
1064
1102
  };
1065
1103
  const { status, invalid } = this.getMatch(matchId);
1066
- if (status === "success" && (invalid || (shouldReload ?? age > staleAge))) {
1104
+ if (preload && route.options.preload === false) {
1105
+ } else if (status === "success" && (invalid || (shouldReload ?? age > staleAge))) {
1067
1106
  ;
1068
1107
  (async () => {
1069
1108
  try {
@@ -1145,7 +1184,7 @@ class Router {
1145
1184
  };
1146
1185
  this.preloadRoute = async (opts) => {
1147
1186
  const next = this.buildLocation(opts);
1148
- let matches = this.matchRoutes(next.pathname, next.search, {
1187
+ let matches = this.matchRoutes(next, {
1149
1188
  throwOnError: true,
1150
1189
  preload: true
1151
1190
  });
@@ -1263,10 +1302,7 @@ class Router {
1263
1302
  this.dehydratedData = ctx.payload;
1264
1303
  (_c = (_b = this.options).hydrate) == null ? void 0 : _c.call(_b, ctx.payload);
1265
1304
  const dehydratedState = ctx.router.state;
1266
- const matches = this.matchRoutes(
1267
- this.state.location.pathname,
1268
- this.state.location.search
1269
- ).map((match) => {
1305
+ const matches = this.matchRoutes(this.state.location).map((match) => {
1270
1306
  const dehydratedMatch = dehydratedState.dehydratedMatches.find(
1271
1307
  (d) => d.id === match.id
1272
1308
  );
@@ -1366,10 +1402,7 @@ class Router {
1366
1402
  notFoundMode: options.notFoundMode ?? "fuzzy",
1367
1403
  stringifySearch: options.stringifySearch ?? defaultStringifySearch,
1368
1404
  parseSearch: options.parseSearch ?? defaultParseSearch,
1369
- transformer: options.transformer ?? {
1370
- parse: JSON.parse,
1371
- stringify: JSON.stringify
1372
- }
1405
+ transformer: options.transformer ?? defaultTransformer
1373
1406
  });
1374
1407
  if (typeof document !== "undefined") {
1375
1408
  window.__TSR__ROUTER__ = this;