@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/dist/router.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { History, Location, Path, To } from "./history";
2
2
  import { Action as HistoryAction } from "./history";
3
- import type { AgnosticDataRouteMatch, AgnosticDataRouteObject, FormEncType, FormMethod, RouteData, AgnosticRouteObject, AgnosticRouteMatch } from "./utils";
3
+ import type { AgnosticDataRouteMatch, AgnosticDataRouteObject, FormEncType, FormMethod, DetectErrorBoundaryFunction, RouteData, AgnosticRouteObject, AgnosticRouteMatch } from "./utils";
4
4
  import { DeferredData } from "./utils";
5
5
  /**
6
6
  * A Router instance manages all navigation and data loading/mutations
@@ -244,6 +244,7 @@ export interface RouterInit {
244
244
  routes: AgnosticRouteObject[];
245
245
  history: History;
246
246
  hydrationData?: HydrationState;
247
+ detectErrorBoundary?: DetectErrorBoundaryFunction;
247
248
  }
248
249
  /**
249
250
  * State returned from a server-side query() call
@@ -423,9 +424,11 @@ export declare const IDLE_BLOCKER: BlockerUnblocked;
423
424
  */
424
425
  export declare function createRouter(init: RouterInit): Router;
425
426
  export declare const UNSAFE_DEFERRED_SYMBOL: unique symbol;
