@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.
@@ -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
  *
@@ -94,7 +94,7 @@
94
94
  }
95
95
 
96
96
  let location = createLocation(entries ? getCurrentLocation().pathname : "/", to, state, key);
97
- warning$1(location.pathname.charAt(0) === "/", "relative pathnames are not supported in memory history: " + JSON.stringify(to));
97
+ warning(location.pathname.charAt(0) === "/", "relative pathnames are not supported in memory history: " + JSON.stringify(to));
98
98
  return location;
99
99
  }
100
100
 
@@ -285,7 +285,7 @@
285
285
  }
286
286
 
287
287
  function validateHashLocation(location, to) {
288
- warning$1(location.pathname.charAt(0) === "/", "relative pathnames are not supported in hash history.push(" + JSON.stringify(to) + ")");
288
+ warning(location.pathname.charAt(0) === "/", "relative pathnames are not supported in hash history.push(" + JSON.stringify(to) + ")");
289
289
  }
290
290
 
291
291
  return getUrlBasedHistory(createHashLocation, createHashHref, validateHashLocation, options);
@@ -303,8 +303,7 @@
303
303
  throw new Error(message);
304
304
  }
305
305
  }
306
-
307
- function warning$1(cond, message) {
306
+ function warning(cond, message) {
308
307
  if (!cond) {
309
308
  // eslint-disable-next-line no-console
310
309
  if (typeof console !== "undefined") console.warn(message);
@@ -567,40 +566,54 @@
567
566
  ResultType["error"] = "error";
568
567
  })(ResultType || (ResultType = {}));
569
568
 
569
+ const immutableRouteKeys = new Set(["lazy", "caseSensitive", "path", "id", "index", "children"]);
570
+ /**
571
+ * lazy() function to load a route definition, which can add non-matching
572
+ * related properties to a route
573
+ */
574
+
570
575
  function isIndexRoute(route) {
571
576
  return route.index === true;
572
577
  } // Walk the route tree generating unique IDs where necessary so we are working
573
578
  // solely with AgnosticDataRouteObject's within the Router
574
579
 
575
580
 
576
- function convertRoutesToDataRoutes(routes, parentPath, allIds) {
581
+ function convertRoutesToDataRoutes(routes, detectErrorBoundary, parentPath, manifest) {
577
582
  if (parentPath === void 0) {
578
583
  parentPath = [];
579
584
  }
580
585
 
581
- if (allIds === void 0) {
582
- allIds = new Set();
586
+ if (manifest === void 0) {
587
+ manifest = {};
583
588
  }
584
589
 
585
590
  return routes.map((route, index) => {
586
591
  let treePath = [...parentPath, index];
587
592
  let id = typeof route.id === "string" ? route.id : treePath.join("-");
588
593
  invariant(route.index !== true || !route.children, "Cannot specify children on an index route");
589
- invariant(!allIds.has(id), "Found a route id collision on id \"" + id + "\". Route " + "id's must be globally unique within Data Router usages");
590
- allIds.add(id);
594
+ invariant(!manifest[id], "Found a route id collision on id \"" + id + "\". Route " + "id's must be globally unique within Data Router usages");
591
595
 
592
596
  if (isIndexRoute(route)) {
593
597
  let indexRoute = _extends({}, route, {
598
+ hasErrorBoundary: detectErrorBoundary(route),
594
599
  id
595
600
  });
596
601
 
602
+ manifest[id] = indexRoute;
597
603
  return indexRoute;
598
604
  } else {
599
605
  let pathOrLayoutRoute = _extends({}, route, {
600
606
  id,
601
- children: route.children ? convertRoutesToDataRoutes(route.children, treePath, allIds) : undefined
607
+ hasErrorBoundary: detectErrorBoundary(route),
608
+ children: undefined
602
609
  });
603
610
 
611
+ manifest[id] = pathOrLayoutRoute;
612
+
613
+ if (route.children) {
614
+ pathOrLayoutRoute.children = convertRoutesToDataRoutes(route.children, detectErrorBoundary, treePath, manifest);
615
+ }
616
+
604
617
  return pathOrLayoutRoute;
605
618
  }
606
619
  });
@@ -847,45 +860,42 @@
847
860
  if (path.endsWith("*") && path !== "*" && !path.endsWith("/*")) {
848
861
  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(/\*$/, "/*") + "\"."));
849
862
  path = path.replace(/\*$/, "/*");
850
- }
863
+ } // ensure `/` is added at the beginning if the path is absolute
851
864
 
852
- return path.replace(/^:(\w+)(\??)/g, (_, key, optional) => {
853
- let param = params[key];
854
865
 
855
- if (optional === "?") {
856
- return param == null ? "" : param;
857
- }
866
+ const prefix = path.startsWith("/") ? "/" : "";
867
+ const segments = path.split(/\/+/).map((segment, index, array) => {
868
+ const isLastSegment = index === array.length - 1; // only apply the splat if it's the last segment
869
+
870
+ if (isLastSegment && segment === "*") {
871
+ const star = "*";
872
+ const starParam = params[star]; // Apply the splat
858
873
 
859
- if (param == null) {
860
- invariant(false, "Missing \":" + key + "\" param");
874
+ return starParam;
861
875
  }
862
876
 
863
- return param;
864
- }).replace(/\/:(\w+)(\??)/g, (_, key, optional) => {
865
- let param = params[key];
877
+ const keyMatch = segment.match(/^:(\w+)(\??)$/);
866
878
 
867
- if (optional === "?") {
868
- return param == null ? "" : "/" + param;
869
- }
879
+ if (keyMatch) {
880
+ const [, key, optional] = keyMatch;
881
+ let param = params[key];
870
882
 
871
- if (param == null) {
872
- invariant(false, "Missing \":" + key + "\" param");
873
- }
883
+ if (optional === "?") {
884
+ return param == null ? "" : param;
885
+ }
874
886
 
875
- return "/" + param;
876
- }) // Remove any optional markers from optional static segments
877
- .replace(/\?/g, "").replace(/(\/?)\*/, (_, prefix, __, str) => {
878
- const star = "*";
887
+ if (param == null) {
888
+ invariant(false, "Missing \":" + key + "\" param");
889
+ }
879
890
 
880
- if (params[star] == null) {
881
- // If no splat was provided, trim the trailing slash _unless_ it's
882
- // the entire path
883
- return str === "/*" ? "/" : "";
884
- } // Apply the splat
891
+ return param;
892
+ } // Remove any optional markers from optional static segments
885
893
 
886
894
 
887
- return "" + prefix + params[star];
888
- });
895
+ return segment.replace(/\?$/g, "");
896
+ }) // Remove empty segments
897
+ .filter(segment => !!segment);
898
+ return prefix + segments.join("/");
889
899
  }
890
900
  /**
891
901
  * A PathPattern is used to match on some portion of a URL pathname.
@@ -1013,25 +1023,6 @@
1013
1023
 
1014
1024
  return pathname.slice(startIndex) || "/";
1015
1025
  }
1016
- /**
1017
- * @private
1018
- */
1019
-
1020
- function warning(cond, message) {
1021
- if (!cond) {
1022
- // eslint-disable-next-line no-console
1023
- if (typeof console !== "undefined") console.warn(message);
1024
-
1025
- try {
1026
- // Welcome to debugging @remix-run/router!
1027
- //
1028
- // This error is thrown as a convenience so you can more easily
1029
- // find the source for a warning that appears in the console by
1030
- // enabling "pause on exceptions" in your JavaScript debugger.
1031
- throw new Error(message); // eslint-disable-next-line no-empty
1032
- } catch (e) {}
1033
- }
1034
- }
1035
1026
  /**
1036
1027
  * Returns a resolved path object relative to the given pathname.
1037
1028
  *
@@ -1480,7 +1471,9 @@
1480
1471
  };
1481
1472
  const ABSOLUTE_URL_REGEX = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
1482
1473
  const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined";
1483
- const isServer = !isBrowser; //#endregion
1474
+ const isServer = !isBrowser;
1475
+
1476
+ const defaultDetectErrorBoundary = route => Boolean(route.hasErrorBoundary); //#endregion
1484
1477
  ////////////////////////////////////////////////////////////////////////////////
1485
1478
  //#region createRouter
1486
1479
  ////////////////////////////////////////////////////////////////////////////////
@@ -1489,9 +1482,14 @@
1489
1482
  * Create a router and listen to history POP navigations
1490
1483
  */
1491
1484
 
1485
+
1492
1486
  function createRouter(init) {
1493
1487
  invariant(init.routes.length > 0, "You must provide a non-empty routes array to createRouter");
1494
- let dataRoutes = convertRoutesToDataRoutes(init.routes);
1488
+ let detectErrorBoundary = init.detectErrorBoundary || defaultDetectErrorBoundary; // Routes keyed by ID
1489
+
1490
+ let manifest = {}; // Routes in tree format for matching
1491
+
1492
+ let dataRoutes = convertRoutesToDataRoutes(init.routes, detectErrorBoundary, undefined, manifest);
1495
1493
  let inFlightDataRoutes; // Cleanup function for history
1496
1494
 
1497
1495
  let unlistenHistory = null; // Externally-provided functions to call on all state changes
@@ -1529,7 +1527,10 @@
1529
1527
  };
1530
1528
  }
