@remix-run/router 0.0.0-experimental-e7e9ce6e → 0.0.0-experimental-91f2bf54

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 v0.0.0-experimental-e7e9ce6e
2
+ * @remix-run/router v0.0.0-experimental-91f2bf54
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
 
@@ -1634,6 +1654,16 @@ function createRouter(init) {
1634
1654
  function updateState(newState) {
1635
1655
  state = _extends({}, state, newState);
1636
1656
  subscribers.forEach(subscriber => subscriber(state));
1657
+ }
1658
+
1659
+ function completeNavigation(location, newState) {
1660
+ // @ts-expect-error
1661
+ if (typeof document !== "undefined" && document.startViewTransition) {
1662
+ // @ts-expect-error
1663
+ document.startViewTransition(() => completeNavigationForRealz(location, newState));
1664
+ } else {
1665
+ completeNavigationForRealz(location, newState);
1666
+ }
1637
1667
  } // Complete a navigation returning the state.navigation back to the IDLE_NAVIGATION
1638
1668
  // and setting state.[historyAction/location/matches] to the new route.
1639
1669
  // - Location is a required param
@@ -1641,7 +1671,7 @@ function createRouter(init) {
1641
1671
  // - Can pass any other state in newState
1642
1672
 
1643
1673
 
1644
- function completeNavigation(location, newState) {
1674
+ function completeNavigationForRealz(location, newState) {
1645
1675
  var _location$state, _location$state2;
1646
1676
 
1647
1677
  // Deduce if we're in a loading/actionReload state:
@@ -1953,7 +1983,7 @@ function createRouter(init) {
1953
1983
  let result;
1954
1984
  let actionMatch = getTargetMatch(matches, location);
1955
1985
 
1956
- if (!actionMatch.route.action) {
1986
+ if (!actionMatch.route.action && !actionMatch.route.lazy) {
1957
1987
  result = {
1958
1988
  type: ResultType.error,
1959
1989
  error: getInternalRouterError(405, {
@@ -1963,7 +1993,7 @@ function createRouter(init) {
1963
1993
  })
1964
1994
  };
1965
1995
  } else {
1966
- result = await callLoaderOrAction("action", request, actionMatch, matches, router.basename);
1996
+ result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, detectErrorBoundary, router.basename);
1967
1997
 
1968
1998
  if (request.signal.aborted) {
1969
1999
  return {
@@ -2209,7 +2239,7 @@ function createRouter(init) {
2209
2239
  interruptActiveLoads();
2210
2240
  fetchLoadMatches.delete(key);
2211
2241
 
2212
- if (!match.route.action) {
2242
+ if (!match.route.action && !match.route.lazy) {
2213
2243
  let error = getInternalRouterError(405, {
2214
2244
  method: submission.formMethod,
2215
2245
  pathname: path,
@@ -2237,7 +2267,7 @@ function createRouter(init) {
2237
2267
  let abortController = new AbortController();
2238
2268
  let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);
2239
2269
  fetchControllers.set(key, abortController);
2240
- let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, router.basename);
2270
+ let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, manifest, detectErrorBoundary, router.basename);
2241
2271
 
2242
2272
  if (fetchRequest.signal.aborted) {
2243
2273
  // We can delete this so long as we weren't aborted by ou our own fetcher
@@ -2408,7 +2438,7 @@ function createRouter(init) {
2408
2438
  let abortController = new AbortController();
2409
2439
  let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
2410
2440
  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
2441
+ 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
2442
  // as a normal load. resolveDeferredData will return undefined if this
2413
2443
  // fetcher gets aborted, so we just leave result untouched and short circuit
2414
2444
  // below if that happens
@@ -2577,9 +2607,9 @@ function createRouter(init) {
2577
2607
  // Call all navigation loaders and revalidating fetcher loaders in parallel,
2578
2608
  // then slice off the results into separate arrays so we can handle them
2579
2609
  // accordingly
2580
- let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, router.basename)), ...fetchersToLoad.map(f => {
2610
+ let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, detectErrorBoundary, router.basename)), ...fetchersToLoad.map(f => {
2581
2611
  if (f.matches && f.match) {
2582
- return callLoaderOrAction("loader", createClientSideRequest(init.history, f.path, request.signal), f.match, f.matches, router.basename);
2612
+ return callLoaderOrAction("loader", createClientSideRequest(init.history, f.path, request.signal), f.match, f.matches, manifest, detectErrorBoundary, router.basename);
2583
2613
  } else {
2584
2614
  let error = {
2585
2615
  type: ResultType.error,
@@ -2871,7 +2901,9 @@ function createRouter(init) {
2871
2901
  const UNSAFE_DEFERRED_SYMBOL = Symbol("deferred");
2872
2902
  function createStaticHandler(routes, opts) {
2873
2903
  invariant(routes.length > 0, "You must provide a non-empty routes array to createStaticHandler");
2874
- let dataRoutes = convertRoutesToDataRoutes(routes);
2904
+ let manifest = {};
2905
+ let detectErrorBoundary = (opts == null ? void 0 : opts.detectErrorBoundary) || defaultDetectErrorBoundary;
2906
+ let dataRoutes = convertRoutesToDataRoutes(routes, detectErrorBoundary, undefined, manifest);
2875
2907
  let basename = (opts ? opts.basename : null) || "/";
2876
2908
  /**
2877
2909
  * The query() method is intended for document requests, in which we want to
@@ -3093,7 +3125,7 @@ function createStaticHandler(routes, opts) {
3093
3125
  async function submit(request, matches, actionMatch, requestContext, isRouteRequest) {
3094
3126
  let result;
3095
3127
 
3096
- if (!actionMatch.route.action) {
3128
+ if (!actionMatch.route.action && !actionMatch.route.lazy) {
3097
3129
  let error = getInternalRouterError(405, {
3098
3130
  method: request.method,
3099
3131
  pathname: new URL(request.url).pathname,
@@ -3109,7 +3141,7 @@ function createStaticHandler(routes, opts) {
3109
3141
  error
3110
3142
  };
3111
3143
  } else {
3112
- result = await callLoaderOrAction("action", request, actionMatch, matches, basename, true, isRouteRequest, requestContext);
3144
+ result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, detectErrorBoundary, basename, true, isRouteRequest, requestContext);
3113
3145
 
3114
3146
  if (request.signal.aborted) {
3115
3147
  let method = isRouteRequest ? "queryRoute" : "query";
@@ -3207,7 +3239,7 @@ function createStaticHandler(routes, opts) {
3207
3239
  async function loadRouteData(request, matches, requestContext, routeMatch, pendingActionError) {
3208
3240
  let isRouteRequest = routeMatch != null; // Short circuit if we have no loaders to run (queryRoute())
3209
3241
 
3210
- if (isRouteRequest && !(routeMatch != null && routeMatch.route.loader)) {
3242
+ if (isRouteRequest && !(routeMatch != null && routeMatch.route.loader) && !(routeMatch != null && routeMatch.route.lazy)) {
3211
3243
  throw getInternalRouterError(400, {
3212
3244
  method: request.method,
3213
3245
  pathname: new URL(request.url).pathname,
@@ -3216,7 +3248,7 @@ function createStaticHandler(routes, opts) {
3216
3248
  }
3217
3249
 
3218
3250
  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())
3251
+ let matchesToLoad = requestMatches.filter(m => m.route.loader || m.route.lazy); // Short circuit if we have no loaders to run (query())
3220
3252
 
3221
3253
  if (matchesToLoad.length === 0) {
3222
3254
  return {
@@ -3232,7 +3264,7 @@ function createStaticHandler(routes, opts) {
3232
3264
  };
3233
3265
  }
3234
3266
 
3235
- let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, basename, true, isRouteRequest, requestContext))]);
3267
+ let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, detectErrorBoundary, basename, true, isRouteRequest, requestContext))]);
3236
3268
 
3237
3269
  if (request.signal.aborted) {
3238
3270
  let method = isRouteRequest ? "queryRoute" : "query";
@@ -3373,6 +3405,11 @@ function getMatchesToLoad(history, state, matches, submission, location, isReval
3373
3405
  let boundaryId = pendingError ? Object.keys(pendingError)[0] : undefined;
3374
3406
  let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
3375
3407
  let navigationMatches = boundaryMatches.filter((match, index) => {
3408
+ if (match.route.lazy) {
3409
+ // We haven't loaded this route yet so we don't know if it's got a loader!
3410
+ return true;
3411
+ }
3412
+
3376
3413
  if (match.route.loader == null) {
3377
3414
  return false;
3378
3415
  } // Always call the loader on new route instances and pending defer cancellations
@@ -3486,8 +3523,66 @@ function shouldRevalidateLoader(loaderMatch, arg) {
3486
3523
 
3487
3524
  return arg.defaultShouldRevalidate;
3488
3525
  }
3526
+ /**
3527
+ * Execute route.lazy() methods to lazily load route modules (loader, action,
3528
+ * shouldRevalidate) and update the routeManifest in place which shares objects
3529
+ * with dataRoutes so those get updated as well.
3530
+ */
3531
+
3532
+
3533
+ async function loadLazyRouteModule(route, detectErrorBoundary, manifest) {
3534
+ if (!route.lazy) {
3535
+ return;
3536
+ }
3537
+
3538
+ let lazyRoute = await route.lazy(); // If the lazy route function was executed and removed by another parallel
3539
+ // call then we can return - first lazy() to finish wins because the return
3540
+ // value of lazy is expected to be static
3541
+
3542
+ if (!route.lazy) {
3543
+ return;
3544
+ }
3545
+
3546
+ let routeToUpdate = manifest[route.id];
3547
+ invariant(routeToUpdate, "No route found in manifest"); // Update the route in place. This should be safe because there's no way
3548
+ // we could yet be sitting on this route as we can't get there without
3549
+ // resolving lazy() first.
3550
+ //
3551
+ // This is different than the HMR "update" use-case where we may actively be
3552
+ // on the route being updated. The main concern boils down to "does this
3553
+ // mutation affect any ongoing navigations or any current state.matches
3554
+ // values?". If not, it should be safe to update in place.
3555
+
3556
+ let routeUpdates = {};
3557
+
3558
+ for (let lazyRouteProperty in lazyRoute) {
3559
+ let staticRouteValue = routeToUpdate[lazyRouteProperty];
3560
+ let isPropertyStaticallyDefined = staticRouteValue !== undefined && // This property isn't static since it should always be updated based
3561
+ // on the route updates
3562
+ lazyRouteProperty !== "hasErrorBoundary";
3563
+ 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."));
3564
+
3565
+ if (!isPropertyStaticallyDefined && !immutableRouteKeys.has(lazyRouteProperty)) {
3566
+ routeUpdates[lazyRouteProperty] = lazyRoute[lazyRouteProperty];
3567
+ }
3568
+ } // Mutate the route with the provided updates. Do this first so we pass
3569
+ // the updated version to detectErrorBoundary
3489
3570
 
3490
- async function callLoaderOrAction(type, request, match, matches, basename, isStaticRequest, isRouteRequest, requestContext) {
3571
+
3572
+ Object.assign(routeToUpdate, routeUpdates); // Mutate the `hasErrorBoundary` property on the route based on the route
3573
+ // updates and remove the `lazy` function so we don't resolve the lazy
3574
+ // route again.
3575
+
3576
+ Object.assign(routeToUpdate, {
3577
+ // To keep things framework agnostic, we use the provided
3578
+ // `detectErrorBoundary` function to set the `hasErrorBoundary` route
3579
+ // property since the logic will differ between frameworks.
3580
+ hasErrorBoundary: detectErrorBoundary(_extends({}, routeToUpdate)),
3581
+ lazy: undefined
3582
+ });
3583
+ }
3584
+
3585
+ async function callLoaderOrAction(type, request, match, matches, manifest, detectErrorBoundary, basename, isStaticRequest, isRouteRequest, requestContext) {
3491
3586
  if (basename === void 0) {
3492
3587
  basename = "/";
3493
3588
  }
@@ -3501,29 +3596,70 @@ async function callLoaderOrAction(type, request, match, matches, basename, isSta
3501
3596
  }
3502
3597
 
3503
3598
  let resultType;
3504
- let result; // Setup a promise we can race against so that abort signals short circuit
3505
-
3506
- let reject;
3507
- let abortPromise = new Promise((_, r) => reject = r);
3599
+ let result;
3600
+ let onReject;
3508
3601
 
3509
- let onReject = () => reject();
3602
+ let runHandler = handler => {
3603
+ // Setup a promise we can race against so that abort signals short circuit
3604
+ let reject;
3605
+ let abortPromise = new Promise((_, r) => reject = r);
3510
3606
 
3511
- request.signal.addEventListener("abort", onReject);
3607
+ onReject = () => reject();
3512
3608
 
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({
3609
+ request.signal.addEventListener("abort", onReject);
3610
+ return Promise.race([handler({
3517
3611
  request,
3518
3612
  params: match.params,
3519
3613
  context: requestContext
3520
3614
  }), abortPromise]);
3615
+ };
3616
+
3617
+ try {
3618
+ let handler = match.route[type];
3619
+
3620
+ if (match.route.lazy) {
3621
+ if (handler) {
3622
+ // Run statically defined handler in parallel with lazy()
3623
+ let values = await Promise.all([runHandler(handler), loadLazyRouteModule(match.route, detectErrorBoundary, manifest)]);
3624
+ result = values[0];
3625
+ } else {
3626
+ // Load lazy route module, then run any returned handler
3627
+ await loadLazyRouteModule(match.route, detectErrorBoundary, manifest);
3628
+ handler = match.route[type];
3629
+
3630
+ if (handler) {
3631
+ // Handler still run even if we got interrupted to maintain consistency
3632
+ // with un-abortable behavior of handler execution on non-lazy or
3633
+ // previously-lazy-loaded routes
3634
+ result = await runHandler(handler);
3635
+ } else if (type === "action") {
3636
+ throw getInternalRouterError(405, {
3637
+ method: request.method,
3638
+ pathname: new URL(request.url).pathname,
3639
+ routeId: match.route.id
3640
+ });
3641
+ } else {
3642
+ // lazy() route has no loader to run. Short circuit here so we don't
3643
+ // hit the invariant below that errors on returning undefined.
3644
+ return {
3645
+ type: ResultType.data,
3646
+ data: undefined
3647
+ };
3648
+ }
3649
+ }
3650
+ } else {
3651
+ invariant(handler, "Could not find the " + type + " to run on the \"" + match.route.id + "\" route");
3652
+ result = await runHandler(handler);
3653
+ }
3654
+
3521
3655
  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
3656
  } catch (e) {
3523
3657
  resultType = ResultType.error;
3524
3658
  result = e;
3525
3659
  } finally {
3526
- request.signal.removeEventListener("abort", onReject);
3660
+ if (onReject) {
3661
+ request.signal.removeEventListener("abort", onReject);
3662
+ }
3527
3663
  }
3528
3664
 
3529
3665
  if (isResponse(result)) {
@@ -4040,5 +4176,5 @@ function getTargetMatch(matches, location) {
4040
4176
  return pathMatches[pathMatches.length - 1];
4041
4177
  } //#endregion
4042
4178
 
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 };
4179
+ 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
4180
  //# sourceMappingURL=router.js.map