426
- export declare function createStaticHandler(routes: AgnosticRouteObject[], opts?: {
427
+ export interface CreateStaticHandlerOptions {
427
428
  basename?: string;
428
- }): StaticHandler;
429
+ detectErrorBoundary?: DetectErrorBoundaryFunction;
430
+ }
431
+ export declare function createStaticHandler(routes: AgnosticRouteObject[], opts?: CreateStaticHandlerOptions): StaticHandler;
429
432
  /**
430
433
  * Given an existing StaticHandlerContext and an error thrown at render time,
431
434
  * provide an updated StaticHandlerContext suitable for a second SSR render
package/dist/router.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @remix-run/router v1.3.3
2
+ * @remix-run/router v1.4.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -95,7 +95,7 @@ function createMemoryHistory(options) {
95
95
  }
96
96
 
97
97
  let location = createLocation(entries ? getCurrentLocation().pathname : "/", to, state, key);
98
- warning$1(location.pathname.charAt(0) === "/", "relative pathnames are not supported in memory history: " + JSON.stringify(to));
98
+ warning(location.pathname.charAt(0) === "/", "relative pathnames are not supported in memory history: " + JSON.stringify(to));
99
99
  return location;
100
100
  }
101
101
 
@@ -260,7 +260,7 @@ function createHashHistory(options) {
260
260
  }
261
261
 
262
262
  function validateHashLocation(location, to) {
263
- warning$1(location.pathname.charAt(0) === "/", "relative pathnames are not supported in hash history.push(" + JSON.stringify(to) + ")");
263
+ warning(location.pathname.charAt(0) === "/", "relative pathnames are not supported in hash history.push(" + JSON.stringify(to) + ")");
264
264
  }
265
265
 
266
266
  return getUrlBasedHistory(createHashLocation, createHashHref, validateHashLocation, options);
@@ -270,8 +270,7 @@ function invariant(value, message) {
270
270
  throw new Error(message);
271
271
  }
272
272
  }
273
-
274
- function warning$1(cond, message) {
273
+ function warning(cond, message) {
275
274
  if (!cond) {
276
275
  // eslint-disable-next-line no-console
277
276
  if (typeof console !== "undefined") console.warn(message);
@@ -527,40 +526,50 @@ var ResultType;
527
526
  ResultType["error"] = "error";
528
527
  })(ResultType || (ResultType = {}));
529
528
 
529
+ const immutableRouteKeys = new Set(["lazy", "caseSensitive", "path", "id", "index", "children"]);
530
+
530
531
  function isIndexRoute(route) {
531
532
  return route.index === true;
532
533
  } // Walk the route tree generating unique IDs where necessary so we are working
533
534
  // solely with AgnosticDataRouteObject's within the Router
534
535
 
535
536
 
536
- function convertRoutesToDataRoutes(routes, parentPath, allIds) {
537
+ function convertRoutesToDataRoutes(routes, detectErrorBoundary, parentPath, manifest) {
537
538
  if (parentPath === void 0) {
538
539
  parentPath = [];
539
540
  }
540
541
 
541
- if (allIds === void 0) {
542
- allIds = new Set();
542
+ if (manifest === void 0) {
543
+ manifest = {};
543
544
  }
544
545
 
545
546
  return routes.map((route, index) => {
546
547
  let treePath = [...parentPath, index];
547
548
  let id = typeof route.id === "string" ? route.id : treePath.join("-");
548
549
  invariant(route.index !== true || !route.children, "Cannot specify children on an index route");
549
- invariant(!allIds.has(id), "Found a route id collision on id \"" + id + "\". Route " + "id's must be globally unique within Data Router usages");
550
- allIds.add(id);
550
+ invariant(!manifest[id], "Found a route id collision on id \"" + id + "\". Route " + "id's must be globally unique within Data Router usages");
551
551
 
552
552
  if (isIndexRoute(route)) {
553
553
  let indexRoute = _extends({}, route, {
554
+ hasErrorBoundary: detectErrorBoundary(route),
554
555
  id
555
556
  });
556
557
 
558
+ manifest[id] = indexRoute;
557
559
  return indexRoute;
558
560
  } else {
559
561
  let pathOrLayoutRoute = _extends({}, route, {
560
562
  id,
561
- children: route.children ? convertRoutesToDataRoutes(route.children, treePath, allIds) : undefined
563
+ hasErrorBoundary: detectErrorBoundary(route),
564
+ children: undefined
562
565
  });
563
566
 
567
+ manifest[id] = pathOrLayoutRoute;
568
+
569
+ if (route.children) {
570
+ pathOrLayoutRoute.children = convertRoutesToDataRoutes(route.children, detectErrorBoundary, treePath, manifest);
571
+ }
572
+
564
573
  return pathOrLayoutRoute;
565
574
  }
566
575
  });
@@ -807,45 +816,42 @@ function generatePath(originalPath, params) {
807
816
  if (path.endsWith("*") && path !== "*" && !path.endsWith("/*")) {
808
817
  warning(false, "Route path \"" + path + "\" will be treated as if it were " + ("\"" + path.replace(/\*$/, "/*") + "\" because the `*` character must ") + "always follow a `/` in the pattern. To get rid of this warning, " + ("please change the route path to \"" + path.replace(/\*$/, "/*") + "\"."));
809
818
  path = path.replace(/\*$/, "/*");
810
- }
819
+ } // ensure `/` is added at the beginning if the path is absolute
811
820
 
812
- return path.replace(/^:(\w+)(\??)/g, (_, key, optional) => {
813
- let param = params[key];
814
821
 
815
- if (optional === "?") {
816
- return param == null ? "" : param;
817
- }
822
+ const prefix = path.startsWith("/") ? "/" : "";
823
+ const segments = path.split(/\/+/).map((segment, index, array) => {
824
+ const isLastSegment = index === array.length - 1; // only apply the splat if it's the last segment
825
+
826
+ if (isLastSegment && segment === "*") {
827
+ const star = "*";
828
+ const starParam = params[star]; // Apply the splat
818
829
 
819
- if (param == null) {
820
- invariant(false, "Missing \":" + key + "\" param");
830
+ return starParam;
821
831
  }
822
832
 
823
- return param;
824
- }).replace(/\/:(\w+)(\??)/g, (_, key, optional) => {
825
- let param = params[key];
833
+ const keyMatch = segment.match(/^:(\w+)(\??)$/);
826
834
 
827
- if (optional === "?") {
828
- return param == null ? "" : "/" + param;
829
- }
835
+ if (keyMatch) {
836
+ const [, key, optional] = keyMatch;
837
+ let param = params[key];
830
838
 
831
- if (param == null) {
832
- invariant(false, "Missing \":" + key + "\" param");
833
- }
839
+ if (optional === "?") {
840
+ return param == null ? "" : param;
841
+ }
834
842
 
835
- return "/" + param;
836
- }) // Remove any optional markers from optional static segments
837
- .replace(/\?/g, "").replace(/(\/?)\*/, (_, prefix, __, str) => {
838
- const star = "*";
843
+ if (param == null) {
844
+ invariant(false, "Missing \":" + key + "\" param");
845
+ }
839
846
 
840
- if (params[star] == null) {
841
- // If no splat was provided, trim the trailing slash _unless_ it's
842
- // the entire path
843
- return str === "/*" ? "/" : "";
844
- } // Apply the splat
847
+ return param;
848
+ } // Remove any optional markers from optional static segments
845
849
 
