@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/CHANGELOG.md CHANGED
@@ -1,17 +1,12 @@
1
1
  # `@remix-run/router`
2
2
 
3
- ## 1.3.3-pre.1
3
+ ## 1.3.3
4
4
 
5
5
  ### Patch Changes
6
6
 
7
- - Correctly perform a "hard" redirect for same-origin absolute URLs outside of the router basename ([#10076](https://github.com/remix-run/react-router/pull/10076))
8
-
9
- ## 1.3.3-pre.0
10
-
11
- ### Patch Changes
12
-
13
- - Change `invariant` to an `UNSAFE_` export since it's only intended for internal use ([#10066](https://github.com/remix-run/react-router/pull/10066))
7
+ - Correctly perform a hard redirect for same-origin absolute URLs outside of the router `basename` ([#10076](https://github.com/remix-run/react-router/pull/10076))
14
8
  - Ensure status code and headers are maintained for `defer` loader responses in `createStaticHandler`'s `query()` method ([#10077](https://github.com/remix-run/react-router/pull/10077))
9
+ - Change `invariant` to an `UNSAFE_invariant` export since it's only intended for internal use ([#10066](https://github.com/remix-run/react-router/pull/10066))
15
10
  - Add internal API for custom HMR implementations ([#9996](https://github.com/remix-run/react-router/pull/9996))
16
11
 
17
12
  ## 1.3.2
package/dist/history.d.ts CHANGED
@@ -229,6 +229,7 @@ export declare function createHashHistory(options?: HashHistoryOptions): HashHis
229
229
  */
230
230
  export declare function invariant(value: boolean, message?: string): asserts value;
231
231
  export declare function invariant<T>(value: T | null | undefined, message?: string): asserts value is T;
232
+ export declare function warning(cond: any, message: string): void;
232
233
  /**
233
234
  * Creates a Location object with a unique key from the given Path
234
235
  */
package/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
- export type { ActionFunction, ActionFunctionArgs, AgnosticDataIndexRouteObject, AgnosticDataNonIndexRouteObject, AgnosticDataRouteMatch, AgnosticDataRouteObject, AgnosticIndexRouteObject, AgnosticNonIndexRouteObject, AgnosticRouteMatch, AgnosticRouteObject, TrackedPromise, FormEncType, FormMethod, JsonFunction, LoaderFunction, LoaderFunctionArgs, ParamParseKey, Params, PathMatch, PathPattern, RedirectFunction, ShouldRevalidateFunction, Submission, } from "./utils";
2
- export { AbortedDeferredError, ErrorResponse, defer, generatePath, getToPathname, isRouteErrorResponse, joinPaths, json, matchPath, matchRoutes, normalizePathname, redirect, resolvePath, resolveTo, stripBasename, warning, } from "./utils";
1
+ export type { ActionFunction, ActionFunctionArgs, AgnosticDataIndexRouteObject, AgnosticDataNonIndexRouteObject, AgnosticDataRouteMatch, AgnosticDataRouteObject, AgnosticIndexRouteObject, AgnosticNonIndexRouteObject, AgnosticRouteMatch, AgnosticRouteObject, LazyRouteFunction, TrackedPromise, FormEncType, FormMethod, JsonFunction, LoaderFunction, LoaderFunctionArgs, ParamParseKey, Params, PathMatch, PathPattern, RedirectFunction, ShouldRevalidateFunction, Submission, } from "./utils";
2
+ export { AbortedDeferredError, ErrorResponse, defer, generatePath, getToPathname, isRouteErrorResponse, joinPaths, json, matchPath, matchRoutes, normalizePathname, redirect, resolvePath, resolveTo, stripBasename, } from "./utils";
3
3
  export type { BrowserHistory, BrowserHistoryOptions, HashHistory, HashHistoryOptions, History, InitialEntry, Location, MemoryHistory, MemoryHistoryOptions, Path, To, } from "./history";
4
4
  export { Action, createBrowserHistory, createPath, createHashHistory, createMemoryHistory, parsePath, } from "./history";
5
5
  export * from "./router";
6
6
  /** @internal */
7
+ export type { RouteManifest as UNSAFE_RouteManifest } from "./utils";
7
8
  export { DeferredData as UNSAFE_DeferredData, convertRoutesToDataRoutes as UNSAFE_convertRoutesToDataRoutes, getPathContributingMatches as UNSAFE_getPathContributingMatches, } from "./utils";
8
- export { invariant as UNSAFE_invariant } from "./history";
9
+ export { invariant as UNSAFE_invariant, warning as UNSAFE_warning, } from "./history";
@@ -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
  *
@@ -92,7 +92,7 @@ function createMemoryHistory(options) {
92
92
  }
93
93
 
94
94
  let location = createLocation(entries ? getCurrentLocation().pathname : "/", to, state, key);
95
- warning$1(location.pathname.charAt(0) === "/", "relative pathnames are not supported in memory history: " + JSON.stringify(to));
95
+ warning(location.pathname.charAt(0) === "/", "relative pathnames are not supported in memory history: " + JSON.stringify(to));
96
96
  return location;
97
97
  }
98
98
 
@@ -283,7 +283,7 @@ function createHashHistory(options) {
283
283
  }
284
284
 
285
285
  function validateHashLocation(location, to) {
286
- warning$1(location.pathname.charAt(0) === "/", "relative pathnames are not supported in hash history.push(" + JSON.stringify(to) + ")");
286
+ warning(location.pathname.charAt(0) === "/", "relative pathnames are not supported in hash history.push(" + JSON.stringify(to) + ")");
287
287
  }
288
288
 
289
289
  return getUrlBasedHistory(createHashLocation, createHashHref, validateHashLocation, options);
@@ -301,8 +301,7 @@ function invariant(value, message) {
301
301
  throw new Error(message);
302
302
  }
303
303
  }
304
-
305
- function warning$1(cond, message) {
304
+ function warning(cond, message) {
306
305
  if (!cond) {
307
306
  // eslint-disable-next-line no-console
308
307
  if (typeof console !== "undefined") console.warn(message);
@@ -565,40 +564,54 @@ let ResultType;
565
564
  ResultType["error"] = "error";
566
565
  })(ResultType || (ResultType = {}));
567
566
 
567
+ const immutableRouteKeys = new Set(["lazy", "caseSensitive", "path", "id", "index", "children"]);
568
+ /**
569
+ * lazy() function to load a route definition, which can add non-matching
570
+ * related properties to a route
571
+ */
572
+
568
573
  function isIndexRoute(route) {
569
574
  return route.index === true;
570
575
  } // Walk the route tree generating unique IDs where necessary so we are working
571
576
  // solely with AgnosticDataRouteObject's within the Router
572
577
 
573
578
 
574
- function convertRoutesToDataRoutes(routes, parentPath, allIds) {
579
+ function convertRoutesToDataRoutes(routes, detectErrorBoundary, parentPath, manifest) {
575
580
  if (parentPath === void 0) {
576
581
  parentPath = [];
577
582
  }
578
583
 
579
- if (allIds === void 0) {
580
- allIds = new Set();
584
+ if (manifest === void 0) {
585
+ manifest = {};
581
586
  }
582
587
 
583
588
  return routes.map((route, index) => {
584
589
  let treePath = [...parentPath, index];
585
590
  let id = typeof route.id === "string" ? route.id : treePath.join("-");
586
591
  invariant(route.index !== true || !route.children, "Cannot specify children on an index route");
587
- invariant(!allIds.has(id), "Found a route id collision on id \"" + id + "\". Route " + "id's must be globally unique within Data Router usages");
588
- allIds.add(id);
592
+ invariant(!manifest[id], "Found a route id collision on id \"" + id + "\". Route " + "id's must be globally unique within Data Router usages");
589
593
 
590
594
  if (isIndexRoute(route)) {
591
595
  let indexRoute = _extends({}, route, {
596
+ hasErrorBoundary: detectErrorBoundary(route),
592
597
  id
593
598
  });
594
599
 
600
+ manifest[id] = indexRoute;
595
601
  return indexRoute;
596
602
  } else {
597
603
  let pathOrLayoutRoute = _extends({}, route, {
598
604
  id,
599
- children: route.children ? convertRoutesToDataRoutes(route.children, treePath, allIds) : undefined
605
+ hasErrorBoundary: detectErrorBoundary(route),
606
+ children: undefined
600
607
  });
601
608
 
609
+ manifest[id] = pathOrLayoutRoute;
610
+
611
+ if (route.children) {
612
+ pathOrLayoutRoute.children = convertRoutesToDataRoutes(route.children, detectErrorBoundary, treePath, manifest);
613
+ }
614
+
602
615
  return pathOrLayoutRoute;
603
616
  }
604
617
  });
@@ -845,45 +858,42 @@ function generatePath(originalPath, params) {
845
858
  if (path.endsWith("*") && path !== "*" && !path.endsWith("/*")) {
846
859
  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(/\*$/, "/*") + "\"."));
847
860
  path = path.replace(/\*$/, "/*");
848
- }
861
+ } // ensure `/` is added at the beginning if the path is absolute
849
862
 
850
- return path.replace(/^:(\w+)(\??)/g, (_, key, optional) => {
851
- let param = params[key];
852
863
 
853
- if (optional === "?") {
854
- return param == null ? "" : param;
855
- }
864
+ const prefix = path.startsWith("/") ? "/" : "";
865
+ const segments = path.split(/\/+/).map((segment, index, array) => {
866
+ const isLastSegment = index === array.length - 1; // only apply the splat if it's the last segment
867
+
868
+ if (isLastSegment && segment === "*") {
869
+ const star = "*";
870
+ const starParam = params[star]; // Apply the splat
856
871
 
857
- if (param == null) {
858
- invariant(false, "Missing \":" + key + "\" param");
872
+ return starParam;
859
873
  }
860
874
 
861
- return param;
862
- }).replace(/\/:(\w+)(\??)/g, (_, key, optional) => {
863
- let param = params[key];
875
+ const keyMatch = segment.match(/^:(\w+)(\??)$/);
864
876
 
865
- if (optional === "?") {
866
- return param == null ? "" : "/" + param;
867
- }
877
+ if (keyMatch) {
878
+ const [, key, optional] = keyMatch;
879
+ let param = params[key];
868
880
 
869
- if (param == null) {
870
- invariant(false, "Missing \":" + key + "\" param");
871
- }
881
+ if (optional === "?") {
882
+ return param == null ? "" : param;
883
+ }
872
884
 
873
- return "/" + param;
874
- }) // Remove any optional markers from optional static segments
875
- .replace(/\?/g, "").replace(/(\/?)\*/, (_, prefix, __, str) => {
876
- const star = "*";
885
+ if (param == null) {
886
+ invariant(false, "Missing \":" + key + "\" param");
887
+ }
877
888
 
878
- if (params[star] == null) {
879
- // If no splat was provided, trim the trailing slash _unless_ it's
880
- // the entire path
881
- return str === "/*" ? "/" : "";
882
- } // Apply the splat
889
+ return param;
890
+ } // Remove any optional markers from optional static segments
883
891
 
884
892
 
885
- return "" + prefix + params[star];
886
- });
893
+ return segment.replace(/\?$/g, "");
894
+ }) // Remove empty segments
895
+ .filter(segment => !!segment);
896
+ return prefix + segments.join("/");
887
897
  }
888
898
  /**
889
899
  * A PathPattern is used to match on some portion of a URL pathname.
@@ -1011,25 +1021,6 @@ function stripBasename(pathname, basename) {
1011
1021
 
1012
1022
  return pathname.slice(startIndex) || "/";
1013
1023
  }
1014
- /**
1015
- * @private
1016
- */
1017
-
1018
- function warning(cond, message) {
1019
- if (!cond) {
1020
- // eslint-disable-next-line no-console
1021
- if (typeof console !== "undefined") console.warn(message);
1022
-
1023
- try {
1024
- // Welcome to debugging @remix-run/router!
1025
- //
1026
- // This error is thrown as a convenience so you can more easily
1027
- // find the source for a warning that appears in the console by
1028
- // enabling "pause on exceptions" in your JavaScript debugger.
1029
- throw new Error(message); // eslint-disable-next-line no-empty
1030
- } catch (e) {}
1031
- }
1032
- }
1033
1024
  /**
1034
1025
  * Returns a resolved path object relative to the given pathname.
1035
1026
  *
@@ -1478,7 +1469,9 @@ const IDLE_BLOCKER = {
1478
1469
  };
1479
1470
  const ABSOLUTE_URL_REGEX = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
1480
1471
  const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined";
1481
- const isServer = !isBrowser; //#endregion
1472
+ const isServer = !isBrowser;
1473
+
1474
+ const defaultDetectErrorBoundary = route => Boolean(route.hasErrorBoundary); //#endregion
1482
1475
  ////////////////////////////////////////////////////////////////////////////////
1483
1476
  //#region createRouter
1484
1477
  ////////////////////////////////////////////////////////////////////////////////
@@ -1487,9 +1480,14 @@ const isServer = !isBrowser; //#endregion
1487
1480
  * Create a router and listen to history POP navigations
1488
1481
  */
1489
1482
 
1483
+
1490
1484
  function createRouter(init) {
1491
1485
  invariant(init.routes.length > 0, "You must provide a non-empty routes array to createRouter");
1492
- let dataRoutes = convertRoutesToDataRoutes(init.routes);
1486
+ let detectErrorBoundary = init.detectErrorBoundary || defaultDetectErrorBoundary; // Routes keyed by ID
1487
+
1488
+ let manifest = {}; // Routes in tree format for matching
1489
+
1490
+ let dataRoutes = convertRoutesToDataRoutes(init.routes, detectErrorBoundary, undefined, manifest);
1493
1491
  let inFlightDataRoutes; // Cleanup function for history
1494
1492
 
1495
1493
  let unlistenHistory = null; // Externally-provided functions to call on all state changes
@@ -1527,7 +1525,10 @@ function createRouter(init) {
1527
1525
  };
1528
1526
  }
1529
1527
 
1530
- let initialized = !initialMatches.some(m => m.route.loader) || init.hydrationData != null;
1528
+ let initialized = // All initialMatches need to be loaded before we're ready. If we have lazy
1529
+ // functions around still then we'll need to run them in initialize()
1530
+ !initialMatches.some(m => m.route.lazy) && ( // And we have to either have no loaders or have been provided hydrationData
1531
+ !initialMatches.some(m => m.route.loader) || init.hydrationData != null);
1531
1532
  let router;
1532
1533
  let state = {
1533
1534
  historyAction: init.history.action,
@@ -1651,12 +1652,35 @@ function createRouter(init) {
1651
1652
  }
1652
1653
 
1653
1654
  return startNavigation(historyAction, location);
1654
- }); // Kick off initial data load if needed. Use Pop to avoid modifying history
1655
+ });
1655
1656
 
1656
- if (!state.initialized) {
1657
- startNavigation(exports.Action.Pop, state.location);
1657
+ if (state.initialized) {
1658
+ return router;
1658
1659
  }
1659
1660
 
1661
+ let lazyMatches = state.matches.filter(m => m.route.lazy);
1662
+
1663
+ if (lazyMatches.length === 0) {
1664
+ // Kick off initial data load if needed. Use Pop to avoid modifying history
1665
+ startNavigation(exports.Action.Pop, state.location);
1666
+ return router;
1667
+ } // Load lazy modules, then kick off initial data load if needed
1668
+
1669
+
1670
+ let lazyPromises = lazyMatches.map(m => loadLazyRouteModule(m.route, detectErrorBoundary, manifest));
1671
+ Promise.all(lazyPromises).then(() => {
1672
+ let initialized = !state.matches.some(m => m.route.loader) || init.hydrationData != null;
1673
+
1674
+ if (initialized) {
1675
+ // We already have required loaderData so we can just set initialized
1676
+ updateState({
1677
+ initialized: true
1678
+ });
1679
+ } else {
1680
+ // We still need to kick off initial data loads
1681
+ startNavigation(exports.Action.Pop, state.location);
1682
+ }
1683
+ });
1660
1684
  return router;
1661
1685
  } // Clean up a router and it's side effects
1662
1686
 
@@ -1682,6 +1706,16 @@ function createRouter(init) {
1682
1706
  function updateState(newState) {
1683
1707
  state = _extends({}, state, newState);
1684
1708
  subscribers.forEach(subscriber => subscriber(state));
1709
+ }
1710
+
1711
+ function completeNavigation(location, newState) {
1712
+ // @ts-expect-error
1713
+ if (typeof document !== "undefined" && document.startViewTransition) {
1714
+ // @ts-expect-error
1715
+ document.startViewTransition(() => completeNavigationForRealz(location, newState));
1716
+ } else {
1717
+ completeNavigationForRealz(location, newState);
1718
+ }
1685
1719
  } // Complete a navigation returning the state.navigation back to the IDLE_NAVIGATION
1686
1720
  // and setting state.[historyAction/location/matches] to the new route.
1687
1721
  // - Location is a required param
@@ -1689,7 +1723,7 @@ function createRouter(init) {
1689
1723
  // - Can pass any other state in newState
1690
1724
 
1691
1725
 
1692
- function completeNavigation(location, newState) {
1726
+ function completeNavigationForRealz(location, newState) {
1693
1727
  var _location$state, _location$state2;
1694
1728
 
1695
1729
  // Deduce if we're in a loading/actionReload state:
@@ -2002,7 +2036,7 @@ function createRouter(init) {
2002
2036
  let result;
2003
2037
  let actionMatch = getTargetMatch(matches, location);
2004
2038
 
2005
- if (!actionMatch.route.action) {
2039
+ if (!actionMatch.route.action && !actionMatch.route.lazy) {
2006
2040
  result = {
2007
2041
  type: ResultType.error,
2008
2042
  error: getInternalRouterError(405, {
@@ -2012,7 +2046,7 @@ function createRouter(init) {
2012
2046
  })
2013
2047
  };
2014
2048
  } else {
2015
- result = await callLoaderOrAction("action", request, actionMatch, matches, router.basename);
2049
+ result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, detectErrorBoundary, router.basename);
2016
2050
 
2017
2051
  if (request.signal.aborted) {
2018
2052
  return {
@@ -2258,7 +2292,7 @@ function createRouter(init) {
2258
2292
  interruptActiveLoads();
2259
2293
  fetchLoadMatches.delete(key);
2260
2294
 
2261
- if (!match.route.action) {
2295
+ if (!match.route.action && !match.route.lazy) {
2262
2296
  let error = getInternalRouterError(405, {
2263
2297
  method: submission.formMethod,
2264
2298
  pathname: path,
@@ -2286,7 +2320,7 @@ function createRouter(init) {
2286
2320
  let abortController = new AbortController();
2287
2321
  let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);
2288
2322
  fetchControllers.set(key, abortController);
2289
- let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, router.basename);
2323
+ let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, manifest, detectErrorBoundary, router.basename);
2290
2324
 
2291
2325
  if (fetchRequest.signal.aborted) {
2292
2326
  // We can delete this so long as we weren't aborted by ou our own fetcher
@@ -2457,7 +2491,7 @@ function createRouter(init) {
2457
2491
  let abortController = new AbortController();
2458
2492
  let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
2459
2493
  fetchControllers.set(key, abortController);
2460
- let result = await callLoaderOrAction("loader", fetchRequest, match, matches, router.basename); // Deferred isn't supported for fetcher loads, await everything and treat it
2494
+ let result = await callLoaderOrAction("loader", fetchRequest, match, matches, manifest, detectErrorBoundary, router.basename); // Deferred isn't supported for fetcher loads, await everything and treat it
2461
2495
  // as a normal load. resolveDeferredData will return undefined if this
2462
2496
  // fetcher gets aborted, so we just leave result untouched and short circuit
2463
2497
  // below if that happens
@@ -2626,9 +2660,9 @@ function createRouter(init) {
2626
2660
  // Call all navigation loaders and revalidating fetcher loaders in parallel,
2627
2661
  // then slice off the results into separate arrays so we can handle them
2628
2662
  // accordingly
2629
- let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, router.basename)), ...fetchersToLoad.map(f => {
2663
+ let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, detectErrorBoundary, router.basename)), ...fetchersToLoad.map(f => {
2630
2664
  if (f.matches && f.match) {
2631
- return callLoaderOrAction("loader", createClientSideRequest(init.history, f.path, request.signal), f.match, f.matches, router.basename);
2665
+ return callLoaderOrAction("loader", createClientSideRequest(init.history, f.path, request.signal), f.match, f.matches, manifest, detectErrorBoundary, router.basename);
2632
2666
  } else {
2633
2667
  let error = {
2634
2668
  type: ResultType.error,
@@ -2920,7 +2954,9 @@ function createRouter(init) {
2920
2954
  const UNSAFE_DEFERRED_SYMBOL = Symbol("deferred");
2921
2955
  function createStaticHandler(routes, opts) {
2922
2956
  invariant(routes.length > 0, "You must provide a non-empty routes array to createStaticHandler");
2923
- let dataRoutes = convertRoutesToDataRoutes(routes);
2957
+ let manifest = {};
2958
+ let detectErrorBoundary = (opts == null ? void 0 : opts.detectErrorBoundary) || defaultDetectErrorBoundary;
2959
+ let dataRoutes = convertRoutesToDataRoutes(routes, detectErrorBoundary, undefined, manifest);
2924
2960
  let basename = (opts ? opts.basename : null) || "/";
2925
2961
  /**
2926
2962
  * The query() method is intended for document requests, in which we want to
@@ -3142,7 +3178,7 @@ function createStaticHandler(routes, opts) {
3142
3178
  async function submit(request, matches, actionMatch, requestContext, isRouteRequest) {
3143
3179
  let result;
3144
3180
 
3145
- if (!actionMatch.route.action) {
3181
+ if (!actionMatch.route.action && !actionMatch.route.lazy) {
3146
3182
  let error = getInternalRouterError(405, {
3147
3183
  method: request.method,
3148
3184
  pathname: new URL(request.url).pathname,
@@ -3158,7 +3194,7 @@ function createStaticHandler(routes, opts) {
3158
3194
  error
3159
3195
  };
3160
3196
  } else {
3161
- result = await callLoaderOrAction("action", request, actionMatch, matches, basename, true, isRouteRequest, requestContext);
3197
+ result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, detectErrorBoundary, basename, true, isRouteRequest, requestContext);
3162
3198
 
3163
3199
  if (request.signal.aborted) {
3164
3200
  let method = isRouteRequest ? "queryRoute" : "query";
@@ -3256,7 +3292,7 @@ function createStaticHandler(routes, opts) {
3256
3292
  async function loadRouteData(request, matches, requestContext, routeMatch, pendingActionError) {
3257
3293
  let isRouteRequest = routeMatch != null; // Short circuit if we have no loaders to run (queryRoute())
3258
3294
 
3259
- if (isRouteRequest && !(routeMatch != null && routeMatch.route.loader)) {
3295
+ if (isRouteRequest && !(routeMatch != null && routeMatch.route.loader) && !(routeMatch != null && routeMatch.route.lazy)) {
3260
3296
  throw getInternalRouterError(400, {
3261
3297
  method: request.method,
3262
3298
  pathname: new URL(request.url).pathname,
@@ -3265,7 +3301,7 @@ function createStaticHandler(routes, opts) {
3265
3301
  }
3266
3302
 
3267
3303
  let requestMatches = routeMatch ? [routeMatch] : getLoaderMatchesUntilBoundary(matches, Object.keys(pendingActionError || {})[0]);
3268
- let matchesToLoad = requestMatches.filter(m => m.route.loader); // Short circuit if we have no loaders to run (query())
3304
+ let matchesToLoad = requestMatches.filter(m => m.route.loader || m.route.lazy); // Short circuit if we have no loaders to run (query())
3269
3305
 
3270
3306
  if (matchesToLoad.length === 0) {
3271
3307
  return {
@@ -3281,7 +3317,7 @@ function createStaticHandler(routes, opts) {
3281
3317
  };
3282
3318
  }
3283
3319
 
3284
- let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, basename, true, isRouteRequest, requestContext))]);
3320
+ let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, detectErrorBoundary, basename, true, isRouteRequest, requestContext))]);
3285
3321
 
3286
3322
  if (request.signal.aborted) {
3287
3323
  let method = isRouteRequest ? "queryRoute" : "query";
@@ -3422,6 +3458,11 @@ function getMatchesToLoad(history, state, matches, submission, location, isReval
3422
3458
  let boundaryId = pendingError ? Object.keys(pendingError)[0] : undefined;
3423
3459
  let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
3424
3460
  let navigationMatches = boundaryMatches.filter((match, index) => {
3461
+ if (match.route.lazy) {
3462
+ // We haven't loaded this route yet so we don't know if it's got a loader!
3463
+ return true;
3464
+ }
3465
+
3425
3466
  if (match.route.loader == null) {
3426
3467
  return false;
3427
3468
  } // Always call the loader on new route instances and pending defer cancellations
@@ -3535,8 +3576,66 @@ function shouldRevalidateLoader(loaderMatch, arg) {
3535
3576
 
3536
3577
  return arg.defaultShouldRevalidate;
3537
3578
  }
3579
+ /**
3580
+ * Execute route.lazy() methods to lazily load route modules (loader, action,
3581
+ * shouldRevalidate) and update the routeManifest in place which shares objects
3582
+ * with dataRoutes so those get updated as well.
3583
+ */
3584
+
3585
+
3586
+ async function loadLazyRouteModule(route, detectErrorBoundary, manifest) {
3587
+ if (!route.lazy) {
3588
+ return;
3589
+ }
3590
+
3591
+ let lazyRoute = await route.lazy(); // If the lazy route function was executed and removed by another parallel
3592
+ // call then we can return - first lazy() to finish wins because the return
3593
+ // value of lazy is expected to be static
3594
+
3595
+ if (!route.lazy) {
3596
+ return;
3597
+ }
3598
+
3599
+ let routeToUpdate = manifest[route.id];
3600
+ invariant(routeToUpdate, "No route found in manifest"); // Update the route in place. This should be safe because there's no way
3601
+ // we could yet be sitting on this route as we can't get there without
3602
+ // resolving lazy() first.
3603
+ //
3604
+ // This is different than the HMR "update" use-case where we may actively be
3605
+ // on the route being updated. The main concern boils down to "does this
3606
+ // mutation affect any ongoing navigations or any current state.matches
3607
+ // values?". If not, it should be safe to update in place.
3608
+
3609
+ let routeUpdates = {};
3610
+
3611
+ for (let lazyRouteProperty in lazyRoute) {
3612
+ let staticRouteValue = routeToUpdate[lazyRouteProperty];
3613
+ let isPropertyStaticallyDefined = staticRouteValue !== undefined && // This property isn't static since it should always be updated based
3614
+ // on the route updates
3615
+ lazyRouteProperty !== "hasErrorBoundary";
3616
+ 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."));
3617
+
3618
+ if (!isPropertyStaticallyDefined && !immutableRouteKeys.has(lazyRouteProperty)) {
3619
+ routeUpdates[lazyRouteProperty] = lazyRoute[lazyRouteProperty];
3620
+ }
3621
+ } // Mutate the route with the provided updates. Do this first so we pass
3622
+ // the updated version to detectErrorBoundary
3623
+
3624
+
3625
+ Object.assign(routeToUpdate, routeUpdates); // Mutate the `hasErrorBoundary` property on the route based on the route
3626
+ // updates and remove the `lazy` function so we don't resolve the lazy
3627
+ // route again.
3628
+
3629
+ Object.assign(routeToUpdate, {
3630
+ // To keep things framework agnostic, we use the provided
3631
+ // `detectErrorBoundary` function to set the `hasErrorBoundary` route
3632
+ // property since the logic will differ between frameworks.
3633
+ hasErrorBoundary: detectErrorBoundary(_extends({}, routeToUpdate)),
3634
+ lazy: undefined
3635
+ });
3636
+ }
3538
3637
 
3539
- async function callLoaderOrAction(type, request, match, matches, basename, isStaticRequest, isRouteRequest, requestContext) {
3638
+ async function callLoaderOrAction(type, request, match, matches, manifest, detectErrorBoundary, basename, isStaticRequest, isRouteRequest, requestContext) {
3540
3639
  if (basename === void 0) {
3541
3640
  basename = "/";
3542
3641
  }
@@ -3550,29 +3649,70 @@ async function callLoaderOrAction(type, request, match, matches, basename, isSta
3550
3649
  }
3551
3650
 
3552
3651
  let resultType;
3553
- let result; // Setup a promise we can race against so that abort signals short circuit
3652
+ let result;
3653
+ let onReject;
3554
3654
 
3555
- let reject;
3556
- let abortPromise = new Promise((_, r) => reject = r);
3557
-
3558
- let onReject = () => reject();
3655
+ let runHandler = handler => {
3656
+ // Setup a promise we can race against so that abort signals short circuit
3657
+ let reject;
3658
+ let abortPromise = new Promise((_, r) => reject = r);
3559
3659
 
3560
- request.signal.addEventListener("abort", onReject);
3660
+ onReject = () => reject();
3561
3661
 
3562
- try {
3563
- let handler = match.route[type];
3564
- invariant(handler, "Could not find the " + type + " to run on the \"" + match.route.id + "\" route");
3565
- result = await Promise.race([handler({
3662
+ request.signal.addEventListener("abort", onReject);
3663
+ return Promise.race([handler({
3566
3664
  request,
3567
3665
  params: match.params,
3568
3666
  context: requestContext
3569
3667
  }), abortPromise]);
3668
+ };
3669
+
3670
+ try {
3671
+ let handler = match.route[type];
3672
+
3673
+ if (match.route.lazy) {
3674
+ if (handler) {
3675
+ // Run statically defined handler in parallel with lazy()
3676
+ let values = await Promise.all([runHandler(handler), loadLazyRouteModule(match.route, detectErrorBoundary, manifest)]);
3677
+ result = values[0];
3678
+ } else {
3679
+ // Load lazy route module, then run any returned handler
3680
+ await loadLazyRouteModule(match.route, detectErrorBoundary, manifest);
3681
+ handler = match.route[type];
3682
+
3683
+ if (handler) {
3684
+ // Handler still run even if we got interrupted to maintain consistency
3685
+ // with un-abortable behavior of handler execution on non-lazy or
3686
+ // previously-lazy-loaded routes
3687
+ result = await runHandler(handler);
3688
+ } else if (type === "action") {
3689
+ throw getInternalRouterError(405, {
3690
+ method: request.method,
3691
+ pathname: new URL(request.url).pathname,
3692
+ routeId: match.route.id
3693
+ });
3694
+ } else {
3695
+ // lazy() route has no loader to run. Short circuit here so we don't
3696
+ // hit the invariant below that errors on returning undefined.
3697
+ return {
3698
+ type: ResultType.data,
3699
+ data: undefined
3700
+ };
3701
+ }
3702
+ }
3703
+ } else {
3704
+ invariant(handler, "Could not find the " + type + " to run on the \"" + match.route.id + "\" route");
3705
+ result = await runHandler(handler);
3706
+ }
3707
+
3570
3708
  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`.");
3571
3709
  } catch (e) {
3572
3710
  resultType = ResultType.error;
3573
3711
  result = e;
3574
3712
  } finally {
3575
- request.signal.removeEventListener("abort", onReject);
3713
+ if (onReject) {
3714
+ request.signal.removeEventListener("abort", onReject);
3715
+ }
3576
3716
  }
3577
3717
 
3578
3718
  if (isResponse(result)) {
@@ -4099,6 +4239,7 @@ exports.UNSAFE_DeferredData = DeferredData;
4099
4239
  exports.UNSAFE_convertRoutesToDataRoutes = convertRoutesToDataRoutes;
4100
4240
  exports.UNSAFE_getPathContributingMatches = getPathContributingMatches;
4101
4241
  exports.UNSAFE_invariant = invariant;
4242
+ exports.UNSAFE_warning = warning;
4102
4243
  exports.createBrowserHistory = createBrowserHistory;
4103
4244
  exports.createHashHistory = createHashHistory;
4104
4245
  exports.createMemoryHistory = createMemoryHistory;
@@ -4120,5 +4261,4 @@ exports.redirect = redirect;
4120
4261
  exports.resolvePath = resolvePath;
4121
4262
  exports.resolveTo = resolveTo;
4122
4263
  exports.stripBasename = stripBasename;
4123
- exports.warning = warning;
4124
4264
  //# sourceMappingURL=router.cjs.js.map