@remix-run/router 1.3.3 → 1.4.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.
package/utils.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Location, Path, To } from "./history";
2
- import { invariant, parsePath } from "./history";
2
+ import { warning, invariant, parsePath } from "./history";
3
3
 
4
4
  /**
5
5
  * Map of routeId -> data returned from a loader/action/error
@@ -139,6 +139,44 @@ export interface ShouldRevalidateFunction {
139
139
  }): boolean;
140
140
  }
141
141
 
142
+ /**
143
+ * Function provided by the framework-aware layers to set `hasErrorBoundary`
144
+ * from the framework-aware `errorElement` prop
145
+ */
146
+ export interface DetectErrorBoundaryFunction {
147
+ (route: AgnosticRouteObject): boolean;
148
+ }
149
+
150
+ /**
151
+ * Keys we cannot change from within a lazy() function. We spread all other keys
152
+ * onto the route. Either they're meaningful to the router, or they'll get
153
+ * ignored.
154
+ */
155
+ export type ImmutableRouteKey =
156
+ | "lazy"
157
+ | "caseSensitive"
158
+ | "path"
159
+ | "id"
160
+ | "index"
161
+ | "children";
162
+
163
+ export const immutableRouteKeys = new Set<ImmutableRouteKey>([
164
+ "lazy",
165
+ "caseSensitive",
166
+ "path",
167
+ "id",
168
+ "index",
169
+ "children",
170
+ ]);
171
+
172
+ /**
173
+ * lazy() function to load a route definition, which can add non-matching
174
+ * related properties to a route
175
+ */
176
+ export interface LazyRouteFunction<R extends AgnosticRouteObject> {
177
+ (): Promise<Omit<R, ImmutableRouteKey>>;
178
+ }
179
+
142
180
  /**
143
181
  * Base RouteObject with common props shared by all types of routes
144
182
  */
@@ -151,6 +189,7 @@ type AgnosticBaseRouteObject = {
151
189
  hasErrorBoundary?: boolean;
152
190
  shouldRevalidate?: ShouldRevalidateFunction;
153
191
  handle?: any;
192
+ lazy?: LazyRouteFunction<AgnosticBaseRouteObject>;
154
193
  };
155
194
 
156
195
  /**
@@ -193,6 +232,8 @@ export type AgnosticDataRouteObject =
193
232
  | AgnosticDataIndexRouteObject
194
233
  | AgnosticDataNonIndexRouteObject;
195
234
 
235
+ export type RouteManifest = Record<string, AgnosticDataRouteObject | undefined>;
236
+
196
237
  // Recursive helper for finding path parameters in the absence of wildcards
197
238
  type _PathParam<Path extends string> =
198
239
  // split path into individual path segments
@@ -217,7 +258,7 @@ type _PathParam<Path extends string> =
217
258
  */
218
259
  type PathParam<Path extends string> =
219
260
  // check if path is just a wildcard
220
- Path extends "*"
261
+ Path extends "*" | "/*"
221
262
  ? "*"
222
263
  : // look for wildcard at the end of the path
223
264
  Path extends `${infer Rest}/*`