846
850
 
847
- return "" + prefix + params[star];
848
- });
851
+ return segment.replace(/\?$/g, "");
852
+ }) // Remove empty segments
853
+ .filter(segment => !!segment);
854
+ return prefix + segments.join("/");
849
855
  }
850
856
  /**
851
857
  * Performs pattern matching on a URL pathname and returns information about
@@ -970,25 +976,6 @@ function stripBasename(pathname, basename) {
970
976
 
971
977
  return pathname.slice(startIndex) || "/";
972
978
  }
973
- /**
974
- * @private
975
- */
976
-
977
- function warning(cond, message) {
978
- if (!cond) {
979
- // eslint-disable-next-line no-console
980
- if (typeof console !== "undefined") console.warn(message);
981
-
982
- try {
983
- // Welcome to debugging @remix-run/router!
984
- //
985
- // This error is thrown as a convenience so you can more easily
986
- // find the source for a warning that appears in the console by
987
- // enabling "pause on exceptions" in your JavaScript debugger.
988
- throw new Error(message); // eslint-disable-next-line no-empty
989
- } catch (e) {}
990
- }
991
- }
992
979
  /**
993
980
  * Returns a resolved path object relative to the given pathname.
994
981
  *
@@ -1430,7 +1417,9 @@ const IDLE_BLOCKER = {
1430
1417
  };
1431
1418
  const ABSOLUTE_URL_REGEX = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
1432
1419
  const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined";
1433
- const isServer = !isBrowser; //#endregion
1420
+ const isServer = !isBrowser;
1421
+
1422
+ const defaultDetectErrorBoundary = route => Boolean(route.hasErrorBoundary); //#endregion
1434
1423
  ////////////////////////////////////////////////////////////////////////////////
1435
1424
  //#region createRouter
1436
1425
  ////////////////////////////////////////////////////////////////////////////////
@@ -1439,9 +1428,14 @@ const isServer = !isBrowser; //#endregion
1439
1428
  * Create a router and listen to history POP navigations
1440
1429
  */
1441
1430
 
1431
+
1442
1432
  function createRouter(init) {
1443
1433
  invariant(init.routes.length > 0, "You must provide a non-empty routes array to createRouter");
1444
- let dataRoutes = convertRoutesToDataRoutes(init.routes);
1434
+ let detectErrorBoundary = init.detectErrorBoundary || defaultDetectErrorBoundary; // Routes keyed by ID
1435
+
1436
+ let manifest = {}; // Routes in tree format for matching
1437
+
1438
+ let dataRoutes = convertRoutesToDataRoutes(init.routes, detectErrorBoundary, undefined, manifest);
1445
1439
  let inFlightDataRoutes; // Cleanup function for history
1446
1440
 
1447
1441
  let unlistenHistory = null; // Externally-provided functions to call on all state changes
@@ -1479,7 +1473,10 @@ function createRouter(init) {
1479
1473
  };
1480
1474
  }
