@remix-run/router 1.3.3-pre.1 → 1.4.0-pre.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/CHANGELOG.md CHANGED
@@ -1,17 +1,78 @@
1
1
  # `@remix-run/router`
2
2
 
3
- ## 1.3.3-pre.1
3
+ ## 1.4.0-pre.0
4
+
5
+ ### Minor Changes
6
+
7
+ - **Introducing Lazy Route Modules!** ([#10045](https://github.com/remix-run/react-router/pull/10045))
8
+
9
+ In order to keep your application bundles small and support code-splitting of your routes, we've introduced a new `lazy()` route property. This is an async function that resolves the non-route-matching portions of your route definition (`loader`, `action`, `element`/`Component`, `errorElement`/`ErrorBoundary`, `shouldRevalidate`, `handle`).
10
+
11
+ Lazy routes are resolved on initial load and during the `loading` or `submitting` phase of a navigation or fetcher call. You cannot lazily define route-matching properties (`path`, `index`, `children`) since we only execute your lazy route functions after we've matched known routes.
12
+
13
+ Your `lazy` functions will typically return the result of a dynamic import.
14
+
15
+ ```jsx
16
+ // In this example, we assume most folks land on the homepage so we include that
17
+ // in our critical-path bundle, but then we lazily load modules for /a and /b so
18
+ // they don't load until the user navigates to those routes
19
+ let routes = createRoutesFromElements(
20
+ <Route path="/" element={<Layout />}>
21
+ <Route index element={<Home />} />
22
+ <Route path="a" lazy={() => import("./a")} />
23
+ <Route path="b" lazy={() => import("./b")} />
24
+ </Route>
25
+ );
26
+ ```
27
+
28
+ Then in your lazy route modules, export the properties you want defined for the route:
29
+
30
+ ```jsx
31
+ export async function loader({ request }) {
32
+ let data = await fetchData(request);
33
+ return json(data);
34
+ }
35
+
36
+ // Export a `Component` directly instead of needing to create a React Element from it
37
+ export function Component() {
38
+ let data = useLoaderData();
39
+
40
+ return (
41
+ <>
42
+ <h1>You made it!</h1>
43
+ <p>{data}</p>
44
+ </>
45
+ );
46
+ }
47
+
48
+ // Export an `ErrorBoundary` directly instead of needing to create a React Element from it
49
+ export function ErrorBoundary() {
50
+ let error = useRouteError();
51
+ return isRouteErrorResponse(error) ? (
52
+ <h1>
53
+ {error.status} {error.statusText}
54
+ </h1>
55
+ ) : (
56
+ <h1>{error.message || error}</h1>
57
+ );
58
+ }
59
+ ```
60
+
61
+ An example of this in action can be found in the [`examples/lazy-loading-router-provider`](https://github.com/remix-run/react-router/tree/main/examples/lazy-loading-router-provider) directory of the repository.
62
+
63
+ 🙌 Huge thanks to @rossipedia for the [Initial Proposal](https://github.com/remix-run/react-router/discussions/9826) and [POC Implementation](https://github.com/remix-run/react-router/pull/9830).
4
64
 
5
65
  ### Patch Changes
6
66
 
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))
67
+ - Fix `generatePath` incorrectly applying parameters in some cases ([`bc6fefa1`](https://github.com/remix-run/react-router/commit/bc6fefa19019ce9f5250c8b5af9b8c5d3390e9d1))
8
68
 
9
- ## 1.3.3-pre.0
69
+ ## 1.3.3
10
70
 
11
71
  ### Patch Changes
12
72
 
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))
73
+ - 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
74
  - 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))