@@ -277,8 +318,9 @@ function isIndexRoute(
277
318
  // solely with AgnosticDataRouteObject's within the Router
278
319
  export function convertRoutesToDataRoutes(
279
320
  routes: AgnosticRouteObject[],
321
+ detectErrorBoundary: DetectErrorBoundaryFunction,
280
322
  parentPath: number[] = [],
281
- allIds: Set<string> = new Set<string>()
323
+ manifest: RouteManifest = {}
282
324
  ): AgnosticDataRouteObject[] {
283
325
  return routes.map((route, index) => {
284
326
  let treePath = [...parentPath, index];
@@ -288,23 +330,37 @@ export function convertRoutesToDataRoutes(
288
330
  `Cannot specify children on an index route`
289
331
  );
290
332
  invariant(
291
- !allIds.has(id),
333
+ !manifest[id],
292
334
  `Found a route id collision on id "${id}". Route ` +
293
335
  "id's must be globally unique within Data Router usages"
294
336
  );
295
- allIds.add(id);
296
337
 
297
338
  if (isIndexRoute(route)) {
298
- let indexRoute: AgnosticDataIndexRouteObject = { ...route, id };
339
+ let indexRoute: AgnosticDataIndexRouteObject = {
340
+ ...route,
341
+ hasErrorBoundary: detectErrorBoundary(route),
342
+ id,
343
+ };
344
+ manifest[id] = indexRoute;
299
345
  return indexRoute;
300
346
  } else {
301
347
  let pathOrLayoutRoute: AgnosticDataNonIndexRouteObject = {
302
348
  ...route,
303
349
  id,
304
- children: route.children
305
- ? convertRoutesToDataRoutes(route.children, treePath, allIds)
306
- : undefined,
350
+ hasErrorBoundary: detectErrorBoundary(route),
351
+ children: undefined,
307
352
  };
353
+ manifest[id] = pathOrLayoutRoute;
354
+
355
+ if (route.children) {
356
+ pathOrLayoutRoute.children = convertRoutesToDataRoutes(
357
+ route.children,
358
+ detectErrorBoundary,
359
+ treePath,
360
+ manifest
361
+ );
362
+ }
363
+
308
364
  return pathOrLayoutRoute;
309
365
  }
310
366
  });
@@ -621,7 +677,7 @@ export function generatePath<Path extends string>(
621
677
  [key in PathParam<Path>]: string | null;
622
678
  } = {} as any
623
679
  ): string {
624
- let path = originalPath;
680
+ let path: string = originalPath;
625
681
  if (path.endsWith("*") && path !== "*" && !path.endsWith("/*")) {
626
682
  warning(
627
683
  false,
@@ -633,49 +689,46 @@ export function generatePath<Path extends string>(
633
689
  path = path.replace(/\*$/, "/*") as Path;
634
690
  }
635
691
 
636
- return (
637
- path
638
- .replace(
639
- /^:(\w+)(\??)/g,
640
- (_, key: PathParam<Path>, optional: string | undefined) => {
641
- let param = params[key];
642
- if (optional === "?") {
643
- return param == null ? "" : param;
644
- }
645
- if (param == null) {
646
- invariant(false, `Missing ":${key}" param`);
647
- }
648
- return param;
649
- }
650
- )
651
- .replace(
652
- /\/:(\w+)(\??)/g,
653
- (_, key: PathParam<Path>, optional: string | undefined) => {
654
- let param = params[key];
655
- if (optional === "?") {
656
- return param == null ? "" : `/${param}`;
657
- }
658
- if (param == null) {
659
- invariant(false, `Missing ":${key}" param`);
660
- }
661
- return `/${param}`;
662
- }
663
- )
664
- // Remove any optional markers from optional static segments
665
- .replace(/\?/g, "")
666
- .replace(/(\/?)\*/, (_, prefix, __, str) => {
692
+ // ensure `/` is added at the beginning if the path is absolute
693
+ const prefix = path.startsWith("/") ? "/" : "";
694
+
695
+ const segments = path
696
+ .split(/\/+/)
697
+ .map((segment, index, array) => {
698
+ const isLastSegment = index === array.length - 1;
699
+
700
+ // only apply the splat if it's the last segment
701
+ if (isLastSegment && segment === "*") {
667
702
  const star = "*" as PathParam<Path>;
703
+ const starParam = params[star];
704
+
705
+ // Apply the splat
706
+ return starParam;
707
+ }
708
+
709
+ const keyMatch = segment.match(/^:(\w+)(\??)$/);
710
+ if (keyMatch) {
711
+ const [, key, optional] = keyMatch;
712
+ let param = params[key as PathParam<Path>];
668
713
 
669
- if (params[star] == null) {
670
- // If no splat was provided, trim the trailing slash _unless_ it's
671
- // the entire path
672
- return str === "/*" ? "/" : "";
714
+ if (optional === "?") {
715
+ return param == null ? "" : param;
673
716
  }
674
717
 
675
- // Apply the splat
676
- return `${prefix}${params[star]}`;
677
- })
678
- );
718
+ if (param == null) {
719
+ invariant(false, `Missing ":${key}" param`);
720
+ }
721
+
722
+ return param;
723
+ }
724
+
725
+ // Remove any optional markers from optional static segments
726
+ return segment.replace(/\?$/g, "");
727
+ })
728
+ // Remove empty segments
729
+ .filter((segment) => !!segment);
730
+
731
+ return prefix + segments.join("/");
679
732
  }
680
733
 
681
734
  /**
@@ -891,26 +944,6 @@ export function stripBasename(
891
944
  return pathname.slice(startIndex) || "/";
892
945
  }
893
946
 
894
- /**
895
- * @private
896
- */
897
- export function warning(cond: any, message: string): void {
898
- if (!cond) {
899
- // eslint-disable-next-line no-console
900
- if (typeof console !== "undefined") console.warn(message);
901
-
902
- try {
903
- // Welcome to debugging @remix-run/router!
904
- //
905
- // This error is thrown as a convenience so you can more easily
906
- // find the source for a warning that appears in the console by
907
- // enabling "pause on exceptions" in your JavaScript debugger.
908
- throw new Error(message);
909
- // eslint-disable-next-line no-empty
910
- } catch (e) {}
911
- }
912
- }
913
-
914
947
  /**
915
948
  * Returns a resolved path object relative to the given pathname.
916
949
  *