@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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @remix-run/router v0.0.0-experimental-c9f8a7b2
2
+ * @remix-run/router v0.0.0-experimental-e960cf1a
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -891,7 +891,7 @@
891
891
  branches.sort((a, b) => a.score !== b.score ? b.score - a.score // Higher score first
892
892
  : compareIndexes(a.routesMeta.map(meta => meta.childrenIndex), b.routesMeta.map(meta => meta.childrenIndex)));
893
893
  }
894
- const paramRe = /^:[\w-]+$/;
894
+ const paramRe = /^:\w+$/;
895
895
  const dynamicSegmentValue = 3;
896
896
  const indexRouteValue = 2;
897
897
  const emptySegmentValue = 1;
@@ -981,7 +981,7 @@
981
981
  // Apply the splat
982
982
  return stringify(params[star]);
983
983
  }
984
- const keyMatch = segment.match(/^:([\w-]+)(\??)$/);
984
+ const keyMatch = segment.match(/^:(\w+)(\??)$/);
985
985
  if (keyMatch) {
986
986
  const [, key, optional] = keyMatch;
987
987
  let param = params[key];
@@ -1063,7 +1063,7 @@
1063
1063
  let regexpSource = "^" + path.replace(/\/*\*?$/, "") // Ignore trailing / and /*, we'll handle it below
1064
1064
  .replace(/^\/*/, "/") // Make sure it has a leading /
1065
1065
  .replace(/[\\.*+^${}|()[\]]/g, "\\$&") // Escape special regex chars
1066
- .replace(/\/:([\w-]+)(\?)?/g, (_, paramName, isOptional) => {
1066
+ .replace(/\/:(\w+)(\?)?/g, (_, paramName, isOptional) => {
1067
1067
  params.push({
1068
1068
  paramName,
1069
1069
  isOptional: isOptional != null
@@ -1656,6 +1656,8 @@
1656
1656
  const isBrowser = typeof routerWindow !== "undefined" && typeof routerWindow.document !== "undefined" && typeof routerWindow.document.createElement !== "undefined";
1657
1657
  const isServer = !isBrowser;
1658
1658
  invariant(init.routes.length > 0, "You must provide a non-empty routes array to createRouter");
1659
+ const dataStrategy = init.unstable_dataStrategy || defaultDataStrategy;
1660
+ const callLoaderOrAction = createCallLoaderOrAction(dataStrategy);
1659
1661
  let mapRouteProperties;
1660
1662
  if (init.mapRouteProperties) {
1661
1663
  mapRouteProperties = init.mapRouteProperties;
@@ -1717,28 +1719,18 @@
1717
1719
  [route.id]: error
1718
1720
  };
1719
1721
  }
1720
- let initialized;
1721
- let hasLazyRoutes = initialMatches.some(m => m.route.lazy);
1722
- let hasLoaders = initialMatches.some(m => m.route.loader);
1723
- if (hasLazyRoutes) {
1724
- // All initialMatches need to be loaded before we're ready. If we have lazy
1725
- // functions around still then we'll need to run them in initialize()
1726
- initialized = false;
1727
- } else if (!hasLoaders) {
1728
- // If we've got no loaders to run, then we're good to go
1729
- initialized = true;
1730
- } else if (future.v7_partialHydration) {
1731
- // If partial hydration is enabled, we're initialized so long as we were
1732
- // provided with hydrationData for every route with a loader, and no loaders
1733
- // were marked for explicit hydration
1734
- let loaderData = init.hydrationData ? init.hydrationData.loaderData : null;
1735
- let errors = init.hydrationData ? init.hydrationData.errors : null;
1736
- initialized = initialMatches.every(m => m.route.loader && m.route.loader.hydrate !== true && (loaderData && loaderData[m.route.id] !== undefined || errors && errors[m.route.id] !== undefined));
1737
- } else {
1738
- // Without partial hydration - we're initialized if we were provided any
1739
- // hydrationData - which is expected to be complete
1740
- initialized = init.hydrationData != null;
1741
- }
1722
+
1723
+ // "Initialized" here really means "Can `RouterProvider` render my route tree?"
1724
+ // Prior to `route.HydrateFallback`, we only had a root `fallbackElement` so we used
1725
+ // `state.initialized` to render that instead of `<DataRoutes>`. Now that we
1726
+ // support route level fallbacks we can always render and we'll just render
1727
+ // as deep as we have data for and detect the nearest ancestor HydrateFallback
1728
+ let initialized = future.v7_partialHydration ||
1729
+ // All initialMatches need to be loaded before we're ready. If we have lazy
1730
+ // functions around still then we'll need to run them in initialize()
1731
+ !initialMatches.some(m => m.route.lazy) && (
1732
+ // And we have to either have no loaders or have been provided hydrationData
1733
+ !initialMatches.some(m => m.route.loader) || init.hydrationData != null);
1742
1734
  let router;
1743
1735
  let state = {
1744
1736
  historyAction: init.history.action,
@@ -1905,7 +1897,7 @@
1905
1897
  // in the normal navigation flow. For SSR it's expected that lazy modules are
1906
1898
  // resolved prior to router creation since we can't go into a fallbackElement
1907
1899
  // UI for SSR'd apps
1908
- if (!state.initialized) {
1900
+ if (!state.initialized || future.v7_partialHydration && state.matches.some(m => isUnhydratedRoute(state, m.route))) {
1909
1901
  startNavigation(Action.Pop, state.location, {
1910
1902
  initialHydration: true
1911
1903
  });
@@ -2484,10 +2476,9 @@
2484
2476
  pendingNavigationController.signal.addEventListener("abort", abortPendingFetchRevalidations);
2485
2477
  }
2486
2478
  let {
2487
- results,
2488
2479
  loaderResults,
2489
2480
  fetcherResults
2490
- } = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, request);
2481
+ } = await loadDataAndMaybeResolveDeferred(state.matches, matches, matchesToLoad, revalidatingFetchers, request);
2491
2482
  if (request.signal.aborted) {
2492
2483
  return {
2493
2484
  shortCircuited: true
@@ -2503,7 +2494,7 @@
2503
2494
  revalidatingFetchers.forEach(rf => fetchControllers.delete(rf.key));
2504
2495
 
2505
2496
  // If any loaders returned a redirect Response, start a new REPLACE navigation
2506
- let redirect = findRedirect(results);
2497
+ let redirect = findRedirect([...loaderResults, ...fetcherResults]);
2507
2498
  if (redirect) {
2508
2499
  if (redirect.idx >= matchesToLoad.length) {
2509
2500
  // If this redirect came from a fetcher make sure we mark it in
@@ -2708,10 +2699,9 @@
2708
2699
  let abortPendingFetchRevalidations = () => revalidatingFetchers.forEach(rf => abortFetcher(rf.key));
2709
2700
  abortController.signal.addEventListener("abort", abortPendingFetchRevalidations);
2710
2701
  let {
2711
- results,
2712
2702
  loaderResults,
2713
2703
  fetcherResults
2714
- } = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, revalidationRequest);
2704
+ } = await loadDataAndMaybeResolveDeferred(state.matches, matches, matchesToLoad, revalidatingFetchers, revalidationRequest);
2715
2705
  if (abortController.signal.aborted) {
2716
2706
  return;
2717
2707
  }
@@ -2719,7 +2709,7 @@
2719
2709
  fetchReloadIds.delete(key);
2720
2710
  fetchControllers.delete(key);
2721
2711
  revalidatingFetchers.forEach(r => fetchControllers.delete(r.key));
2722
- let redirect = findRedirect(results);
2712
+ let redirect = findRedirect([...loaderResults, ...fetcherResults]);
2723
2713
  if (redirect) {
2724
2714
  if (redirect.idx >= matchesToLoad.length) {
2725
2715
  // If this redirect came from a fetcher make sure we mark it in
@@ -2929,28 +2919,36 @@
2929
2919
  });
2930
2920
  }
2931
2921
  }
2932
- async function callLoadersAndMaybeResolveData(currentMatches, matches, matchesToLoad, fetchersToLoad, request) {
2933
- // Call all navigation loaders and revalidating fetcher loaders in parallel,
2934
- // then slice off the results into separate arrays so we can handle them
2935
- // accordingly
2936
- let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath)), ...fetchersToLoad.map(f => {
2937
- if (f.matches && f.match && f.controller) {
2938
- return callLoaderOrAction("loader", createClientSideRequest(init.history, f.path, f.controller.signal), f.match, f.matches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath);
2939
- } else {
2940
- let error = {
2922
+ async function loadDataAndMaybeResolveDeferred(currentMatches, matches, matchesToLoad, fetchersToLoad, request) {
2923
+ let [loaderResults, ...fetcherResults] = await Promise.all([matchesToLoad.length ? dataStrategy({
2924
+ matches: matchesToLoad.map(m => finesseToAgnosticDataStrategyMatch(m, mapRouteProperties, manifest)),
2925
+ request,
2926
+ type: "loader",
2927
+ defaultStrategy(match) {
2928
+ return callLoaderOrActionImplementation("loader", request, match, matches, basename, future.v7_relativeSplatPath);
2929
+ }
2930
+ }) : [], ...fetchersToLoad.map(f => {
2931
+ if (!f.matches || !f.match || !f.controller) {
2932
+ return Promise.resolve({
2941
2933
  type: ResultType.error,
2942
2934
  error: getInternalRouterError(404, {
2943
2935
  pathname: f.path
2944
2936
  })
2945
- };
2946
- return error;
2937
+ });
2947
2938
  }
2939
+ return dataStrategy({
2940
+ matches: [finesseToAgnosticDataStrategyMatch(f.match, mapRouteProperties, manifest)],
2941
+ request,
2942
+ type: "loader",
2943
+ defaultStrategy(match) {
2944
+ invariant(f.controller, "Expected controller for fetcher in defaultStrategy");
2945
+ invariant(f.matches, "Expected matches for fetcher in defaultStrategy");
2946
+ return callLoaderOrActionImplementation("loader", createClientSideRequest(init.history, f.path, f.controller.signal), match, f.matches, basename, future.v7_relativeSplatPath);
2947
+ }
2948
+ }).then(r => r[0]);
2948
2949
  })]);
2949
- let loaderResults = results.slice(0, matchesToLoad.length);
2950
- let fetcherResults = results.slice(matchesToLoad.length);
2951
2950
  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)]);
2952
2951
  return {
2953
- results,
2954
2952
  loaderResults,
2955
2953
  fetcherResults
2956
2954
  };
@@ -3261,6 +3259,8 @@
3261
3259
 
3262
3260
  function createStaticHandler(routes, opts) {
3263
3261
  invariant(routes.length > 0, "You must provide a non-empty routes array to createStaticHandler");
3262
+ const dataStrategy = (opts == null ? void 0 : opts.dataStrategy) || defaultDataStrategy;
3263
+ const callLoaderOrAction = createCallLoaderOrAction(dataStrategy);
3264
3264
  let manifest = {};
3265
3265
  let basename = (opts ? opts.basename : null) || "/";
3266
3266
  let mapRouteProperties;
@@ -3277,8 +3277,7 @@
3277
3277
  }
3278
3278
  // Config driven behavior flags
3279
3279
  let future = _extends({
3280
- v7_relativeSplatPath: false,
3281
- v7_throwAbortReason: false
3280
+ v7_relativeSplatPath: false
3282
3281
  }, opts ? opts.future : null);
3283
3282
  let dataRoutes = convertRoutesToDataRoutes(routes, mapRouteProperties, undefined, manifest);
3284
3283
 
@@ -3501,7 +3500,8 @@
3501
3500
  requestContext
3502
3501
  });
3503
3502
  if (request.signal.aborted) {
3504
- throwStaticHandlerAbortedError(request, isRouteRequest, future);
3503
+ let method = isRouteRequest ? "queryRoute" : "query";
3504
+ throw new Error(method + "() call aborted: " + request.method + " " + request.url);
3505
3505
  }
3506
3506
  }
3507
3507
  if (isRedirectResult(result)) {
@@ -3613,13 +3613,21 @@
3613
3613
  activeDeferreds: null
3614
3614
  };
3615
3615
  }
3616
- let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath, {
3617
- isStaticRequest: true,
3618
- isRouteRequest,
3619
- requestContext
3620
- }))]);
3616
+ let results = await dataStrategy({
3617
+ matches: matchesToLoad.map(m => finesseToAgnosticDataStrategyMatch(m, mapRouteProperties, manifest)),
3618
+ request,
3619
+ type: "loader",
3620
+ defaultStrategy(match) {
3621
+ return callLoaderOrActionImplementation("loader", request, match, matches, basename, future.v7_relativeSplatPath, {
3622
+ isStaticRequest: true,
3623
+ isRouteRequest,
3624
+ requestContext
3625
+ });
3626
+ }
3627
+ });
3621
3628
  if (request.signal.aborted) {
3622
- throwStaticHandlerAbortedError(request, isRouteRequest, future);
3629
+ let method = isRouteRequest ? "queryRoute" : "query";
3630
+ throw new Error(method + "() call aborted: " + request.method + " " + request.url);
3623
3631
  }