75
+ - 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
76
  - Add internal API for custom HMR implementations ([#9996](https://github.com/remix-run/react-router/pull/9996))
16
77
 
17
78
  ## 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 v1.3.3-pre.1
2
+ * @remix-run/router v1.4.0-pre.0
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
 
@@ -2002,7 +2026,7 @@ function createRouter(init) {
2002
2026
  let result;
2003
2027
  let actionMatch = getTargetMatch(matches, location);
2004
2028
 
2005
- if (!actionMatch.route.action) {
2029
+ if (!actionMatch.route.action && !actionMatch.route.lazy) {
2006
2030
  result = {
2007
2031
  type: ResultType.error,
2008
2032
  error: getInternalRouterError(405, {
@@ -2012,7 +2036,7 @@ function createRouter(init) {
2012
2036
  })
2013
2037
  };
2014
2038
  } else {
2015
- result = await callLoaderOrAction("action", request, actionMatch, matches, router.basename);
2039
+ result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, detectErrorBoundary, router.basename);
2016
2040
 
2017
2041
  if (request.signal.aborted) {
2018
2042
  return {
@@ -2258,7 +2282,7 @@ function createRouter(init) {
2258
2282
  interruptActiveLoads();
2259
2283
  fetchLoadMatches.delete(key);
2260
2284
 
2261
- if (!match.route.action) {
2285
+ if (!match.route.action && !match.route.lazy) {
2262
2286
  let error = getInternalRouterError(405, {
2263
2287
  method: submission.formMethod,
2264
2288
  pathname: path,
@@ -2286,7 +2310,7 @@ function createRouter(init) {
2286
2310
  let abortController = new AbortController();
2287
2311
  let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);
2288
2312
  fetchControllers.set(key, abortController);
2289
- let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, router.basename);
2313
+ let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, manifest, detectErrorBoundary, router.basename);
2290
2314
 
2291
2315
  if (fetchRequest.signal.aborted) {
2292
2316
  // We can delete this so long as we weren't aborted by ou our own fetcher
@@ -2457,7 +2481,7 @@ function createRouter(init) {
2457
2481
  let abortController = new AbortController();
2458
2482
  let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
2459
2483
  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
2484
+ 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
2485
  // as a normal load. resolveDeferredData will return undefined if this
2462
2486
  // fetcher gets aborted, so we just leave result untouched and short circuit
2463
2487
  // below if that happens
@@ -2626,9 +2650,9 @@ function createRouter(init) {
2626
2650
  // Call all navigation loaders and revalidating fetcher loaders in parallel,
2627
2651
  // then slice off the results into separate arrays so we can handle them
2628
2652
  // accordingly
2629
- let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, router.basename)), ...fetchersToLoad.map(f => {
2653
+ let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, detectErrorBoundary, router.basename)), ...fetchersToLoad.map(f => {
2630
2654
  if (f.matches && f.match) {
2631
- return callLoaderOrAction("loader", createClientSideRequest(init.history, f.path, request.signal), f.match, f.matches, router.basename);
2655
+ return callLoaderOrAction("loader", createClientSideRequest(init.history, f.path, request.signal), f.match, f.matches, manifest, detectErrorBoundary, router.basename);
2632
2656
  } else {
2633
2657
  let error = {
2634
2658
  type: ResultType.error,
@@ -2920,7 +2944,9 @@ function createRouter(init) {
2920
2944
  const UNSAFE_DEFERRED_SYMBOL = Symbol("deferred");
2921
2945
  function createStaticHandler(routes, opts) {
2922
2946
  invariant(routes.length > 0, "You must provide a non-empty routes array to createStaticHandler");
2923
- let dataRoutes = convertRoutesToDataRoutes(routes);
2947
+ let manifest = {};
2948
+ let detectErrorBoundary = (opts == null ? void 0 : opts.detectErrorBoundary) || defaultDetectErrorBoundary;
2949
+ let dataRoutes = convertRoutesToDataRoutes(routes, detectErrorBoundary, undefined, manifest);
2924
2950
  let basename = (opts ? opts.basename : null) || "/";
2925
2951
  /**
2926
2952
  * The query() method is intended for document requests, in which we want to
@@ -3142,7 +3168,7 @@ function createStaticHandler(routes, opts) {
3142
3168
  async function submit(request, matches, actionMatch, requestContext, isRouteRequest) {
3143
3169
  let result;
3144
3170
 
3145
- if (!actionMatch.route.action) {
3171
+ if (!actionMatch.route.action && !actionMatch.route.lazy) {
3146
3172
  let error = getInternalRouterError(405, {
3147
3173
  method: request.method,
3148
3174
  pathname: new URL(request.url).pathname,
@@ -3158,7 +3184,7 @@ function createStaticHandler(routes, opts) {
3158
3184
  error
3159
3185
  };
3160
3186
  } else {
3161
- result = await callLoaderOrAction("action", request, actionMatch, matches, basename, true, isRouteRequest, requestContext);
3187
+ result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, detectErrorBoundary, basename, true, isRouteRequest, requestContext);
3162
3188
 
3163
3189
  if (request.signal.aborted) {
3164
3190
  let method = isRouteRequest ? "queryRoute" : "query";
@@ -3256,7 +3282,7 @@ function createStaticHandler(routes, opts) {
3256
3282
  async function loadRouteData(request, matches, requestContext, routeMatch, pendingActionError) {
3257
3283
  let isRouteRequest = routeMatch != null; // Short circuit if we have no loaders to run (queryRoute())
3258
3284
 
3259
- if (isRouteRequest && !(routeMatch != null && routeMatch.route.loader)) {
3285
+ if (isRouteRequest && !(routeMatch != null && routeMatch.route.loader) && !(routeMatch != null && routeMatch.route.lazy)) {
3260
3286
  throw getInternalRouterError(400, {
3261
3287
  method: request.method,
3262
3288
  pathname: new URL(request.url).pathname,
@@ -3265,7 +3291,7 @@ function createStaticHandler(routes, opts) {
3265
3291
  }
3266
3292
 
3267
3293
  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())
3294
+ let matchesToLoad = requestMatches.filter(m => m.route.loader || m.route.lazy); // Short circuit if we have no loaders to run (query())
3269
3295
 
3270
3296
  if (matchesToLoad.length === 0) {
3271
3297
  return {
@@ -3281,7 +3307,7 @@ function createStaticHandler(routes, opts) {
3281
3307
  };
3282
3308
  }
3283
3309
 
3284
- let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, basename, true, isRouteRequest, requestContext))]);
3310
+ let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, detectErrorBoundary, basename, true, isRouteRequest, requestContext))]);
3285
3311
 
3286
3312
  if (request.signal.aborted) {
3287
3313
  let method = isRouteRequest ? "queryRoute" : "query";
@@ -3422,6 +3448,11 @@ function getMatchesToLoad(history, state, matches, submission, location, isReval
3422
3448
  let boundaryId = pendingError ? Object.keys(pendingError)[0] : undefined;
3423
3449
  let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
3424
3450
  let navigationMatches = boundaryMatches.filter((match, index) => {
3451
+ if (match.route.lazy) {
3452
+ // We haven't loaded this route yet so we don't know if it's got a loader!
3453
+ return true;
3454
+ }
3455
+
3425
3456
  if (match.route.loader == null) {
3426
3457
  return false;
3427
3458
  } // Always call the loader on new route instances and pending defer cancellations
@@ -3535,8 +3566,66 @@ function shouldRevalidateLoader(loaderMatch, arg) {
3535
3566
 
3536
3567
  return arg.defaultShouldRevalidate;
3537
3568
  }
3569
+ /**
3570
+ * Execute route.lazy() methods to lazily load route modules (loader, action,
3571
+ * shouldRevalidate) and update the routeManifest in place which shares objects
3572
+ * with dataRoutes so those get updated as well.
3573
+ */
3574
+
3575
+
3576
+ async function loadLazyRouteModule(route, detectErrorBoundary, manifest) {
3577
+ if (!route.lazy) {
3578
+ return;
3579
+ }
3580
+
3581
+ let lazyRoute = await route.lazy(); // If the lazy route function was executed and removed by another parallel
3582
+ // call then we can return - first lazy() to finish wins because the return
3583
+ // value of lazy is expected to be static
3584
+
3585
+ if (!route.lazy) {
3586
+ return;
3587
+ }
3588
+
3589
+ let routeToUpdate = manifest[route.id];
3590
+ invariant(routeToUpdate, "No route found in manifest"); // Update the route in place. This should be safe because there's no way
3591
+ // we could yet be sitting on this route as we can't get there without
3592
+ // resolving lazy() first.
3593
+ //
3594
+ // This is different than the HMR "update" use-case where we may actively be
3595
+ // on the route being updated. The main concern boils down to "does this
3596
+ // mutation affect any ongoing navigations or any current state.matches
3597
+ // values?". If not, it should be safe to update in place.
3598
+
3599
+ let routeUpdates = {};
3600
+
3601
+ for (let lazyRouteProperty in lazyRoute) {
3602
+ let staticRouteValue = routeToUpdate[lazyRouteProperty];
3603
+ let isPropertyStaticallyDefined = staticRouteValue !== undefined && // This property isn't static since it should always be updated based
3604
+ // on the route updates
3605
+ lazyRouteProperty !== "hasErrorBoundary";
3606
+ 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."));
3607
+
3608
+ if (!isPropertyStaticallyDefined && !immutableRouteKeys.has(lazyRouteProperty)) {
3609
+ routeUpdates[lazyRouteProperty] = lazyRoute[lazyRouteProperty];
3610
+ }
3611
+ } // Mutate the route with the provided updates. Do this first so we pass
3612
+ // the updated version to detectErrorBoundary
3613
+
3614
+
3615
+ Object.assign(routeToUpdate, routeUpdates); // Mutate the `hasErrorBoundary` property on the route based on the route
3616
+ // updates and remove the `lazy` function so we don't resolve the lazy
3617
+ // route again.
3618
+
3619
+ Object.assign(routeToUpdate, {
3620
+ // To keep things framework agnostic, we use the provided
3621
+ // `detectErrorBoundary` function to set the `hasErrorBoundary` route
3622
+ // property since the logic will differ between frameworks.
3623
+ hasErrorBoundary: detectErrorBoundary(_extends({}, routeToUpdate)),
3624
+ lazy: undefined
3625
+ });
3626
+ }
3538
3627
 
3539
- async function callLoaderOrAction(type, request, match, matches, basename, isStaticRequest, isRouteRequest, requestContext) {
3628
+ async function callLoaderOrAction(type, request, match, matches, manifest, detectErrorBoundary, basename, isStaticRequest, isRouteRequest, requestContext) {
3540
3629
  if (basename === void 0) {
3541
3630
  basename = "/";
3542
3631
  }
@@ -3550,29 +3639,70 @@ async function callLoaderOrAction(type, request, match, matches, basename, isSta
3550
3639
  }
3551
3640
 
3552
3641
  let resultType;
3553
- let result; // Setup a promise we can race against so that abort signals short circuit
3554
-
3555
- let reject;
3556
- let abortPromise = new Promise((_, r) => reject = r);
3642
+ let result;
3643
+ let onReject;
3557
3644
 
3558
- let onReject = () => reject();
3645
+ let runHandler = handler => {
3646
+ // Setup a promise we can race against so that abort signals short circuit
3647
+ let reject;
3648
+ let abortPromise = new Promise((_, r) => reject = r);
3559
3649
 
3560
- request.signal.addEventListener("abort", onReject);
3650
+ onReject = () => reject();
3561
3651
 
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({
3652
+ request.signal.addEventListener("abort", onReject);
3653
+ return Promise.race([handler({
3566
3654
  request,
3567
3655
  params: match.params,
3568
3656
  context: requestContext
3569
3657
  }), abortPromise]);
3658
+ };
3659
+
3660
+ try {
3661
+ let handler = match.route[type];
3662
+
3663
+ if (match.route.lazy) {
3664
+ if (handler) {
3665
+ // Run statically defined handler in parallel with lazy()
3666
+ let values = await Promise.all([runHandler(handler), loadLazyRouteModule(match.route, detectErrorBoundary, manifest)]);
3667
+ result = values[0];
3668
+ } else {
3669
+ // Load lazy route module, then run any returned handler
3670
+ await loadLazyRouteModule(match.route, detectErrorBoundary, manifest);
3671
+ handler = match.route[type];
3672
+
3673
+ if (handler) {
3674
+ // Handler still run even if we got interrupted to maintain consistency
3675
+ // with un-abortable behavior of handler execution on non-lazy or
3676
+ // previously-lazy-loaded routes
3677
+ result = await runHandler(handler);
3678
+ } else if (type === "action") {
3679
+ throw getInternalRouterError(405, {
3680
+ method: request.method,
3681
+ pathname: new URL(request.url).pathname,
3682
+ routeId: match.route.id
3683
+ });
3684
+ } else {
3685
+ // lazy() route has no loader to run. Short circuit here so we don't
3686
+ // hit the invariant below that errors on returning undefined.
3687
+ return {
3688
+ type: ResultType.data,
3689
+ data: undefined
3690
+ };
3691
+ }
3692
+ }
3693
+ } else {
3694
+ invariant(handler, "Could not find the " + type + " to run on the \"" + match.route.id + "\" route");
3695
+ result = await runHandler(handler);
3696
+ }
3697
+
3570
3698
  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
3699
  } catch (e) {
3572
3700
  resultType = ResultType.error;
3573
3701
  result = e;
3574
3702
  } finally {
3575
- request.signal.removeEventListener("abort", onReject);
3703
+ if (onReject) {
3704
+ request.signal.removeEventListener("abort", onReject);
3705
+ }
3576
3706
  }
3577
3707
 
3578
3708
  if (isResponse(result)) {
@@ -4099,6 +4229,7 @@ exports.UNSAFE_DeferredData = DeferredData;
4099
4229
  exports.UNSAFE_convertRoutesToDataRoutes = convertRoutesToDataRoutes;
4100
4230
  exports.UNSAFE_getPathContributingMatches = getPathContributingMatches;
4101
4231
  exports.UNSAFE_invariant = invariant;
4232
+ exports.UNSAFE_warning = warning;
4102
4233
  exports.createBrowserHistory = createBrowserHistory;
4103
4234
  exports.createHashHistory = createHashHistory;
4104
4235
  exports.createMemoryHistory = createMemoryHistory;
@@ -4120,5 +4251,4 @@ exports.redirect = redirect;
4120
4251
  exports.resolvePath = resolvePath;
4121
4252
  exports.resolveTo = resolveTo;
4122
4253
  exports.stripBasename = stripBasename;
4123
- exports.warning = warning;
4124
4254
  //# sourceMappingURL=router.cjs.js.map