1481
1475
 
1482
- let initialized = !initialMatches.some(m => m.route.loader) || init.hydrationData != null;
1476
+ let initialized = // All initialMatches need to be loaded before we're ready. If we have lazy
1477
+ // functions around still then we'll need to run them in initialize()
1478
+ !initialMatches.some(m => m.route.lazy) && ( // And we have to either have no loaders or have been provided hydrationData
1479
+ !initialMatches.some(m => m.route.loader) || init.hydrationData != null);
1483
1480
  let router;
1484
1481
  let state = {
1485
1482
  historyAction: init.history.action,
@@ -1603,12 +1600,35 @@ function createRouter(init) {
1603
1600
  }
1604
1601
 
1605
1602
  return startNavigation(historyAction, location);
1606
- }); // Kick off initial data load if needed. Use Pop to avoid modifying history
1603
+ });
1607
1604
 
1608
- if (!state.initialized) {
1609
- startNavigation(Action.Pop, state.location);
1605
+ if (state.initialized) {
1606
+ return router;
1610
1607
  }
1611
1608
 
1609
+ let lazyMatches = state.matches.filter(m => m.route.lazy);
1610
+
1611
+ if (lazyMatches.length === 0) {
1612
+ // Kick off initial data load if needed. Use Pop to avoid modifying history
1613
+ startNavigation(Action.Pop, state.location);
1614
+ return router;
1615
+ } // Load lazy modules, then kick off initial data load if needed
1616
+
1617
+
1618
+ let lazyPromises = lazyMatches.map(m => loadLazyRouteModule(m.route, detectErrorBoundary, manifest));
1619
+ Promise.all(lazyPromises).then(() => {
1620
+ let initialized = !state.matches.some(m => m.route.loader) || init.hydrationData != null;
1621
+
1622
+ if (initialized) {
1623
+ // We already have required loaderData so we can just set initialized
1624
+ updateState({
1625
+ initialized: true
1626
+ });
1627
+ } else {
1628
+ // We still need to kick off initial data loads
1629
+ startNavigation(Action.Pop, state.location);
1630
+ }
1631
+ });
1612
1632
  return router;
1613
1633
  } // Clean up a router and it's side effects
1614
1634
 
