@remix-run/router 1.0.5 → 1.1.0-pre.1

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.0.5
2
+ * @remix-run/router v1.1.0-pre.1
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -613,9 +613,9 @@
613
613
  parentPath = "";
614
614
  }
615
615
 
616
- routes.forEach((route, index) => {
616
+ let flattenRoute = (route, index, relativePath) => {
617
617
  let meta = {
618
- relativePath: route.path || "",
618
+ relativePath: relativePath === undefined ? route.path || "" : relativePath,
619
619
  caseSensitive: route.caseSensitive === true,
620
620
  childrenIndex: index,
621
621
  route
@@ -649,9 +649,71 @@
649
649
  score: computeScore(path, route.index),
650
650
  routesMeta
651
651
  });
652
+ };
653
+
654
+ routes.forEach((route, index) => {
655
+ var _route$path;
656
+
657
+ // coarse-grain check for optional params
658
+ if (route.path === "" || !((_route$path = route.path) != null && _route$path.includes("?"))) {
659
+ flattenRoute(route, index);
660
+ } else {
661
+ for (let exploded of explodeOptionalSegments(route.path)) {
662
+ flattenRoute(route, index, exploded);
663
+ }
664
+ }
652
665
  });
653
666
  return branches;
654
667
  }
668
+ /**
669
+ * Computes all combinations of optional path segments for a given path,
670
+ * excluding combinations that are ambiguous and of lower priority.
671
+ *
672
+ * For example, `/one/:two?/three/:four?/:five?` explodes to:
673
+ * - `/one/three`
674
+ * - `/one/:two/three`
675
+ * - `/one/three/:four`
676
+ * - `/one/three/:five`
677
+ * - `/one/:two/three/:four`
678
+ * - `/one/:two/three/:five`
679
+ * - `/one/three/:four/:five`
680
+ * - `/one/:two/three/:four/:five`
681
+ */
682
+
683
+
684
+ function explodeOptionalSegments(path) {
685
+ let segments = path.split("/");
686
+ if (segments.length === 0) return [];
687
+ let [first, ...rest] = segments; // Optional path segments are denoted by a trailing `?`
688
+
689
+ let isOptional = first.endsWith("?"); // Compute the corresponding required segment: `foo?` -> `foo`
690
+
691
+ let required = first.replace(/\?$/, "");
692
+
693
+ if (rest.length === 0) {
694
+ // Intepret empty string as omitting an optional segment
695
+ // `["one", "", "three"]` corresponds to omitting `:two` from `/one/:two?/three` -> `/one/three`
696
+ return isOptional ? [required, ""] : [required];
697
+ }
698
+
699
+ let restExploded = explodeOptionalSegments(rest.join("/"));
700
+ let result = []; // All child paths with the prefix. Do this for all children before the
701
+ // optional version for all children so we get consistent ordering where the
702
+ // parent optional aspect is preferred as required. Otherwise, we can get
703
+ // child sections interspersed where deeper optional segments are higher than
704
+ // parent optional segments, where for example, /:two would explodes _earlier_
705
+ // then /:one. By always including the parent as required _for all children_
706
+ // first, we avoid this issue
707
+
708
+ result.push(...restExploded.map(subpath => subpath === "" ? required : [required, subpath].join("/"))); // Then if this is an optional value, add all child versions without
709
+
710
+ if (isOptional) {
711
+ result.push(...restExploded);
712
+ } // for absolute paths, ensure `/` instead of empty segment
713
+
714
+
715
+ return result.map(exploded => path.startsWith("/") && exploded === "" ? "/" : exploded);
716
+ }
655
717
 
