@remix-run/router 1.0.5-pre.2 → 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.
package/dist/router.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @remix-run/router v1.0.5-pre.2
2
+ * @remix-run/router v1.1.0-pre.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -573,9 +573,9 @@ function flattenRoutes(routes, branches, parentsMeta, parentPath) {
573
573
  parentPath = "";
574
574
  }
575
575
 
576
- routes.forEach((route, index) => {
576
+ let flattenRoute = (route, index, relativePath) => {
577
577
  let meta = {
578
- relativePath: route.path || "",
578
+ relativePath: relativePath === undefined ? route.path || "" : relativePath,
579
579
  caseSensitive: route.caseSensitive === true,
580
580
  childrenIndex: index,
581
581
  route
@@ -609,9 +609,67 @@ function flattenRoutes(routes, branches, parentsMeta, parentPath) {
609
609
  score: computeScore(path, route.index),
610
610
  routesMeta
611
611
  });
612
+ };
613
+
614
+ routes.forEach((route, index) => {
615
+ var _route$path;
616
+
617
+ // coarse-grain check for optional params
618
+ if (route.path === "" || !((_route$path = route.path) != null && _route$path.includes("?"))) {
619
+ flattenRoute(route, index);
620
+ } else {
621
+ for (let exploded of explodeOptionalSegments(route.path)) {
622
+ flattenRoute(route, index, exploded);
623
+ }
624
+ }
612
625
  });
613
626
  return branches;
614
627
  }
628
+ /**
629
+ * Computes all combinations of optional path segments for a given path,
630
+ * excluding combinations that are ambiguous and of lower priority.
631
+ *
632
+ * For example, `/one/:two?/three/:four?/:five?` explodes to:
633
+ * - `/one/three`
634
+ * - `/one/:two/three`
635
+ * - `/one/three/:four`
636
+ * - `/one/three/:five`
637
+ * - `/one/:two/three/:four`
638
+ * - `/one/:two/three/:five`
639
+ * - `/one/three/:four/:five`
640
+ * - `/one/:two/three/:four/:five`
641
+ */
642
+
643
+
644
+ function explodeOptionalSegments(path) {
645
+ let segments = path.split("/");
646
+ if (segments.length === 0) return [];
647
+ let [first, ...rest] = segments; // Optional path segments are denoted by a trailing `?`
648
+
649
+ let isOptional = first.endsWith("?"); // Compute the corresponding required segment: `foo?` -> `foo`
650
+
651
+ let required = first.replace(/\?$/, "");
652
+
653
+ if (rest.length === 0) {
654
+ // Intepret empty string as omitting an optional segment
655
+ // `["one", "", "three"]` corresponds to omitting `:two` from `/one/:two?/three` -> `/one/three`
656
+ return isOptional ? ["", required] : [required];
657
+ }
658
+
659
+ let restExploded = explodeOptionalSegments(rest.join("/"));
660
+ return restExploded.flatMap(subpath => {
661
+ // /one + / + :two/three -> /one/:two/three
662
+ let requiredExploded = subpath === "" ? required : required + "/" + subpath; // For optional segments, return the exploded path _without_ current segment first (`subpath`)
663
+ // and exploded path _with_ current segment later (`subpath`)
664
+ // This ensures that exploded paths are emitted in priority order
665
+ // `/one/three/:four` will come before `/one/three/:five`
666
+
667
+ return isOptional ? [subpath, requiredExploded] : [requiredExploded];
668
+ }).map(exploded => {
669
+ // for absolute paths, ensure `/` instead of empty segment
670
+ return path.startsWith("/") && exploded === "" ? "/" : exploded;
671
+ });
672
+ }
615
673
 
616
674
  function rankRouteBranches(branches) {
617
675
  branches.sort((a, b) => a.score !== b.score ? b.score - a.score // Higher score first
@@ -695,14 +753,24 @@ function matchRouteBranch(branch, pathname) {
695
753
  */
696
754
 
697
755
 
698
- function generatePath(path, params) {
756
+ function generatePath(originalPath, params) {
699
757
  if (params === void 0) {
700
758
  params = {};
701
759
  }
702
760
 
703
- return path.replace(/:(\w+)/g, (_, key) => {
761
+ let path = originalPath;
762
+
763
+ if (path.endsWith("*") && path !== "*" && !path.endsWith("/*")) {
764
+ 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(/\*$/, "/*") + "\"."));
765
+ path = path.replace(/\*$/, "/*");
766
+ }
767
+
768
+ return path.replace(/^:(\w+)/g, (_, key) => {
704
769
  invariant(params[key] != null, "Missing \":" + key + "\" param");
705
770
  return params[key];
771
+ }).replace(/\/:(\w+)/g, (_, key) => {
772
+ invariant(params[key] != null, "Missing \":" + key + "\" param");
773
+ return "/" + params[key];
706
774
  }).replace(/(\/?)\*/, (_, prefix, __, str) => {
707
775
  const star = "*";
708
776
 
@@ -771,9 +839,9 @@ function compilePath(path, caseSensitive, end) {
771
839
  let regexpSource = "^" + path.replace(/\/*\*?$/, "") // Ignore trailing / and /*, we'll handle it below
772
840
  .replace(/^\/*/, "/") // Make sure it has a leading /
773
841
  .replace(/[\\.*+^$?{}|()[\]]/g, "\\$&") // Escape special regex chars
774
- .replace(/:(\w+)/g, (_, paramName) => {
842
+ .replace(/\/:(\w+)/g, (_, paramName) => {
775
843
  paramNames.push(paramName);
776
- return "([^\\/]+)";
844
+ return "/([^\\/]+)";
777
845
  });
778
846
 
779
847
  if (path.endsWith("*")) {
@@ -1247,9 +1315,9 @@ function isRouteErrorResponse(e) {
1247
1315
  return e instanceof ErrorResponse;
1248
1316
  }
1249
1317
 
1250
- const validActionMethodsArr = ["post", "put", "patch", "delete"];
1251
- const validActionMethods = new Set(validActionMethodsArr);
1252
- const validRequestMethodsArr = ["get", ...validActionMethodsArr];
1318
+ const validMutationMethodsArr = ["post", "put", "patch", "delete"];
1319
+ const validMutationMethods = new Set(validMutationMethodsArr);
1320
+ const validRequestMethodsArr = ["get", ...validMutationMethodsArr];
1253
1321
  const validRequestMethods = new Set(validRequestMethodsArr);
1254
1322
  const redirectStatusCodes = new Set([301, 302, 303, 307, 308]);
1255
1323
  const redirectPreserveMethodStatusCodes = new Set([307, 308]);
@@ -1486,7 +1554,7 @@ function createRouter(init) {
1486
1554
  // without having to touch history
1487
1555
 
1488
1556
  location = _extends({}, location, init.history.encodeLocation(location));
1489
- let historyAction = (opts && opts.replace) === true || submission != null ? Action.Replace : Action.Push;
1557
+ let historyAction = (opts && opts.replace) === true || submission != null && isMutationMethod(submission.formMethod) ? Action.Replace : Action.Push;
1490
1558
  let preventScrollReset = opts && "preventScrollReset" in opts ? opts.preventScrollReset === true : undefined;
1491
1559
  return await startNavigation(historyAction, location, {
1492
1560
  submission,
@@ -1590,7 +1658,7 @@ function createRouter(init) {
1590
1658
  pendingError = {
1591
1659
  [findNearestBoundary(matches).route.id]: opts.pendingError
1592
1660
  };
1593
- } else if (opts && opts.submission) {
1661
+ } else if (opts && opts.submission && isMutationMethod(opts.submission.formMethod)) {
1594
1662
  // Call action if we received an action submission
1595
1663
  let actionOutput = await handleAction(request, location, opts.submission, matches, {
1596
1664
  replace: opts.replace
@@ -1717,14 +1785,15 @@ function createRouter(init) {
1717
1785
  let loadingNavigation = overrideNavigation;
1718
1786
 
1719
1787
  if (!loadingNavigation) {
1720
- let navigation = {
1788
+ let navigation = _extends({
1721
1789
  state: "loading",
1722
1790
  location,
1723
1791
  formMethod: undefined,
1724
1792
  formAction: undefined,
1725
1793
  formEncType: undefined,
1726
1794
  formData: undefined
1727
- };
1795
+ }, submission);
1796
+
1728
1797
  loadingNavigation = navigation;
1729
1798
  }
1730
1799
 
@@ -1859,7 +1928,7 @@ function createRouter(init) {
1859
1928
  } = normalizeNavigateOptions(href, opts, true);
1860
1929
  let match = getTargetMatch(matches, path);
1861
1930
 
1862
- if (submission) {
1931
+ if (submission && isMutationMethod(submission.formMethod)) {
1863
1932
  handleFetcherAction(key, routeId, path, match, matches, submission);
1864
1933
  return;
1865
1934
  } // Store off the match so we can call it's shouldRevalidate on subsequent
@@ -1867,7 +1936,7 @@ function createRouter(init) {
1867
1936
 
1868
1937
 
1869
1938
  fetchLoadMatches.set(key, [path, match, matches]);
1870
- handleFetcherLoader(key, routeId, path, match, matches);
1939
+ handleFetcherLoader(key, routeId, path, match, matches, submission);
1871
1940
  } // Call the action for the matched fetcher.submit(), and then handle redirects,
1872
1941
  // errors, and revalidation
1873
1942
 
@@ -2048,17 +2117,19 @@ function createRouter(init) {
2048
2117
  } // Call the matched loader for fetcher.load(), handling redirects, errors, etc.
2049
2118
 
2050
2119
 
2051
- async function handleFetcherLoader(key, routeId, path, match, matches) {
2120
+ async function handleFetcherLoader(key, routeId, path, match, matches, submission) {
2052
2121
  let existingFetcher = state.fetchers.get(key); // Put this fetcher into it's loading state
2053
2122
 
2054
- let loadingFetcher = {
2123
+ let loadingFetcher = _extends({
2055
2124
  state: "loading",
2056
2125
  formMethod: undefined,
2057
2126
  formAction: undefined,
2058
2127
  formEncType: undefined,
2059
- formData: undefined,
2128
+ formData: undefined
2129
+ }, submission, {
2060
2130
  data: existingFetcher && existingFetcher.data
2061
- };
2131
+ });
2132
+
2062
2133
  state.fetchers.set(key, loadingFetcher);
2063
2134
  updateState({
2064
2135
  fetchers: new Map(state.fetchers)
@@ -2178,10 +2249,10 @@ function createRouter(init) {
2178
2249
  formEncType,
2179
2250
  formData
2180
2251
  } = state.navigation; // If this was a 307/308 submission we want to preserve the HTTP method and
2181
- // re-submit the POST/PUT/PATCH/DELETE as a submission navigation to the
2252
+ // re-submit the GET/POST/PUT/PATCH/DELETE as a submission navigation to the
2182
2253
  // redirected location
2183
2254
 
2184
- if (redirectPreserveMethodStatusCodes.has(redirect.status) && formMethod && isSubmissionMethod(formMethod) && formEncType && formData) {
2255
+ if (redirectPreserveMethodStatusCodes.has(redirect.status) && formMethod && isMutationMethod(formMethod) && formEncType && formData) {
2185
2256
  await startNavigation(redirectHistoryAction, redirectLocation, {
2186
2257
  submission: {
2187
2258
  formMethod,
@@ -2592,7 +2663,7 @@ function unstable_createStaticHandler(routes, opts) {
2592
2663
  invariant(request.signal, "query()/queryRoute() requests must contain an AbortController signal");
2593
2664
 
2594
2665
  try {
2595
- if (isSubmissionMethod(request.method.toLowerCase())) {
2666
+ if (isMutationMethod(request.method.toLowerCase())) {
2596
2667
  let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, routeMatch != null);
2597
2668
  return result;
2598
2669
  }
@@ -2709,6 +2780,8 @@ function unstable_createStaticHandler(routes, opts) {
2709
2780
 
2710
2781
 
2711
2782
  let loaderRequest = new Request(request.url, {
2783
+ headers: request.headers,
2784
+ redirect: request.redirect,
2712
2785
  signal: request.signal
2713
2786
  });
2714
2787
  let context = await loadRouteData(loaderRequest, matches, requestContext);
@@ -2824,16 +2897,22 @@ function normalizeNavigateOptions(to, opts, isFetcher) {
2824
2897
  } // Create a Submission on non-GET navigations
2825
2898
 
2826
2899
 
2827
- if (opts.formMethod && isSubmissionMethod(opts.formMethod)) {
2828
- return {
2829
- path,
2830
- submission: {
2831
- formMethod: opts.formMethod,
2832
- formAction: stripHashFromPath(path),
2833
- formEncType: opts && opts.formEncType || "application/x-www-form-urlencoded",
2834
- formData: opts.formData
2835
- }
2900
+ let submission;
2901
+
2902
+ if (opts.formData) {
2903
+ submission = {
2904
+ formMethod: opts.formMethod || "get",
2905
+ formAction: stripHashFromPath(path),
2906
+ formEncType: opts && opts.formEncType || "application/x-www-form-urlencoded",
2907
+ formData: opts.formData
2836
2908
  };
2909
+
2910
+ if (isMutationMethod(submission.formMethod)) {
2911
+ return {
2912
+ path,
2913
+ submission
2914
+ };
2915
+ }
2837
2916
  } // Flatten submission onto URLSearchParams for GET submissions
2838
2917
 
2839
2918
 
@@ -2857,7 +2936,8 @@ function normalizeNavigateOptions(to, opts, isFetcher) {
2857
2936
  }
2858
2937
 
2859
2938
  return {
2860
- path: createPath(parsedPath)
2939
+ path: createPath(parsedPath),
2940
+ submission
2861
2941
  };
2862
2942
  } // Filter out all routes below any caught error as they aren't going to
2863
2943
  // render so we don't need to load them
@@ -3101,7 +3181,7 @@ function createClientSideRequest(location, signal, submission) {
3101
3181
  signal
3102
3182
  };
3103
3183
 
3104
- if (submission) {
3184
+ if (submission && isMutationMethod(submission.formMethod)) {
3105
3185
  let {
3106
3186
  formMethod,
3107
3187
  formEncType,
@@ -3151,11 +3231,14 @@ function processRouteLoaderData(matches, matchesToLoad, results, pendingError, a
3151
3231
  pendingError = undefined;
3152
3232
  }
3153
3233
 
3154
- errors = Object.assign(errors || {}, {
3155
- [boundaryMatch.route.id]: error
3156
- }); // Once we find our first (highest) error, we set the status code and
3234
+ errors = errors || {}; // Prefer higher error values if lower errors bubble to the same boundary
3235
+
3236
+ if (errors[boundaryMatch.route.id] == null) {
3237
+ errors[boundaryMatch.route.id] = error;
3238
+ } // Once we find our first (highest) error, we set the status code and
3157
3239
  // prevent deeper status codes from overriding
3158
3240
 
3241
+
3159
3242
  if (!foundError) {
3160
3243
  foundError = true;
3161
3244
  statusCode = isRouteErrorResponse(result.error) ? result.error.status : 500;
@@ -3371,8 +3454,8 @@ function isValidMethod(method) {
3371
3454
  return validRequestMethods.has(method);
3372
3455
  }
3373
3456
 
3374
- function isSubmissionMethod(method) {
3375
- return validActionMethods.has(method);
3457
+ function isMutationMethod(method) {
3458
+ return validMutationMethods.has(method);
3376
3459
  }
3377
3460
 
3378
3461
  async function resolveDeferredResults(currentMatches, matchesToLoad, results, signal, isFetcher, currentLoaderData) {