@@ -1953,7 +1973,7 @@ function createRouter(init) {
1953
1973
  let result;
1954
1974
  let actionMatch = getTargetMatch(matches, location);
1955
1975
 
1956
- if (!actionMatch.route.action) {
1976
+ if (!actionMatch.route.action && !actionMatch.route.lazy) {
1957
1977
  result = {
1958
1978
  type: ResultType.error,
1959
1979
  error: getInternalRouterError(405, {
@@ -1963,7 +1983,7 @@ function createRouter(init) {
1963
1983
  })
1964
1984
  };
1965
1985
  } else {
1966
- result = await callLoaderOrAction("action", request, actionMatch, matches, router.basename);
1986
+ result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, detectErrorBoundary, router.basename);
1967
1987
 
1968
1988
  if (request.signal.aborted) {
1969
1989
  return {
@@ -2209,7 +2229,7 @@ function createRouter(init) {
2209
2229
  interruptActiveLoads();
2210
2230
  fetchLoadMatches.delete(key);
2211
2231
 
2212
- if (!match.route.action) {
2232
+ if (!match.route.action && !match.route.lazy) {
2213
2233
  let error = getInternalRouterError(405, {
2214
2234
  method: submission.formMethod,
2215
2235
  pathname: path,
@@ -2237,7 +2257,7 @@ function createRouter(init) {
2237
2257
  let abortController = new AbortController();
2238
2258
  let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);
2239
2259
  fetchControllers.set(key, abortController);
2240
- let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, router.basename);
2260
+ let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, manifest, detectErrorBoundary, router.basename);
2241
2261
 
2242
2262
  if (fetchRequest.signal.aborted) {
2243
2263
  // We can delete this so long as we weren't aborted by ou our own fetcher
@@ -2408,7 +2428,7 @@ function createRouter(init) {
2408
2428
  let abortController = new AbortController();
2409
2429
  let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
2410
2430
  fetchControllers.set(key, abortController);
2411
- let result = await callLoaderOrAction("loader", fetchRequest, match, matches, router.basename); // Deferred isn't supported for fetcher loads, await everything and treat it
2431
+ let result = await callLoaderOrAction("loader", fetchRequest, match, matches, manifest, detectErrorBoundary, router.basename); // Deferred isn't supported for fetcher loads, await everything and treat it
2412
2432
  // as a normal load. resolveDeferredData will return undefined if this
2413
2433
  // fetcher gets aborted, so we just leave result untouched and short circuit
2414
2434
  // below if that happens
@@ -2577,9 +2597,9 @@ function createRouter(init) {
2577
2597
  // Call all navigation loaders and revalidating fetcher loaders in parallel,
2578
2598
  // then slice off the results into separate arrays so we can handle them
2579
2599
  // accordingly
2580
- let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, router.basename)), ...fetchersToLoad.map(f => {
2600
+ let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, detectErrorBoundary, router.basename)), ...fetchersToLoad.map(f => {
2581
2601
  if (f.matches && f.match) {
2582
- return callLoaderOrAction("loader", createClientSideRequest(init.history, f.path, request.signal), f.match, f.matches, router.basename);
2602
+ return callLoaderOrAction("loader", createClientSideRequest(init.history, f.path, request.signal), f.match, f.matches, manifest, detectErrorBoundary, router.basename);
2583
2603
  } else {
2584
2604
  let error = {
2585
2605
  type: ResultType.error,
@@ -2871,7 +2891,9 @@ function createRouter(init) {
2871
2891
  const UNSAFE_DEFERRED_SYMBOL = Symbol("deferred");
2872
2892
  function createStaticHandler(routes, opts) {
2873
2893
  invariant(routes.length > 0, "You must provide a non-empty routes array to createStaticHandler");
2874
- let dataRoutes = convertRoutesToDataRoutes(routes);
2894
+ let manifest = {};
2895
+ let detectErrorBoundary = (opts == null ? void 0 : opts.detectErrorBoundary) || defaultDetectErrorBoundary;
2896
+ let dataRoutes = convertRoutesToDataRoutes(routes, detectErrorBoundary, undefined, manifest);
2875
2897
  let basename = (opts ? opts.basename : null) || "/";
2876
2898
  /**
2877
2899
  * The query() method is intended for document requests, in which we want to
@@ -3093,7 +3115,7 @@ function createStaticHandler(routes, opts) {
3093
3115
  async function submit(request, matches, actionMatch, requestContext, isRouteRequest) {
3094
3116
  let result;
3095
3117
 
3096
- if (!actionMatch.route.action) {
3118
+ if (!actionMatch.route.action && !actionMatch.route.lazy) {
3097
3119
  let error = getInternalRouterError(405, {
3098
3120
  method: request.method,
3099
3121
  pathname: new URL(request.url).pathname,
@@ -3109,7 +3131,7 @@ function createStaticHandler(routes, opts) {
3109
3131
  error
3110
3132
  };
3111
3133
  } else {
3112
- result = await callLoaderOrAction("action", request, actionMatch, matches, basename, true, isRouteRequest, requestContext);
3134
+ result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, detectErrorBoundary, basename, true, isRouteRequest, requestContext);
3113
3135
 
3114
3136
  if (request.signal.aborted) {
3115
3137
  let method = isRouteRequest ? "queryRoute" : "query";
@@ -3207,7 +3229,7 @@ function createStaticHandler(routes, opts) {
3207
3229
  async function loadRouteData(request, matches, requestContext, routeMatch, pendingActionError) {
3208
3230
  let isRouteRequest = routeMatch != null; // Short circuit if we have no loaders to run (queryRoute())
3209
3231
 
3210
- if (isRouteRequest && !(routeMatch != null && routeMatch.route.loader)) {
3232
+ if (isRouteRequest && !(routeMatch != null && routeMatch.route.loader) && !(routeMatch != null && routeMatch.route.lazy)) {
3211
3233
  throw getInternalRouterError(400, {
3212
3234
  method: request.method,
3213
3235
  pathname: new URL(request.url).pathname,
@@ -3216,7 +3238,7 @@ function createStaticHandler(routes, opts) {
3216
3238
  }
3217
3239
 
3218
3240
  let requestMatches = routeMatch ? [routeMatch] : getLoaderMatchesUntilBoundary(matches, Object.keys(pendingActionError || {})[0]);
3219
- let matchesToLoad = requestMatches.filter(m => m.route.loader); // Short circuit if we have no loaders to run (query())
3241
+ let matchesToLoad = requestMatches.filter(m => m.route.loader || m.route.lazy); // Short circuit if we have no loaders to run (query())
3220
3242
 
3221
3243
  if (matchesToLoad.length === 0) {
3222
3244
  return {
@@ -3232,7 +3254,7 @@ function createStaticHandler(routes, opts) {
3232
3254
  };
3233
3255
  }
3234
3256
 
3235
- let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, basename, true, isRouteRequest, requestContext))]);
3257
+ let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, detectErrorBoundary, basename, true, isRouteRequest, requestContext))]);
3236
3258
 
3237
3259
  if (request.signal.aborted) {
3238
3260
  let method = isRouteRequest ? "queryRoute" : "query";
@@ -3373,6 +3395,11 @@ function getMatchesToLoad(history, state, matches, submission, location, isReval
3373
3395
  let boundaryId = pendingError ? Object.keys(pendingError)[0] : undefined;
3374
3396
  let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
3375
3397
  let navigationMatches = boundaryMatches.filter((match, index) => {
3398
+ if (match.route.lazy) {
3399
+ // We haven't loaded this route yet so we don't know if it's got a loader!
3400
+ return true;
3401
+ }
3402
+
3376
3403
  if (match.route.loader == null) {
3377
3404
  return false;
3378
3405
  } // Always call the loader on new route instances and pending defer cancellations
@@ -3486,8 +3513,66 @@ function shouldRevalidateLoader(loaderMatch, arg) {
3486
3513
 
3487
3514
  return arg.defaultShouldRevalidate;
3488
3515
  }
3516
+ /**
3517
+ * Execute route.lazy() methods to lazily load route modules (loader, action,
3518
+ * shouldRevalidate) and update the routeManifest in place which shares objects
3519
+ * with dataRoutes so those get updated as well.
3520
+ */
3521
+
3522
+
3523
+ async function loadLazyRouteModule(route, detectErrorBoundary, manifest) {
3524
+ if (!route.lazy) {
3525
+ return;
3526
+ }
3527
+
3528
+ let lazyRoute = await route.lazy(); // If the lazy route function was executed and removed by another parallel
3529
+ // call then we can return - first lazy() to finish wins because the return
3530
+ // value of lazy is expected to be static
3531
+
3532
+ if (!route.lazy) {
3533
+ return;
3534
+ }
3535
+
3536
+ let routeToUpdate = manifest[route.id];
3537
+ invariant(routeToUpdate, "No route found in manifest"); // Update the route in place. This should be safe because there's no way
3538
+ // we could yet be sitting on this route as we can't get there without
3539
+ // resolving lazy() first.
3540
+ //
3541
+ // This is different than the HMR "update" use-case where we may actively be
3542
+ // on the route being updated. The main concern boils down to "does this
3543
+ // mutation affect any ongoing navigations or any current state.matches
3544
+ // values?". If not, it should be safe to update in place.
3545
+
3546
+ let routeUpdates = {};
3547
+
3548
+ for (let lazyRouteProperty in lazyRoute) {
3549
+ let staticRouteValue = routeToUpdate[lazyRouteProperty];
3550
+ let isPropertyStaticallyDefined = staticRouteValue !== undefined && // This property isn't static since it should always be updated based
3551
+ // on the route updates
3552
+ lazyRouteProperty !== "hasErrorBoundary";
3553
+ warning(!isPropertyStaticallyDefined, "Route \"" + routeToUpdate.id + "\" has a static property \"" + lazyRouteProperty + "\" " + "defined but its lazy function is also returning a value for this property. " + ("The lazy route property \"" + lazyRouteProperty + "\" will be ignored."));
3554
+
3555
+ if (!isPropertyStaticallyDefined && !immutableRouteKeys.has(lazyRouteProperty)) {
3556
+ routeUpdates[lazyRouteProperty] = lazyRoute[lazyRouteProperty];
3557
+ }
3558
+ } // Mutate the route with the provided updates. Do this first so we pass
3559
+ // the updated version to detectErrorBoundary
3560
+
3561
+
3562
+ Object.assign(routeToUpdate, routeUpdates); // Mutate the `hasErrorBoundary` property on the route based on the route
3563
+ // updates and remove the `lazy` function so we don't resolve the lazy
3564
+ // route again.
3489
3565
 
3490
- async function callLoaderOrAction(type, request, match, matches, basename, isStaticRequest, isRouteRequest, requestContext) {
3566
+ Object.assign(routeToUpdate, {
3567
+ // To keep things framework agnostic, we use the provided
3568
+ // `detectErrorBoundary` function to set the `hasErrorBoundary` route
3569
+ // property since the logic will differ between frameworks.
3570
+ hasErrorBoundary: detectErrorBoundary(_extends({}, routeToUpdate)),
3571
+ lazy: undefined
3572
+ });
3573
+ }
3574
+
3575
+ async function callLoaderOrAction(type, request, match, matches, manifest, detectErrorBoundary, basename, isStaticRequest, isRouteRequest, requestContext) {
3491
3576
  if (basename === void 0) {
3492
3577
  basename = "/";
3493
3578
  }
@@ -3501,29 +3586,70 @@ async function callLoaderOrAction(type, request, match, matches, basename, isSta
3501
3586
  }
3502
3587
 
3503
3588
  let resultType;
3504
- let result; // Setup a promise we can race against so that abort signals short circuit
3589
+ let result;
3590
+ let onReject;
3505
3591
 
3506
- let reject;
3507
- let abortPromise = new Promise((_, r) => reject = r);
3508
-
3509
- let onReject = () => reject();
3592
+ let runHandler = handler => {
3593
+ // Setup a promise we can race against so that abort signals short circuit
3594
+ let reject;
3595
+ let abortPromise = new Promise((_, r) => reject = r);
3510
3596
 
3511
- request.signal.addEventListener("abort", onReject);
3597
+ onReject = () => reject();
3512
3598
 
3513
- try {
3514
- let handler = match.route[type];
3515
- invariant(handler, "Could not find the " + type + " to run on the \"" + match.route.id + "\" route");
3516
- result = await Promise.race([handler({
3599
+ request.signal.addEventListener("abort", onReject);
3600
+ return Promise.race([handler({
3517
3601
  request,
3518
3602
  params: match.params,
3519
3603
  context: requestContext
3520
3604
  }), abortPromise]);
3605
+ };
3606
+
3607
+ try {
3608
+ let handler = match.route[type];
3609
+
3610
+ if (match.route.lazy) {
3611
+ if (handler) {
3612
+ // Run statically defined handler in parallel with lazy()
3613
+ let values = await Promise.all([runHandler(handler), loadLazyRouteModule(match.route, detectErrorBoundary, manifest)]);
3614
+ result = values[0];
3615
+ } else {
3616
+ // Load lazy route module, then run any returned handler
3617
+ await loadLazyRouteModule(match.route, detectErrorBoundary, manifest);
3618
+ handler = match.route[type];
3619
+
3620
+ if (handler) {
3621
+ // Handler still run even if we got interrupted to maintain consistency
3622
+ // with un-abortable behavior of handler execution on non-lazy or
3623
+ // previously-lazy-loaded routes
3624
+ result = await runHandler(handler);
3625
+ } else if (type === "action") {
3626
+ throw getInternalRouterError(405, {
3627
+ method: request.method,
3628
+ pathname: new URL(request.url).pathname,
3629
+ routeId: match.route.id
3630
+ });
3631
+ } else {
3632
+ // lazy() route has no loader to run. Short circuit here so we don't
3633
+ // hit the invariant below that errors on returning undefined.
3634
+ return {
3635
+ type: ResultType.data,
3636
+ data: undefined
3637
+ };
3638
+ }
3639
+ }
3640
+ } else {
3641
+ invariant(handler, "Could not find the " + type + " to run on the \"" + match.route.id + "\" route");
3642
+ result = await runHandler(handler);
3643
+ }
3644
+
3521
3645
  invariant(result !== undefined, "You defined " + (type === "action" ? "an action" : "a loader") + " for route " + ("\"" + match.route.id + "\" but didn't return anything from your `" + type + "` ") + "function. Please return a value or `null`.");
3522
3646
  } catch (e) {
3523
3647
  resultType = ResultType.error;
3524
3648
  result = e;
3525
3649
  } finally {
3526
- request.signal.removeEventListener("abort", onReject);
3650
+ if (onReject) {
3651
+ request.signal.removeEventListener("abort", onReject);
3652
+ }
3527
3653
  }
3528
3654
 
3529
3655
  if (isResponse(result)) {
@@ -4040,5 +4166,5 @@ function getTargetMatch(matches, location) {
4040
4166
  return pathMatches[pathMatches.length - 1];
4041
4167
  } //#endregion
4042
4168
 
4043
- export { AbortedDeferredError, Action, ErrorResponse, IDLE_BLOCKER, IDLE_FETCHER, IDLE_NAVIGATION, UNSAFE_DEFERRED_SYMBOL, DeferredData as UNSAFE_DeferredData, convertRoutesToDataRoutes as UNSAFE_convertRoutesToDataRoutes, getPathContributingMatches as UNSAFE_getPathContributingMatches, invariant as UNSAFE_invariant, createBrowserHistory, createHashHistory, createMemoryHistory, createPath, createRouter, createStaticHandler, defer, generatePath, getStaticContextFromError, getToPathname, isRouteErrorResponse, joinPaths, json, matchPath, matchRoutes, normalizePathname, parsePath, redirect, resolvePath, resolveTo, stripBasename, warning };
4169
+ export { AbortedDeferredError, Action, ErrorResponse, IDLE_BLOCKER, IDLE_FETCHER, IDLE_NAVIGATION, UNSAFE_DEFERRED_SYMBOL, DeferredData as UNSAFE_DeferredData, convertRoutesToDataRoutes as UNSAFE_convertRoutesToDataRoutes, getPathContributingMatches as UNSAFE_getPathContributingMatches, invariant as UNSAFE_invariant, warning as UNSAFE_warning, createBrowserHistory, createHashHistory, createMemoryHistory, createPath, createRouter, createStaticHandler, defer, generatePath, getStaticContextFromError, getToPathname, isRouteErrorResponse, joinPaths, json, matchPath, matchRoutes, normalizePathname, parsePath, redirect, resolvePath, resolveTo, stripBasename };
4044
4170
  //# sourceMappingURL=router.js.map