656
718
  function rankRouteBranches(branches) {
657
719
  branches.sort((a, b) => a.score !== b.score ? b.score - a.score // Higher score first
@@ -735,14 +797,24 @@
735
797
  */
736
798
 
737
799
 
738
- function generatePath(path, params) {
800
+ function generatePath(originalPath, params) {
739
801
  if (params === void 0) {
740
802
  params = {};
741
803
  }
742
804
 
743
- return path.replace(/:(\w+)/g, (_, key) => {
805
+ let path = originalPath;
806
+
807
+ if (path.endsWith("*") && path !== "*" && !path.endsWith("/*")) {
808
+ warning(false, "Route path \"" + path + "\" will be treated as if it were " + ("\"" + path.replace(/\*$/, "/*") + "\" because the `*` character must ") + "always follow a `/` in the pattern. To get rid of this warning, " + ("please change the route path to \"" + path.replace(/\*$/, "/*") + "\"."));
809
+ path = path.replace(/\*$/, "/*");
810
+ }
811
+
812
+ return path.replace(/^:(\w+)/g, (_, key) => {
744
813
  invariant(params[key] != null, "Missing \":" + key + "\" param");
745
814
  return params[key];
815
+ }).replace(/\/:(\w+)/g, (_, key) => {
816
+ invariant(params[key] != null, "Missing \":" + key + "\" param");
817
+ return "/" + params[key];
746
818
  }).replace(/(\/?)\*/, (_, prefix, __, str) => {
747
819
  const star = "*";
748
820
 
@@ -814,9 +886,9 @@
814
886
  let regexpSource = "^" + path.replace(/\/*\*?$/, "") // Ignore trailing / and /*, we'll handle it below
815
887
  .replace(/^\/*/, "/") // Make sure it has a leading /
816
888
  .replace(/[\\.*+^$?{}|()[\]]/g, "\\$&") // Escape special regex chars
817
- .replace(/:(\w+)/g, (_, paramName) => {
889
+ .replace(/\/:(\w+)/g, (_, paramName) => {
818
890
  paramNames.push(paramName);
819
- return "([^\\/]+)";
891
+ return "/([^\\/]+)";
820
892
  });
821
893
 
822
894
  if (path.endsWith("*")) {
@@ -1297,9 +1369,9 @@
1297
1369
  * A Router instance manages all navigation and data loading/mutations
1298
1370
  */
1299
1371
 
1300
- const validActionMethodsArr = ["post", "put", "patch", "delete"];
1301
- const validActionMethods = new Set(validActionMethodsArr);
1302
- const validRequestMethodsArr = ["get", ...validActionMethodsArr];
1372
+ const validMutationMethodsArr = ["post", "put", "patch", "delete"];
1373
+ const validMutationMethods = new Set(validMutationMethodsArr);
1374
+ const validRequestMethodsArr = ["get", ...validMutationMethodsArr];
1303
1375
  const validRequestMethods = new Set(validRequestMethodsArr);
1304
1376
  const redirectStatusCodes = new Set([301, 302, 303, 307, 308]);
1305
1377
  const redirectPreserveMethodStatusCodes = new Set([307, 308]);
@@ -1536,7 +1608,7 @@
1536
1608
  // without having to touch history
1537
1609
 
1538
1610
  location = _extends({}, location, init.history.encodeLocation(location));
1539
- let historyAction = (opts && opts.replace) === true || submission != null ? exports.Action.Replace : exports.Action.Push;
1611
+ let historyAction = (opts && opts.replace) === true || submission != null && isMutationMethod(submission.formMethod) ? exports.Action.Replace : exports.Action.Push;
1540
1612
  let preventScrollReset = opts && "preventScrollReset" in opts ? opts.preventScrollReset === true : undefined;
1541
1613
  return await startNavigation(historyAction, location, {
1542
1614
  submission,
@@ -1640,7 +1712,7 @@
1640
1712
  pendingError = {
1641
1713
  [findNearestBoundary(matches).route.id]: opts.pendingError
1642
1714
  };
1643
- } else if (opts && opts.submission) {
1715
+ } else if (opts && opts.submission && isMutationMethod(opts.submission.formMethod)) {
1644
1716
  // Call action if we received an action submission
1645
1717
  let actionOutput = await handleAction(request, location, opts.submission, matches, {
1646
1718
  replace: opts.replace
@@ -1767,14 +1839,15 @@
1767
1839
  let loadingNavigation = overrideNavigation;
1768
1840
 
1769
1841
  if (!loadingNavigation) {
1770
- let navigation = {
1842
+ let navigation = _extends({
1771
1843
  state: "loading",
1772
1844
  location,
1773
1845
  formMethod: undefined,
1774
1846
  formAction: undefined,
1775
1847
  formEncType: undefined,
1776
1848
  formData: undefined
1777
- };
1849
+ }, submission);
1850
+
1778
1851
  loadingNavigation = navigation;
1779
1852
  }
1780
1853
 
@@ -1909,7 +1982,7 @@
1909
1982
  } = normalizeNavigateOptions(href, opts, true);
1910
1983
  let match = getTargetMatch(matches, path);
1911
1984
 
1912
- if (submission) {
1985
+ if (submission && isMutationMethod(submission.formMethod)) {
1913
1986
  handleFetcherAction(key, routeId, path, match, matches, submission);
1914
1987
  return;
1915
1988
  } // Store off the match so we can call it's shouldRevalidate on subsequent
@@ -1917,7 +1990,7 @@
1917
1990
 
1918
1991
 
1919
1992
  fetchLoadMatches.set(key, [path, match, matches]);
1920
- handleFetcherLoader(key, routeId, path, match, matches);
1993
+ handleFetcherLoader(key, routeId, path, match, matches, submission);
1921
1994
  } // Call the action for the matched fetcher.submit(), and then handle redirects,
1922
1995
  // errors, and revalidation
1923
1996
 
@@ -2098,17 +2171,19 @@
2098
2171
  } // Call the matched loader for fetcher.load(), handling redirects, errors, etc.
2099
2172
 
2100
2173
 
2101
- async function handleFetcherLoader(key, routeId, path, match, matches) {
2174
+ async function handleFetcherLoader(key, routeId, path, match, matches, submission) {
2102
2175
  let existingFetcher = state.fetchers.get(key); // Put this fetcher into it's loading state
2103
2176
 
2104
- let loadingFetcher = {
2177
+ let loadingFetcher = _extends({
2105
2178
  state: "loading",
2106
2179
  formMethod: undefined,
2107
2180
  formAction: undefined,
2108
2181
  formEncType: undefined,
2109
- formData: undefined,
2182
+ formData: undefined
2183
+ }, submission, {
2110
2184
  data: existingFetcher && existingFetcher.data
2111
- };
2185
+ });
2186
+
2112
2187
  state.fetchers.set(key, loadingFetcher);
2113
2188
  updateState({
2114
2189
  fetchers: new Map(state.fetchers)
@@ -2228,10 +2303,10 @@
2228
2303
  formEncType,
2229
2304
  formData
2230
2305
  } = state.navigation; // If this was a 307/308 submission we want to preserve the HTTP method and
2231
- // re-submit the POST/PUT/PATCH/DELETE as a submission navigation to the
2306
+ // re-submit the GET/POST/PUT/PATCH/DELETE as a submission navigation to the
2232
2307
  // redirected location
2233
2308
 
2234
- if (redirectPreserveMethodStatusCodes.has(redirect.status) && formMethod && isSubmissionMethod(formMethod) && formEncType && formData) {
2309
+ if (redirectPreserveMethodStatusCodes.has(redirect.status) && formMethod && isMutationMethod(formMethod) && formEncType && formData) {
2235
2310
  await startNavigation(redirectHistoryAction, redirectLocation, {
2236
2311
  submission: {
2237
2312
  formMethod,
@@ -2642,7 +2717,7 @@
2642
2717
  invariant(request.signal, "query()/queryRoute() requests must contain an AbortController signal");
2643
2718
 
2644
2719
  try {
2645
- if (isSubmissionMethod(request.method.toLowerCase())) {
2720
+ if (isMutationMethod(request.method.toLowerCase())) {
2646
2721
  let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, routeMatch != null);
2647
2722
  return result;
2648
2723
  }
@@ -2759,6 +2834,8 @@
2759
2834
 
2760
2835
 
2761
2836
  let loaderRequest = new Request(request.url, {
2837
+ headers: request.headers,
2838
+ redirect: request.redirect,
2762
2839
  signal: request.signal
2763
2840
  });
2764
2841
  let context = await loadRouteData(loaderRequest, matches, requestContext);
@@ -2874,16 +2951,22 @@
2874
2951
  } // Create a Submission on non-GET navigations
2875
2952
 
2876
2953
 
2877
- if (opts.formMethod && isSubmissionMethod(opts.formMethod)) {
2878
- return {
2879
- path,
2880
- submission: {
2881
- formMethod: opts.formMethod,
2882
- formAction: stripHashFromPath(path),
2883
- formEncType: opts && opts.formEncType || "application/x-www-form-urlencoded",
2884
- formData: opts.formData
2885
- }
2954
+ let submission;
2955
+
2956
+ if (opts.formData) {
2957
+ submission = {
2958
+ formMethod: opts.formMethod || "get",
2959
+ formAction: stripHashFromPath(path),
2960
+ formEncType: opts && opts.formEncType || "application/x-www-form-urlencoded",
2961
+ formData: opts.formData
2886
2962
  };
2963
+
2964
+ if (isMutationMethod(submission.formMethod)) {
2965
+ return {
2966
+ path,
2967
+ submission
2968
+ };
2969
+ }
2887
2970
  } // Flatten submission onto URLSearchParams for GET submissions
2888
2971
 
2889
2972
 
@@ -2907,7 +2990,8 @@
2907
2990
  }
2908
2991
 
2909
2992
  return {
2910
- path: createPath(parsedPath)
2993
+ path: createPath(parsedPath),
2994
+ submission
2911
2995
  };
2912
2996
  } // Filter out all routes below any caught error as they aren't going to
2913
2997
  // render so we don't need to load them
@@ -3151,7 +3235,7 @@
3151
3235
  signal
3152
3236
  };
3153
3237
 
3154
- if (submission) {
3238
+ if (submission && isMutationMethod(submission.formMethod)) {
3155
3239
  let {
3156
3240
  formMethod,
3157
3241
  formEncType,
@@ -3201,11 +3285,14 @@
3201
3285
  pendingError = undefined;
3202
3286
  }
3203
3287
 
3204
- errors = Object.assign(errors || {}, {
3205
- [boundaryMatch.route.id]: error
3206
- }); // Once we find our first (highest) error, we set the status code and
3288
+ errors = errors || {}; // Prefer higher error values if lower errors bubble to the same boundary
3289
+
3290
+ if (errors[boundaryMatch.route.id] == null) {
3291
+ errors[boundaryMatch.route.id] = error;
3292
+ } // Once we find our first (highest) error, we set the status code and
3207
3293
  // prevent deeper status codes from overriding
3208
3294
 
3295
+
3209
3296
  if (!foundError) {
3210
3297
  foundError = true;
3211
3298
  statusCode = isRouteErrorResponse(result.error) ? result.error.status : 500;
@@ -3421,8 +3508,8 @@
3421
3508
  return validRequestMethods.has(method);
3422
3509
  }
3423
3510
 
3424
- function isSubmissionMethod(method) {
3425
- return validActionMethods.has(method);
3511
+ function isMutationMethod(method) {
3512
+ return validMutationMethods.has(method);
3426
3513
  }
3427
3514
 
3428
3515
  async function resolveDeferredResults(currentMatches, matchesToLoad, results, signal, isFetcher, currentLoaderData) {