@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/CHANGELOG.md +48 -10
- package/dist/router.cjs.js +122 -39
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.js +122 -39
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +122 -39
- package/dist/router.umd.js.map +1 -1
- package/dist/router.umd.min.js +2 -2
- package/dist/router.umd.min.js.map +1 -1
- package/dist/utils.d.ts +5 -5
- package/package.json +1 -1
- package/router.ts +54 -30
- package/utils.ts +95 -11
package/dist/router.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @remix-run/router v1.0
|
|
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
|
-
|
|
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(
|
|
756
|
+
function generatePath(originalPath, params) {
|
|
699
757
|
if (params === void 0) {
|
|
700
758
|
params = {};
|
|
701
759
|
}
|
|
702
760
|
|
|
703
|
-
|
|
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(
|
|
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
|
|
1251
|
-
const
|
|
1252
|
-
const validRequestMethodsArr = ["get", ...
|
|
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 &&
|
|
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 (
|
|
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
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
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 =
|
|
3155
|
-
|
|
3156
|
-
|
|
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
|
|
3375
|
-
return
|
|
3457
|
+
function isMutationMethod(method) {
|
|
3458
|
+
return validMutationMethods.has(method);
|
|
3376
3459
|
}
|
|
3377
3460
|
|
|
3378
3461
|
async function resolveDeferredResults(currentMatches, matchesToLoad, results, signal, isFetcher, currentLoaderData) {
|