3624
3632
 
3625
3633
  // Process and commit output from loaders
@@ -3651,6 +3659,14 @@
3651
3659
  //#region Helpers
3652
3660
  ////////////////////////////////////////////////////////////////////////////////
3653
3661
 
3662
+ function defaultDataStrategy(_ref3) {
3663
+ let {
3664
+ defaultStrategy,
3665
+ matches
3666
+ } = _ref3;
3667
+ return Promise.all(matches.map(match => defaultStrategy(match)));
3668
+ }
3669
+
3654
3670
  /**
3655
3671
  * Given an existing StaticHandlerContext and an error thrown at render time,
3656
3672
  * provide an updated StaticHandlerContext suitable for a second SSR render
@@ -3664,13 +3680,6 @@
3664
3680
  });
3665
3681
  return newContext;
3666
3682
  }
3667
- function throwStaticHandlerAbortedError(request, isRouteRequest, future) {
3668
- if (future.v7_throwAbortReason && request.signal.reason !== undefined) {
3669
- throw request.signal.reason;
3670
- }
3671
- let method = isRouteRequest ? "queryRoute" : "query";
3672
- throw new Error(method + "() call aborted: " + request.method + " " + request.url);
3673
- }
3674
3683
  function isSubmissionNavigation(opts) {
3675
3684
  return opts != null && ("formData" in opts && opts.formData != null || "body" in opts && opts.body !== undefined);
3676
3685
  }
@@ -3755,8 +3764,8 @@
3755
3764
  }
3756
3765
  let text = typeof opts.body === "string" ? opts.body : opts.body instanceof FormData || opts.body instanceof URLSearchParams ?
3757
3766
  // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#plain-text-form-data
3758
- Array.from(opts.body.entries()).reduce((acc, _ref3) => {
3759
- let [name, value] = _ref3;
3767
+ Array.from(opts.body.entries()).reduce((acc, _ref4) => {
3768
+ let [name, value] = _ref4;
3760
3769
  return "" + acc + name + "=" + value + "\n";
3761
3770
  }, "") : String(opts.body);
3762
3771
  return {
@@ -3867,24 +3876,18 @@
3867
3876
  let boundaryId = pendingError ? Object.keys(pendingError)[0] : undefined;
3868
3877
  let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
3869
3878
  let navigationMatches = boundaryMatches.filter((match, index) => {
3870
- let {
3871
- route
3872
- } = match;
3873
- if (route.lazy) {
3879
+ if (isInitialLoad) {
3880
+ // On initial hydration we don't do any shouldRevalidate stuff - we just
3881
+ // call the unhydrated loaders
3882
+ return isUnhydratedRoute(state, match.route);
3883
+ }
3884
+ if (match.route.lazy) {
3874
3885
  // We haven't loaded this route yet so we don't know if it's got a loader!
3875
3886
  return true;
3876
3887
  }
3877
- if (route.loader == null) {
3888
+ if (match.route.loader == null) {
3878
3889
  return false;
3879
3890
  }
3880
- if (isInitialLoad) {
3881
- if (route.loader.hydrate) {
3882
- return true;
3883
- }
3884
- return state.loaderData[route.id] === undefined && (
3885
- // Don't re-run if the loader ran and threw an error
3886
- !state.errors || state.errors[route.id] === undefined);
3887
- }
3888
3891
 
3889
3892
  // Always call the loader on new route instances and pending defer cancellations
3890
3893
  if (isNewLoader(state.loaderData, state.matches[index], match) || cancelledDeferredRoutes.some(id => id === match.route.id)) {
@@ -3986,6 +3989,20 @@
3986
3989
  });
3987
3990
  return [navigationMatches, revalidatingFetchers];
3988
3991
  }
3992
+
3993
+ // Is this route unhydrated (when v7_partialHydration=true) such that we need
3994
+ // to call it's loader on the initial router creation
3995
+ function isUnhydratedRoute(state, route) {
3996
+ if (!route.loader) {
3997
+ return false;
3998
+ }
3999
+ if (route.loader.hydrate) {
4000
+ return true;
4001
+ }
4002
+ return state.loaderData[route.id] === undefined && (!state.errors ||
4003
+ // Loader ran but errored - don't re-run
4004
+ state.errors[route.id] === undefined);
4005
+ }
3989
4006
  function isNewLoader(currentLoaderData, currentMatch, match) {
3990
4007
  let isNew =
3991
4008
  // [a] -> [a, b]
@@ -4027,7 +4044,7 @@
4027
4044
  */
