@remix-run/router 1.3.3 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @remix-run/router v1.3.3
2
+ * @remix-run/router v1.4.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -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
 
@@ -2004,7 +2028,7 @@
2004
2028
  let result;
2005
2029
  let actionMatch = getTargetMatch(matches, location);
2006
2030
 
2007
- if (!actionMatch.route.action) {
2031
+ if (!actionMatch.route.action && !actionMatch.route.lazy) {
2008
2032
  result = {
2009
2033
  type: ResultType.error,
2010
2034
  error: getInternalRouterError(405, {
@@ -2014,7 +2038,7 @@
2014
2038
  })
2015
2039
  };
2016
2040
  } else {
2017
- result = await callLoaderOrAction("action", request, actionMatch, matches, router.basename);
2041
+ result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, detectErrorBoundary, router.basename);
2018
2042
 
2019
2043
  if (request.signal.aborted) {
2020
2044
  return {
@@ -2260,7 +2284,7 @@
2260
2284
  interruptActiveLoads();
2261
2285
  fetchLoadMatches.delete(key);
2262
2286
 
2263
- if (!match.route.action) {
2287
+ if (!match.route.action && !match.route.lazy) {
2264
2288
  let error = getInternalRouterError(405, {
2265
2289
  method: submission.formMethod,
2266
2290
  pathname: path,
@@ -2288,7 +2312,7 @@
2288
2312
  let abortController = new AbortController();
2289
2313
  let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);
2290
2314
  fetchControllers.set(key, abortController);
2291
- let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, router.basename);
2315
+ let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, manifest, detectErrorBoundary, router.basename);
2292
2316
 
2293
2317
  if (fetchRequest.signal.aborted) {
2294
2318
  // We can delete this so long as we weren't aborted by ou our own fetcher
@@ -2459,7 +2483,7 @@
2459
2483
  let abortController = new AbortController();
2460
2484
  let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
2461
2485
  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
2486
+ 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
2487
  // as a normal load. resolveDeferredData will return undefined if this
2464
2488
  // fetcher gets aborted, so we just leave result untouched and short circuit
2465
2489
  // below if that happens
@@ -2628,9 +2652,9 @@
2628
2652
  // Call all navigation loaders and revalidating fetcher loaders in parallel,
2629
2653
  // then slice off the results into separate arrays so we can handle them
2630
2654
  // accordingly
2631
- let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, router.basename)), ...fetchersToLoad.map(f => {
2655
+ let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, detectErrorBoundary, router.basename)), ...fetchersToLoad.map(f => {
2632
2656
  if (f.matches && f.match) {
2633
- return callLoaderOrAction("loader", createClientSideRequest(init.history, f.path, request.signal), f.match, f.matches, router.basename);
2657
+ return callLoaderOrAction("loader", createClientSideRequest(init.history, f.path, request.signal), f.match, f.matches, manifest, detectErrorBoundary, router.basename);
2634
2658
  } else {
2635
2659
  let error = {
2636
2660
  type: ResultType.error,
@@ -2922,7 +2946,9 @@
2922
2946
  const UNSAFE_DEFERRED_SYMBOL = Symbol("deferred");
2923
2947
  function createStaticHandler(routes, opts) {
2924
2948
  invariant(routes.length > 0, "You must provide a non-empty routes array to createStaticHandler");
2925
- let dataRoutes = convertRoutesToDataRoutes(routes);
2949
+ let manifest = {};
2950
+ let detectErrorBoundary = (opts == null ? void 0 : opts.detectErrorBoundary) || defaultDetectErrorBoundary;
2951
+ let dataRoutes = convertRoutesToDataRoutes(routes, detectErrorBoundary, undefined, manifest);
2926
2952
  let basename = (opts ? opts.basename : null) || "/";
2927
2953
  /**
2928
2954
  * The query() method is intended for document requests, in which we want to
@@ -3144,7 +3170,7 @@
3144
3170
  async function submit(request, matches, actionMatch, requestContext, isRouteRequest) {
3145
3171
  let result;
3146
3172
 
3147
- if (!actionMatch.route.action) {
3173
+ if (!actionMatch.route.action && !actionMatch.route.lazy) {
3148
3174
  let error = getInternalRouterError(405, {
3149
3175
  method: request.method,
3150
3176
  pathname: new URL(request.url).pathname,
@@ -3160,7 +3186,7 @@
3160
3186
  error
3161
3187
  };
3162
3188
  } else {
3163
- result = await callLoaderOrAction("action", request, actionMatch, matches, basename, true, isRouteRequest, requestContext);
3189
+ result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, detectErrorBoundary, basename, true, isRouteRequest, requestContext);
3164
3190
 
3165
3191
  if (request.signal.aborted) {
3166
3192
  let method = isRouteRequest ? "queryRoute" : "query";
@@ -3258,7 +3284,7 @@
3258
3284
  async function loadRouteData(request, matches, requestContext, routeMatch, pendingActionError) {
3259
3285
  let isRouteRequest = routeMatch != null; // Short circuit if we have no loaders to run (queryRoute())
3260
3286
 
3261
- if (isRouteRequest && !(routeMatch != null && routeMatch.route.loader)) {
3287
+ if (isRouteRequest && !(routeMatch != null && routeMatch.route.loader) && !(routeMatch != null && routeMatch.route.lazy)) {
3262
3288
  throw getInternalRouterError(400, {
3263
3289
  method: request.method,
3264
3290
  pathname: new URL(request.url).pathname,
@@ -3267,7 +3293,7 @@
3267
3293
  }
3268
3294
 
3269
3295
  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())
3296
+ let matchesToLoad = requestMatches.filter(m => m.route.loader || m.route.lazy); // Short circuit if we have no loaders to run (query())
3271
3297
 
3272
3298
  if (matchesToLoad.length === 0) {
3273
3299
  return {
@@ -3283,7 +3309,7 @@
3283
3309
  };
3284
3310
  }
3285
3311
 
3286
- let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, basename, true, isRouteRequest, requestContext))]);
3312
+ let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, detectErrorBoundary, basename, true, isRouteRequest, requestContext))]);
3287
3313
 
3288
3314
  if (request.signal.aborted) {
3289
3315
  let method = isRouteRequest ? "queryRoute" : "query";
@@ -3424,6 +3450,11 @@
3424
3450
  let boundaryId = pendingError ? Object.keys(pendingError)[0] : undefined;
3425
3451
  let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
3426
3452
  let navigationMatches = boundaryMatches.filter((match, index) => {
3453
+ if (match.route.lazy) {
3454
+ // We haven't loaded this route yet so we don't know if it's got a loader!
3455
+ return true;
3456
+ }
3457
+
3427
3458
  if (match.route.loader == null) {
3428
3459
  return false;
3429
3460
  } // Always call the loader on new route instances and pending defer cancellations
@@ -3537,8 +3568,66 @@
3537
3568
 
3538
3569
  return arg.defaultShouldRevalidate;
3539
3570
  }
3571
+ /**
3572
+ * Execute route.lazy() methods to lazily load route modules (loader, action,
3573
+ * shouldRevalidate) and update the routeManifest in place which shares objects
3574
+ * with dataRoutes so those get updated as well.
3575
+ */
3576
+
3577
+
3578
+ async function loadLazyRouteModule(route, detectErrorBoundary, manifest) {
3579
+ if (!route.lazy) {
3580
+ return;
3581
+ }
3582
+
3583
+ let lazyRoute = await route.lazy(); // If the lazy route function was executed and removed by another parallel
3584
+ // call then we can return - first lazy() to finish wins because the return
3585
+ // value of lazy is expected to be static
3586
+
3587
+ if (!route.lazy) {
3588
+ return;
3589
+ }
3590
+
3591
+ let routeToUpdate = manifest[route.id];
3592
+ invariant(routeToUpdate, "No route found in manifest"); // Update the route in place. This should be safe because there's no way
3593
+ // we could yet be sitting on this route as we can't get there without
3594
+ // resolving lazy() first.
3595
+ //
3596
+ // This is different than the HMR "update" use-case where we may actively be
3597
+ // on the route being updated. The main concern boils down to "does this
3598
+ // mutation affect any ongoing navigations or any current state.matches
3599
+ // values?". If not, it should be safe to update in place.
3600
+
3601
+ let routeUpdates = {};
3602
+
3603
+ for (let lazyRouteProperty in lazyRoute) {
3604
+ let staticRouteValue = routeToUpdate[lazyRouteProperty];
3605
+ let isPropertyStaticallyDefined = staticRouteValue !== undefined && // This property isn't static since it should always be updated based
3606
+ // on the route updates
3607
+ lazyRouteProperty !== "hasErrorBoundary";
3608
+ 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."));
3609
+
3610
+ if (!isPropertyStaticallyDefined && !immutableRouteKeys.has(lazyRouteProperty)) {
3611
+ routeUpdates[lazyRouteProperty] = lazyRoute[lazyRouteProperty];
3612
+ }
3613
+ } // Mutate the route with the provided updates. Do this first so we pass
3614
+ // the updated version to detectErrorBoundary
3615
+
3616
+
3617
+ Object.assign(routeToUpdate, routeUpdates); // Mutate the `hasErrorBoundary` property on the route based on the route
3618
+ // updates and remove the `lazy` function so we don't resolve the lazy
3619
+ // route again.
3620
+
3621
+ Object.assign(routeToUpdate, {
3622
+ // To keep things framework agnostic, we use the provided
3623
+ // `detectErrorBoundary` function to set the `hasErrorBoundary` route
3624
+ // property since the logic will differ between frameworks.
3625
+ hasErrorBoundary: detectErrorBoundary(_extends({}, routeToUpdate)),
3626
+ lazy: undefined
3627
+ });
3628
+ }
3540
3629
 
3541
- async function callLoaderOrAction(type, request, match, matches, basename, isStaticRequest, isRouteRequest, requestContext) {
3630
+ async function callLoaderOrAction(type, request, match, matches, manifest, detectErrorBoundary, basename, isStaticRequest, isRouteRequest, requestContext) {
3542
3631
  if (basename === void 0) {
3543
3632
  basename = "/";
3544
3633
  }
@@ -3552,29 +3641,70 @@
3552
3641
  }
3553
3642
 
3554
3643
  let resultType;
3555
- let result; // Setup a promise we can race against so that abort signals short circuit
3556
-
3557
- let reject;
3558
- let abortPromise = new Promise((_, r) => reject = r);
3644
+ let result;
3645
+ let onReject;
3559
3646
 
3560
- let onReject = () => reject();
3647
+ let runHandler = handler => {
3648
+ // Setup a promise we can race against so that abort signals short circuit
3649
+ let reject;
3650
+ let abortPromise = new Promise((_, r) => reject = r);
3561
3651
 
3562
- request.signal.addEventListener("abort", onReject);
3652
+ onReject = () => reject();
3563
3653
 
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({
3654
+ request.signal.addEventListener("abort", onReject);
3655
+ return Promise.race([handler({
3568
3656
  request,
3569
3657
  params: match.params,
3570
3658
  context: requestContext
3571
3659
  }), abortPromise]);
3660
+ };
3661
+
3662
+ try {
3663
+ let handler = match.route[type];
3664
+
3665
+ if (match.route.lazy) {
3666
+ if (handler) {
3667
+ // Run statically defined handler in parallel with lazy()
3668
+ let values = await Promise.all([runHandler(handler), loadLazyRouteModule(match.route, detectErrorBoundary, manifest)]);
3669
+ result = values[0];
3670
+ } else {
3671
+ // Load lazy route module, then run any returned handler
3672
+ await loadLazyRouteModule(match.route, detectErrorBoundary, manifest);
3673
+ handler = match.route[type];
3674
+
3675
+ if (handler) {
3676
+ // Handler still run even if we got interrupted to maintain consistency
3677
+ // with un-abortable behavior of handler execution on non-lazy or
3678
+ // previously-lazy-loaded routes
3679
+ result = await runHandler(handler);
3680
+ } else if (type === "action") {
3681
+ throw getInternalRouterError(405, {
3682
+ method: request.method,
3683
+ pathname: new URL(request.url).pathname,
3684
+ routeId: match.route.id
3685
+ });
3686
+ } else {
3687
+ // lazy() route has no loader to run. Short circuit here so we don't
3688
+ // hit the invariant below that errors on returning undefined.
3689
+ return {
3690
+ type: ResultType.data,
3691
+ data: undefined
3692
+ };
3693
+ }
3694
+ }
3695
+ } else {
3696
+ invariant(handler, "Could not find the " + type + " to run on the \"" + match.route.id + "\" route");
3697
+ result = await runHandler(handler);
3698
+ }
3699
+
3572
3700
  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
3701
  } catch (e) {
3574
3702
  resultType = ResultType.error;
3575
3703
  result = e;
3576
3704
  } finally {
3577
- request.signal.removeEventListener("abort", onReject);
3705
+ if (onReject) {
3706
+ request.signal.removeEventListener("abort", onReject);
3707
+ }
3578
3708
  }
3579
3709
 
3580
3710
  if (isResponse(result)) {
@@ -4101,6 +4231,7 @@
4101
4231
  exports.UNSAFE_convertRoutesToDataRoutes = convertRoutesToDataRoutes;
4102
4232
  exports.UNSAFE_getPathContributingMatches = getPathContributingMatches;
4103
4233
  exports.UNSAFE_invariant = invariant;
4234
+ exports.UNSAFE_warning = warning;
4104
4235
  exports.createBrowserHistory = createBrowserHistory;
4105
4236
  exports.createHashHistory = createHashHistory;
4106
4237
  exports.createMemoryHistory = createMemoryHistory;
@@ -4122,7 +4253,6 @@
4122
4253
  exports.resolvePath = resolvePath;
4123
4254
  exports.resolveTo = resolveTo;
4124
4255
  exports.stripBasename = stripBasename;
4125
- exports.warning = warning;
4126
4256
 
4127
4257
  Object.defineProperty(exports, '__esModule', { value: true });
4128
4258