@remix-run/router 0.0.0-experimental-c9f8a7b2 → 0.0.0-experimental-e960cf1a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/utils.d.ts CHANGED
@@ -182,6 +182,15 @@ export interface ShouldRevalidateFunction {
182
182
  export interface DetectErrorBoundaryFunction {
183
183
  (route: AgnosticRouteObject): boolean;
184
184
  }
185
+ export interface DataStrategyFunctionArgs {
186
+ request: Request;
187
+ matches: AgnosticDataStrategyMatch[];
188
+ type: "loader" | "action";
189
+ defaultStrategy(match: AgnosticDataStrategyMatch): Promise<DataResult>;
190
+ }
191
+ export interface DataStrategyFunction {
192
+ (args: DataStrategyFunctionArgs): Promise<DataResult[]>;
193
+ }
185
194
  /**
186
195
  * Function provided by the framework-aware layers to set any framework-specific
187
196
  * properties from framework-agnostic properties
@@ -296,6 +305,10 @@ export interface AgnosticRouteMatch<ParamKey extends string = string, RouteObjec
296
305
  }
297
306
  export interface AgnosticDataRouteMatch extends AgnosticRouteMatch<string, AgnosticDataRouteObject> {
298
307
  }
308
+ export type LazyRoutePromise = PromiseLike<AgnosticDataRouteObject> & AgnosticDataRouteObject;
309
+ export interface AgnosticDataStrategyMatch extends Omit<AgnosticRouteMatch<string, AgnosticDataRouteObject>, "route"> {
310
+ route: LazyRoutePromise;
311
+ }
299
312
  export declare function convertRoutesToDataRoutes(routes: AgnosticRouteObject[], mapRouteProperties: MapRoutePropertiesFunction, parentPath?: number[], manifest?: RouteManifest): AgnosticDataRouteObject[];
300
313
  /**
301
314
  * Matches the given routes to a location and returns the match data.
package/index.ts CHANGED
@@ -5,10 +5,14 @@ export type {
5
5
  AgnosticDataNonIndexRouteObject,
6
6
  AgnosticDataRouteMatch,
7
7
  AgnosticDataRouteObject,
8
+ AgnosticDataStrategyMatch,
8
9
  AgnosticIndexRouteObject,
9
10
  AgnosticNonIndexRouteObject,
10
11
  AgnosticRouteMatch,
11
12
  AgnosticRouteObject,
13
+ DataResult,
14
+ DataStrategyFunction,
15
+ DataStrategyFunctionArgs,
12
16
  ErrorResponse,
13
17
  FormEncType,
14
18
  FormMethod,
@@ -45,6 +49,7 @@ export {
45
49
  redirectDocument,
46
50
  resolvePath,
47
51
  resolveTo,
52
+ ResultType,
48
53
  stripBasename,
49
54
  } from "./utils";
50
55
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remix-run/router",
3
- "version": "0.0.0-experimental-c9f8a7b2",
3
+ "version": "0.0.0-experimental-e960cf1a",
4
4
  "description": "Nested/Data-driven/Framework-agnostic Routing",
5
5
  "keywords": [
6
6
  "remix",
package/router.ts CHANGED
@@ -11,8 +11,11 @@ import type {
11
11
  ActionFunction,
12
12
  AgnosticDataRouteMatch,
13
13
  AgnosticDataRouteObject,
14
+ AgnosticDataStrategyMatch,
14
15
  AgnosticRouteObject,
15
16
  DataResult,
17
+ DataStrategyFunction,
18
+ DataStrategyFunctionArgs,
16
19
  DeferredData,
17
20
  DeferredResult,
18
21
  DetectErrorBoundaryFunction,
@@ -21,6 +24,7 @@ import type {
21
24
  FormMethod,
22
25
  HTMLFormMethod,
23
26
  ImmutableRouteKey,
27
+ LazyRoutePromise,
24
28
  LoaderFunction,
25
29
  MapRoutePropertiesFunction,
26
30
  MutationFormMethod,
@@ -374,6 +378,7 @@ export interface RouterInit {
374
378
  future?: Partial<FutureConfig>;
375
379
  hydrationData?: HydrationState;
376
380
  window?: Window;
381
+ unstable_dataStrategy?: DataStrategyFunction;
377
382
  }
378
383
 
379
384
  /**
@@ -752,6 +757,9 @@ export function createRouter(init: RouterInit): Router {
752
757
  "You must provide a non-empty routes array to createRouter"
753
758
  );
754
759
 
760
+ const dataStrategy = init.unstable_dataStrategy || defaultDataStrategy;
761
+ const callLoaderOrAction = createCallLoaderOrAction(dataStrategy);
762
+
755
763
  let mapRouteProperties: MapRoutePropertiesFunction;
756
764
  if (init.mapRouteProperties) {
757
765
  mapRouteProperties = init.mapRouteProperties;
@@ -817,34 +825,19 @@ export function createRouter(init: RouterInit): Router {
817
825
  initialErrors = { [route.id]: error };
818
826
  }
819
827
 
820
- let initialized: boolean;
821
- let hasLazyRoutes = initialMatches.some((m) => m.route.lazy);
822
- let hasLoaders = initialMatches.some((m) => m.route.loader);
823
- if (hasLazyRoutes) {
828
+ // "Initialized" here really means "Can `RouterProvider` render my route tree?"
829
+ // Prior to `route.HydrateFallback`, we only had a root `fallbackElement` so we used
830
+ // `state.initialized` to render that instead of `<DataRoutes>`. Now that we
831
+ // support route level fallbacks we can always render and we'll just render
832
+ // as deep as we have data for and detect the nearest ancestor HydrateFallback
833
+ let initialized =
834
+ future.v7_partialHydration ||
824
835
  // All initialMatches need to be loaded before we're ready. If we have lazy
825
836
  // functions around still then we'll need to run them in initialize()
826
- initialized = false;
827
- } else if (!hasLoaders) {
828
- // If we've got no loaders to run, then we're good to go
829
- initialized = true;
830
- } else if (future.v7_partialHydration) {
831
- // If partial hydration is enabled, we're initialized so long as we were
832
- // provided with hydrationData for every route with a loader, and no loaders
833
- // were marked for explicit hydration
834
- let loaderData = init.hydrationData ? init.hydrationData.loaderData : null;
835
- let errors = init.hydrationData ? init.hydrationData.errors : null;
836
- initialized = initialMatches.every(
837
- (m) =>
838
- m.route.loader &&
839
- m.route.loader.hydrate !== true &&
840
- ((loaderData && loaderData[m.route.id] !== undefined) ||
841
- (errors && errors[m.route.id] !== undefined))
842
- );
843
- } else {
844
- // Without partial hydration - we're initialized if we were provided any
845
- // hydrationData - which is expected to be complete
846
- initialized = init.hydrationData != null;
847
- }
837
+ (!initialMatches.some((m) => m.route.lazy) &&
838
+ // And we have to either have no loaders or have been provided hydrationData
839
+ (!initialMatches.some((m) => m.route.loader) ||
840
+ init.hydrationData != null));
848
841
 
849
842
  let router: Router;
850
843
  let state: RouterState = {
@@ -1025,7 +1018,11 @@ export function createRouter(init: RouterInit): Router {
1025
1018
  // in the normal navigation flow. For SSR it's expected that lazy modules are
1026
1019
  // resolved prior to router creation since we can't go into a fallbackElement
1027
1020
  // UI for SSR'd apps
1028
- if (!state.initialized) {
1021
+ if (
1022
+ !state.initialized ||
1023
+ (future.v7_partialHydration &&
1024
+ state.matches.some((m) => isUnhydratedRoute(state, m.route)))
1025
+ ) {
1029
1026
  startNavigation(HistoryAction.Pop, state.location, {
1030
1027
  initialHydration: true,
1031
1028
  });
@@ -1772,8 +1769,8 @@ export function createRouter(init: RouterInit): Router {
1772
1769
  );
1773
1770
  }
1774
1771
 
1775
- let { results, loaderResults, fetcherResults } =
1776
- await callLoadersAndMaybeResolveData(
1772
+ let { loaderResults, fetcherResults } =
1773
+ await loadDataAndMaybeResolveDeferred(
1777
1774
  state.matches,
1778
1775
  matches,
1779
1776
  matchesToLoad,
@@ -1797,7 +1794,7 @@ export function createRouter(init: RouterInit): Router {
1797
1794
  revalidatingFetchers.forEach((rf) => fetchControllers.delete(rf.key));
1798
1795
 
1799
1796
  // If any loaders returned a redirect Response, start a new REPLACE navigation
1800
- let redirect = findRedirect(results);
1797
+ let redirect = findRedirect([...loaderResults, ...fetcherResults]);
1801
1798
  if (redirect) {
1802
1799
  if (redirect.idx >= matchesToLoad.length) {
1803
1800
  // If this redirect came from a fetcher make sure we mark it in
@@ -2103,8 +2100,8 @@ export function createRouter(init: RouterInit): Router {
2103
2100
  abortPendingFetchRevalidations
2104
2101
  );
2105
2102
 
2106
- let { results, loaderResults, fetcherResults } =
2107
- await callLoadersAndMaybeResolveData(
2103
+ let { loaderResults, fetcherResults } =
2104
+ await loadDataAndMaybeResolveDeferred(
2108
2105
  state.matches,
2109
2106
  matches,
2110
2107
  matchesToLoad,
@@ -2125,7 +2122,7 @@ export function createRouter(init: RouterInit): Router {
2125
2122
  fetchControllers.delete(key);
2126
2123
  revalidatingFetchers.forEach((r) => fetchControllers.delete(r.key));
2127
2124
 
2128
- let redirect = findRedirect(results);
2125
+ let redirect = findRedirect([...loaderResults, ...fetcherResults]);
2129
2126
  if (redirect) {
2130
2127
  if (redirect.idx >= matchesToLoad.length) {
2131
2128
  // If this redirect came from a fetcher make sure we mark it in
@@ -2410,52 +2407,83 @@ export function createRouter(init: RouterInit): Router {
2410
2407
  }
2411
2408
  }
2412
2409
 
2413
- async function callLoadersAndMaybeResolveData(
2410
+ async function loadDataAndMaybeResolveDeferred(
2414
2411
  currentMatches: AgnosticDataRouteMatch[],
2415
2412
  matches: AgnosticDataRouteMatch[],
2416
2413
  matchesToLoad: AgnosticDataRouteMatch[],
2417
2414
  fetchersToLoad: RevalidatingFetcher[],
2418
2415
  request: Request
2419
2416
  ) {
2420
- // Call all navigation loaders and revalidating fetcher loaders in parallel,
2421
- // then slice off the results into separate arrays so we can handle them
2422
- // accordingly
2423
- let results = await Promise.all([
2424
- ...matchesToLoad.map((match) =>
2425
- callLoaderOrAction(
2426
- "loader",
2427
- request,
2428
- match,
2429
- matches,
2430
- manifest,
2431
- mapRouteProperties,
2432
- basename,
2433
- future.v7_relativeSplatPath
2434
- )
2435
- ),
2417
+ let [loaderResults, ...fetcherResults] = await Promise.all([
2418
+ matchesToLoad.length
2419
+ ? dataStrategy({
2420
+ matches: matchesToLoad.map((m) =>
2421
+ finesseToAgnosticDataStrategyMatch(
2422
+ m,
2423
+ mapRouteProperties,
2424
+ manifest
2425
+ )
2426
+ ),
2427
+ request,
2428
+ type: "loader",
2429
+ defaultStrategy(match) {
2430
+ return callLoaderOrActionImplementation(
2431
+ "loader",
2432
+ request,
2433
+ match,
2434
+ matches,
2435
+ basename,
2436
+ future.v7_relativeSplatPath
2437
+ );
2438
+ },
2439
+ })
2440
+ : [],
2436
2441
  ...fetchersToLoad.map((f) => {
2437
- if (f.matches && f.match && f.controller) {
2438
- return callLoaderOrAction(
2439
- "loader",
2440
- createClientSideRequest(init.history, f.path, f.controller.signal),
2441
- f.match,
2442
- f.matches,
2443
- manifest,
2444
- mapRouteProperties,
2445
- basename,
2446
- future.v7_relativeSplatPath
2447
- );
2448
- } else {
2449
- let error: ErrorResult = {
2442
+ if (!f.matches || !f.match || !f.controller) {
2443
+ return Promise.resolve<DataResult>({
2450
2444
  type: ResultType.error,
2451
- error: getInternalRouterError(404, { pathname: f.path }),
2452
- };
2453
- return error;
2445
+ error: getInternalRouterError(404, {
2446
+ pathname: f.path,
2447
+ }),
2448
+ });
2454
2449
  }
2450
+
2451
+ return dataStrategy({
2452
+ matches: [
2453
+ finesseToAgnosticDataStrategyMatch(
2454
+ f.match!,
2455
+ mapRouteProperties,
2456
+ manifest
2457
+ ),
2458
+ ],
2459
+ request,
2460
+ type: "loader",
2461
+ defaultStrategy(match) {
2462
+ invariant(
2463
+ f.controller,
2464
+ "Expected controller for fetcher in defaultStrategy"
2465
+ );
2466
+ invariant(
2467
+ f.matches,
2468
+ "Expected matches for fetcher in defaultStrategy"
2469
+ );
2470
+
2471
+ return callLoaderOrActionImplementation(
2472
+ "loader",
2473
+ createClientSideRequest(
2474
+ init.history,
2475
+ f.path,
2476
+ f.controller.signal
2477
+ ),
2478
+ match,
2479
+ f.matches,
2480
+ basename,
2481
+ future.v7_relativeSplatPath
2482
+ );
2483
+ },
2484
+ }).then((r) => r[0]);
2455
2485
  }),
2456
2486
  ]);
2457
- let loaderResults = results.slice(0, matchesToLoad.length);
2458
- let fetcherResults = results.slice(matchesToLoad.length);
2459
2487
 
2460
2488
  await Promise.all([
2461
2489
  resolveDeferredResults(
@@ -2475,7 +2503,10 @@ export function createRouter(init: RouterInit): Router {
2475
2503
  ),
2476
2504
  ]);
2477
2505
 
2478
- return { results, loaderResults, fetcherResults };
2506
+ return {
2507
+ loaderResults,
2508
+ fetcherResults,
2509
+ };
2479
2510
  }
2480
2511
 
2481
2512
  function interruptActiveLoads() {
@@ -2834,7 +2865,6 @@ export const UNSAFE_DEFERRED_SYMBOL = Symbol("deferred");
2834
2865
  */
2835
2866
  export interface StaticHandlerFutureConfig {
2836
2867
  v7_relativeSplatPath: boolean;
2837
- v7_throwAbortReason: boolean;
2838
2868
  }
2839
2869
 
2840
2870
  export interface CreateStaticHandlerOptions {
@@ -2843,6 +2873,7 @@ export interface CreateStaticHandlerOptions {
2843
2873
  * @deprecated Use `mapRouteProperties` instead
2844
2874
  */
2845
2875
  detectErrorBoundary?: DetectErrorBoundaryFunction;
2876
+ dataStrategy?: DataStrategyFunction;
2846
2877
  mapRouteProperties?: MapRoutePropertiesFunction;
2847
2878
  future?: Partial<StaticHandlerFutureConfig>;
2848
2879
  }
@@ -2856,6 +2887,9 @@ export function createStaticHandler(
2856
2887
  "You must provide a non-empty routes array to createStaticHandler"
2857
2888
  );
2858
2889
 
2890
+ const dataStrategy = opts?.dataStrategy || defaultDataStrategy;
2891
+ const callLoaderOrAction = createCallLoaderOrAction(dataStrategy);
2892
+
2859
2893
  let manifest: RouteManifest = {};
2860
2894
  let basename = (opts ? opts.basename : null) || "/";
2861
2895
  let mapRouteProperties: MapRoutePropertiesFunction;
@@ -2873,7 +2907,6 @@ export function createStaticHandler(
2873
2907
  // Config driven behavior flags
2874
2908
  let future: StaticHandlerFutureConfig = {
2875
2909
  v7_relativeSplatPath: false,
2876
- v7_throwAbortReason: false,
2877
2910
  ...(opts ? opts.future : null),
2878
2911
  };
2879
2912
 
@@ -3143,7 +3176,10 @@ export function createStaticHandler(
3143
3176
  );
3144
3177
 
3145
3178
  if (request.signal.aborted) {
3146
- throwStaticHandlerAbortedError(request, isRouteRequest, future);
3179
+ let method = isRouteRequest ? "queryRoute" : "query";
3180
+ throw new Error(
3181
+ `${method}() call aborted: ${request.method} ${request.url}`
3182
+ );
3147
3183
  }
3148
3184
  }
3149
3185
 
@@ -3294,24 +3330,30 @@ export function createStaticHandler(
3294
3330
  };
3295
3331
  }
3296
3332
 
3297
- let results = await Promise.all([
3298
- ...matchesToLoad.map((match) =>
3299
- callLoaderOrAction(
3333
+ let results = await dataStrategy({
3334
+ matches: matchesToLoad.map((m) =>
3335
+ finesseToAgnosticDataStrategyMatch(m, mapRouteProperties, manifest)
3336
+ ),
3337
+ request,
3338
+ type: "loader",
3339
+ defaultStrategy(match) {
3340
+ return callLoaderOrActionImplementation(
3300
3341
  "loader",
3301
3342
  request,
3302
3343
  match,
3303
3344
  matches,
3304
- manifest,
3305
- mapRouteProperties,
3306
3345
  basename,
3307
3346
  future.v7_relativeSplatPath,
3308
3347
  { isStaticRequest: true, isRouteRequest, requestContext }
3309
- )
3310
- ),
3311
- ]);
3348
+ );
3349
+ },
3350
+ });
3312
3351
 
3313
3352
  if (request.signal.aborted) {
3314
- throwStaticHandlerAbortedError(request, isRouteRequest, future);
3353
+ let method = isRouteRequest ? "queryRoute" : "query";
3354
+ throw new Error(
3355
+ `${method}() call aborted: ${request.method} ${request.url}`
3356
+ );
3315
3357
  }
3316
3358
 
3317
3359
  // Process and commit output from loaders
@@ -3357,6 +3399,13 @@ export function createStaticHandler(
3357
3399
  //#region Helpers
3358
3400
  ////////////////////////////////////////////////////////////////////////////////
3359
3401
 
3402
+ function defaultDataStrategy({
3403
+ defaultStrategy,
3404
+ matches,
3405
+ }: DataStrategyFunctionArgs) {
3406
+ return Promise.all(matches.map((match) => defaultStrategy(match)));
3407
+ }
3408
+
3360
3409
  /**
3361
3410
  * Given an existing StaticHandlerContext and an error thrown at render time,
3362
3411
  * provide an updated StaticHandlerContext suitable for a second SSR render
@@ -3376,19 +3425,6 @@ export function getStaticContextFromError(
3376
3425
  return newContext;
3377
3426
  }
3378
3427
 
3379
- function throwStaticHandlerAbortedError(
3380
- request: Request,
3381
- isRouteRequest: boolean,
3382
- future: StaticHandlerFutureConfig
3383
- ) {
3384
- if (future.v7_throwAbortReason && request.signal.reason !== undefined) {
3385
- throw request.signal.reason;
3386
- }
3387
-
3388
- let method = isRouteRequest ? "queryRoute" : "query";
3389
- throw new Error(`${method}() call aborted: ${request.method} ${request.url}`);
3390
- }
3391
-
3392
3428
  function isSubmissionNavigation(
3393
3429
  opts: BaseNavigateOrFetchOptions
3394
3430
  ): opts is SubmissionNavigateOptions {
@@ -3664,27 +3700,21 @@ function getMatchesToLoad(
3664
3700
  let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
3665
3701
 
3666
3702
  let navigationMatches = boundaryMatches.filter((match, index) => {
3667
- let { route } = match;
3668
- if (route.lazy) {
3703
+ if (isInitialLoad) {
3704
+ // On initial hydration we don't do any shouldRevalidate stuff - we just
3705
+ // call the unhydrated loaders
3706
+ return isUnhydratedRoute(state, match.route);
3707
+ }
3708
+
3709
+ if (match.route.lazy) {
3669
3710
  // We haven't loaded this route yet so we don't know if it's got a loader!
3670
3711
  return true;
3671
3712
  }
3672
3713
 
3673
- if (route.loader == null) {
3714
+ if (match.route.loader == null) {
3674
3715
  return false;
3675
3716
  }
3676
3717
 
3677
- if (isInitialLoad) {
3678
- if (route.loader.hydrate) {
3679
- return true;
3680
- }
3681
- return (
3682
- state.loaderData[route.id] === undefined &&
3683
- // Don't re-run if the loader ran and threw an error
3684
- (!state.errors || state.errors[route.id] === undefined)
3685
- );
3686
- }
3687
-
3688
3718
  // Always call the loader on new route instances and pending defer cancellations
3689
3719
  if (
3690
3720
  isNewLoader(state.loaderData, state.matches[index], match) ||
@@ -3804,6 +3834,23 @@ function getMatchesToLoad(
3804
3834
  return [navigationMatches, revalidatingFetchers];
3805
3835
  }
3806
3836
 
3837
+ // Is this route unhydrated (when v7_partialHydration=true) such that we need
3838
+ // to call it's loader on the initial router creation
3839
+ function isUnhydratedRoute(state: RouterState, route: AgnosticDataRouteObject) {
3840
+ if (!route.loader) {
3841
+ return false;
3842
+ }
3843
+ if (route.loader.hydrate) {
3844
+ return true;
3845
+ }
3846
+ return (
3847
+ state.loaderData[route.id] === undefined &&
3848
+ (!state.errors ||
3849
+ // Loader ran but errored - don't re-run
3850
+ state.errors[route.id] === undefined)
3851
+ );
3852
+ }
3853
+
3807
3854
  function isNewLoader(
3808
3855
  currentLoaderData: RouteData,
3809
3856
  currentMatch: AgnosticDataRouteMatch,
@@ -3862,9 +3909,9 @@ async function loadLazyRouteModule(
3862
3909
  route: AgnosticDataRouteObject,
3863
3910
  mapRouteProperties: MapRoutePropertiesFunction,
3864
3911
  manifest: RouteManifest
3865
- ) {
3912
+ ): Promise<AgnosticDataRouteObject> {
3866
3913
  if (!route.lazy) {
3867
- return;
3914
+ return route;
3868
3915
  }
3869
3916
 
3870
3917
  let lazyRoute = await route.lazy();
@@ -3873,7 +3920,7 @@ async function loadLazyRouteModule(
3873
3920
  // call then we can return - first lazy() to finish wins because the return
3874
3921
  // value of lazy is expected to be static
3875
3922
  if (!route.lazy) {
3876
- return;
3923
+ return route;
3877
3924
  }
3878
3925
 
3879
3926
  let routeToUpdate = manifest[route.id];
@@ -3929,15 +3976,88 @@ async function loadLazyRouteModule(
3929
3976
  ...mapRouteProperties(routeToUpdate),
3930
3977
  lazy: undefined,
3931
3978
  });
3979
+
3980
+ return routeToUpdate;
3981
+ }
3982
+
3983
+ function createCallLoaderOrAction(dataStrategy: DataStrategyFunction) {
3984
+ return async (
3985
+ type: "loader" | "action",
3986
+ request: Request,
3987
+ match: AgnosticDataRouteMatch,
3988
+ matches: AgnosticDataRouteMatch[],
3989
+ manifest: RouteManifest,
3990
+ mapRouteProperties: MapRoutePropertiesFunction,
3991
+ basename: string,
3992
+ v7_relativeSplatPath: boolean,
3993
+ opts: {
3994
+ isStaticRequest?: boolean;
3995
+ isRouteRequest?: boolean;
3996
+ requestContext?: unknown;
3997
+ } = {}
3998
+ ): Promise<DataResult> => {
3999
+ let [result] = await dataStrategy({
4000
+ matches: [
4001
+ finesseToAgnosticDataStrategyMatch(match, mapRouteProperties, manifest),
4002
+ ],
4003
+ request,
4004
+ type,
4005
+ defaultStrategy(match) {
4006
+ return callLoaderOrActionImplementation(
4007
+ type,
4008
+ request,
4009
+ match,
4010
+ matches,
4011
+ basename,
4012
+ v7_relativeSplatPath,
4013
+ opts
4014
+ );
4015
+ },
4016
+ });
4017
+
4018
+ return result;
4019
+ };
4020
+ }
4021
+
4022
+ function finesseToAgnosticDataStrategyMatch(
4023
+ match: AgnosticDataRouteMatch,
4024
+ mapRouteProperties: MapRoutePropertiesFunction,
4025
+ manifest: RouteManifest
4026
+ ): AgnosticDataStrategyMatch {
4027
+ let loadRoutePromise: Promise<AgnosticDataRouteObject> | undefined;
4028
+
4029
+ if (match.route.lazy) {
4030
+ try {
4031
+ loadRoutePromise = loadLazyRouteModule(
4032
+ match.route,
4033
+ mapRouteProperties,
4034
+ manifest
4035
+ );
4036
+ } catch (error) {
4037
+ loadRoutePromise = Promise.reject(error);
4038
+ }
4039
+ }
4040
+
4041
+ if (!loadRoutePromise) {
4042
+ loadRoutePromise = Promise.resolve(match.route);
4043
+ }
4044
+
4045
+ loadRoutePromise.catch(() => {});
4046
+
4047
+ return {
4048
+ ...match,
4049
+ route: Object.assign(
4050
+ loadRoutePromise,
4051
+ match.route
4052
+ ) as unknown as LazyRoutePromise,
4053
+ };
3932
4054
  }
3933
4055
 
3934
- async function callLoaderOrAction(
4056
+ async function callLoaderOrActionImplementation(
3935
4057
  type: "loader" | "action",
3936
4058
  request: Request,
3937
- match: AgnosticDataRouteMatch,
4059
+ match: AgnosticDataStrategyMatch,
3938
4060
  matches: AgnosticDataRouteMatch[],
3939
- manifest: RouteManifest,
3940
- mapRouteProperties: MapRoutePropertiesFunction,
3941
4061
  basename: string,
3942
4062
  v7_relativeSplatPath: boolean,
3943
4063
  opts: {
@@ -3980,7 +4100,7 @@ async function callLoaderOrAction(
3980
4100
  runHandler(handler).catch((e) => {
3981
4101
  handlerError = e;
3982
4102
  }),
3983
- loadLazyRouteModule(match.route, mapRouteProperties, manifest),
4103
+ match.route,
3984
4104
  ]);
3985
4105
  if (handlerError) {
3986
4106
  throw handlerError;
@@ -3988,9 +4108,9 @@ async function callLoaderOrAction(
3988
4108
  result = values[0];
3989
4109
  } else {
3990
4110
  // Load lazy route module, then run any returned handler
3991
- await loadLazyRouteModule(match.route, mapRouteProperties, manifest);
4111
+ let route = await match.route;
3992
4112
 
3993
- handler = match.route[type];
4113
+ handler = route[type];
3994
4114
  if (handler) {
3995
4115
  // Handler still run even if we got interrupted to maintain consistency
3996
4116
  // with un-abortable behavior of handler execution on non-lazy or
@@ -4050,7 +4170,10 @@ async function callLoaderOrAction(
4050
4170
  if (!ABSOLUTE_URL_REGEX.test(location)) {
4051
4171
  location = normalizeTo(
4052
4172
  new URL(request.url),
4053
- matches.slice(0, matches.indexOf(match) + 1),
4173
+ matches.slice(
4174
+ 0,
4175
+ matches.findIndex((m) => m.route.id === match.route.id) + 1
4176
+ ),
4054
4177
  basename,
4055
4178
  true,
4056
4179
  location,
@@ -4107,11 +4230,7 @@ async function callLoaderOrAction(
4107
4230
  // Check between word boundaries instead of startsWith() due to the last
4108
4231
  // paragraph of https://httpwg.org/specs/rfc9110.html#field.content-type
4109
4232
  if (contentType && /\bapplication\/json\b/.test(contentType)) {
4110
- if (result.body == null) {
4111
- data = null;
4112
- } else {
4113
- data = await result.json();
4114
- }
4233
+ data = await result.json();
4115
4234
  } else {
4116
4235
  data = await result.text();
4117
4236
  }