@remix-run/router 0.0.0-experimental-e960cf1a → 0.0.0-experimental-a0888892
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 +211 -8
- package/dist/index.d.ts +2 -2
- package/dist/router.cjs.js +306 -272
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.d.ts +2 -1
- package/dist/router.js +295 -259
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +306 -272
- 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 +21 -21
- package/history.ts +4 -0
- package/index.ts +1 -3
- package/package.json +1 -1
- package/router.ts +467 -406
- package/utils.ts +60 -63
package/dist/router.cjs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @remix-run/router v0.0.0-experimental-
|
|
2
|
+
* @remix-run/router v0.0.0-experimental-a0888892
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Remix Software Inc.
|
|
5
5
|
*
|
|
@@ -496,6 +496,10 @@ function getUrlBasedHistory(getLocation, createHref, validateLocation, options)
|
|
|
496
496
|
// See https://bugzilla.mozilla.org/show_bug.cgi?id=878297
|
|
497
497
|
let base = window.location.origin !== "null" ? window.location.origin : window.location.href;
|
|
498
498
|
let href = typeof to === "string" ? to : createPath(to);
|
|
499
|
+
// Treating this as a full URL will strip any trailing spaces so we need to
|
|
500
|
+
// pre-encode them since they might be part of a matching splat param from
|
|
501
|
+
// an ancestor route
|
|
502
|
+
href = href.replace(/ $/, "%20");
|
|
499
503
|
invariant(base, "No window.location.(origin|href) available to create URL for href: " + href);
|
|
500
504
|
return new URL(href, base);
|
|
501
505
|
}
|
|
@@ -573,6 +577,10 @@ let ResultType = /*#__PURE__*/function (ResultType) {
|
|
|
573
577
|
* Result from a loader or action - potentially successful or unsuccessful
|
|
574
578
|
*/
|
|
575
579
|
|
|
580
|
+
/**
|
|
581
|
+
* Result from a loader or action called via dataStrategy
|
|
582
|
+
*/
|
|
583
|
+
|
|
576
584
|
/**
|
|
577
585
|
* Users can specify either lowercase or uppercase form methods on `<Form>`,
|
|
578
586
|
* useSubmit(), `<fetcher.Form>`, etc.
|
|
@@ -752,14 +760,14 @@ function matchRoutes(routes, locationArg, basename) {
|
|
|
752
760
|
rankRouteBranches(branches);
|
|
753
761
|
let matches = null;
|
|
754
762
|
for (let i = 0; matches == null && i < branches.length; ++i) {
|
|
755
|
-
matches = matchRouteBranch(branches[i],
|
|
756
763
|
// Incoming pathnames are generally encoded from either window.location
|
|
757
764
|
// or from router.navigate, but we want to match against the unencoded
|
|
758
765
|
// paths in the route definitions. Memory router locations won't be
|
|
759
766
|
// encoded here but there also shouldn't be anything to decode so this
|
|
760
767
|
// should be a safe operation. This avoids needing matchRoutes to be
|
|
761
768
|
// history-aware.
|
|
762
|
-
|
|
769
|
+
let decoded = decodePath(pathname);
|
|
770
|
+
matches = matchRouteBranch(branches[i], decoded);
|
|
763
771
|
}
|
|
764
772
|
return matches;
|
|
765
773
|
}
|
|
@@ -889,7 +897,7 @@ function rankRouteBranches(branches) {
|
|
|
889
897
|
branches.sort((a, b) => a.score !== b.score ? b.score - a.score // Higher score first
|
|
890
898
|
: compareIndexes(a.routesMeta.map(meta => meta.childrenIndex), b.routesMeta.map(meta => meta.childrenIndex)));
|
|
891
899
|
}
|
|
892
|
-
const paramRe =
|
|
900
|
+
const paramRe = /^:[\w-]+$/;
|
|
893
901
|
const dynamicSegmentValue = 3;
|
|
894
902
|
const indexRouteValue = 2;
|
|
895
903
|
const emptySegmentValue = 1;
|
|
@@ -979,7 +987,7 @@ function generatePath(originalPath, params) {
|
|
|
979
987
|
// Apply the splat
|
|
980
988
|
return stringify(params[star]);
|
|
981
989
|
}
|
|
982
|
-
const keyMatch = segment.match(/^:(\w+)(\??)$/);
|
|
990
|
+
const keyMatch = segment.match(/^:([\w-]+)(\??)$/);
|
|
983
991
|
if (keyMatch) {
|
|
984
992
|
const [, key, optional] = keyMatch;
|
|
985
993
|
let param = params[key];
|
|
@@ -1038,7 +1046,7 @@ function matchPath(pattern, pathname) {
|
|
|
1038
1046
|
if (isOptional && !value) {
|
|
1039
1047
|
memo[paramName] = undefined;
|
|
1040
1048
|
} else {
|
|
1041
|
-
memo[paramName] =
|
|
1049
|
+
memo[paramName] = (value || "").replace(/%2F/g, "/");
|
|
1042
1050
|
}
|
|
1043
1051
|
return memo;
|
|
1044
1052
|
}, {});
|
|
@@ -1061,7 +1069,7 @@ function compilePath(path, caseSensitive, end) {
|
|
|
1061
1069
|
let regexpSource = "^" + path.replace(/\/*\*?$/, "") // Ignore trailing / and /*, we'll handle it below
|
|
1062
1070
|
.replace(/^\/*/, "/") // Make sure it has a leading /
|
|
1063
1071
|
.replace(/[\\.*+^${}|()[\]]/g, "\\$&") // Escape special regex chars
|
|
1064
|
-
.replace(/\/:(\w+)(\?)?/g, (_, paramName, isOptional) => {
|
|
1072
|
+
.replace(/\/:([\w-]+)(\?)?/g, (_, paramName, isOptional) => {
|
|
1065
1073
|
params.push({
|
|
1066
1074
|
paramName,
|
|
1067
1075
|
isOptional: isOptional != null
|
|
@@ -1090,22 +1098,14 @@ function compilePath(path, caseSensitive, end) {
|
|
|
1090
1098
|
let matcher = new RegExp(regexpSource, caseSensitive ? undefined : "i");
|
|
1091
1099
|
return [matcher, params];
|
|
1092
1100
|
}
|
|
1093
|
-
function
|
|
1101
|
+
function decodePath(value) {
|
|
1094
1102
|
try {
|
|
1095
|
-
return
|
|
1103
|
+
return value.split("/").map(v => decodeURIComponent(v).replace(/\//g, "%2F")).join("/");
|
|
1096
1104
|
} catch (error) {
|
|
1097
1105
|
warning(false, "The URL path \"" + value + "\" could not be decoded because it is is a " + "malformed URL segment. This is probably due to a bad percent " + ("encoding (" + error + ")."));
|
|
1098
1106
|
return value;
|
|
1099
1107
|
}
|
|
1100
1108
|
}
|
|
1101
|
-
function safelyDecodeURIComponent(value, paramName) {
|
|
1102
|
-
try {
|
|
1103
|
-
return decodeURIComponent(value);
|
|
1104
|
-
} catch (error) {
|
|
1105
|
-
warning(false, "The value for the URL param \"" + paramName + "\" will not be decoded because" + (" the string \"" + value + "\" is a malformed URL segment. This is probably") + (" due to a bad percent encoding (" + error + ")."));
|
|
1106
|
-
return value;
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
1109
|
|
|
1110
1110
|
/**
|
|
1111
1111
|
* @private
|
|
@@ -1597,11 +1597,6 @@ function isRouteErrorResponse(error) {
|
|
|
1597
1597
|
/**
|
|
1598
1598
|
* Identified fetcher.load() calls that need to be revalidated
|
|
1599
1599
|
*/
|
|
1600
|
-
/**
|
|
1601
|
-
* Wrapper object to allow us to throw any response out from callLoaderOrAction
|
|
1602
|
-
* for queryRouter while preserving whether or not it was thrown or returned
|
|
1603
|
-
* from the loader/action
|
|
1604
|
-
*/
|
|
1605
1600
|
const validMutationMethodsArr = ["post", "put", "patch", "delete"];
|
|
1606
1601
|
const validMutationMethods = new Set(validMutationMethodsArr);
|
|
1607
1602
|
const validRequestMethodsArr = ["get", ...validMutationMethodsArr];
|
|
@@ -1654,8 +1649,6 @@ function createRouter(init) {
|
|
|
1654
1649
|
const isBrowser = typeof routerWindow !== "undefined" && typeof routerWindow.document !== "undefined" && typeof routerWindow.document.createElement !== "undefined";
|
|
1655
1650
|
const isServer = !isBrowser;
|
|
1656
1651
|
invariant(init.routes.length > 0, "You must provide a non-empty routes array to createRouter");
|
|
1657
|
-
const dataStrategy = init.unstable_dataStrategy || defaultDataStrategy;
|
|
1658
|
-
const callLoaderOrAction = createCallLoaderOrAction(dataStrategy);
|
|
1659
1652
|
let mapRouteProperties;
|
|
1660
1653
|
if (init.mapRouteProperties) {
|
|
1661
1654
|
mapRouteProperties = init.mapRouteProperties;
|
|
@@ -1675,6 +1668,7 @@ function createRouter(init) {
|
|
|
1675
1668
|
let dataRoutes = convertRoutesToDataRoutes(init.routes, mapRouteProperties, undefined, manifest);
|
|
1676
1669
|
let inFlightDataRoutes;
|
|
1677
1670
|
let basename = init.basename || "/";
|
|
1671
|
+
let dataStrategyImpl = init.unstable_dataStrategy || defaultDataStrategy;
|
|
1678
1672
|
// Config driven behavior flags
|
|
1679
1673
|
let future = _extends({
|
|
1680
1674
|
v7_fetcherPersist: false,
|
|
@@ -1717,18 +1711,28 @@ function createRouter(init) {
|
|
|
1717
1711
|
[route.id]: error
|
|
1718
1712
|
};
|
|
1719
1713
|
}
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1714
|
+
let initialized;
|
|
1715
|
+
let hasLazyRoutes = initialMatches.some(m => m.route.lazy);
|
|
1716
|
+
let hasLoaders = initialMatches.some(m => m.route.loader);
|
|
1717
|
+
if (hasLazyRoutes) {
|
|
1718
|
+
// All initialMatches need to be loaded before we're ready. If we have lazy
|
|
1719
|
+
// functions around still then we'll need to run them in initialize()
|
|
1720
|
+
initialized = false;
|
|
1721
|
+
} else if (!hasLoaders) {
|
|
1722
|
+
// If we've got no loaders to run, then we're good to go
|
|
1723
|
+
initialized = true;
|
|
1724
|
+
} else if (future.v7_partialHydration) {
|
|
1725
|
+
// If partial hydration is enabled, we're initialized so long as we were
|
|
1726
|
+
// provided with hydrationData for every route with a loader, and no loaders
|
|
1727
|
+
// were marked for explicit hydration
|
|
1728
|
+
let loaderData = init.hydrationData ? init.hydrationData.loaderData : null;
|
|
1729
|
+
let errors = init.hydrationData ? init.hydrationData.errors : null;
|
|
1730
|
+
initialized = initialMatches.every(m => m.route.loader && (typeof m.route.loader !== "function" || m.route.loader.hydrate !== true) && (loaderData && loaderData[m.route.id] !== undefined || errors && errors[m.route.id] !== undefined));
|
|
1731
|
+
} else {
|
|
1732
|
+
// Without partial hydration - we're initialized if we were provided any
|
|
1733
|
+
// hydrationData - which is expected to be complete
|
|
1734
|
+
initialized = init.hydrationData != null;
|
|
1735
|
+
}
|
|
1732
1736
|
let router;
|
|
1733
1737
|
let state = {
|
|
1734
1738
|
historyAction: init.history.action,
|
|
@@ -1895,7 +1899,7 @@ function createRouter(init) {
|
|
|
1895
1899
|
// in the normal navigation flow. For SSR it's expected that lazy modules are
|
|
1896
1900
|
// resolved prior to router creation since we can't go into a fallbackElement
|
|
1897
1901
|
// UI for SSR'd apps
|
|
1898
|
-
if (!state.initialized
|
|
1902
|
+
if (!state.initialized) {
|
|
1899
1903
|
startNavigation(Action.Pop, state.location, {
|
|
1900
1904
|
initialHydration: true
|
|
1901
1905
|
});
|
|
@@ -2336,7 +2340,8 @@ function createRouter(init) {
|
|
|
2336
2340
|
})
|
|
2337
2341
|
};
|
|
2338
2342
|
} else {
|
|
2339
|
-
|
|
2343
|
+
let results = await callDataStrategy("action", request, [actionMatch], matches);
|
|
2344
|
+
result = results[0];
|
|
2340
2345
|
if (request.signal.aborted) {
|
|
2341
2346
|
return {
|
|
2342
2347
|
shortCircuited: true
|
|
@@ -2351,9 +2356,9 @@ function createRouter(init) {
|
|
|
2351
2356
|
// If the user didn't explicity indicate replace behavior, replace if
|
|
2352
2357
|
// we redirected to the exact same location we're currently at to avoid
|
|
2353
2358
|
// double back-buttons
|
|
2354
|
-
replace = result.
|
|
2359
|
+
replace = result.response.headers.get("Location") === state.location.pathname + state.location.search;
|
|
2355
2360
|
}
|
|
2356
|
-
await startRedirectNavigation(
|
|
2361
|
+
await startRedirectNavigation(request, result, {
|
|
2357
2362
|
submission,
|
|
2358
2363
|
replace
|
|
2359
2364
|
});
|
|
@@ -2476,7 +2481,7 @@ function createRouter(init) {
|
|
|
2476
2481
|
let {
|
|
2477
2482
|
loaderResults,
|
|
2478
2483
|
fetcherResults
|
|
2479
|
-
} = await
|
|
2484
|
+
} = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, request);
|
|
2480
2485
|
if (request.signal.aborted) {
|
|
2481
2486
|
return {
|
|
2482
2487
|
shortCircuited: true
|
|
@@ -2501,7 +2506,7 @@ function createRouter(init) {
|
|
|
2501
2506
|
let fetcherKey = revalidatingFetchers[redirect.idx - matchesToLoad.length].key;
|
|
2502
2507
|
fetchRedirectIds.add(fetcherKey);
|
|
2503
2508
|
}
|
|
2504
|
-
await startRedirectNavigation(
|
|
2509
|
+
await startRedirectNavigation(request, redirect.result, {
|
|
2505
2510
|
replace
|
|
2506
2511
|
});
|
|
2507
2512
|
return {
|
|
@@ -2610,7 +2615,8 @@ function createRouter(init) {
|
|
|
2610
2615
|
let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);
|
|
2611
2616
|
fetchControllers.set(key, abortController);
|
|
2612
2617
|
let originatingLoadId = incrementingLoadId;
|
|
2613
|
-
let
|
|
2618
|
+
let actionResults = await callDataStrategy("action", fetchRequest, [match], requestMatches);
|
|
2619
|
+
let actionResult = actionResults[0];
|
|
2614
2620
|
if (fetchRequest.signal.aborted) {
|
|
2615
2621
|
// We can delete this so long as we weren't aborted by our own fetcher
|
|
2616
2622
|
// re-submit which would have put _new_ controller is in fetchControllers
|
|
@@ -2642,7 +2648,7 @@ function createRouter(init) {
|
|
|
2642
2648
|
} else {
|
|
2643
2649
|
fetchRedirectIds.add(key);
|
|
2644
2650
|
updateFetcherState(key, getLoadingFetcher(submission));
|
|
2645
|
-
return startRedirectNavigation(
|
|
2651
|
+
return startRedirectNavigation(fetchRequest, actionResult, {
|
|
2646
2652
|
fetcherSubmission: submission
|
|
2647
2653
|
});
|
|
2648
2654
|
}
|
|
@@ -2699,7 +2705,7 @@ function createRouter(init) {
|
|
|
2699
2705
|
let {
|
|
2700
2706
|
loaderResults,
|
|
2701
2707
|
fetcherResults
|
|
2702
|
-
} = await
|
|
2708
|
+
} = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, revalidationRequest);
|
|
2703
2709
|
if (abortController.signal.aborted) {
|
|
2704
2710
|
return;
|
|
2705
2711
|
}
|
|
@@ -2716,7 +2722,7 @@ function createRouter(init) {
|
|
|
2716
2722
|
let fetcherKey = revalidatingFetchers[redirect.idx - matchesToLoad.length].key;
|
|
2717
2723
|
fetchRedirectIds.add(fetcherKey);
|
|
2718
2724
|
}
|
|
2719
|
-
return startRedirectNavigation(
|
|
2725
|
+
return startRedirectNavigation(revalidationRequest, redirect.result);
|
|
2720
2726
|
}
|
|
2721
2727
|
|
|
2722
2728
|
// Process and commit output from loaders
|
|
@@ -2770,7 +2776,8 @@ function createRouter(init) {
|
|
|
2770
2776
|
let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
|
|
2771
2777
|
fetchControllers.set(key, abortController);
|
|
2772
2778
|
let originatingLoadId = incrementingLoadId;
|
|
2773
|
-
let
|
|
2779
|
+
let results = await callDataStrategy("loader", fetchRequest, [match], matches);
|
|
2780
|
+
let result = results[0];
|
|
2774
2781
|
|
|
2775
2782
|
// Deferred isn't supported for fetcher loads, await everything and treat it
|
|
2776
2783
|
// as a normal load. resolveDeferredData will return undefined if this
|
|
@@ -2805,7 +2812,7 @@ function createRouter(init) {
|
|
|
2805
2812
|
return;
|
|
2806
2813
|
} else {
|
|
2807
2814
|
fetchRedirectIds.add(key);
|
|
2808
|
-
await startRedirectNavigation(
|
|
2815
|
+
await startRedirectNavigation(fetchRequest, result);
|
|
2809
2816
|
return;
|
|
2810
2817
|
}
|
|
2811
2818
|
}
|
|
@@ -2840,26 +2847,40 @@ function createRouter(init) {
|
|
|
2840
2847
|
* actually touch history until we've processed redirects, so we just use
|
|
2841
2848
|
* the history action from the original navigation (PUSH or REPLACE).
|
|
2842
2849
|
*/
|
|
2843
|
-
async function startRedirectNavigation(
|
|
2850
|
+
async function startRedirectNavigation(request, redirect, _temp2) {
|
|
2844
2851
|
let {
|
|
2845
2852
|
submission,
|
|
2846
2853
|
fetcherSubmission,
|
|
2847
2854
|
replace
|
|
2848
2855
|
} = _temp2 === void 0 ? {} : _temp2;
|
|
2849
|
-
if (redirect.
|
|
2856
|
+
if (redirect.response.headers.has("X-Remix-Revalidate")) {
|
|
2850
2857
|
isRevalidationRequired = true;
|
|
2851
2858
|
}
|
|
2852
|
-
let
|
|
2859
|
+
let location = redirect.response.headers.get("Location");
|
|
2860
|
+
invariant(location, "Expected a Location header on the redirect Response");
|
|
2861
|
+
let redirectLocation = createLocation(state.location, location, {
|
|
2853
2862
|
_isRedirect: true
|
|
2854
2863
|
});
|
|
2855
|
-
|
|
2864
|
+
if (ABSOLUTE_URL_REGEX.test(location)) {
|
|
2865
|
+
// Strip off the protocol+origin for same-origin + same-basename absolute redirects
|
|
2866
|
+
let normalizedLocation = location;
|
|
2867
|
+
let currentUrl = new URL(request.url);
|
|
2868
|
+
let url = normalizedLocation.startsWith("//") ? new URL(currentUrl.protocol + normalizedLocation) : new URL(normalizedLocation);
|
|
2869
|
+
let isSameBasename = stripBasename(url.pathname, basename) != null;
|
|
2870
|
+
if (url.origin === currentUrl.origin && isSameBasename) {
|
|
2871
|
+
normalizedLocation = url.pathname + url.search + url.hash;
|
|
2872
|
+
redirectLocation = createLocation(state.location, normalizedLocation, {
|
|
2873
|
+
_isRedirect: true
|
|
2874
|
+
});
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2856
2877
|
if (isBrowser) {
|
|
2857
2878
|
let isDocumentReload = false;
|
|
2858
|
-
if (redirect.
|
|
2879
|
+
if (redirect.response.headers.has("X-Remix-Reload-Document")) {
|
|
2859
2880
|
// Hard reload if the response contained X-Remix-Reload-Document
|
|
2860
2881
|
isDocumentReload = true;
|
|
2861
|
-
} else if (ABSOLUTE_URL_REGEX.test(
|
|
2862
|
-
const url = init.history.createURL(
|
|
2882
|
+
} else if (ABSOLUTE_URL_REGEX.test(location)) {
|
|
2883
|
+
const url = init.history.createURL(location);
|
|
2863
2884
|
isDocumentReload =
|
|
2864
2885
|
// Hard reload if it's an absolute URL to a new origin
|
|
2865
2886
|
url.origin !== routerWindow.location.origin ||
|
|
@@ -2868,9 +2889,9 @@ function createRouter(init) {
|
|
|
2868
2889
|
}
|
|
2869
2890
|
if (isDocumentReload) {
|
|
2870
2891
|
if (replace) {
|
|
2871
|
-
routerWindow.location.replace(
|
|
2892
|
+
routerWindow.location.replace(location);
|
|
2872
2893
|
} else {
|
|
2873
|
-
routerWindow.location.assign(
|
|
2894
|
+
routerWindow.location.assign(location);
|
|
2874
2895
|
}
|
|
2875
2896
|
return;
|
|
2876
2897
|
}
|
|
@@ -2896,10 +2917,10 @@ function createRouter(init) {
|
|
|
2896
2917
|
// re-submit the GET/POST/PUT/PATCH/DELETE as a submission navigation to the
|
|
2897
2918
|
// redirected location
|
|
2898
2919
|
let activeSubmission = submission || fetcherSubmission;
|
|
2899
|
-
if (redirectPreserveMethodStatusCodes.has(redirect.status) && activeSubmission && isMutationMethod(activeSubmission.formMethod)) {
|
|
2920
|
+
if (redirectPreserveMethodStatusCodes.has(redirect.response.status) && activeSubmission && isMutationMethod(activeSubmission.formMethod)) {
|
|
2900
2921
|
await startNavigation(redirectHistoryAction, redirectLocation, {
|
|
2901
2922
|
submission: _extends({}, activeSubmission, {
|
|
2902
|
-
formAction:
|
|
2923
|
+
formAction: location
|
|
2903
2924
|
}),
|
|
2904
2925
|
// Preserve this flag across redirects
|
|
2905
2926
|
preventScrollReset: pendingPreventScrollReset
|
|
@@ -2917,16 +2938,36 @@ function createRouter(init) {
|
|
|
2917
2938
|
});
|
|
2918
2939
|
}
|
|
2919
2940
|
}
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2941
|
+
|
|
2942
|
+
// Utility wrapper for calling dataStrategy client-side without having to
|
|
2943
|
+
// pass around the manifest, mapRouteProperties, etc.
|
|
2944
|
+
async function callDataStrategy(type, request, matchesToLoad, matches) {
|
|
2945
|
+
try {
|
|
2946
|
+
let results = await callDataStrategyImpl(dataStrategyImpl, type, request, matchesToLoad, matches, manifest, mapRouteProperties);
|
|
2947
|
+
return await Promise.all(results.map((result, i) => {
|
|
2948
|
+
if (isRedirectHandlerResult(result)) {
|
|
2949
|
+
return {
|
|
2950
|
+
type: ResultType.redirect,
|
|
2951
|
+
response: normalizeRelativeRoutingRedirectResponse(result.result, request, matchesToLoad[i].route.id, matches, basename, future.v7_relativeSplatPath)
|
|
2952
|
+
};
|
|
2953
|
+
}
|
|
2954
|
+
return convertHandlerResultToDataResult(result);
|
|
2955
|
+
}));
|
|
2956
|
+
} catch (e) {
|
|
2957
|
+
// If the outer dataStrategy method throws, just return the error for all
|
|
2958
|
+
// matches - and it'll naturally bubble to the root
|
|
2959
|
+
return matchesToLoad.map(() => ({
|
|
2960
|
+
type: ResultType.error,
|
|
2961
|
+
error: e
|
|
2962
|
+
}));
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2965
|
+
async function callLoadersAndMaybeResolveData(currentMatches, matches, matchesToLoad, fetchersToLoad, request) {
|
|
2966
|
+
let [loaderResults, ...fetcherResults] = await Promise.all([matchesToLoad.length ? callDataStrategy("loader", request, matchesToLoad, matches) : [], ...fetchersToLoad.map(f => {
|
|
2967
|
+
if (f.matches && f.match && f.controller) {
|
|
2968
|
+
let fetcherRequest = createClientSideRequest(init.history, f.path, f.controller.signal);
|
|
2969
|
+
return callDataStrategy("loader", fetcherRequest, [f.match], f.matches).then(r => r[0]);
|
|
2970
|
+
} else {
|
|
2930
2971
|
return Promise.resolve({
|
|
2931
2972
|
type: ResultType.error,
|
|
2932
2973
|
error: getInternalRouterError(404, {
|
|
@@ -2934,16 +2975,6 @@ function createRouter(init) {
|
|
|
2934
2975
|
})
|
|
2935
2976
|
});
|
|
2936
2977
|
}
|
|
2937
|
-
return dataStrategy({
|
|
2938
|
-
matches: [finesseToAgnosticDataStrategyMatch(f.match, mapRouteProperties, manifest)],
|
|
2939
|
-
request,
|
|
2940
|
-
type: "loader",
|
|
2941
|
-
defaultStrategy(match) {
|
|
2942
|
-
invariant(f.controller, "Expected controller for fetcher in defaultStrategy");
|
|
2943
|
-
invariant(f.matches, "Expected matches for fetcher in defaultStrategy");
|
|
2944
|
-
return callLoaderOrActionImplementation("loader", createClientSideRequest(init.history, f.path, f.controller.signal), match, f.matches, basename, future.v7_relativeSplatPath);
|
|
2945
|
-
}
|
|
2946
|
-
}).then(r => r[0]);
|
|
2947
2978
|
})]);
|
|
2948
2979
|
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)]);
|
|
2949
2980
|
return {
|
|
@@ -3257,10 +3288,9 @@ const UNSAFE_DEFERRED_SYMBOL = Symbol("deferred");
|
|
|
3257
3288
|
|
|
3258
3289
|
function createStaticHandler(routes, opts) {
|
|
3259
3290
|
invariant(routes.length > 0, "You must provide a non-empty routes array to createStaticHandler");
|
|
3260
|
-
const dataStrategy = (opts == null ? void 0 : opts.dataStrategy) || defaultDataStrategy;
|
|
3261
|
-
const callLoaderOrAction = createCallLoaderOrAction(dataStrategy);
|
|
3262
3291
|
let manifest = {};
|
|
3263
3292
|
let basename = (opts ? opts.basename : null) || "/";
|
|
3293
|
+
let dataStrategyImpl = (opts == null ? void 0 : opts.unstable_dataStrategy) || defaultDataStrategy;
|
|
3264
3294
|
let mapRouteProperties;
|
|
3265
3295
|
if (opts != null && opts.mapRouteProperties) {
|
|
3266
3296
|
mapRouteProperties = opts.mapRouteProperties;
|
|
@@ -3275,7 +3305,8 @@ function createStaticHandler(routes, opts) {
|
|
|
3275
3305
|
}
|
|
3276
3306
|
// Config driven behavior flags
|
|
3277
3307
|
let future = _extends({
|
|
3278
|
-
v7_relativeSplatPath: false
|
|
3308
|
+
v7_relativeSplatPath: false,
|
|
3309
|
+
v7_throwAbortReason: false
|
|
3279
3310
|
}, opts ? opts.future : null);
|
|
3280
3311
|
let dataRoutes = convertRoutesToDataRoutes(routes, mapRouteProperties, undefined, manifest);
|
|
3281
3312
|
|
|
@@ -3459,14 +3490,14 @@ function createStaticHandler(routes, opts) {
|
|
|
3459
3490
|
actionHeaders: {}
|
|
3460
3491
|
});
|
|
3461
3492
|
} catch (e) {
|
|
3462
|
-
// If the user threw/returned a Response in callLoaderOrAction
|
|
3463
|
-
//
|
|
3464
|
-
//
|
|
3465
|
-
if (
|
|
3493
|
+
// If the user threw/returned a Response in callLoaderOrAction for a
|
|
3494
|
+
// `queryRoute` call, we throw the `HandlerResult` to bail out early
|
|
3495
|
+
// and then return or throw the raw Response here accordingly
|
|
3496
|
+
if (isHandlerResult(e) && isResponse(e.result)) {
|
|
3466
3497
|
if (e.type === ResultType.error) {
|
|
3467
|
-
throw e.
|
|
3498
|
+
throw e.result;
|
|
3468
3499
|
}
|
|
3469
|
-
return e.
|
|
3500
|
+
return e.result;
|
|
3470
3501
|
}
|
|
3471
3502
|
// Redirects are always returned since they don't propagate to catch
|
|
3472
3503
|
// boundaries
|
|
@@ -3492,14 +3523,10 @@ function createStaticHandler(routes, opts) {
|
|
|
3492
3523
|
error
|
|
3493
3524
|
};
|
|
3494
3525
|
} else {
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
isRouteRequest,
|
|
3498
|
-
requestContext
|
|
3499
|
-
});
|
|
3526
|
+
let results = await callDataStrategy("action", request, [actionMatch], matches, isRouteRequest, requestContext);
|
|
3527
|
+
result = results[0];
|
|
3500
3528
|
if (request.signal.aborted) {
|
|
3501
|
-
|
|
3502
|
-
throw new Error(method + "() call aborted: " + request.method + " " + request.url);
|
|
3529
|
+
throwStaticHandlerAbortedError(request, isRouteRequest, future);
|
|
3503
3530
|
}
|
|
3504
3531
|
}
|
|
3505
3532
|
if (isRedirectResult(result)) {
|
|
@@ -3508,9 +3535,9 @@ function createStaticHandler(routes, opts) {
|
|
|
3508
3535
|
// can get back on the "throw all redirect responses" train here should
|
|
3509
3536
|
// this ever happen :/
|
|
3510
3537
|
throw new Response(null, {
|
|
3511
|
-
status: result.status,
|
|
3538
|
+
status: result.response.status,
|
|
3512
3539
|
headers: {
|
|
3513
|
-
Location: result.
|
|
3540
|
+
Location: result.response.headers.get("Location")
|
|
3514
3541
|
}
|
|
3515
3542
|
});
|
|
3516
3543
|
}
|
|
@@ -3559,8 +3586,8 @@ function createStaticHandler(routes, opts) {
|
|
|
3559
3586
|
return _extends({}, context, {
|
|
3560
3587
|
statusCode: isRouteErrorResponse(result.error) ? result.error.status : 500,
|
|
3561
3588
|
actionData: null,
|
|
3562
|
-
actionHeaders: _extends({}, result.
|
|
3563
|
-
[actionMatch.route.id]: result.headers
|
|
3589
|
+
actionHeaders: _extends({}, result.response ? {
|
|
3590
|
+
[actionMatch.route.id]: result.response.headers
|
|
3564
3591
|
} : {})
|
|
3565
3592
|
});
|
|
3566
3593
|
}
|
|
@@ -3572,15 +3599,17 @@ function createStaticHandler(routes, opts) {
|
|
|
3572
3599
|
signal: request.signal
|
|
3573
3600
|
});
|
|
3574
3601
|
let context = await loadRouteData(loaderRequest, matches, requestContext);
|
|
3575
|
-
return _extends({}, context,
|
|
3576
|
-
statusCode: result.statusCode
|
|
3577
|
-
} : {}, {
|
|
3602
|
+
return _extends({}, context, {
|
|
3578
3603
|
actionData: {
|
|
3579
3604
|
[actionMatch.route.id]: result.data
|
|
3580
|
-
}
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3605
|
+
}
|
|
3606
|
+
}, result.response ? {
|
|
3607
|
+
statusCode: result.response.status,
|
|
3608
|
+
actionHeaders: {
|
|
3609
|
+
[actionMatch.route.id]: result.response.headers
|
|
3610
|
+
}
|
|
3611
|
+
} : {
|
|
3612
|
+
actionHeaders: {}
|
|
3584
3613
|
});
|
|
3585
3614
|
}
|
|
3586
3615
|
async function loadRouteData(request, matches, requestContext, routeMatch, pendingActionError) {
|
|
@@ -3611,21 +3640,9 @@ function createStaticHandler(routes, opts) {
|
|
|
3611
3640
|
activeDeferreds: null
|
|
3612
3641
|
};
|
|
3613
3642
|
}
|
|
3614
|
-
let results = await
|
|
3615
|
-
matches: matchesToLoad.map(m => finesseToAgnosticDataStrategyMatch(m, mapRouteProperties, manifest)),
|
|
3616
|
-
request,
|
|
3617
|
-
type: "loader",
|
|
3618
|
-
defaultStrategy(match) {
|
|
3619
|
-
return callLoaderOrActionImplementation("loader", request, match, matches, basename, future.v7_relativeSplatPath, {
|
|
3620
|
-
isStaticRequest: true,
|
|
3621
|
-
isRouteRequest,
|
|
3622
|
-
requestContext
|
|
3623
|
-
});
|
|
3624
|
-
}
|
|
3625
|
-
});
|
|
3643
|
+
let results = await callDataStrategy("loader", request, matchesToLoad, matches, isRouteRequest, requestContext);
|
|
3626
3644
|
if (request.signal.aborted) {
|
|
3627
|
-
|
|
3628
|
-
throw new Error(method + "() call aborted: " + request.method + " " + request.url);
|
|
3645
|
+
throwStaticHandlerAbortedError(request, isRouteRequest, future);
|
|
3629
3646
|
}
|
|
3630
3647
|
|
|
3631
3648
|
// Process and commit output from loaders
|
|
@@ -3644,6 +3661,25 @@ function createStaticHandler(routes, opts) {
|
|
|
3644
3661
|
activeDeferreds: activeDeferreds.size > 0 ? Object.fromEntries(activeDeferreds.entries()) : null
|
|
3645
3662
|
});
|
|
3646
3663
|
}
|
|
3664
|
+
|
|
3665
|
+
// Utility wrapper for calling dataStrategy server-side without having to
|
|
3666
|
+
// pass around the manifest, mapRouteProperties, etc.
|
|
3667
|
+
async function callDataStrategy(type, request, matchesToLoad, matches, isRouteRequest, requestContext) {
|
|
3668
|
+
let results = await callDataStrategyImpl(dataStrategyImpl, type, request, matchesToLoad, matches, manifest, mapRouteProperties, requestContext);
|
|
3669
|
+
return await Promise.all(results.map((result, i) => {
|
|
3670
|
+
if (isRedirectHandlerResult(result)) {
|
|
3671
|
+
// Throw redirects and let the server handle them with an HTTP redirect
|
|
3672
|
+
throw normalizeRelativeRoutingRedirectResponse(result.result, request, matchesToLoad[i].route.id, matches, basename, future.v7_relativeSplatPath);
|
|
3673
|
+
}
|
|
3674
|
+
if (isResponse(result.result) && isRouteRequest) {
|
|
3675
|
+
// For SSR single-route requests, we want to hand Responses back directly
|
|
3676
|
+
// without unwrapping. We do this with the QueryRouteResponse wrapper
|
|
3677
|
+
// interface so we can know whether it was returned or thrown
|
|
3678
|
+
throw result;
|
|
3679
|
+
}
|
|
3680
|
+
return convertHandlerResultToDataResult(result);
|
|
3681
|
+
}));
|
|
3682
|
+
}
|
|
3647
3683
|
return {
|
|
3648
3684
|
dataRoutes,
|
|
3649
3685
|
query,
|
|
@@ -3657,27 +3693,26 @@ function createStaticHandler(routes, opts) {
|
|
|
3657
3693
|
//#region Helpers
|
|
3658
3694
|
////////////////////////////////////////////////////////////////////////////////
|
|
3659
3695
|
|
|
3660
|
-
function defaultDataStrategy(_ref3) {
|
|
3661
|
-
let {
|
|
3662
|
-
defaultStrategy,
|
|
3663
|
-
matches
|
|
3664
|
-
} = _ref3;
|
|
3665
|
-
return Promise.all(matches.map(match => defaultStrategy(match)));
|
|
3666
|
-
}
|
|
3667
|
-
|
|
3668
3696
|
/**
|
|
3669
3697
|
* Given an existing StaticHandlerContext and an error thrown at render time,
|
|
3670
3698
|
* provide an updated StaticHandlerContext suitable for a second SSR render
|
|
3671
3699
|
*/
|
|
3672
3700
|
function getStaticContextFromError(routes, context, error) {
|
|
3673
3701
|
let newContext = _extends({}, context, {
|
|
3674
|
-
statusCode: 500,
|
|
3702
|
+
statusCode: isRouteErrorResponse(error) ? error.status : 500,
|
|
3675
3703
|
errors: {
|
|
3676
3704
|
[context._deepestRenderedBoundaryId || routes[0].id]: error
|
|
3677
3705
|
}
|
|
3678
3706
|
});
|
|
3679
3707
|
return newContext;
|
|
3680
3708
|
}
|
|
3709
|
+
function throwStaticHandlerAbortedError(request, isRouteRequest, future) {
|
|
3710
|
+
if (future.v7_throwAbortReason && request.signal.reason !== undefined) {
|
|
3711
|
+
throw request.signal.reason;
|
|
3712
|
+
}
|
|
3713
|
+
let method = isRouteRequest ? "queryRoute" : "query";
|
|
3714
|
+
throw new Error(method + "() call aborted: " + request.method + " " + request.url);
|
|
3715
|
+
}
|
|
3681
3716
|
function isSubmissionNavigation(opts) {
|
|
3682
3717
|
return opts != null && ("formData" in opts && opts.formData != null || "body" in opts && opts.body !== undefined);
|
|
3683
3718
|
}
|
|
@@ -3762,8 +3797,8 @@ function normalizeNavigateOptions(normalizeFormMethod, isFetcher, path, opts) {
|
|
|
3762
3797
|
}
|
|
3763
3798
|
let text = typeof opts.body === "string" ? opts.body : opts.body instanceof FormData || opts.body instanceof URLSearchParams ?
|
|
3764
3799
|
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#plain-text-form-data
|
|
3765
|
-
Array.from(opts.body.entries()).reduce((acc,
|
|
3766
|
-
let [name, value] =
|
|
3800
|
+
Array.from(opts.body.entries()).reduce((acc, _ref3) => {
|
|
3801
|
+
let [name, value] = _ref3;
|
|
3767
3802
|
return "" + acc + name + "=" + value + "\n";
|
|
3768
3803
|
}, "") : String(opts.body);
|
|
3769
3804
|
return {
|
|
@@ -3874,18 +3909,24 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
|
|
|
3874
3909
|
let boundaryId = pendingError ? Object.keys(pendingError)[0] : undefined;
|
|
3875
3910
|
let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
|
|
3876
3911
|
let navigationMatches = boundaryMatches.filter((match, index) => {
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
}
|
|
3882
|
-
if (match.route.lazy) {
|
|
3912
|
+
let {
|
|
3913
|
+
route
|
|
3914
|
+
} = match;
|
|
3915
|
+
if (route.lazy) {
|
|
3883
3916
|
// We haven't loaded this route yet so we don't know if it's got a loader!
|
|
3884
3917
|
return true;
|
|
3885
3918
|
}
|
|
3886
|
-
if (
|
|
3919
|
+
if (route.loader == null) {
|
|
3887
3920
|
return false;
|
|
3888
3921
|
}
|
|
3922
|
+
if (isInitialLoad) {
|
|
3923
|
+
if (typeof route.loader !== "function" || route.loader.hydrate) {
|
|
3924
|
+
return true;
|
|
3925
|
+
}
|
|
3926
|
+
return state.loaderData[route.id] === undefined && (
|
|
3927
|
+
// Don't re-run if the loader ran and threw an error
|
|
3928
|
+
!state.errors || state.errors[route.id] === undefined);
|
|
3929
|
+
}
|
|
3889
3930
|
|
|
3890
3931
|
// Always call the loader on new route instances and pending defer cancellations
|
|
3891
3932
|
if (isNewLoader(state.loaderData, state.matches[index], match) || cancelledDeferredRoutes.some(id => id === match.route.id)) {
|
|
@@ -3987,20 +4028,6 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
|
|
|
3987
4028
|
});
|
|
3988
4029
|
return [navigationMatches, revalidatingFetchers];
|
|
3989
4030
|
}
|
|
3990
|
-
|
|
3991
|
-
// Is this route unhydrated (when v7_partialHydration=true) such that we need
|
|
3992
|
-
// to call it's loader on the initial router creation
|
|
3993
|
-
function isUnhydratedRoute(state, route) {
|
|
3994
|
-
if (!route.loader) {
|
|
3995
|
-
return false;
|
|
3996
|
-
}
|
|
3997
|
-
if (route.loader.hydrate) {
|
|
3998
|
-
return true;
|
|
3999
|
-
}
|
|
4000
|
-
return state.loaderData[route.id] === undefined && (!state.errors ||
|
|
4001
|
-
// Loader ran but errored - don't re-run
|
|
4002
|
-
state.errors[route.id] === undefined);
|
|
4003
|
-
}
|
|
4004
4031
|
function isNewLoader(currentLoaderData, currentMatch, match) {
|
|
4005
4032
|
let isNew =
|
|
4006
4033
|
// [a] -> [a, b]
|
|
@@ -4088,44 +4115,57 @@ async function loadLazyRouteModule(route, mapRouteProperties, manifest) {
|
|
|
4088
4115
|
}));
|
|
4089
4116
|
return routeToUpdate;
|
|
4090
4117
|
}
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
}
|
|
4096
|
-
let [result] = await dataStrategy({
|
|
4097
|
-
matches: [finesseToAgnosticDataStrategyMatch(match, mapRouteProperties, manifest)],
|
|
4098
|
-
request,
|
|
4099
|
-
type,
|
|
4100
|
-
defaultStrategy(match) {
|
|
4101
|
-
return callLoaderOrActionImplementation(type, request, match, matches, basename, v7_relativeSplatPath, opts);
|
|
4102
|
-
}
|
|
4103
|
-
});
|
|
4104
|
-
return result;
|
|
4105
|
-
};
|
|
4118
|
+
|
|
4119
|
+
// Default implementation of `dataStrategy` which fetches all loaders in parallel
|
|
4120
|
+
function defaultDataStrategy(opts) {
|
|
4121
|
+
return Promise.all(opts.matches.map(m => m.bikeshed_loadRoute()));
|
|
4106
4122
|
}
|
|
4107
|
-
function
|
|
4108
|
-
let
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4123
|
+
async function callDataStrategyImpl(dataStrategyImpl, type, request, matchesToLoad, matches, manifest, mapRouteProperties, requestContext) {
|
|
4124
|
+
let routeIdsToLoad = matchesToLoad.reduce((acc, m) => acc.add(m.route.id), new Set());
|
|
4125
|
+
let loadedMatches = new Set();
|
|
4126
|
+
|
|
4127
|
+
// Send all matches here to allow for a middleware-type implementation.
|
|
4128
|
+
// handler will be a no-op for unneeded routes and we filter those results
|
|
4129
|
+
// back out below.
|
|
4130
|
+
let results = await dataStrategyImpl({
|
|
4131
|
+
matches: matches.map(match => {
|
|
4132
|
+
// `bikeshed_loadRoute` encapsulates the route.lazy, executing the
|
|
4133
|
+
// loader/action, and mapping return values/thrown errors to a
|
|
4134
|
+
// HandlerResult. Users can pass a callback to take fine-grained control
|
|
4135
|
+
// over the execution of the loader/action
|
|
4136
|
+
let bikeshed_loadRoute = handlerOverride => {
|
|
4137
|
+
loadedMatches.add(match.route.id);
|
|
4138
|
+
return routeIdsToLoad.has(match.route.id) ? callLoaderOrAction(type, request, match, manifest, mapRouteProperties, handlerOverride, requestContext) :
|
|
4139
|
+
// TODO: What's the best thing to do here - return an empty "success" result?
|
|
4140
|
+
// Or return a success result with the current route loader/action data?
|
|
4141
|
+
// We strip these results out if the route didn't need to be revalidated in
|
|
4142
|
+
// `callDataStrategy` so it doesn't matter for us. It's more of a question
|
|
4143
|
+
// of whether exposing the current data to the user is useful?
|
|
4144
|
+
Promise.resolve({
|
|
4145
|
+
type: ResultType.data,
|
|
4146
|
+
result: undefined
|
|
4147
|
+
});
|
|
4148
|
+
};
|
|
4149
|
+
return _extends({}, match, {
|
|
4150
|
+
bikeshed_loadRoute
|
|
4151
|
+
});
|
|
4152
|
+
}),
|
|
4153
|
+
request,
|
|
4154
|
+
params: matches[0].params
|
|
4122
4155
|
});
|
|
4156
|
+
|
|
4157
|
+
// Throw if any loadRoute implementations not called since they are what
|
|
4158
|
+
// ensures a route is fully loaded
|
|
4159
|
+
// Throw if any loadRoute implementations not called since they are what
|
|
4160
|
+
// ensure a route is fully loaded
|
|
4161
|
+
matches.forEach(m => invariant(loadedMatches.has(m.route.id), "`match.bikeshed_loadRoute()` was not called for route id \"" + m.route.id + "\". " + "You must call `match.bikeshed_loadRoute()` on every match passed to " + "`dataStrategy` to ensure all routes are properly loaded."));
|
|
4162
|
+
|
|
4163
|
+
// Filter out any middleware-only matches for which we didn't need to run handlers
|
|
4164
|
+
return results.filter((_, i) => routeIdsToLoad.has(matches[i].route.id));
|
|
4123
4165
|
}
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
}
|
|
4128
|
-
let resultType;
|
|
4166
|
+
|
|
4167
|
+
// Default logic for calling a loader/action is the user has no specified a dataStrategy
|
|
4168
|
+
async function callLoaderOrAction(type, request, match, manifest, mapRouteProperties, handlerOverride, staticContext) {
|
|
4129
4169
|
let result;
|
|
4130
4170
|
let onReject;
|
|
4131
4171
|
let runHandler = handler => {
|
|
@@ -4134,11 +4174,18 @@ async function callLoaderOrActionImplementation(type, request, match, matches, b
|
|
|
4134
4174
|
let abortPromise = new Promise((_, r) => reject = r);
|
|
4135
4175
|
onReject = () => reject();
|
|
4136
4176
|
request.signal.addEventListener("abort", onReject);
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4177
|
+
let runHandlerForReal = ctx => {
|
|
4178
|
+
if (typeof handler !== "function") {
|
|
4179
|
+
return Promise.reject(new Error("You cannot call the handler for a route which defines a boolean " + ("\"" + type + "\" [routeId: " + match.route.id + "]")));
|
|
4180
|
+
}
|
|
4181
|
+
return handler({
|
|
4182
|
+
request,
|
|
4183
|
+
params: match.params,
|
|
4184
|
+
context: staticContext
|
|
4185
|
+
}, ...(ctx !== undefined ? [ctx] : []));
|
|
4186
|
+
};
|
|
4187
|
+
let handlerPromise = handlerOverride ? handlerOverride(async ctx => runHandlerForReal(ctx)) : runHandlerForReal();
|
|
4188
|
+
return Promise.race([handlerPromise, abortPromise]);
|
|
4142
4189
|
};
|
|
4143
4190
|
try {
|
|
4144
4191
|
let handler = match.route[type];
|
|
@@ -4152,17 +4199,17 @@ async function callLoaderOrActionImplementation(type, request, match, matches, b
|
|
|
4152
4199
|
// route has a boundary that can handle the error
|
|
4153
4200
|
runHandler(handler).catch(e => {
|
|
4154
4201
|
handlerError = e;
|
|
4155
|
-
}), match.route]);
|
|
4202
|
+
}), loadLazyRouteModule(match.route, mapRouteProperties, manifest)]);
|
|
4156
4203
|
if (handlerError) {
|
|
4157
4204
|
throw handlerError;
|
|
4158
4205
|
}
|
|
4159
4206
|
result = values[0];
|
|
4160
4207
|
} else {
|
|
4161
4208
|
// Load lazy route module, then run any returned handler
|
|
4162
|
-
|
|
4163
|
-
handler = route[type];
|
|
4209
|
+
await loadLazyRouteModule(match.route, mapRouteProperties, manifest);
|
|
4210
|
+
handler = match.route[type];
|
|
4164
4211
|
if (handler) {
|
|
4165
|
-
// Handler still
|
|
4212
|
+
// Handler still runs even if we got interrupted to maintain consistency
|
|
4166
4213
|
// with un-abortable behavior of handler execution on non-lazy or
|
|
4167
4214
|
// previously-lazy-loaded routes
|
|
4168
4215
|
result = await runHandler(handler);
|
|
@@ -4179,7 +4226,7 @@ async function callLoaderOrActionImplementation(type, request, match, matches, b
|
|
|
4179
4226
|
// hit the invariant below that errors on returning undefined.
|
|
4180
4227
|
return {
|
|
4181
4228
|
type: ResultType.data,
|
|
4182
|
-
|
|
4229
|
+
result: undefined
|
|
4183
4230
|
};
|
|
4184
4231
|
}
|
|
4185
4232
|
}
|
|
@@ -4194,70 +4241,37 @@ async function callLoaderOrActionImplementation(type, request, match, matches, b
|
|
|
4194
4241
|
}
|
|
4195
4242
|
invariant(result !== undefined, "You defined " + (type === "action" ? "an action" : "a loader") + " for route " + ("\"" + match.route.id + "\" but didn't return anything from your `" + type + "` ") + "function. Please return a value or `null`.");
|
|
4196
4243
|
} catch (e) {
|
|
4197
|
-
|
|
4198
|
-
|
|
4244
|
+
return {
|
|
4245
|
+
type: ResultType.error,
|
|
4246
|
+
result: e
|
|
4247
|
+
};
|
|
4199
4248
|
} finally {
|
|
4200
4249
|
if (onReject) {
|
|
4201
4250
|
request.signal.removeEventListener("abort", onReject);
|
|
4202
4251
|
}
|
|
4203
4252
|
}
|
|
4253
|
+
return {
|
|
4254
|
+
type: ResultType.data,
|
|
4255
|
+
result
|
|
4256
|
+
};
|
|
4257
|
+
}
|
|
4258
|
+
async function convertHandlerResultToDataResult(handlerResult) {
|
|
4259
|
+
let {
|
|
4260
|
+
result,
|
|
4261
|
+
type
|
|
4262
|
+
} = handlerResult;
|
|
4204
4263
|
if (isResponse(result)) {
|
|
4205
|
-
let status = result.status;
|
|
4206
|
-
|
|
4207
|
-
// Process redirects
|
|
4208
|
-
if (redirectStatusCodes.has(status)) {
|
|
4209
|
-
let location = result.headers.get("Location");
|
|
4210
|
-
invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header");
|
|
4211
|
-
|
|
4212
|
-
// Support relative routing in internal redirects
|
|
4213
|
-
if (!ABSOLUTE_URL_REGEX.test(location)) {
|
|
4214
|
-
location = normalizeTo(new URL(request.url), matches.slice(0, matches.findIndex(m => m.route.id === match.route.id) + 1), basename, true, location, v7_relativeSplatPath);
|
|
4215
|
-
} else if (!opts.isStaticRequest) {
|
|
4216
|
-
// Strip off the protocol+origin for same-origin + same-basename absolute
|
|
4217
|
-
// redirects. If this is a static request, we can let it go back to the
|
|
4218
|
-
// browser as-is
|
|
4219
|
-
let currentUrl = new URL(request.url);
|
|
4220
|
-
let url = location.startsWith("//") ? new URL(currentUrl.protocol + location) : new URL(location);
|
|
4221
|
-
let isSameBasename = stripBasename(url.pathname, basename) != null;
|
|
4222
|
-
if (url.origin === currentUrl.origin && isSameBasename) {
|
|
4223
|
-
location = url.pathname + url.search + url.hash;
|
|
4224
|
-
}
|
|
4225
|
-
}
|
|
4226
|
-
|
|
4227
|
-
// Don't process redirects in the router during static requests requests.
|
|
4228
|
-
// Instead, throw the Response and let the server handle it with an HTTP
|
|
4229
|
-
// redirect. We also update the Location header in place in this flow so
|
|
4230
|
-
// basename and relative routing is taken into account
|
|
4231
|
-
if (opts.isStaticRequest) {
|
|
4232
|
-
result.headers.set("Location", location);
|
|
4233
|
-
throw result;
|
|
4234
|
-
}
|
|
4235
|
-
return {
|
|
4236
|
-
type: ResultType.redirect,
|
|
4237
|
-
status,
|
|
4238
|
-
location,
|
|
4239
|
-
revalidate: result.headers.get("X-Remix-Revalidate") !== null,
|
|
4240
|
-
reloadDocument: result.headers.get("X-Remix-Reload-Document") !== null
|
|
4241
|
-
};
|
|
4242
|
-
}
|
|
4243
|
-
|
|
4244
|
-
// For SSR single-route requests, we want to hand Responses back directly
|
|
4245
|
-
// without unwrapping. We do this with the QueryRouteResponse wrapper
|
|
4246
|
-
// interface so we can know whether it was returned or thrown
|
|
4247
|
-
if (opts.isRouteRequest) {
|
|
4248
|
-
let queryRouteResponse = {
|
|
4249
|
-
type: resultType === ResultType.error ? ResultType.error : ResultType.data,
|
|
4250
|
-
response: result
|
|
4251
|
-
};
|
|
4252
|
-
throw queryRouteResponse;
|
|
4253
|
-
}
|
|
4254
4264
|
let data;
|
|
4255
4265
|
try {
|
|
4256
4266
|
let contentType = result.headers.get("Content-Type");
|
|
4257
4267
|
// Check between word boundaries instead of startsWith() due to the last
|
|
4258
4268
|
// paragraph of https://httpwg.org/specs/rfc9110.html#field.content-type
|
|
4259
4269
|
if (contentType && /\bapplication\/json\b/.test(contentType)) {
|
|
4260
|
-
|
|
4270
|
+
if (result.body == null) {
|
|
4271
|
+
data = null;
|
|
4272
|
+
} else {
|
|
4273
|
+
data = await result.json();
|
|
4274
|
+
}
|
|
4261
4275
|
} else {
|
|
4262
4276
|
data = await result.text();
|
|
4263
4277
|
}
|
|
@@ -4267,23 +4281,22 @@ async function callLoaderOrActionImplementation(type, request, match, matches, b
|
|
|
4267
4281
|
error: e
|
|
4268
4282
|
};
|
|
4269
4283
|
}
|
|
4270
|
-
if (
|
|
4284
|
+
if (type === ResultType.error) {
|
|
4271
4285
|
return {
|
|
4272
|
-
type
|
|
4273
|
-
error: new ErrorResponseImpl(status, result.statusText, data),
|
|
4274
|
-
|
|
4286
|
+
type,
|
|
4287
|
+
error: new ErrorResponseImpl(result.status, result.statusText, data),
|
|
4288
|
+
response: result
|
|
4275
4289
|
};
|
|
4276
4290
|
}
|
|
4277
4291
|
return {
|
|
4278
4292
|
type: ResultType.data,
|
|
4279
4293
|
data,
|
|
4280
|
-
|
|
4281
|
-
headers: result.headers
|
|
4294
|
+
response: result
|
|
4282
4295
|
};
|
|
4283
4296
|
}
|
|
4284
|
-
if (
|
|
4297
|
+
if (type === ResultType.error) {
|
|
4285
4298
|
return {
|
|
4286
|
-
type
|
|
4299
|
+
type,
|
|
4287
4300
|
error: result
|
|
4288
4301
|
};
|
|
4289
4302
|
}
|
|
@@ -4302,6 +4315,18 @@ async function callLoaderOrActionImplementation(type, request, match, matches, b
|
|
|
4302
4315
|
};
|
|
4303
4316
|
}
|
|
4304
4317
|
|
|
4318
|
+
// Support relative routing in internal redirects
|
|
4319
|
+
function normalizeRelativeRoutingRedirectResponse(response, request, routeId, matches, basename, v7_relativeSplatPath) {
|
|
4320
|
+
let location = response.headers.get("Location");
|
|
4321
|
+
invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header");
|
|
4322
|
+
if (!ABSOLUTE_URL_REGEX.test(location)) {
|
|
4323
|
+
let trimmedMatches = matches.slice(0, matches.findIndex(m => m.route.id === routeId) + 1);
|
|
4324
|
+
location = normalizeTo(new URL(request.url), trimmedMatches, basename, true, location, v7_relativeSplatPath);
|
|
4325
|
+
response.headers.set("Location", location);
|
|
4326
|
+
}
|
|
4327
|
+
return response;
|
|
4328
|
+
}
|
|
4329
|
+
|
|
4305
4330
|
// Utility method for creating the Request instances for loaders/actions during
|
|
4306
4331
|
// client-side navigations and fetches. During SSR we will always have a
|
|
4307
4332
|
// Request instance from the static handler (query/queryRoute)
|
|
@@ -4392,24 +4417,31 @@ function processRouteLoaderData(matches, matchesToLoad, results, pendingError, a
|
|
|
4392
4417
|
foundError = true;
|
|
4393
4418
|
statusCode = isRouteErrorResponse(result.error) ? result.error.status : 500;
|
|
4394
4419
|
}
|
|
4395
|
-
if (result.
|
|
4396
|
-
loaderHeaders[id] = result.headers;
|
|
4420
|
+
if (result.response) {
|
|
4421
|
+
loaderHeaders[id] = result.response.headers;
|
|
4397
4422
|
}
|
|
4398
4423
|
} else {
|
|
4399
4424
|
if (isDeferredResult(result)) {
|
|
4400
4425
|
activeDeferreds.set(id, result.deferredData);
|
|
4401
4426
|
loaderData[id] = result.deferredData.data;
|
|
4427
|
+
// Error status codes always override success status codes, but if all
|
|
4428
|
+
// loaders are successful we take the deepest status code.
|
|
4429
|
+
if (result.statusCode != null && result.statusCode !== 200 && !foundError) {
|
|
4430
|
+
statusCode = result.statusCode;
|
|
4431
|
+
}
|
|
4432
|
+
if (result.headers) {
|
|
4433
|
+
loaderHeaders[id] = result.headers;
|
|
4434
|
+
}
|
|
4402
4435
|
} else {
|
|
4403
4436
|
loaderData[id] = result.data;
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
loaderHeaders[id] = result.headers;
|
|
4437
|
+
// Error status codes always override success status codes, but if all
|
|
4438
|
+
// loaders are successful we take the deepest status code.
|
|
4439
|
+
if (result.response) {
|
|
4440
|
+
if (result.response.status !== 200 && !foundError) {
|
|
4441
|
+
statusCode = result.response.status;
|
|
4442
|
+
}
|
|
4443
|
+
loaderHeaders[id] = result.response.headers;
|
|
4444
|
+
}
|
|
4413
4445
|
}
|
|
4414
4446
|
}
|
|
4415
4447
|
});
|
|
@@ -4589,6 +4621,12 @@ function isHashChangeOnly(a, b) {
|
|
|
4589
4621
|
// /page#hash -> /page
|
|
4590
4622
|
return false;
|
|
4591
4623
|
}
|
|
4624
|
+
function isHandlerResult(result) {
|
|
4625
|
+
return result != null && typeof result === "object" && "type" in result && "result" in result && (result.type === ResultType.data || result.type === ResultType.error);
|
|
4626
|
+
}
|
|
4627
|
+
function isRedirectHandlerResult(result) {
|
|
4628
|
+
return isResponse(result.result) && redirectStatusCodes.has(result.result.status);
|
|
4629
|
+
}
|
|
4592
4630
|
function isDeferredResult(result) {
|
|
4593
4631
|
return result.type === ResultType.deferred;
|
|
4594
4632
|
}
|
|
@@ -4613,9 +4651,6 @@ function isRedirectResponse(result) {
|
|
|
4613
4651
|
let location = result.headers.get("Location");
|
|
4614
4652
|
return status >= 300 && status <= 399 && location != null;
|
|
4615
4653
|
}
|
|
4616
|
-
function isQueryRouteResponse(obj) {
|
|
4617
|
-
return obj && isResponse(obj.response) && (obj.type === ResultType.data || obj.type === ResultType.error);
|
|
4618
|
-
}
|
|
4619
4654
|
function isValidMethod(method) {
|
|
4620
4655
|
return validRequestMethods.has(method.toLowerCase());
|
|
4621
4656
|
}
|
|
@@ -4859,7 +4894,6 @@ exports.Action = Action;
|
|
|
4859
4894
|
exports.IDLE_BLOCKER = IDLE_BLOCKER;
|
|
4860
4895
|
exports.IDLE_FETCHER = IDLE_FETCHER;
|
|
4861
4896
|
exports.IDLE_NAVIGATION = IDLE_NAVIGATION;
|
|
4862
|
-
exports.ResultType = ResultType;
|
|
4863
4897
|
exports.UNSAFE_DEFERRED_SYMBOL = UNSAFE_DEFERRED_SYMBOL;
|
|
4864
4898
|
exports.UNSAFE_DeferredData = DeferredData;
|
|
4865
4899
|
exports.UNSAFE_ErrorResponseImpl = ErrorResponseImpl;
|