@remix-run/router 1.19.0 → 1.19.1

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.
package/dist/utils.d.ts CHANGED
@@ -199,7 +199,7 @@ export interface DataStrategyFunctionArgs<Context = any> extends DataFunctionArg
199
199
  export interface DataStrategyFunction {
200
200
  (args: DataStrategyFunctionArgs): Promise<HandlerResult[]>;
201
201
  }
202
- export interface AgnosticPatchRoutesOnMissFunction<M extends AgnosticRouteMatch = AgnosticRouteMatch> {
202
+ export interface AgnosticPatchRoutesOnNavigationFunction<M extends AgnosticRouteMatch = AgnosticRouteMatch> {
203
203
  (opts: {
204
204
  path: string;
205
205
  matches: M[];
package/index.ts CHANGED
@@ -23,7 +23,7 @@ export type {
23
23
  LoaderFunctionArgs,
24
24
  ParamParseKey,
25
25
  Params,
26
- AgnosticPatchRoutesOnMissFunction as unstable_AgnosticPatchRoutesOnMissFunction,
26
+ AgnosticPatchRoutesOnNavigationFunction as unstable_AgnosticPatchRoutesOnNavigationFunction,
27
27
  PathMatch,
28
28
  PathParam,
29
29
  PathPattern,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remix-run/router",
3
- "version": "1.19.0",
3
+ "version": "1.19.1",
4
4
  "description": "Nested/Data-driven/Framework-agnostic Routing",
5
5
  "keywords": [
6
6
  "remix",
package/router.ts CHANGED
@@ -35,7 +35,7 @@ import type {
35
35
  UIMatch,
36
36
  V7_FormMethod,
37
37
  V7_MutationFormMethod,
38
- AgnosticPatchRoutesOnMissFunction,
38
+ AgnosticPatchRoutesOnNavigationFunction,
39
39
  DataWithResponseInit,
40
40
  } from "./utils";
41
41
  import {
@@ -391,7 +391,7 @@ export interface RouterInit {
391
391
  future?: Partial<FutureConfig>;
392
392
  hydrationData?: HydrationState;
393
393
  window?: Window;
394
- unstable_patchRoutesOnMiss?: AgnosticPatchRoutesOnMissFunction;
394
+ unstable_patchRoutesOnNavigation?: AgnosticPatchRoutesOnNavigationFunction;
395
395
  unstable_dataStrategy?: DataStrategyFunction;
396
396
  }
397
397
 
@@ -798,7 +798,7 @@ export function createRouter(init: RouterInit): Router {
798
798
  let inFlightDataRoutes: AgnosticDataRouteObject[] | undefined;
799
799
  let basename = init.basename || "/";
800
800
  let dataStrategyImpl = init.unstable_dataStrategy || defaultDataStrategy;
801
- let patchRoutesOnMissImpl = init.unstable_patchRoutesOnMiss;
801
+ let patchRoutesOnNavigationImpl = init.unstable_patchRoutesOnNavigation;
802
802
 
803
803
  // Config driven behavior flags
804
804
  let future: FutureConfig = {
@@ -814,6 +814,10 @@ export function createRouter(init: RouterInit): Router {
814
814
  let unlistenHistory: (() => void) | null = null;
815
815
  // Externally-provided functions to call on all state changes
816
816
  let subscribers = new Set<RouterSubscriber>();
817
+ // FIFO queue of previously discovered routes to prevent re-calling on
818
+ // subsequent navigations to the same path
819
+ let discoveredRoutesMaxSize = 1000;
820
+ let discoveredRoutes = new Set<string>();
817
821
  // Externally-provided object to hold scroll restoration locations during routing
818
822
  let savedScrollPositions: Record<string, number> | null = null;
819
823
  // Externally-provided function to get scroll restoration keys
@@ -831,7 +835,7 @@ export function createRouter(init: RouterInit): Router {
831
835
  let initialMatches = matchRoutes(dataRoutes, init.history.location, basename);
832
836
  let initialErrors: RouteData | null = null;
833
837
 
834
- if (initialMatches == null && !patchRoutesOnMissImpl) {
838
+ if (initialMatches == null && !patchRoutesOnNavigationImpl) {
835
839
  // If we do not match a user-provided-route, fall back to the root
836
840
  // to allow the error boundary to take over
837
841
  let error = getInternalRouterError(404, {
@@ -842,7 +846,7 @@ export function createRouter(init: RouterInit): Router {
842
846
  initialErrors = { [route.id]: error };
843
847
  }
844
848
 
845
- // In SPA apps, if the user provided a patchRoutesOnMiss implementation and
849
+ // In SPA apps, if the user provided a patchRoutesOnNavigation implementation and
846
850
  // our initial match is a splat route, clear them out so we run through lazy
847
851
  // discovery on hydration in case there's a more accurate lazy route match.
848
852
  // In SSR apps (with `hydrationData`), we expect that the server will send
@@ -865,7 +869,7 @@ export function createRouter(init: RouterInit): Router {
865
869
  initialMatches = [];
866
870
 
867
871
  // If partial hydration and fog of war is enabled, we will be running
868
- // `patchRoutesOnMiss` during hydration so include any partial matches as
872
+ // `patchRoutesOnNavigation` during hydration so include any partial matches as
869
873
  // the initial matches so we can properly render `HydrateFallback`'s
870
874
  if (future.v7_partialHydration) {
871
875
  let fogOfWar = checkFogOfWar(
@@ -1020,11 +1024,11 @@ export function createRouter(init: RouterInit): Router {
1020
1024
  // we don't need to update UI state if they change
1021
1025
  let blockerFunctions = new Map<string, BlockerFunction>();
1022
1026
 
1023
- // Map of pending patchRoutesOnMiss() promises (keyed by path/matches) so
1027
+ // Map of pending patchRoutesOnNavigation() promises (keyed by path/matches) so
1024
1028
  // that we only kick them off once for a given combo
1025
1029
  let pendingPatchRoutes = new Map<
1026
1030
  string,
1027
- ReturnType<AgnosticPatchRoutesOnMissFunction>
1031
+ ReturnType<AgnosticPatchRoutesOnNavigationFunction>
1028
1032
  >();
1029
1033
 
1030
1034
  // Flag to ignore the next history update, so we can revert the URL change on
@@ -3182,7 +3186,14 @@ export function createRouter(init: RouterInit): Router {
3182
3186
  routesToUse: AgnosticDataRouteObject[],
3183
3187
  pathname: string
3184
3188
  ): { active: boolean; matches: AgnosticDataRouteMatch[] | null } {
3185
- if (patchRoutesOnMissImpl) {
3189
+ if (patchRoutesOnNavigationImpl) {
3190
+ // Don't bother re-calling patchRouteOnMiss for a path we've already
3191
+ // processed. the last execution would have patched the route tree
3192
+ // accordingly so `matches` here are already accurate.
3193
+ if (discoveredRoutes.has(pathname)) {
3194
+ return { active: false, matches };
3195
+ }
3196
+
3186
3197
  if (!matches) {
3187
3198
  let fogMatches = matchRoutesImpl<AgnosticDataRouteObject>(
3188
3199
  routesToUse,
@@ -3193,14 +3204,10 @@ export function createRouter(init: RouterInit): Router {
3193
3204
 
3194
3205
  return { active: true, matches: fogMatches || [] };
3195
3206
  } else {
3196
- let leafRoute = matches[matches.length - 1].route;
3197
- if (
3198
- leafRoute.path &&
3199
- (leafRoute.path === "*" || leafRoute.path.endsWith("/*"))
3200
- ) {
3201
- // If we matched a splat, it might only be because we haven't yet fetched
3202
- // the children that would match with a higher score, so let's fetch
3203
- // around and find out
3207
+ if (Object.keys(matches[0].params).length > 0) {
3208
+ // If we matched a dynamic param or a splat, it might only be because
3209
+ // we haven't yet discovered other routes that would match with a
3210
+ // higher score. Call patchRoutesOnNavigation just to be sure
3204
3211
  let partialMatches = matchRoutesImpl<AgnosticDataRouteObject>(
3205
3212
  routesToUse,
3206
3213
  pathname,
@@ -3236,16 +3243,12 @@ export function createRouter(init: RouterInit): Router {
3236
3243
  signal: AbortSignal
3237
3244
  ): Promise<DiscoverRoutesResult> {
3238
3245
  let partialMatches: AgnosticDataRouteMatch[] | null = matches;
3239
- let route =
3240
- partialMatches.length > 0
3241
- ? partialMatches[partialMatches.length - 1].route
3242
- : null;
3243
3246
  while (true) {
3244
3247
  let isNonHMR = inFlightDataRoutes == null;
3245
3248
  let routesToUse = inFlightDataRoutes || dataRoutes;
3246
3249
  try {
3247
3250
  await loadLazyRouteChildren(
3248
- patchRoutesOnMissImpl!,
3251
+ patchRoutesOnNavigationImpl!,
3249
3252
  pathname,
3250
3253
  partialMatches,
3251
3254
  routesToUse,
@@ -3273,26 +3276,9 @@ export function createRouter(init: RouterInit): Router {
3273
3276
  }
3274
3277
 
3275
3278
  let newMatches = matchRoutes(routesToUse, pathname, basename);
3276
- let matchedSplat = false;
3277
3279
  if (newMatches) {
3278
- let leafRoute = newMatches[newMatches.length - 1].route;
3279
-
3280
- if (leafRoute.index) {
3281
- // If we found an index route, we can stop
3282
- return { type: "success", matches: newMatches };
3283
- }
3284
-
3285
- if (leafRoute.path && leafRoute.path.length > 0) {
3286
- if (leafRoute.path === "*") {
3287
- // If we found a splat route, we can't be sure there's not a
3288
- // higher-scoring route down some partial matches trail so we need
3289
- // to check that out
3290
- matchedSplat = true;
3291
- } else {
3292
- // If we found a non-splat route, we can stop
3293
- return { type: "success", matches: newMatches };
3294
- }
3295
- }
3280
+ addToFifoQueue(pathname, discoveredRoutes);
3281
+ return { type: "success", matches: newMatches };
3296
3282
  }
3297
3283
 
3298
3284
  let newPartialMatches = matchRoutesImpl<AgnosticDataRouteObject>(
@@ -3302,26 +3288,30 @@ export function createRouter(init: RouterInit): Router {
3302
3288
  true
3303
3289
  );
3304
3290
 
3305
- // If we are no longer partially matching anything, this was either a
3306
- // legit splat match above, or it's a 404. Also avoid loops if the
3307
- // second pass results in the same partial matches
3291
+ // Avoid loops if the second pass results in the same partial matches
3308
3292
  if (
3309
3293
  !newPartialMatches ||
3310
- partialMatches.map((m) => m.route.id).join("-") ===
3311
- newPartialMatches.map((m) => m.route.id).join("-")
3294
+ (partialMatches.length === newPartialMatches.length &&
3295
+ partialMatches.every(
3296
+ (m, i) => m.route.id === newPartialMatches![i].route.id
3297
+ ))
3312
3298
  ) {
3313
- return { type: "success", matches: matchedSplat ? newMatches : null };
3299
+ addToFifoQueue(pathname, discoveredRoutes);
3300
+ return { type: "success", matches: null };
3314
3301
  }
3315
3302
 
3316
3303
  partialMatches = newPartialMatches;
3317
- route = partialMatches[partialMatches.length - 1].route;
3318
- if (route.path === "*") {
3319
- // The splat is still our most accurate partial, so run with it
3320
- return { type: "success", matches: partialMatches };
3321
- }
3322
3304
  }
3323
3305
  }
3324
3306
 
3307
+ function addToFifoQueue(path: string, queue: Set<string>) {
3308
+ if (queue.size >= discoveredRoutesMaxSize) {
3309
+ let first = queue.values().next().value;
3310
+ queue.delete(first);
3311
+ }
3312
+ queue.add(path);
3313
+ }
3314
+
3325
3315
  function _internalSetRoutes(newRoutes: AgnosticDataRouteObject[]) {
3326
3316
  manifest = {};
3327
3317
  inFlightDataRoutes = convertRoutesToDataRoutes(
@@ -4554,24 +4544,27 @@ function shouldRevalidateLoader(
4554
4544
  }
4555
4545
 
4556
4546
  /**
4557
- * Idempotent utility to execute patchRoutesOnMiss() to lazily load route
4547
+ * Idempotent utility to execute patchRoutesOnNavigation() to lazily load route
4558
4548
  * definitions and update the routes/routeManifest
4559
4549
  */
4560
4550
  async function loadLazyRouteChildren(
4561
- patchRoutesOnMissImpl: AgnosticPatchRoutesOnMissFunction,
4551
+ patchRoutesOnNavigationImpl: AgnosticPatchRoutesOnNavigationFunction,
4562
4552
  path: string,
4563
4553
  matches: AgnosticDataRouteMatch[],
4564
4554
  routes: AgnosticDataRouteObject[],
4565
4555
  manifest: RouteManifest,
4566
4556
  mapRouteProperties: MapRoutePropertiesFunction,
4567
- pendingRouteChildren: Map<string, ReturnType<typeof patchRoutesOnMissImpl>>,
4557
+ pendingRouteChildren: Map<
4558
+ string,
4559
+ ReturnType<typeof patchRoutesOnNavigationImpl>
4560
+ >,
4568
4561
  signal: AbortSignal
4569
4562
  ) {
4570
4563
  let key = [path, ...matches.map((m) => m.route.id)].join("-");
4571
4564
  try {
4572
4565
  let pending = pendingRouteChildren.get(key);
4573
4566
  if (!pending) {
4574
- pending = patchRoutesOnMissImpl({
4567
+ pending = patchRoutesOnNavigationImpl({
4575
4568
  path,
4576
4569
  matches,
4577
4570
  patch: (routeId, children) => {
@@ -5410,7 +5403,7 @@ function getInternalRouterError(
5410
5403
  statusText = "Bad Request";
5411
5404
  if (type === "route-discovery") {
5412
5405
  errorMessage =
5413
- `Unable to match URL "${pathname}" - the \`unstable_patchRoutesOnMiss()\` ` +
5406
+ `Unable to match URL "${pathname}" - the \`unstable_patchRoutesOnNavigation()\` ` +
5414
5407
  `function threw the following error:\n${message}`;
5415
5408
  } else if (method && pathname && routeId) {
5416
5409
  errorMessage =
package/utils.ts CHANGED
@@ -254,7 +254,7 @@ export interface DataStrategyFunction {
254
254
  (args: DataStrategyFunctionArgs): Promise<HandlerResult[]>;
255
255
  }
256
256
 
257
- export interface AgnosticPatchRoutesOnMissFunction<
257
+ export interface AgnosticPatchRoutesOnNavigationFunction<
258
258
  M extends AgnosticRouteMatch = AgnosticRouteMatch
259
259
  > {
260
260
  (opts: {