@tanstack/router-core 1.154.14 → 1.156.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.
@@ -27,10 +27,19 @@ interface ResolvePathOptions {
27
27
  * and supporting relative segments (`.`/`..`) and absolute `to` values.
28
28
  */
29
29
  export declare function resolvePath({ base, to, trailingSlash, cache, }: ResolvePathOptions): string;
30
+ /**
31
+ * Create a pre-compiled decode config from allowed characters.
32
+ * This should be called once at router initialization.
33
+ */
34
+ export declare function compileDecodeCharMap(pathParamsAllowedCharacters: ReadonlyArray<string>): (encoded: string) => string;
30
35
  interface InterpolatePathOptions {
31
36
  path?: string;
32
37
  params: Record<string, unknown>;
33
- decodeCharMap?: Map<string, string>;
38
+ /**
39
+ * A function that decodes a path parameter value.
40
+ * Obtained from `compileDecodeCharMap(pathParamsAllowedCharacters)`.
41
+ */
42
+ decoder?: (encoded: string) => string;
34
43
  }
35
44
  type InterPolatePathResult = {
36
45
  interpolatedPath: string;
@@ -43,5 +52,5 @@ type InterPolatePathResult = {
43
52
  * - Encodes params safely (configurable allowed characters)
44
53
  * - Supports `{-$optional}` segments, `{prefix{$id}suffix}` and `{$}` wildcards
45
54
  */
46
- export declare function interpolatePath({ path, params, decodeCharMap, }: InterpolatePathOptions): InterPolatePathResult;
55
+ export declare function interpolatePath({ path, params, decoder, }: InterpolatePathOptions): InterPolatePathResult;
47
56
  export {};
@@ -71,12 +71,10 @@ class RouterCore {
71
71
  ...newOptions
72
72
  };
73
73
  this.isServer = this.options.isServer ?? typeof document === "undefined";
74
- this.pathParamsDecodeCharMap = this.options.pathParamsAllowedCharacters ? new Map(
75
- this.options.pathParamsAllowedCharacters.map((char) => [
76
- encodeURIComponent(char),
77
- char
78
- ])
79
- ) : void 0;
74
+ if (this.options.pathParamsAllowedCharacters)
75
+ this.pathParamsDecoder = path.compileDecodeCharMap(
76
+ this.options.pathParamsAllowedCharacters
77
+ );
80
78
  if (!this.history || this.options.history && this.options.history !== this.history) {
81
79
  if (!this.options.history) {
82
80
  if (!this.isServer) {
@@ -99,7 +97,23 @@ class RouterCore {
99
97
  }
100
98
  if (this.options.routeTree !== this.routeTree) {
101
99
  this.routeTree = this.options.routeTree;
102
- this.buildRouteTree();
100
+ let processRouteTreeResult;
101
+ if (this.isServer && globalThis.__TSR_CACHE__ && globalThis.__TSR_CACHE__.routeTree === this.routeTree) {
102
+ const cached = globalThis.__TSR_CACHE__;
103
+ this.resolvePathCache = cached.resolvePathCache;
104
+ processRouteTreeResult = cached.processRouteTreeResult;
105
+ } else {
106
+ this.resolvePathCache = lruCache.createLRUCache(1e3);
107
+ processRouteTreeResult = this.buildRouteTree();
108
+ if (this.isServer && globalThis.__TSR_CACHE__ === void 0) {
109
+ globalThis.__TSR_CACHE__ = {
110
+ routeTree: this.routeTree,
111
+ processRouteTreeResult,
112
+ resolvePathCache: this.resolvePathCache
113
+ };
114
+ }
115
+ }
116
+ this.setRoutes(processRouteTreeResult);
103
117
  }
104
118
  if (!this.__store && this.latestLocation) {
105
119
  this.__store = new store.Store(getInitialRouterState(this.latestLocation), {
@@ -157,7 +171,7 @@ class RouterCore {
157
171
  );
158
172
  };
159
173
  this.buildRouteTree = () => {
160
- const { routesById, routesByPath, processedTree } = newProcessRouteTree.processRouteTree(
174
+ const result = newProcessRouteTree.processRouteTree(
161
175
  this.routeTree,
162
176
  this.options.caseSensitive,
163
177
  (route, i) => {
@@ -167,18 +181,9 @@ class RouterCore {
167
181
  }
168
182
  );
169
183
  if (this.options.routeMasks) {
170
- newProcessRouteTree.processRouteMasks(this.options.routeMasks, processedTree);
171
- }
172
- this.routesById = routesById;
173
- this.routesByPath = routesByPath;
174
- this.processedTree = processedTree;
175
- const notFoundRoute = this.options.notFoundRoute;
176
- if (notFoundRoute) {
177
- notFoundRoute.init({
178
- originalIndex: 99999999999
179
- });
180
- this.routesById[notFoundRoute.id] = notFoundRoute;
184
+ newProcessRouteTree.processRouteMasks(this.options.routeMasks, result.processedTree);
181
185
  }
186
+ return result;
182
187
  };
183
188
  this.subscribe = (eventType, fn) => {
184
189
  const listener = {
@@ -233,7 +238,6 @@ class RouterCore {
233
238
  }
234
239
  return location;
235
240
  };
236
- this.resolvePathCache = lruCache.createLRUCache(1e3);
237
241
  this.resolvePathWithBase = (from, path$1) => {
238
242
  const resolvedPath = path.resolvePath({
239
243
  base: from,
@@ -288,26 +292,23 @@ class RouterCore {
288
292
  this.buildLocation = (opts) => {
289
293
  const build = (dest = {}) => {
290
294
  const currentLocation = dest._fromLocation || this.pendingBuiltLocation || this.latestLocation;
291
- const allCurrentLocationMatches = this.matchRoutes(currentLocation, {
292
- _buildLocation: true
293
- });
294
- const lastMatch = utils.last(allCurrentLocationMatches);
295
+ const lightweightResult = this.matchRoutesLightweight(currentLocation);
295
296
  if (dest.from && process.env.NODE_ENV !== "production" && dest._isNavigate) {
296
297
  const allFromMatches = this.getMatchedRoutes(dest.from).matchedRoutes;
297
- const matchedFrom = utils.findLast(allCurrentLocationMatches, (d) => {
298
+ const matchedFrom = utils.findLast(lightweightResult.matchedRoutes, (d) => {
298
299
  return comparePaths(d.fullPath, dest.from);
299
300
  });
300
301
  const matchedCurrent = utils.findLast(allFromMatches, (d) => {
301
- return comparePaths(d.fullPath, lastMatch.fullPath);
302
+ return comparePaths(d.fullPath, lightweightResult.fullPath);
302
303
  });
303
304
  if (!matchedFrom && !matchedCurrent) {
304
305
  console.warn(`Could not find match for from: ${dest.from}`);
305
306
  }
306
307
  }
307
- const defaultedFromPath = dest.unsafeRelative === "path" ? currentLocation.pathname : dest.from ?? lastMatch.fullPath;
308
+ const defaultedFromPath = dest.unsafeRelative === "path" ? currentLocation.pathname : dest.from ?? lightweightResult.fullPath;
308
309
  const fromPath = this.resolvePathWithBase(defaultedFromPath, ".");
309
- const fromSearch = lastMatch.search;
310
- const fromParams = { ...lastMatch.params };
310
+ const fromSearch = lightweightResult.search;
311
+ const fromParams = { ...lightweightResult.params };
311
312
  const nextTo = dest.to ? this.resolvePathWithBase(fromPath, `${dest.to}`) : this.resolvePathWithBase(fromPath, ".");
312
313
  const nextParams = dest.params === false || dest.params === null ? {} : (dest.params ?? true) === true ? fromParams : Object.assign(
313
314
  fromParams,
@@ -346,7 +347,7 @@ class RouterCore {
346
347
  path.interpolatePath({
347
348
  path: nextTo,
348
349
  params: nextParams,
349
- decodeCharMap: this.pathParamsDecodeCharMap
350
+ decoder: this.pathParamsDecoder
350
351
  }).interpolatedPath
351
352
  );
352
353
  let nextSearch = fromSearch;
@@ -1036,6 +1037,22 @@ class RouterCore {
1036
1037
  get state() {
1037
1038
  return this.__store.state;
1038
1039
  }
1040
+ setRoutes({
1041
+ routesById,
1042
+ routesByPath,
1043
+ processedTree
1044
+ }) {
1045
+ this.routesById = routesById;
1046
+ this.routesByPath = routesByPath;
1047
+ this.processedTree = processedTree;
1048
+ const notFoundRoute = this.options.notFoundRoute;
1049
+ if (notFoundRoute) {
1050
+ notFoundRoute.init({
1051
+ originalIndex: 99999999999
1052
+ });
1053
+ this.routesById[notFoundRoute.id] = notFoundRoute;
1054
+ }
1055
+ }
1039
1056
  get looseRoutesById() {
1040
1057
  return this.routesById;
1041
1058
  }
@@ -1099,7 +1116,7 @@ class RouterCore {
1099
1116
  const { interpolatedPath, usedParams } = path.interpolatePath({
1100
1117
  path: route.fullPath,
1101
1118
  params: routeParams,
1102
- decodeCharMap: this.pathParamsDecodeCharMap
1119
+ decoder: this.pathParamsDecoder
1103
1120
  });
1104
1121
  const matchId = (
1105
1122
  // route.id for disambiguation
@@ -1114,32 +1131,18 @@ class RouterCore {
1114
1131
  const strictParams = existingMatch?._strictParams ?? usedParams;
1115
1132
  let paramsError = void 0;
1116
1133
  if (!existingMatch) {
1117
- if (route.options.skipRouteOnParseError) {
1118
- for (const key in usedParams) {
1119
- if (key in parsedParams) {
1120
- strictParams[key] = parsedParams[key];
1121
- }
1134
+ try {
1135
+ extractStrictParams(route, usedParams, parsedParams, strictParams);
1136
+ } catch (err) {
1137
+ if (notFound.isNotFound(err) || redirect.isRedirect(err)) {
1138
+ paramsError = err;
1139
+ } else {
1140
+ paramsError = new PathParamError(err.message, {
1141
+ cause: err
1142
+ });
1122
1143
  }
1123
- } else {
1124
- const strictParseParams = route.options.params?.parse ?? route.options.parseParams;
1125
- if (strictParseParams) {
1126
- try {
1127
- Object.assign(
1128
- strictParams,
1129
- strictParseParams(strictParams)
1130
- );
1131
- } catch (err) {
1132
- if (notFound.isNotFound(err) || redirect.isRedirect(err)) {
1133
- paramsError = err;
1134
- } else {
1135
- paramsError = new PathParamError(err.message, {
1136
- cause: err
1137
- });
1138
- }
1139
- if (opts?.throwOnError) {
1140
- throw paramsError;
1141
- }
1142
- }
1144
+ if (opts?.throwOnError) {
1145
+ throw paramsError;
1143
1146
  }
1144
1147
  }
1145
1148
  }
@@ -1208,7 +1211,7 @@ class RouterCore {
1208
1211
  matches.forEach((match, index) => {
1209
1212
  const route = this.looseRoutesById[match.routeId];
1210
1213
  const existingMatch = this.getMatch(match.id);
1211
- if (!existingMatch && opts?._buildLocation !== true) {
1214
+ if (!existingMatch) {
1212
1215
  const parentMatch = matches[index - 1];
1213
1216
  const parentContext = getParentContext(parentMatch);
1214
1217
  if (route.options.context) {
@@ -1235,6 +1238,53 @@ class RouterCore {
1235
1238
  });
1236
1239
  return matches;
1237
1240
  }
1241
+ /**
1242
+ * Lightweight route matching for buildLocation.
1243
+ * Only computes fullPath, accumulated search, and params - skipping expensive
1244
+ * operations like AbortController, ControlledPromise, loaderDeps, and full match objects.
1245
+ */
1246
+ matchRoutesLightweight(location) {
1247
+ const { matchedRoutes, routeParams, parsedParams } = this.getMatchedRoutes(
1248
+ location.pathname
1249
+ );
1250
+ const lastRoute = utils.last(matchedRoutes);
1251
+ const accumulatedSearch = { ...location.search };
1252
+ for (const route of matchedRoutes) {
1253
+ try {
1254
+ Object.assign(
1255
+ accumulatedSearch,
1256
+ validateSearch(route.options.validateSearch, accumulatedSearch)
1257
+ );
1258
+ } catch {
1259
+ }
1260
+ }
1261
+ const lastStateMatch = utils.last(this.state.matches);
1262
+ const canReuseParams = lastStateMatch && lastStateMatch.routeId === lastRoute.id && location.pathname === this.state.location.pathname;
1263
+ let params;
1264
+ if (canReuseParams) {
1265
+ params = lastStateMatch.params;
1266
+ } else {
1267
+ const strictParams = { ...routeParams };
1268
+ for (const route of matchedRoutes) {
1269
+ try {
1270
+ extractStrictParams(
1271
+ route,
1272
+ routeParams,
1273
+ parsedParams ?? {},
1274
+ strictParams
1275
+ );
1276
+ } catch {
1277
+ }
1278
+ }
1279
+ params = strictParams;
1280
+ }
1281
+ return {
1282
+ matchedRoutes,
1283
+ fullPath: lastRoute.fullPath,
1284
+ search: accumulatedSearch,
1285
+ params
1286
+ };
1287
+ }
1238
1288
  }
1239
1289
  class SearchParamError extends Error {
1240
1290
  }
@@ -1397,6 +1447,21 @@ function findGlobalNotFoundRouteId(notFoundMode, routes) {
1397
1447
  }
1398
1448
  return root.rootRouteId;
1399
1449
  }
1450
+ function extractStrictParams(route, referenceParams, parsedParams, accumulatedParams) {
1451
+ const parseParams = route.options.params?.parse ?? route.options.parseParams;
1452
+ if (parseParams) {
1453
+ if (route.options.skipRouteOnParseError) {
1454
+ for (const key in referenceParams) {
1455
+ if (key in parsedParams) {
1456
+ accumulatedParams[key] = parsedParams[key];
1457
+ }
1458
+ }
1459
+ } else {
1460
+ const result = parseParams(accumulatedParams);
1461
+ Object.assign(accumulatedParams, result);
1462
+ }
1463
+ }
1464
+ }
1400
1465
  exports.PathParamError = PathParamError;
1401
1466
  exports.RouterCore = RouterCore;
1402
1467
  exports.SearchParamError = SearchParamError;