4028
4045
  async function loadLazyRouteModule(route, mapRouteProperties, manifest) {
4029
4046
  if (!route.lazy) {
4030
- return;
4047
+ return route;
4031
4048
  }
4032
4049
  let lazyRoute = await route.lazy();
4033
4050
 
@@ -4035,7 +4052,7 @@
4035
4052
  // call then we can return - first lazy() to finish wins because the return
4036
4053
  // value of lazy is expected to be static
4037
4054
  if (!route.lazy) {
4038
- return;
4055
+ return route;
4039
4056
  }
4040
4057
  let routeToUpdate = manifest[route.id];
4041
4058
  invariant(routeToUpdate, "No route found in manifest");
@@ -4071,8 +4088,42 @@
4071
4088
  Object.assign(routeToUpdate, _extends({}, mapRouteProperties(routeToUpdate), {
4072
4089
  lazy: undefined
4073
4090
  }));
4091
+ return routeToUpdate;
4092
+ }
4093
+ function createCallLoaderOrAction(dataStrategy) {
4094
+ return async function (type, request, match, matches, manifest, mapRouteProperties, basename, v7_relativeSplatPath, opts) {
4095
+ if (opts === void 0) {
4096
+ opts = {};
4097
+ }
4098
+ let [result] = await dataStrategy({
4099
+ matches: [finesseToAgnosticDataStrategyMatch(match, mapRouteProperties, manifest)],
4100
+ request,
4101
+ type,
4102
+ defaultStrategy(match) {
4103
+ return callLoaderOrActionImplementation(type, request, match, matches, basename, v7_relativeSplatPath, opts);
4104
+ }
4105
+ });
4106
+ return result;
4107
+ };
4074
4108
  }