1531
1529
 
1532
- let initialized = !initialMatches.some(m => m.route.loader) || init.hydrationData != null;
1530
+ let initialized = // All initialMatches need to be loaded before we're ready. If we have lazy
1531
+ // functions around still then we'll need to run them in initialize()
1532
+ !initialMatches.some(m => m.route.lazy) && ( // And we have to either have no loaders or have been provided hydrationData
1533
+ !initialMatches.some(m => m.route.loader) || init.hydrationData != null);
1533
1534
  let router;
1534
1535
  let state = {
1535
1536
  historyAction: init.history.action,
@@ -1653,12 +1654,35 @@
1653
1654
  }
1654
1655
 
1655
1656
  return startNavigation(historyAction, location);
1656
- }); // Kick off initial data load if needed. Use Pop to avoid modifying history
1657
+ });
1657
1658
 
1658
- if (!state.initialized) {
1659
- startNavigation(exports.Action.Pop, state.location);
1659
+ if (state.initialized) {
1660
+ return router;
1660
1661
  }
1661
1662
 
1663
+ let lazyMatches = state.matches.filter(m => m.route.lazy);
1664
+
1665
+ if (lazyMatches.length === 0) {
1666
+ // Kick off initial data load if needed. Use Pop to avoid modifying history
1667
+ startNavigation(exports.Action.Pop, state.location);
1668
+ return router;
1669
+ } // Load lazy modules, then kick off initial data load if needed
1670
+
1671
+
1672
+ let lazyPromises = lazyMatches.map(m => loadLazyRouteModule(m.route, detectErrorBoundary, manifest));
1673
+ Promise.all(lazyPromises).then(() => {
1674
+ let initialized = !state.matches.some(m => m.route.loader) || init.hydrationData != null;
1675
+
1676
+ if (initialized) {
1677
+ // We already have required loaderData so we can just set initialized
1678
+ updateState({
1679
+ initialized: true
1680
+ });
1681
+ } else {
1682
+ // We still need to kick off initial data loads
1683
+ startNavigation(exports.Action.Pop, state.location);
1684
+ }
1685
+ });
1662
1686
  return router;
1663
1687
  } // Clean up a router and it's side effects
