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