4075
- async function callLoaderOrAction(type, request, match, matches, manifest, mapRouteProperties, basename, v7_relativeSplatPath, opts) {
4109
+ function finesseToAgnosticDataStrategyMatch(match, mapRouteProperties, manifest) {
4110
+ let loadRoutePromise;
4111
+ if (match.route.lazy) {
4112
+ try {
4113
+ loadRoutePromise = loadLazyRouteModule(match.route, mapRouteProperties, manifest);
4114
+ } catch (error) {
4115
+ loadRoutePromise = Promise.reject(error);
4116
+ }
4117
+ }
4118
+ if (!loadRoutePromise) {
4119
+ loadRoutePromise = Promise.resolve(match.route);
4120
+ }
4121
+ loadRoutePromise.catch(() => {});
4122
+ return _extends({}, match, {
4123
+ route: Object.assign(loadRoutePromise, match.route)
4124
+ });
4125
+ }
4126
+ async function callLoaderOrActionImplementation(type, request, match, matches, basename, v7_relativeSplatPath, opts) {
4076
4127
  if (opts === void 0) {
4077
4128
  opts = {};
4078
4129
  }
@@ -4103,15 +4154,15 @@
4103
4154
  // route has a boundary that can handle the error
4104
4155
  runHandler(handler).catch(e => {
4105
4156
  handlerError = e;
4106
- }), loadLazyRouteModule(match.route, mapRouteProperties, manifest)]);
4157
+ }), match.route]);
4107
4158
  if (handlerError) {
4108
4159
  throw handlerError;
4109
4160
  }
