@remix-run/router 1.19.1 → 1.19.2

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.19.1
2
+ * @remix-run/router v1.19.2
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -579,10 +579,6 @@
579
579
  * Result from a loader or action - potentially successful or unsuccessful
580
580
  */
581
581
 
582
- /**
583
- * Result from a loader or action called via dataStrategy
584
- */
585
-
586
582
  /**
587
583
  * Users can specify either lowercase or uppercase form methods on `<Form>`,
588
584
  * useSubmit(), `<fetcher.Form>`, etc.
@@ -648,6 +644,9 @@
648
644
  *
649
645
  * @deprecated Use `mapRouteProperties` instead
650
646
  */
647
+ /**
648
+ * Result from a loader or action called via dataStrategy
649
+ */
651
650
  /**
652
651
  * Function provided by the framework-aware layers to set any framework-specific
653
652
  * properties from framework-agnostic properties
@@ -1928,7 +1927,7 @@
1928
1927
 
1929
1928
  // Flag to ignore the next history update, so we can revert the URL change on
1930
1929
  // a POP navigation that was blocked by the user without touching router state
1931
- let ignoreNextHistoryUpdate = false;
1930
+ let unblockBlockerHistoryUpdate = undefined;
1932
1931
 
1933
1932
  // Initialize the router, all side effects should be kicked off from here.
1934
1933
  // Implemented as a Fluent API for ease of:
@@ -1944,8 +1943,9 @@
1944
1943
  } = _ref;
1945
1944
  // Ignore this event if it was just us resetting the URL from a
1946
1945
  // blocked POP navigation
1947
- if (ignoreNextHistoryUpdate) {
1948
- ignoreNextHistoryUpdate = false;
1946
+ if (unblockBlockerHistoryUpdate) {
1947
+ unblockBlockerHistoryUpdate();
1948
+ unblockBlockerHistoryUpdate = undefined;
1949
1949
  return;
1950
1950
  }
1951
1951
  warning(blockerFunctions.size === 0 || delta != null, "You are trying to use a blocker on a POP navigation to a location " + "that was not created by @remix-run/router. This will fail silently in " + "production. This can happen if you are navigating outside the router " + "via `window.history.pushState`/`window.location.hash` instead of using " + "router navigation APIs. This can also happen if you are using " + "createHashRouter and the user manually changes the URL.");
@@ -1956,7 +1956,9 @@
1956
1956
  });
1957
1957
  if (blockerKey && delta != null) {
1958
1958
  // Restore the URL to match the current UI, but don't update router state
1959
- ignoreNextHistoryUpdate = true;
1959
+ let nextHistoryUpdatePromise = new Promise(resolve => {
1960
+ unblockBlockerHistoryUpdate = resolve;
1961
+ });
1960
1962
  init.history.go(delta * -1);
1961
1963
 
1962
1964
  // Put the blocker into a blocked state
@@ -1970,8 +1972,10 @@
1970
1972
  reset: undefined,
1971
1973
  location
1972
1974
  });
1973
- // Re-do the same POP navigation we just blocked
1974
- init.history.go(delta);
1975
+ // Re-do the same POP navigation we just blocked, after the url
1976
+ // restoration is also complete. See:
1977
+ // https://github.com/remix-run/react-router/issues/11613
1978
+ nextHistoryUpdatePromise.then(() => init.history.go(delta));
1975
1979
  },
1976
1980
  reset() {
1977
1981
  let blockers = new Map(state.blockers);
@@ -2291,7 +2295,9 @@
2291
2295
  // navigation to the navigation.location but do not trigger an uninterrupted
2292
2296
  // revalidation so that history correctly updates once the navigation completes
2293
2297
  startNavigation(pendingAction || state.historyAction, state.navigation.location, {
2294
- overrideNavigation: state.navigation
2298
+ overrideNavigation: state.navigation,
2299
+ // Proxy through any rending view transition
2300
+ enableViewTransition: pendingViewTransitionEnabled === true
2295
2301
  });
2296
2302
  }
2297
2303
 
@@ -2492,8 +2498,8 @@
2492
2498
  })
2493
2499
  };
2494
2500
  } else {
2495
- let results = await callDataStrategy("action", request, [actionMatch], matches);
2496
- result = results[0];
2501
+ let results = await callDataStrategy("action", state, request, [actionMatch], matches, null);
2502
+ result = results[actionMatch.route.id];
2497
2503
  if (request.signal.aborted) {
2498
2504
  return {
2499
2505
  shortCircuited: true
@@ -2511,7 +2517,7 @@
2511
2517
  let location = normalizeRedirectLocation(result.response.headers.get("Location"), new URL(request.url), basename);
2512
2518
  replace = location === state.location.pathname + state.location.search;
2513
2519
  }
2514
- await startRedirectNavigation(request, result, {
2520
+ await startRedirectNavigation(request, result, true, {
2515
2521
  submission,
2516
2522
  replace
2517
2523
  });
@@ -2681,7 +2687,7 @@
2681
2687
  let {
2682
2688
  loaderResults,
2683
2689
  fetcherResults
2684
- } = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, request);
2690
+ } = await callLoadersAndMaybeResolveData(state, matches, matchesToLoad, revalidatingFetchers, request);
2685
2691
  if (request.signal.aborted) {
2686
2692
  return {
2687
2693
  shortCircuited: true
@@ -2697,16 +2703,22 @@
2697
2703
  revalidatingFetchers.forEach(rf => fetchControllers.delete(rf.key));
2698
2704
 
2699
2705
  // If any loaders returned a redirect Response, start a new REPLACE navigation
2700
- let redirect = findRedirect([...loaderResults, ...fetcherResults]);
2706
+ let redirect = findRedirect(loaderResults);
2701
2707
  if (redirect) {
2702
- if (redirect.idx >= matchesToLoad.length) {
2703
- // If this redirect came from a fetcher make sure we mark it in
2704
- // fetchRedirectIds so it doesn't get revalidated on the next set of
2705
- // loader executions
2706
- let fetcherKey = revalidatingFetchers[redirect.idx - matchesToLoad.length].key;
2707
- fetchRedirectIds.add(fetcherKey);
2708
- }
2709
- await startRedirectNavigation(request, redirect.result, {
2708
+ await startRedirectNavigation(request, redirect.result, true, {
2709
+ replace
2710
+ });
2711
+ return {
2712
+ shortCircuited: true
2713
+ };
2714
+ }
2715
+ redirect = findRedirect(fetcherResults);
2716
+ if (redirect) {
2717
+ // If this redirect came from a fetcher make sure we mark it in
2718
+ // fetchRedirectIds so it doesn't get revalidated on the next set of
2719
+ // loader executions
2720
+ fetchRedirectIds.add(redirect.key);
2721
+ await startRedirectNavigation(request, redirect.result, true, {
2710
2722
  replace
2711
2723
  });
2712
2724
  return {
@@ -2890,8 +2902,8 @@
2890
2902
  // Call the action for the fetcher
2891
2903
  fetchControllers.set(key, abortController);
2892
2904
  let originatingLoadId = incrementingLoadId;
2893
- let actionResults = await callDataStrategy("action", fetchRequest, [match], requestMatches);
2894
- let actionResult = actionResults[0];
2905
+ let actionResults = await callDataStrategy("action", state, fetchRequest, [match], requestMatches, key);
2906
+ let actionResult = actionResults[match.route.id];
2895
2907
  if (fetchRequest.signal.aborted) {
2896
2908
  // We can delete this so long as we weren't aborted by our own fetcher
2897
2909
  // re-submit which would have put _new_ controller is in fetchControllers
@@ -2923,7 +2935,7 @@
2923
2935
  } else {
2924
2936
  fetchRedirectIds.add(key);
2925
2937
  updateFetcherState(key, getLoadingFetcher(submission));
2926
- return startRedirectNavigation(fetchRequest, actionResult, {
2938
+ return startRedirectNavigation(fetchRequest, actionResult, false, {
2927
2939
  fetcherSubmission: submission
2928
2940
  });
2929
2941
  }
@@ -2977,7 +2989,7 @@
2977
2989
  let {
2978
2990
  loaderResults,
2979
2991
  fetcherResults
2980
- } = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, revalidationRequest);
2992
+ } = await callLoadersAndMaybeResolveData(state, matches, matchesToLoad, revalidatingFetchers, revalidationRequest);
2981
2993
  if (abortController.signal.aborted) {
2982
2994
  return;
2983
2995
  }
@@ -2985,23 +2997,24 @@
2985
2997
  fetchReloadIds.delete(key);
2986
2998
  fetchControllers.delete(key);
2987
2999
  revalidatingFetchers.forEach(r => fetchControllers.delete(r.key));
2988
- let redirect = findRedirect([...loaderResults, ...fetcherResults]);
3000
+ let redirect = findRedirect(loaderResults);
2989
3001
  if (redirect) {
2990
- if (redirect.idx >= matchesToLoad.length) {
2991
- // If this redirect came from a fetcher make sure we mark it in
2992
- // fetchRedirectIds so it doesn't get revalidated on the next set of
2993
- // loader executions
2994
- let fetcherKey = revalidatingFetchers[redirect.idx - matchesToLoad.length].key;
2995
- fetchRedirectIds.add(fetcherKey);
2996
- }
2997
- return startRedirectNavigation(revalidationRequest, redirect.result);
3002
+ return startRedirectNavigation(revalidationRequest, redirect.result, false);
3003
+ }
3004
+ redirect = findRedirect(fetcherResults);
3005
+ if (redirect) {
3006
+ // If this redirect came from a fetcher make sure we mark it in
3007
+ // fetchRedirectIds so it doesn't get revalidated on the next set of
3008
+ // loader executions
3009
+ fetchRedirectIds.add(redirect.key);
3010
+ return startRedirectNavigation(revalidationRequest, redirect.result, false);
2998
3011
  }
2999
3012
 
3000
3013
  // Process and commit output from loaders
3001
3014
  let {
3002
3015
  loaderData,
3003
3016
  errors
3004
- } = processLoaderData(state, state.matches, matchesToLoad, loaderResults, undefined, revalidatingFetchers, fetcherResults, activeDeferreds);
3017
+ } = processLoaderData(state, matches, matchesToLoad, loaderResults, undefined, revalidatingFetchers, fetcherResults, activeDeferreds);
3005
3018
 
3006
3019
  // Since we let revalidations complete even if the submitting fetcher was
3007
3020
  // deleted, only put it back to idle if it hasn't been deleted
@@ -3072,8 +3085,8 @@
3072
3085
  // Call the loader for this fetcher route match
3073
3086
  fetchControllers.set(key, abortController);
3074
3087
  let originatingLoadId = incrementingLoadId;
3075
- let results = await callDataStrategy("loader", fetchRequest, [match], matches);
3076
- let result = results[0];
3088
+ let results = await callDataStrategy("loader", state, fetchRequest, [match], matches, key);
3089
+ let result = results[match.route.id];
3077
3090
 
3078
3091
  // Deferred isn't supported for fetcher loads, await everything and treat it
3079
3092
  // as a normal load. resolveDeferredData will return undefined if this
@@ -3108,7 +3121,7 @@
3108
3121
  return;
3109
3122
  } else {
3110
3123
  fetchRedirectIds.add(key);
3111
- await startRedirectNavigation(fetchRequest, result);
3124
+ await startRedirectNavigation(fetchRequest, result, false);
3112
3125
  return;
3113
3126
  }
3114
3127
  }
@@ -3143,7 +3156,7 @@
3143
3156
  * actually touch history until we've processed redirects, so we just use
3144
3157
  * the history action from the original navigation (PUSH or REPLACE).
3145
3158
  */