1664
1688
 
@@ -1684,6 +1708,16 @@
1684
1708
  function updateState(newState) {
1685
1709
  state = _extends({}, state, newState);
1686
1710
  subscribers.forEach(subscriber => subscriber(state));
1711
+ }
1712
+
1713
+ function completeNavigation(location, newState) {
1714
+ // @ts-expect-error
1715
+ if (typeof document !== "undefined" && document.startViewTransition) {
1716
+ // @ts-expect-error
1717
+ document.startViewTransition(() => completeNavigationForRealz(location, newState));
1718
+ } else {
1719
+ completeNavigationForRealz(location, newState);
1720
+ }
1687
1721
  } // Complete a navigation returning the state.navigation back to the IDLE_NAVIGATION
1688
1722
  // and setting state.[historyAction/location/matches] to the new route.
1689
1723
  // - Location is a required param
@@ -1691,7 +1725,7 @@
1691
1725
  // - Can pass any other state in newState
1692
1726
 
1693
1727
 
1694
- function completeNavigation(location, newState) {
1728
+ function completeNavigationForRealz(location, newState) {
1695
1729
  var _location$state, _location$state2;
1696
1730
 
1697
1731
  // Deduce if we're in a loading/actionReload state:
@@ -2004,7 +2038,7 @@
2004
2038
  let result;
2005
2039
  let actionMatch = getTargetMatch(matches, location);
2006
2040
 
2007
- if (!actionMatch.route.action) {
2041
+ if (!actionMatch.route.action && !actionMatch.route.lazy) {
2008
2042
  result = {
2009
2043
  type: ResultType.error,
2010
2044
  error: getInternalRouterError(405, {
@@ -2014,7 +2048,7 @@
2014
2048
  })
2015
2049
  };
2016
2050
  } else {
2017
- result = await callLoaderOrAction("action", request, actionMatch, matches, router.basename);
2051
+ result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, detectErrorBoundary, router.basename);
2018
2052
 
2019
2053
  if (request.signal.aborted) {
2020
2054
  return {
@@ -2260,7 +2294,7 @@
2260
2294
  interruptActiveLoads();
2261
2295
  fetchLoadMatches.delete(key);
2262
2296
 
2263
- if (!match.route.action) {
2297
+ if (!match.route.action && !match.route.lazy) {
2264
2298
  let error = getInternalRouterError(405, {
2265
2299
  method: submission.formMethod,
2266
2300
  pathname: path,
@@ -2288,7 +2322,7 @@
2288
2322
  let abortController = new AbortController();
2289
2323
  let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);
2290
2324
  fetchControllers.set(key, abortController);
2291
- let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, router.basename);
2325
+ let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, manifest, detectErrorBoundary, router.basename);
2292
2326
 
2293
2327
  if (fetchRequest.signal.aborted) {
2294
2328
  // We can delete this so long as we weren't aborted by ou our own fetcher
@@ -2459,7 +2493,7 @@
2459
2493
  let abortController = new AbortController();
2460
2494
  let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
2461
2495
  fetchControllers.set(key, abortController);
2462
- let result = await callLoaderOrAction("loader", fetchRequest, match, matches, router.basename); // Deferred isn't supported for fetcher loads, await everything and treat it
2496
+ let result = await callLoaderOrAction("loader", fetchRequest, match, matches, manifest, detectErrorBoundary, router.basename); // Deferred isn't supported for fetcher loads, await everything and treat it
2463
2497
  // as a normal load. resolveDeferredData will return undefined if this
2464
2498
  // fetcher gets aborted, so we just leave result untouched and short circuit
2465
2499
  // below if that happens
@@ -2628,9 +2662,9 @@
2628
2662
  // Call all navigation loaders and revalidating fetcher loaders in parallel,
2629
2663
  // then slice off the results into separate arrays so we can handle them
2630
2664
  // accordingly
2631
- let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, router.basename)), ...fetchersToLoad.map(f => {
2665
+ let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, detectErrorBoundary, router.basename)), ...fetchersToLoad.map(f => {
2632
2666
  if (f.matches && f.match) {
2633
- return callLoaderOrAction("loader", createClientSideRequest(init.history, f.path, request.signal), f.match, f.matches, router.basename);
2667
+ return callLoaderOrAction("loader", createClientSideRequest(init.history, f.path, request.signal), f.match, f.matches, manifest, detectErrorBoundary, router.basename);
2634
2668
  } else {
2635
2669
  let error = {
2636
2670
  type: ResultType.error,
@@ -2922,7 +2956,9 @@
2922
2956
  const UNSAFE_DEFERRED_SYMBOL = Symbol("deferred");
2923
2957
  function createStaticHandler(routes, opts) {
2924
2958
  invariant(routes.length > 0, "You must provide a non-empty routes array to createStaticHandler");
2925
- let dataRoutes = convertRoutesToDataRoutes(routes);
2959
+ let manifest = {};
2960
+ let detectErrorBoundary = (opts == null ? void 0 : opts.detectErrorBoundary) || defaultDetectErrorBoundary;
2961
+ let dataRoutes = convertRoutesToDataRoutes(routes, detectErrorBoundary, undefined, manifest);
2926
2962
  let basename = (opts ? opts.basename : null) || "/";
2927
2963
  /**
2928
2964
  * The query() method is intended for document requests, in which we want to
@@ -3144,7 +3180,7 @@
3144
3180
  async function submit(request, matches, actionMatch, requestContext, isRouteRequest) {
3145
3181
  let result;
3146
3182
 
3147
- if (!actionMatch.route.action) {
3183
+ if (!actionMatch.route.action && !actionMatch.route.lazy) {
3148
3184
  let error = getInternalRouterError(405, {
3149
3185
  method: request.method,
3150
3186
  pathname: new URL(request.url).pathname,
@@ -3160,7 +3196,7 @@
3160
3196
  error
3161
3197
  };
3162
3198
  } else {
3163
- result = await callLoaderOrAction("action", request, actionMatch, matches, basename, true, isRouteRequest, requestContext);
3199
+ result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, detectErrorBoundary, basename, true, isRouteRequest, requestContext);
3164
3200
 
3165
3201
  if (request.signal.aborted) {
3166
3202
  let method = isRouteRequest ? "queryRoute" : "query";
@@ -3258,7 +3294,7 @@
3258
3294
  async function loadRouteData(request, matches, requestContext, routeMatch, pendingActionError) {
3259
3295
  let isRouteRequest = routeMatch != null; // Short circuit if we have no loaders to run (queryRoute())
3260
3296
 
3261
- if (isRouteRequest && !(routeMatch != null && routeMatch.route.loader)) {
3297
+ if (isRouteRequest && !(routeMatch != null && routeMatch.route.loader) && !(routeMatch != null && routeMatch.route.lazy)) {
3262
3298
  throw getInternalRouterError(400, {
3263
3299
  method: request.method,
3264
3300
  pathname: new URL(request.url).pathname,
@@ -3267,7 +3303,7 @@
3267
3303
  }
3268
3304
 
3269
3305
  let requestMatches = routeMatch ? [routeMatch] : getLoaderMatchesUntilBoundary(matches, Object.keys(pendingActionError || {})[0]);
3270
- let matchesToLoad = requestMatches.filter(m => m.route.loader); // Short circuit if we have no loaders to run (query())
3306
+ let matchesToLoad = requestMatches.filter(m => m.route.loader || m.route.lazy); // Short circuit if we have no loaders to run (query())
3271
3307
 
3272
3308
  if (matchesToLoad.length === 0) {
3273
3309
  return {
@@ -3283,7 +3319,7 @@
3283
3319
  };
3284
3320
  }
3285
3321
 
3286
- let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, basename, true, isRouteRequest, requestContext))]);
3322
+ let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, detectErrorBoundary, basename, true, isRouteRequest, requestContext))]);
3287
3323
 
3288
3324
  if (request.signal.aborted) {
3289
3325
  let method = isRouteRequest ? "queryRoute" : "query";
@@ -3424,6 +3460,11 @@
3424
3460
  let boundaryId = pendingError ? Object.keys(pendingError)[0] : undefined;
3425
3461
  let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
3426
3462
  let navigationMatches = boundaryMatches.filter((match, index) => {
3463
+ if (match.route.lazy) {
3464
+ // We haven't loaded this route yet so we don't know if it's got a loader!
3465
+ return true;
3466
+ }
3467
+
3427
3468
  if (match.route.loader == null) {
3428
3469
  return false;
3429
3470
  } // Always call the loader on new route instances and pending defer cancellations
@@ -3537,8 +3578,66 @@
3537
3578
 
3538
3579
  return arg.defaultShouldRevalidate;
3539
3580
  }
3581
+ /**
3582
+ * Execute route.lazy() methods to lazily load route modules (loader, action,
3583
+ * shouldRevalidate) and update the routeManifest in place which shares objects
3584
+ * with dataRoutes so those get updated as well.
3585
+ */
3586
+
3587
+
3588
+ async function loadLazyRouteModule(route, detectErrorBoundary, manifest) {
3589
+ if (!route.lazy) {
3590
+ return;
3591
+ }
3592
+
3593
+ let lazyRoute = await route.lazy(); // If the lazy route function was executed and removed by another parallel
3594
+ // call then we can return - first lazy() to finish wins because the return
3595
+ // value of lazy is expected to be static
3596
+
3597
+ if (!route.lazy) {
3598
+ return;
3599
+ }
3600
+
3601
+ let routeToUpdate = manifest[route.id];
3602
+ invariant(routeToUpdate, "No route found in manifest"); // Update the route in place. This should be safe because there's no way
3603
+ // we could yet be sitting on this route as we can't get there without
3604
+ // resolving lazy() first.
3605
+ //
3606
+ // This is different than the HMR "update" use-case where we may actively be
3607
+ // on the route being updated. The main concern boils down to "does this
3608
+ // mutation affect any ongoing navigations or any current state.matches
3609
+ // values?". If not, it should be safe to update in place.
3610
+
3611
+ let routeUpdates = {};
3612
+
3613
+ for (let lazyRouteProperty in lazyRoute) {
3614
+ let staticRouteValue = routeToUpdate[lazyRouteProperty];
3615
+ let isPropertyStaticallyDefined = staticRouteValue !== undefined && // This property isn't static since it should always be updated based
3616
+ // on the route updates
3617
+ lazyRouteProperty !== "hasErrorBoundary";
3618
+ 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."));
3619
+
3620
+ if (!isPropertyStaticallyDefined && !immutableRouteKeys.has(lazyRouteProperty)) {
3621
+ routeUpdates[lazyRouteProperty] = lazyRoute[lazyRouteProperty];
3622
+ }
3623
+ } // Mutate the route with the provided updates. Do this first so we pass
3624
+ // the updated version to detectErrorBoundary
3625
+
3626
+
3627
+ Object.assign(routeToUpdate, routeUpdates); // Mutate the `hasErrorBoundary` property on the route based on the route
3628
+ // updates and remove the `lazy` function so we don't resolve the lazy
3629
+ // route again.
3630
+
3631
+ Object.assign(routeToUpdate, {
3632
+ // To keep things framework agnostic, we use the provided
3633
+ // `detectErrorBoundary` function to set the `hasErrorBoundary` route
3634
+ // property since the logic will differ between frameworks.
3635
+ hasErrorBoundary: detectErrorBoundary(_extends({}, routeToUpdate)),
3636
+ lazy: undefined
3637
+ });
3638
+ }
3540
3639
 
3541
- async function callLoaderOrAction(type, request, match, matches, basename, isStaticRequest, isRouteRequest, requestContext) {
3640
+ async function callLoaderOrAction(type, request, match, matches, manifest, detectErrorBoundary, basename, isStaticRequest, isRouteRequest, requestContext) {
3542
3641
  if (basename === void 0) {
3543
3642
  basename = "/";
3544
3643
  }
@@ -3552,29 +3651,70 @@
3552
3651
  }
3553
3652
 
3554
3653
  let resultType;
3555
- let result; // Setup a promise we can race against so that abort signals short circuit
3654
+ let result;
3655
+ let onReject;
3556
3656
 
3557
- let reject;
3558
- let abortPromise = new Promise((_, r) => reject = r);
3559
-
3560
- let onReject = () => reject();
3657
+ let runHandler = handler => {
3658
+ // Setup a promise we can race against so that abort signals short circuit
3659
+ let reject;
3660
+ let abortPromise = new Promise((_, r) => reject = r);
3561
3661
 
3562
- request.signal.addEventListener("abort", onReject);
3662
+ onReject = () => reject();
3563
3663
 
3564
- try {
3565
- let handler = match.route[type];
3566
- invariant(handler, "Could not find the " + type + " to run on the \"" + match.route.id + "\" route");
3567
- result = await Promise.race([handler({
3664
+ request.signal.addEventListener("abort", onReject);
3665
+ return Promise.race([handler({
3568
3666
  request,
3569
3667
  params: match.params,
3570
3668
  context: requestContext
3571
3669
  }), abortPromise]);
3670
+ };
3671
+
3672
+ try {
3673
+ let handler = match.route[type];
3674
+
3675
+ if (match.route.lazy) {
3676
+ if (handler) {
3677
+ // Run statically defined handler in parallel with lazy()
3678
+ let values = await Promise.all([runHandler(handler), loadLazyRouteModule(match.route, detectErrorBoundary, manifest)]);
3679
+ result = values[0];
3680
+ } else {
3681
+ // Load lazy route module, then run any returned handler
3682
+ await loadLazyRouteModule(match.route, detectErrorBoundary, manifest);
3683
+ handler = match.route[type];
3684
+
3685
+ if (handler) {
3686
+ // Handler still run even if we got interrupted to maintain consistency
3687
+ // with un-abortable behavior of handler execution on non-lazy or
3688
+ // previously-lazy-loaded routes
3689
+ result = await runHandler(handler);
3690
+ } else if (type === "action") {
3691
+ throw getInternalRouterError(405, {
3692
+ method: request.method,
3693
+ pathname: new URL(request.url).pathname,
3694
+ routeId: match.route.id
3695
+ });
3696
+ } else {
3697
+ // lazy() route has no loader to run. Short circuit here so we don't
3698
+ // hit the invariant below that errors on returning undefined.
3699
+ return {
3700
+ type: ResultType.data,
3701
+ data: undefined
3702
+ };
3703
+ }
3704
+ }
3705
+ } else {
3706
+ invariant(handler, "Could not find the " + type + " to run on the \"" + match.route.id + "\" route");
3707
+ result = await runHandler(handler);
3708
+ }
3709
+
3572
3710
  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`.");
3573
3711
  } catch (e) {
3574
3712
  resultType = ResultType.error;
3575
3713
  result = e;
3576
3714
  } finally {
3577
- request.signal.removeEventListener("abort", onReject);
3715
+ if (onReject) {
3716
+ request.signal.removeEventListener("abort", onReject);
3717
+ }
3578
3718
  }
3579
3719
 
3580
3720
  if (isResponse(result)) {
@@ -4101,6 +4241,7 @@
4101
4241
  exports.UNSAFE_convertRoutesToDataRoutes = convertRoutesToDataRoutes;
4102
4242
  exports.UNSAFE_getPathContributingMatches = getPathContributingMatches;
4103
4243
  exports.UNSAFE_invariant = invariant;
4244
+ exports.UNSAFE_warning = warning;
4104
4245
  exports.createBrowserHistory = createBrowserHistory;
4105
4246
  exports.createHashHistory = createHashHistory;
4106
4247
  exports.createMemoryHistory = createMemoryHistory;
@@ -4122,7 +4263,6 @@
4122
4263
  exports.resolvePath = resolvePath;
4123
4264
  exports.resolveTo = resolveTo;
4124
4265
  exports.stripBasename = stripBasename;
4125
- exports.warning = warning;
4126
4266
 
4127
4267
  Object.defineProperty(exports, '__esModule', { value: true });
4128
4268