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

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.0
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,67 @@
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
+ return restExploded.flatMap(subpath => {
701
+ // /one + / + :two/three -> /one/:two/three
702
+ let requiredExploded = subpath === "" ? required : required + "/" + subpath; // For optional segments, return the exploded path _without_ current segment first (`subpath`)
703
+ // and exploded path _with_ current segment later (`subpath`)
704
+ // This ensures that exploded paths are emitted in priority order
705
+ // `/one/three/:four` will come before `/one/three/:five`
706
+
707
+ return isOptional ? [subpath, requiredExploded] : [requiredExploded];
708
+ }).map(exploded => {
709
+ // for absolute paths, ensure `/` instead of empty segment
710
+ return path.startsWith("/") && exploded === "" ? "/" : exploded;
711
+ });
712
+ }
655
713
 
656
714
  function rankRouteBranches(branches) {
657
715
  branches.sort((a, b) => a.score !== b.score ? b.score - a.score // Higher score first
@@ -735,14 +793,24 @@
735
793
  */
736
794
 
737
795
 
738
- function generatePath(path, params) {
796
+ function generatePath(originalPath, params) {
739
797
  if (params === void 0) {
740
798
  params = {};
741
799
  }
742
800
 
743
- return path.replace(/:(\w+)/g, (_, key) => {
801
+ let path = originalPath;
802
+
803
+ if (path.endsWith("*") && path !== "*" && !path.endsWith("/*")) {
804
+ 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(/\*$/, "/*") + "\"."));
805
+ path = path.replace(/\*$/, "/*");
806
+ }
807
+
808
+ return path.replace(/^:(\w+)/g, (_, key) => {
744
809
  invariant(params[key] != null, "Missing \":" + key + "\" param");
745
810
  return params[key];
811
+ }).replace(/\/:(\w+)/g, (_, key) => {
812
+ invariant(params[key] != null, "Missing \":" + key + "\" param");
813
+ return "/" + params[key];
746
814
  }).replace(/(\/?)\*/, (_, prefix, __, str) => {
747
815
  const star = "*";
748
816
 
@@ -814,9 +882,9 @@
814
882
  let regexpSource = "^" + path.replace(/\/*\*?$/, "") // Ignore trailing / and /*, we'll handle it below
815
883
  .replace(/^\/*/, "/") // Make sure it has a leading /
816
884
  .replace(/[\\.*+^$?{}|()[\]]/g, "\\$&") // Escape special regex chars
817
- .replace(/:(\w+)/g, (_, paramName) => {
885
+ .replace(/\/:(\w+)/g, (_, paramName) => {
818
886
  paramNames.push(paramName);
819
- return "([^\\/]+)";
887
+ return "/([^\\/]+)";
820
888
  });
821
889
 
822
890
  if (path.endsWith("*")) {
@@ -1297,9 +1365,9 @@
1297
1365
  * A Router instance manages all navigation and data loading/mutations
1298
1366
  */
1299
1367
 
1300
- const validActionMethodsArr = ["post", "put", "patch", "delete"];
1301
- const validActionMethods = new Set(validActionMethodsArr);
1302
- const validRequestMethodsArr = ["get", ...validActionMethodsArr];
1368
+ const validMutationMethodsArr = ["post", "put", "patch", "delete"];
1369
+ const validMutationMethods = new Set(validMutationMethodsArr);
1370
+ const validRequestMethodsArr = ["get", ...validMutationMethodsArr];
1303
1371
  const validRequestMethods = new Set(validRequestMethodsArr);
1304
1372
  const redirectStatusCodes = new Set([301, 302, 303, 307, 308]);
1305
1373
  const redirectPreserveMethodStatusCodes = new Set([307, 308]);
@@ -1536,7 +1604,7 @@
1536
1604
  // without having to touch history
1537
1605
 
1538
1606
  location = _extends({}, location, init.history.encodeLocation(location));
1539
- let historyAction = (opts && opts.replace) === true || submission != null ? exports.Action.Replace : exports.Action.Push;
1607
+ let historyAction = (opts && opts.replace) === true || submission != null && isMutationMethod(submission.formMethod) ? exports.Action.Replace : exports.Action.Push;
1540
1608
  let preventScrollReset = opts && "preventScrollReset" in opts ? opts.preventScrollReset === true : undefined;
1541
1609
  return await startNavigation(historyAction, location, {
1542
1610
  submission,
@@ -1640,7 +1708,7 @@
1640
1708
  pendingError = {
1641
1709
  [findNearestBoundary(matches).route.id]: opts.pendingError
1642
1710
  };
1643
- } else if (opts && opts.submission) {
1711
+ } else if (opts && opts.submission && isMutationMethod(opts.submission.formMethod)) {
1644
1712
  // Call action if we received an action submission
1645
1713
  let actionOutput = await handleAction(request, location, opts.submission, matches, {
1646
1714
  replace: opts.replace
@@ -1767,14 +1835,15 @@
1767
1835
  let loadingNavigation = overrideNavigation;
1768
1836
 
1769
1837
  if (!loadingNavigation) {
1770
- let navigation = {
1838
+ let navigation = _extends({
1771
1839
  state: "loading",
1772
1840
  location,
1773
1841
  formMethod: undefined,
1774
1842
  formAction: undefined,
1775
1843
  formEncType: undefined,
1776
1844
  formData: undefined
1777
- };
1845
+ }, submission);
1846
+
1778
1847
  loadingNavigation = navigation;
1779
1848
  }
1780
1849
 
@@ -1909,7 +1978,7 @@
1909
1978
  } = normalizeNavigateOptions(href, opts, true);
1910
1979
  let match = getTargetMatch(matches, path);
1911
1980
 
1912
- if (submission) {
1981
+ if (submission && isMutationMethod(submission.formMethod)) {
1913
1982
  handleFetcherAction(key, routeId, path, match, matches, submission);
1914
1983
  return;
1915
1984
  } // Store off the match so we can call it's shouldRevalidate on subsequent
@@ -1917,7 +1986,7 @@
1917
1986
 
1918
1987
 
1919
1988
  fetchLoadMatches.set(key, [path, match, matches]);
1920
- handleFetcherLoader(key, routeId, path, match, matches);
1989
+ handleFetcherLoader(key, routeId, path, match, matches, submission);
1921
1990
  } // Call the action for the matched fetcher.submit(), and then handle redirects,
1922
1991
  // errors, and revalidation
1923
1992
 
@@ -2098,17 +2167,19 @@
2098
2167
  } // Call the matched loader for fetcher.load(), handling redirects, errors, etc.
2099
2168
 
2100
2169
 
2101
- async function handleFetcherLoader(key, routeId, path, match, matches) {
2170
+ async function handleFetcherLoader(key, routeId, path, match, matches, submission) {
2102
2171
  let existingFetcher = state.fetchers.get(key); // Put this fetcher into it's loading state
2103
2172
 
2104
- let loadingFetcher = {
2173
+ let loadingFetcher = _extends({
2105
2174
  state: "loading",
2106
2175
  formMethod: undefined,
2107
2176
  formAction: undefined,
2108
2177
  formEncType: undefined,
2109
- formData: undefined,
2178
+ formData: undefined
2179
+ }, submission, {
2110
2180
  data: existingFetcher && existingFetcher.data
2111
- };
2181
+ });
2182
+
2112
2183
  state.fetchers.set(key, loadingFetcher);
2113
2184
  updateState({
2114
2185
  fetchers: new Map(state.fetchers)
@@ -2228,10 +2299,10 @@
2228
2299
  formEncType,
2229
2300
  formData
2230
2301
  } = 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
2302
+ // re-submit the GET/POST/PUT/PATCH/DELETE as a submission navigation to the
2232
2303
  // redirected location
2233
2304
 
2234
- if (redirectPreserveMethodStatusCodes.has(redirect.status) && formMethod && isSubmissionMethod(formMethod) && formEncType && formData) {
2305
+ if (redirectPreserveMethodStatusCodes.has(redirect.status) && formMethod && isMutationMethod(formMethod) && formEncType && formData) {
2235
2306
  await startNavigation(redirectHistoryAction, redirectLocation, {
2236
2307
  submission: {
2237
2308
  formMethod,
@@ -2642,7 +2713,7 @@
2642
2713
  invariant(request.signal, "query()/queryRoute() requests must contain an AbortController signal");
2643
2714
 
2644
2715
  try {
2645
- if (isSubmissionMethod(request.method.toLowerCase())) {
2716
+ if (isMutationMethod(request.method.toLowerCase())) {
2646
2717
  let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, routeMatch != null);
2647
2718
  return result;
2648
2719
  }
@@ -2759,6 +2830,8 @@
2759
2830
 
2760
2831
 
2761
2832
  let loaderRequest = new Request(request.url, {
2833
+ headers: request.headers,
2834
+ redirect: request.redirect,
2762
2835
  signal: request.signal
2763
2836
  });
2764
2837
  let context = await loadRouteData(loaderRequest, matches, requestContext);
@@ -2874,16 +2947,22 @@
2874
2947
  } // Create a Submission on non-GET navigations
2875
2948
 
2876
2949
 
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
- }
2950
+ let submission;
2951
+
2952
+ if (opts.formData) {
2953
+ submission = {
2954
+ formMethod: opts.formMethod || "get",
2955
+ formAction: stripHashFromPath(path),
2956
+ formEncType: opts && opts.formEncType || "application/x-www-form-urlencoded",
2957
+ formData: opts.formData
2886
2958
  };
2959
+
2960
+ if (isMutationMethod(submission.formMethod)) {
2961
+ return {
2962
+ path,
2963
+ submission
2964
+ };
2965
+ }
2887
2966
  } // Flatten submission onto URLSearchParams for GET submissions
2888
2967
 
2889
2968
 
@@ -2907,7 +2986,8 @@
2907
2986
  }
2908
2987
 
2909
2988
  return {
2910
- path: createPath(parsedPath)
2989
+ path: createPath(parsedPath),
2990
+ submission
2911
2991
  };
2912
2992
  } // Filter out all routes below any caught error as they aren't going to
2913
2993
  // render so we don't need to load them
@@ -3151,7 +3231,7 @@
3151
3231
  signal
3152
3232
  };
3153
3233
 
3154
- if (submission) {
3234
+ if (submission && isMutationMethod(submission.formMethod)) {
3155
3235
  let {
3156
3236
  formMethod,
3157
3237
  formEncType,
@@ -3201,11 +3281,14 @@
3201
3281
  pendingError = undefined;
3202
3282
  }
3203
3283
 
3204
- errors = Object.assign(errors || {}, {
3205
- [boundaryMatch.route.id]: error
3206
- }); // Once we find our first (highest) error, we set the status code and
3284
+ errors = errors || {}; // Prefer higher error values if lower errors bubble to the same boundary
3285
+
3286
+ if (errors[boundaryMatch.route.id] == null) {
3287
+ errors[boundaryMatch.route.id] = error;
3288
+ } // Once we find our first (highest) error, we set the status code and
3207
3289
  // prevent deeper status codes from overriding
3208
3290
 
3291
+
3209
3292
  if (!foundError) {
3210
3293
  foundError = true;
3211
3294
  statusCode = isRouteErrorResponse(result.error) ? result.error.status : 500;
@@ -3421,8 +3504,8 @@
3421
3504
  return validRequestMethods.has(method);
3422
3505
  }
3423
3506
 
3424
- function isSubmissionMethod(method) {
3425
- return validActionMethods.has(method);
3507
+ function isMutationMethod(method) {
3508
+ return validMutationMethods.has(method);
3426
3509
  }
3427
3510
 
3428
3511
  async function resolveDeferredResults(currentMatches, matchesToLoad, results, signal, isFetcher, currentLoaderData) {