3146
- async function startRedirectNavigation(request, redirect, _temp2) {
3159
+ async function startRedirectNavigation(request, redirect, isNavigation, _temp2) {
3147
3160
  let {
3148
3161
  submission,
3149
3162
  fetcherSubmission,
@@ -3206,8 +3219,9 @@
3206
3219
  submission: _extends({}, activeSubmission, {
3207
3220
  formAction: location
3208
3221
  }),
3209
- // Preserve this flag across redirects
3210
- preventScrollReset: pendingPreventScrollReset
3222
+ // Preserve these flags across redirects
3223
+ preventScrollReset: pendingPreventScrollReset,
3224
+ enableViewTransition: isNavigation ? pendingViewTransitionEnabled : undefined
3211
3225
  });
3212
3226
  } else {
3213
3227
  // If we have a navigation submission, we will preserve it through the
@@ -3217,51 +3231,71 @@
3217
3231
  overrideNavigation,
3218
3232
  // Send fetcher submissions through for shouldRevalidate
3219
3233
  fetcherSubmission,
3220
- // Preserve this flag across redirects
3221
- preventScrollReset: pendingPreventScrollReset
3234
+ // Preserve these flags across redirects
3235
+ preventScrollReset: pendingPreventScrollReset,
3236
+ enableViewTransition: isNavigation ? pendingViewTransitionEnabled : undefined
3222
3237
  });