4110
4161
  result = values[0];
4111
4162
  } else {
4112
4163
  // Load lazy route module, then run any returned handler
4113
- await loadLazyRouteModule(match.route, mapRouteProperties, manifest);
4114
- handler = match.route[type];
4164
+ let route = await match.route;
4165
+ handler = route[type];
4115
4166
  if (handler) {
4116
4167
  // Handler still run even if we got interrupted to maintain consistency
4117
4168
  // with un-abortable behavior of handler execution on non-lazy or
@@ -4162,7 +4213,7 @@
4162
4213
 
4163
4214
  // Support relative routing in internal redirects
4164
4215
  if (!ABSOLUTE_URL_REGEX.test(location)) {
4165
- location = normalizeTo(new URL(request.url), matches.slice(0, matches.indexOf(match) + 1), basename, true, location, v7_relativeSplatPath);
4216
+ location = normalizeTo(new URL(request.url), matches.slice(0, matches.findIndex(m => m.route.id === match.route.id) + 1), basename, true, location, v7_relativeSplatPath);
4166
4217
  } else if (!opts.isStaticRequest) {
4167
4218
  // Strip off the protocol+origin for same-origin + same-basename absolute
4168
4219
  // redirects. If this is a static request, we can let it go back to the
@@ -4208,11 +4259,7 @@
4208
4259
  // Check between word boundaries instead of startsWith() due to the last
4209
4260
  // paragraph of https://httpwg.org/specs/rfc9110.html#field.content-type
4210
4261
  if (contentType && /\bapplication\/json\b/.test(contentType)) {
4211
- if (result.body == null) {
4212
- data = null;
4213
- } else {
4214
- data = await result.json();
4215
- }
4262
+ data = await result.json();
4216
4263
  } else {
4217
4264
  data = await result.text();
4218
4265
  }
@@ -4814,6 +4861,7 @@
4814
4861
  exports.IDLE_BLOCKER = IDLE_BLOCKER;
4815
4862
  exports.IDLE_FETCHER = IDLE_FETCHER;
4816
4863
  exports.IDLE_NAVIGATION = IDLE_NAVIGATION;
4864
+ exports.ResultType = ResultType;
4817
4865
  exports.UNSAFE_DEFERRED_SYMBOL = UNSAFE_DEFERRED_SYMBOL;
4818
4866
  exports.UNSAFE_DeferredData = DeferredData;
4819
4867
  exports.UNSAFE_ErrorResponseImpl = ErrorResponseImpl;