3223
3238
  }
3224
3239
  }
3225
3240
 
3226
3241
  // Utility wrapper for calling dataStrategy client-side without having to
3227
3242
  // pass around the manifest, mapRouteProperties, etc.
3228
- async function callDataStrategy(type, request, matchesToLoad, matches) {
3243
+ async function callDataStrategy(type, state, request, matchesToLoad, matches, fetcherKey) {
3244
+ let results;
3245
+ let dataResults = {};
3229
3246
  try {
3230
- let results = await callDataStrategyImpl(dataStrategyImpl, type, request, matchesToLoad, matches, manifest, mapRouteProperties);
3231
- return await Promise.all(results.map((result, i) => {
3232
- if (isRedirectHandlerResult(result)) {
3233
- let response = result.result;
3234
- return {
3235
- type: ResultType.redirect,
3236
- response: normalizeRelativeRoutingRedirectResponse(response, request, matchesToLoad[i].route.id, matches, basename, future.v7_relativeSplatPath)
3237
- };
3238
- }
3239
- return convertHandlerResultToDataResult(result);
3240
- }));
3247
+ results = await callDataStrategyImpl(dataStrategyImpl, type, state, request, matchesToLoad, matches, fetcherKey, manifest, mapRouteProperties);
3241
3248
  } catch (e) {
3242
3249
  // If the outer dataStrategy method throws, just return the error for all
3243
3250
  // matches - and it'll naturally bubble to the root
3244
- return matchesToLoad.map(() => ({
3245
- type: ResultType.error,
3246
- error: e
3247
- }));
3251
+ matchesToLoad.forEach(m => {
3252
+ dataResults[m.route.id] = {
3253
+ type: ResultType.error,
3254
+ error: e
3255
+ };
3256
+ });
3257
+ return dataResults;
3258
+ }
3259
+ for (let [routeId, result] of Object.entries(results)) {
3260
+ if (isRedirectDataStrategyResultResult(result)) {
3261
+ let response = result.result;
3262
+ dataResults[routeId] = {
3263
+ type: ResultType.redirect,
3264
+ response: normalizeRelativeRoutingRedirectResponse(response, request, routeId, matches, basename, future.v7_relativeSplatPath)
3265
+ };
3266
+ } else {
3267
+ dataResults[routeId] = await convertDataStrategyResultToDataResult(result);
3268
+ }
3248
3269
  }
3270
+ return dataResults;
3249
3271
  }
3250
- async function callLoadersAndMaybeResolveData(currentMatches, matches, matchesToLoad, fetchersToLoad, request) {
3251
- let [loaderResults, ...fetcherResults] = await Promise.all([matchesToLoad.length ? callDataStrategy("loader", request, matchesToLoad, matches) : [], ...fetchersToLoad.map(f => {
3272
+ async function callLoadersAndMaybeResolveData(state, matches, matchesToLoad, fetchersToLoad, request) {
3273
+ let currentMatches = state.matches;
3274
+
3275
+ // Kick off loaders and fetchers in parallel
3276
+ let loaderResultsPromise = callDataStrategy("loader", state, request, matchesToLoad, matches, null);
3277
+ let fetcherResultsPromise = Promise.all(fetchersToLoad.map(async f => {
3252
3278
  if (f.matches && f.match && f.controller) {
3253
- let fetcherRequest = createClientSideRequest(init.history, f.path, f.controller.signal);
3254
- return callDataStrategy("loader", fetcherRequest, [f.match], f.matches).then(r => r[0]);
3279
+ let results = await callDataStrategy("loader", state, createClientSideRequest(init.history, f.path, f.controller.signal), [f.match], f.matches, f.key);
3280
+ let result = results[f.match.route.id];
3281
+ // Fetcher results are keyed by fetcher key from here on out, not routeId
3282
+ return {
3283
+ [f.key]: result
3284
+ };
3255
3285
  } else {
3256
3286
  return Promise.resolve({
3257
- type: ResultType.error,
3258
- error: getInternalRouterError(404, {
3259
- pathname: f.path
3260
- })
3287
+ [f.key]: {
3288
+ type: ResultType.error,
3289
+ error: getInternalRouterError(404, {
3290
+ pathname: f.path
3291
+ })
3292
+ }
3261
3293
  });
3262
3294
  }
3263
- })]);
3264
- await Promise.all([resolveDeferredResults(currentMatches, matchesToLoad, loaderResults, loaderResults.map(() => request.signal), false, state.loaderData), resolveDeferredResults(currentMatches, fetchersToLoad.map(f => f.match), fetcherResults, fetchersToLoad.map(f => f.controller ? f.controller.signal : null), true)]);
3295
+ }));
3296
+ let loaderResults = await loaderResultsPromise;
3297
+ let fetcherResults = (await fetcherResultsPromise).reduce((acc, r) => Object.assign(acc, r), {});
3298
+ await Promise.all([resolveNavigationDeferredResults(matches, loaderResults, request.signal, currentMatches, state.loaderData), resolveFetcherDeferredResults(matches, fetcherResults, fetchersToLoad)]);
3265
3299
  return {
3266
3300
  loaderResults,
3267
3301
  fetcherResults
@@ -3928,9 +3962,9 @@
3928
3962
  });
3929
3963
  } catch (e) {
3930
3964
  // If the user threw/returned a Response in callLoaderOrAction for a
3931
- // `queryRoute` call, we throw the `HandlerResult` to bail out early
3965
+ // `queryRoute` call, we throw the `DataStrategyResult` to bail out early
3932
3966
  // and then return or throw the raw Response here accordingly
3933
- if (isHandlerResult(e) && isResponse(e.result)) {
3967
+ if (isDataStrategyResult(e) && isResponse(e.result)) {
3934
3968
  if (e.type === ResultType.error) {
3935
3969
  throw e.result;
3936
3970
  }
@@ -3961,7 +3995,7 @@
3961
3995
  };
3962
3996
  } else {
3963
3997
  let results = await callDataStrategy("action", request, [actionMatch], matches, isRouteRequest, requestContext, unstable_dataStrategy);
3964
- result = results[0];
3998
+ result = results[actionMatch.route.id];
3965
3999
  if (request.signal.aborted) {
3966
4000
  throwStaticHandlerAbortedError(request, isRouteRequest, future);
3967
4001
  }
@@ -4083,7 +4117,7 @@
4083
4117
 
4084
4118
  // Process and commit output from loaders
4085
4119
  let activeDeferreds = new Map();
4086
- let context = processRouteLoaderData(matches, matchesToLoad, results, pendingActionResult, activeDeferreds, skipLoaderErrorBubbling);
4120
+ let context = processRouteLoaderData(matches, results, pendingActionResult, activeDeferreds, skipLoaderErrorBubbling);
4087
4121
 
4088
4122
  // Add a null for any non-loader matches for proper revalidation on the client
4089
4123
  let executedLoaders = new Set(matchesToLoad.map(match => match.route.id));
@@ -4101,20 +4135,26 @@
4101
4135
  // Utility wrapper for calling dataStrategy server-side without having to
4102
4136
  // pass around the manifest, mapRouteProperties, etc.
4103
4137
  async function callDataStrategy(type, request, matchesToLoad, matches, isRouteRequest, requestContext, unstable_dataStrategy) {
4104
- let results = await callDataStrategyImpl(unstable_dataStrategy || defaultDataStrategy, type, request, matchesToLoad, matches, manifest, mapRouteProperties, requestContext);
4105
- return await Promise.all(results.map((result, i) => {
4106
- if (isRedirectHandlerResult(result)) {
4138
+ let results = await callDataStrategyImpl(unstable_dataStrategy || defaultDataStrategy, type, null, request, matchesToLoad, matches, null, manifest, mapRouteProperties, requestContext);
4139
+ let dataResults = {};
4140
+ await Promise.all(matches.map(async match => {
4141
+ if (!(match.route.id in results)) {
4142
+ return;
4143
+ }
4144
+ let result = results[match.route.id];
4145
+ if (isRedirectDataStrategyResultResult(result)) {
4107
4146
  let response = result.result;
4108
4147
  // Throw redirects and let the server handle them with an HTTP redirect
4109
- throw normalizeRelativeRoutingRedirectResponse(response, request, matchesToLoad[i].route.id, matches, basename, future.v7_relativeSplatPath);
4148
+ throw normalizeRelativeRoutingRedirectResponse(response, request, match.route.id, matches, basename, future.v7_relativeSplatPath);
4110
4149
  }
4111
4150
  if (isResponse(result.result) && isRouteRequest) {
4112
4151
  // For SSR single-route requests, we want to hand Responses back
4113
4152
  // directly without unwrapping
4114
4153
  throw result;
4115
4154
  }
4116
- return convertHandlerResultToDataResult(result);
4155
+ dataResults[match.route.id] = await convertDataStrategyResultToDataResult(result);
4117
4156
  }));
4157
+ return dataResults;
4118
4158
  }
4119
4159
  return {
4120
4160
  dataRoutes,
@@ -4603,56 +4643,70 @@
4603
4643
  }
4604
4644
 
4605
4645
  // Default implementation of `dataStrategy` which fetches all loaders in parallel
4606
- function defaultDataStrategy(opts) {
4607
- return Promise.all(opts.matches.map(m => m.resolve()));
4646
+ async function defaultDataStrategy(_ref6) {
4647
+ let {
4648
+ matches
4649
+ } = _ref6;
4650
+ let matchesToLoad = matches.filter(m => m.shouldLoad);
4651
+ let results = await Promise.all(matchesToLoad.map(m => m.resolve()));
4652
+ return results.reduce((acc, result, i) => Object.assign(acc, {
4653
+ [matchesToLoad[i].route.id]: result
4654
+ }), {});
4608
4655
  }
4609
- async function callDataStrategyImpl(dataStrategyImpl, type, request, matchesToLoad, matches, manifest, mapRouteProperties, requestContext) {
4610
- let routeIdsToLoad = matchesToLoad.reduce((acc, m) => acc.add(m.route.id), new Set());
4611
- let loadedMatches = new Set();
4656
+ async function callDataStrategyImpl(dataStrategyImpl, type, state, request, matchesToLoad, matches, fetcherKey, manifest, mapRouteProperties, requestContext) {
4657
+ let loadRouteDefinitionsPromises = matches.map(m => m.route.lazy ? loadLazyRouteModule(m.route, mapRouteProperties, manifest) : undefined);
4658
+ let dsMatches = matches.map((match, i) => {
4659
+ let loadRoutePromise = loadRouteDefinitionsPromises[i];
4660
+ let shouldLoad = matchesToLoad.some(m => m.route.id === match.route.id);
4661
+ // `resolve` encapsulates route.lazy(), executing the loader/action,
4662
+ // and mapping return values/thrown errors to a `DataStrategyResult`. Users
4663
+ // can pass a callback to take fine-grained control over the execution
4664
+ // of the loader/action
4665
+ let resolve = async handlerOverride => {
4666
+ if (handlerOverride && request.method === "GET" && (match.route.lazy || match.route.loader)) {
4667
+ shouldLoad = true;
4668
+ }
4669
+ return shouldLoad ? callLoaderOrAction(type, request, match, loadRoutePromise, handlerOverride, requestContext) : Promise.resolve({
4670
+ type: ResultType.data,
4671
+ result: undefined
4672
+ });
4673
+ };
4674
+ return _extends({}, match, {
4675
+ shouldLoad,
4676
+ resolve
4677
+ });
4678
+ });
4612
4679
 
4613
4680
  // Send all matches here to allow for a middleware-type implementation.
4614
4681
  // handler will be a no-op for unneeded routes and we filter those results
4615
4682
  // back out below.
4616
4683
  let results = await dataStrategyImpl({
4617
- matches: matches.map(match => {
4618
- let shouldLoad = routeIdsToLoad.has(match.route.id);
4619
- // `resolve` encapsulates the route.lazy, executing the
4620
- // loader/action, and mapping return values/thrown errors to a
4621
- // HandlerResult. Users can pass a callback to take fine-grained control
4622
- // over the execution of the loader/action
4623
- let resolve = handlerOverride => {
4624
- loadedMatches.add(match.route.id);
4625
- return shouldLoad ? callLoaderOrAction(type, request, match, manifest, mapRouteProperties, handlerOverride, requestContext) : Promise.resolve({
4626
- type: ResultType.data,
4627
- result: undefined
4628
- });
4629
- };
4630
- return _extends({}, match, {
4631
- shouldLoad,
4632
- resolve
4633
- });
4634
- }),
4684
+ matches: dsMatches,
4635
4685
  request,
4636
4686
  params: matches[0].params,
4687
+ fetcherKey,
4637
4688
  context: requestContext
4638
4689
  });
4639
4690
 
4640
- // Throw if any loadRoute implementations not called since they are what
4641
- // ensures a route is fully loaded
4642
- matches.forEach(m => invariant(loadedMatches.has(m.route.id), "`match.resolve()` was not called for route id \"" + m.route.id + "\". " + "You must call `match.resolve()` on every match passed to " + "`dataStrategy` to ensure all routes are properly loaded."));
4643
-
4644
- // Filter out any middleware-only matches for which we didn't need to run handlers
4645
- return results.filter((_, i) => routeIdsToLoad.has(matches[i].route.id));
4691
+ // Wait for all routes to load here but 'swallow the error since we want
4692
+ // it to bubble up from the `await loadRoutePromise` in `callLoaderOrAction` -
4693
+ // called from `match.resolve()`
4694
+ try {
4695
+ await Promise.all(loadRouteDefinitionsPromises);
4696
+ } catch (e) {
4697
+ // No-op
4698
+ }
4699
+ return results;
4646
4700
  }
4647
4701
 
4648
4702
  // Default logic for calling a loader/action is the user has no specified a dataStrategy
4649
- async function callLoaderOrAction(type, request, match, manifest, mapRouteProperties, handlerOverride, staticContext) {
4703
+ async function callLoaderOrAction(type, request, match, loadRoutePromise, handlerOverride, staticContext) {
4650
4704
  let result;
4651
4705
  let onReject;
4652
4706
  let runHandler = handler => {
4653
4707
  // Setup a promise we can race against so that abort signals short circuit
4654
4708
  let reject;
4655
- // This will never resolve so safe to type it as Promise<HandlerResult> to
4709
+ // This will never resolve so safe to type it as Promise<DataStrategyResult> to
4656
4710
  // satisfy the function return value
4657
4711
  let abortPromise = new Promise((_, r) => reject = r);
4658
4712
  onReject = () => reject();
@@ -4667,30 +4721,27 @@
4667
4721
  context: staticContext
4668
4722
  }, ...(ctx !== undefined ? [ctx] : []));
4669
4723
  };
4670
- let handlerPromise;
4671
- if (handlerOverride) {
4672
- handlerPromise = handlerOverride(ctx => actualHandler(ctx));
4673
- } else {
4674
- handlerPromise = (async () => {
4675
- try {
4676
- let val = await actualHandler();
4677
- return {
4678
- type: "data",
4679
- result: val
4680
- };
4681
- } catch (e) {
4682
- return {
4683
- type: "error",
4684
- result: e
4685
- };
4686
- }
4687
- })();
4688
- }
4724
+ let handlerPromise = (async () => {
4725
+ try {
4726
+ let val = await (handlerOverride ? handlerOverride(ctx => actualHandler(ctx)) : actualHandler());
4727
+ return {
4728
+ type: "data",
4729
+ result: val
4730
+ };
4731
+ } catch (e) {
4732
+ return {
4733
+ type: "error",
4734
+ result: e
4735
+ };
4736
+ }
4737
+ })();
4689
4738
  return Promise.race([handlerPromise, abortPromise]);
4690
4739
  };
4691
4740
  try {
4692
4741
  let handler = match.route[type];
4693
- if (match.route.lazy) {
4742
+
4743
+ // If we have a route.lazy promise, await that first
4744
+ if (loadRoutePromise) {
4694
4745
  if (handler) {
4695
4746
  // Run statically defined handler in parallel with lazy()
4696
4747
  let handlerError;
@@ -4700,14 +4751,14 @@
4700
4751
  // route has a boundary that can handle the error
4701
4752
  runHandler(handler).catch(e => {
4702
4753
  handlerError = e;
4703
- }), loadLazyRouteModule(match.route, mapRouteProperties, manifest)]);
4754
+ }), loadRoutePromise]);
4704
4755
  if (handlerError !== undefined) {
4705
4756
  throw handlerError;
4706
4757
  }
4707
4758
  result = value;
4708
4759
  } else {
4709
4760
  // Load lazy route module, then run any returned handler
4710
- await loadLazyRouteModule(match.route, mapRouteProperties, manifest);
4761
+ await loadRoutePromise;
4711
4762
  handler = match.route[type];
4712
4763
  if (handler) {
4713
4764
  // Handler still runs even if we got interrupted to maintain consistency
@@ -4743,7 +4794,7 @@
4743
4794
  invariant(result.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`.");
4744
4795
  } catch (e) {
4745
4796
  // We should already be catching and converting normal handler executions to
4746
- // HandlerResults and returning them, so anything that throws here is an
4797
+ // DataStrategyResults and returning them, so anything that throws here is an
4747
4798
  // unexpected error we still need to wrap
4748
4799
  return {
4749
4800
  type: ResultType.error,
@@ -4756,11 +4807,11 @@
4756
4807
  }
4757
4808
  return result;
4758
4809
  }
4759
- async function convertHandlerResultToDataResult(handlerResult) {
4810
+ async function convertDataStrategyResultToDataResult(dataStrategyResult) {
4760
4811
  let {
4761
4812
  result,
4762
4813
  type
4763
- } = handlerResult;
4814
+ } = dataStrategyResult;
4764
4815
  if (isResponse(result)) {
4765
4816
  let data;
4766
4817
  try {
@@ -4916,7 +4967,7 @@
4916
4967
  }
4917
4968
  return formData;
4918
4969
  }
4919
- function processRouteLoaderData(matches, matchesToLoad, results, pendingActionResult, activeDeferreds, skipLoaderErrorBubbling) {
4970
+ function processRouteLoaderData(matches, results, pendingActionResult, activeDeferreds, skipLoaderErrorBubbling) {
4920
4971
  // Fill in loaderData/errors from our loaders
4921
4972
  let loaderData = {};
4922
4973
  let errors = null;
@@ -4926,8 +4977,12 @@
4926
4977
  let pendingError = pendingActionResult && isErrorResult(pendingActionResult[1]) ? pendingActionResult[1].error : undefined;
4927
4978
 
4928
4979
  // Process loader results into state.loaderData/state.errors
4929
- results.forEach((result, index) => {
4930
- let id = matchesToLoad[index].route.id;
4980
+ matches.forEach(match => {
4981
+ if (!(match.route.id in results)) {
4982
+ return;
4983
+ }
4984
+ let id = match.route.id;
4985
+ let result = results[id];
4931
4986
  invariant(!isRedirectResult(result), "Cannot handle redirect results in processLoaderData");
4932
4987
  if (isErrorResult(result)) {
4933
4988
  let error = result.error;
@@ -5009,23 +5064,23 @@
5009
5064
  let {
5010
5065
  loaderData,
5011
5066
  errors
5012
- } = processRouteLoaderData(matches, matchesToLoad, results, pendingActionResult, activeDeferreds, false // This method is only called client side so we always want to bubble
5067
+ } = processRouteLoaderData(matches, results, pendingActionResult, activeDeferreds, false // This method is only called client side so we always want to bubble
5013
5068
  );
5014
5069
 
5015
5070
  // Process results from our revalidating fetchers
5016
- for (let index = 0; index < revalidatingFetchers.length; index++) {
5071
+ revalidatingFetchers.forEach(rf => {
5017
5072
  let {
5018
5073
  key,
5019
5074
  match,
5020
5075
  controller
5021
- } = revalidatingFetchers[index];
5022
- invariant(fetcherResults !== undefined && fetcherResults[index] !== undefined, "Did not find corresponding fetcher result");
5023
- let result = fetcherResults[index];
5076
+ } = rf;
5077
+ let result = fetcherResults[key];
5078
+ invariant(result, "Did not find corresponding fetcher result");
5024
5079
 
5025
5080
  // Process fetcher non-redirect errors
5026
5081
  if (controller && controller.signal.aborted) {
5027
5082
  // Nothing to do for aborted fetchers
5028
- continue;
5083
+ return;
5029
5084
  } else if (isErrorResult(result)) {
5030
5085
  let boundaryMatch = findNearestBoundary(state.matches, match == null ? void 0 : match.route.id);
5031
5086
  if (!(errors && errors[boundaryMatch.route.id])) {
@@ -5046,7 +5101,7 @@
5046
5101
  let doneFetcher = getDoneFetcher(result.data);
5047
5102
  state.fetchers.set(key, doneFetcher);
5048
5103
  }
5049
- }
5104
+ });
5050
5105
  return {
5051
5106
  loaderData,
5052
5107
  errors
@@ -5148,12 +5203,13 @@
5148
5203
 
5149
5204
  // Find any returned redirect errors, starting from the lowest match
5150
5205
  function findRedirect(results) {
5151
- for (let i = results.length - 1; i >= 0; i--) {
5152
- let result = results[i];
5206
+ let entries = Object.entries(results);
5207
+ for (let i = entries.length - 1; i >= 0; i--) {
5208
+ let [key, result] = entries[i];
5153
5209
  if (isRedirectResult(result)) {
5154
5210
  return {
5155
- result,
5156
- idx: i
5211
+ key,
5212
+ result
5157
5213
  };
5158
5214
  }
5159
5215
  }
@@ -5186,10 +5242,10 @@
5186
5242
  function isPromise(val) {
5187
5243
  return typeof val === "object" && val != null && "then" in val;
5188
5244
  }
5189
- function isHandlerResult(result) {
5245
+ function isDataStrategyResult(result) {
5190
5246
  return result != null && typeof result === "object" && "type" in result && "result" in result && (result.type === ResultType.data || result.type === ResultType.error);
5191
5247
  }
5192
- function isRedirectHandlerResult(result) {
5248
+ function isRedirectDataStrategyResultResult(result) {
5193
5249
  return isResponse(result.result) && redirectStatusCodes.has(result.result.status);
5194
5250
  }
5195
5251
  function isDeferredResult(result) {
@@ -5225,10 +5281,11 @@
5225
5281
  function isMutationMethod(method) {
5226
5282
  return validMutationMethods.has(method.toLowerCase());
5227
5283
  }
5228
- async function resolveDeferredResults(currentMatches, matchesToLoad, results, signals, isFetcher, currentLoaderData) {
5229
- for (let index = 0; index < results.length; index++) {
5230
- let result = results[index];
5231
- let match = matchesToLoad[index];
5284
+ async function resolveNavigationDeferredResults(matches, results, signal, currentMatches, currentLoaderData) {
5285
+ let entries = Object.entries(results);
5286
+ for (let index = 0; index < entries.length; index++) {
5287
+ let [routeId, result] = entries[index];
5288
+ let match = matches.find(m => (m == null ? void 0 : m.route.id) === routeId);
5232
5289
  // If we don't have a match, then we can have a deferred result to do
5233
5290
  // anything with. This is for revalidating fetchers where the route was
5234
5291
  // removed during HMR
@@ -5237,15 +5294,41 @@
5237
5294
  }
5238
5295
  let currentMatch = currentMatches.find(m => m.route.id === match.route.id);
5239
5296
  let isRevalidatingLoader = currentMatch != null && !isNewRouteInstance(currentMatch, match) && (currentLoaderData && currentLoaderData[match.route.id]) !== undefined;
5240
- if (isDeferredResult(result) && (isFetcher || isRevalidatingLoader)) {
5297
+ if (isDeferredResult(result) && isRevalidatingLoader) {
5298
+ // Note: we do not have to touch activeDeferreds here since we race them
5299
+ // against the signal in resolveDeferredData and they'll get aborted
5300
+ // there if needed
5301
+ await resolveDeferredData(result, signal, false).then(result => {
5302
+ if (result) {
5303
+ results[routeId] = result;
5304
+ }
5305
+ });
5306
+ }
5307
+ }
5308
+ }
5309
+ async function resolveFetcherDeferredResults(matches, results, revalidatingFetchers) {
5310
+ for (let index = 0; index < revalidatingFetchers.length; index++) {
5311
+ let {
5312
+ key,
5313
+ routeId,
5314
+ controller
5315
+ } = revalidatingFetchers[index];
5316
+ let result = results[key];
5317
+ let match = matches.find(m => (m == null ? void 0 : m.route.id) === routeId);
5318
+ // If we don't have a match, then we can have a deferred result to do
5319
+ // anything with. This is for revalidating fetchers where the route was
5320
+ // removed during HMR
5321
+ if (!match) {
5322
+ continue;
5323
+ }
5324
+ if (isDeferredResult(result)) {
5241
5325
  // Note: we do not have to touch activeDeferreds here since we race them
5242
5326
  // against the signal in resolveDeferredData and they'll get aborted
5243
5327
  // there if needed
5244
- let signal = signals[index];
5245
- invariant(signal, "Expected an AbortSignal for revalidating fetcher deferred result");
5246
- await resolveDeferredData(result, signal, isFetcher).then(result => {
5328
+ invariant(controller, "Expected an AbortController for revalidating fetcher deferred result");
5329
+ await resolveDeferredData(result, controller.signal, true).then(result => {
5247
5330
  if (result) {
5248
- results[index] = result || results[index];
5331
+ results[key] = result;
5249
5332
  }
5250
5333
  });
5251
5334
  }