@remix-run/router 0.0.0-experimental-e960cf1a → 0.0.0-experimental-bc2c864b
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 +199 -8
- package/dist/index.d.ts +2 -2
- package/dist/router.cjs.js +290 -257
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.d.ts +2 -1
- package/dist/router.js +279 -244
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +290 -257
- 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 +19 -19
- package/index.ts +1 -3
- package/package.json +1 -1
- package/router.ts +453 -405
- package/utils.ts +44 -33
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-bc2c864b
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Remix Software Inc.
|
|
5
5
|
*
|
|
@@ -573,6 +573,10 @@ let ResultType = /*#__PURE__*/function (ResultType) {
|
|
|
573
573
|
* Result from a loader or action - potentially successful or unsuccessful
|
|
574
574
|
*/
|
|
575
575
|
|
|
576
|
+
/**
|
|
577
|
+
* Result from a loader or action called via dataStrategy
|
|
578
|
+
*/
|
|
579
|
+
|
|
576
580
|
/**
|
|
577
581
|
* Users can specify either lowercase or uppercase form methods on `<Form>`,
|
|
578
582
|
* useSubmit(), `<fetcher.Form>`, etc.
|
|
@@ -889,7 +893,7 @@ function rankRouteBranches(branches) {
|
|
|
889
893
|
branches.sort((a, b) => a.score !== b.score ? b.score - a.score // Higher score first
|
|
890
894
|
: compareIndexes(a.routesMeta.map(meta => meta.childrenIndex), b.routesMeta.map(meta => meta.childrenIndex)));
|
|
891
895
|
}
|
|
892
|
-
const paramRe =
|
|
896
|
+
const paramRe = /^:[\w-]+$/;
|
|
893
897
|
const dynamicSegmentValue = 3;
|
|
894
898
|
const indexRouteValue = 2;
|
|
895
899
|
const emptySegmentValue = 1;
|
|
@@ -979,7 +983,7 @@ function generatePath(originalPath, params) {
|
|
|
979
983
|
// Apply the splat
|
|
980
984
|
return stringify(params[star]);
|
|
981
985
|
}
|
|
982
|
-
const keyMatch = segment.match(/^:(\w+)(\??)$/);
|
|
986
|
+
const keyMatch = segment.match(/^:([\w-]+)(\??)$/);
|
|
983
987
|
if (keyMatch) {
|
|
984
988
|
const [, key, optional] = keyMatch;
|
|
985
989
|
let param = params[key];
|
|
@@ -1061,7 +1065,7 @@ function compilePath(path, caseSensitive, end) {
|
|
|
1061
1065
|
let regexpSource = "^" + path.replace(/\/*\*?$/, "") // Ignore trailing / and /*, we'll handle it below
|
|
1062
1066
|
.replace(/^\/*/, "/") // Make sure it has a leading /
|
|
1063
1067
|
.replace(/[\\.*+^${}|()[\]]/g, "\\$&") // Escape special regex chars
|
|
1064
|
-
.replace(/\/:(\w+)(\?)?/g, (_, paramName, isOptional) => {
|
|
1068
|
+
.replace(/\/:([\w-]+)(\?)?/g, (_, paramName, isOptional) => {
|
|
1065
1069
|
params.push({
|
|
1066
1070
|
paramName,
|
|
1067
1071
|
isOptional: isOptional != null
|
|
@@ -1597,11 +1601,6 @@ function isRouteErrorResponse(error) {
|
|
|
1597
1601
|
/**
|
|
1598
1602
|
* Identified fetcher.load() calls that need to be revalidated
|
|
1599
1603
|
*/
|
|
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
1604
|
const validMutationMethodsArr = ["post", "put", "patch", "delete"];
|
|
1606
1605
|
const validMutationMethods = new Set(validMutationMethodsArr);
|
|
1607
1606
|
const validRequestMethodsArr = ["get", ...validMutationMethodsArr];
|
|
@@ -1654,8 +1653,6 @@ function createRouter(init) {
|
|
|
1654
1653
|
const isBrowser = typeof routerWindow !== "undefined" && typeof routerWindow.document !== "undefined" && typeof routerWindow.document.createElement !== "undefined";
|
|
1655
1654
|
const isServer = !isBrowser;
|
|
1656
1655
|
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
1656
|
let mapRouteProperties;
|
|
1660
1657
|
if (init.mapRouteProperties) {
|
|
1661
1658
|
mapRouteProperties = init.mapRouteProperties;
|
|
@@ -1675,6 +1672,7 @@ function createRouter(init) {
|
|
|
1675
1672
|
let dataRoutes = convertRoutesToDataRoutes(init.routes, mapRouteProperties, undefined, manifest);
|
|
1676
1673
|
let inFlightDataRoutes;
|
|
1677
1674
|
let basename = init.basename || "/";
|
|
1675
|
+
let dataStrategyImpl = init.unstable_dataStrategy || defaultDataStrategy;
|
|
1678
1676
|
// Config driven behavior flags
|
|
1679
1677
|
let future = _extends({
|
|
1680
1678
|
v7_fetcherPersist: false,
|
|
@@ -1717,18 +1715,28 @@ function createRouter(init) {
|
|
|
1717
1715
|
[route.id]: error
|
|
1718
1716
|
};
|
|
1719
1717
|
}
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1718
|
+
let initialized;
|
|
1719
|
+
let hasLazyRoutes = initialMatches.some(m => m.route.lazy);
|
|
1720
|
+
let hasLoaders = initialMatches.some(m => m.route.loader);
|
|
1721
|
+
if (hasLazyRoutes) {
|
|
1722
|
+
// All initialMatches need to be loaded before we're ready. If we have lazy
|
|
1723
|
+
// functions around still then we'll need to run them in initialize()
|
|
1724
|
+
initialized = false;
|
|
1725
|
+
} else if (!hasLoaders) {
|
|
1726
|
+
// If we've got no loaders to run, then we're good to go
|
|
1727
|
+
initialized = true;
|
|
1728
|
+
} else if (future.v7_partialHydration) {
|
|
1729
|
+
// If partial hydration is enabled, we're initialized so long as we were
|
|
1730
|
+
// provided with hydrationData for every route with a loader, and no loaders
|
|
1731
|
+
// were marked for explicit hydration
|
|
1732
|
+
let loaderData = init.hydrationData ? init.hydrationData.loaderData : null;
|
|
1733
|
+
let errors = init.hydrationData ? init.hydrationData.errors : null;
|
|
1734
|
+
initialized = initialMatches.every(m => m.route.loader && m.route.loader.hydrate !== true && (loaderData && loaderData[m.route.id] !== undefined || errors && errors[m.route.id] !== undefined));
|
|
1735
|
+
} else {
|
|
1736
|
+
// Without partial hydration - we're initialized if we were provided any
|
|
1737
|
+
// hydrationData - which is expected to be complete
|
|
1738
|
+
initialized = init.hydrationData != null;
|
|
1739
|
+
}
|
|
1732
1740
|
let router;
|
|
1733
1741
|
let state = {
|
|
1734
1742
|
historyAction: init.history.action,
|
|
@@ -1895,7 +1903,7 @@ function createRouter(init) {
|
|
|
1895
1903
|
// in the normal navigation flow. For SSR it's expected that lazy modules are
|
|
1896
1904
|
// resolved prior to router creation since we can't go into a fallbackElement
|
|
1897
1905
|
// UI for SSR'd apps
|
|
1898
|
-
if (!state.initialized
|
|
1906
|
+
if (!state.initialized) {
|
|
1899
1907
|
startNavigation(Action.Pop, state.location, {
|
|
1900
1908
|
initialHydration: true
|
|
1901
1909
|
});
|
|
@@ -2336,7 +2344,8 @@ function createRouter(init) {
|
|
|
2336
2344
|
})
|
|
2337
2345
|
};
|
|
2338
2346
|
} else {
|
|
2339
|
-
|
|
2347
|
+
let results = await callDataStrategy("action", request, [actionMatch], matches);
|
|
2348
|
+
result = results[0];
|
|
2340
2349
|
if (request.signal.aborted) {
|
|
2341
2350
|
return {
|
|
2342
2351
|
shortCircuited: true
|
|
@@ -2351,9 +2360,9 @@ function createRouter(init) {
|
|
|
2351
2360
|
// If the user didn't explicity indicate replace behavior, replace if
|
|
2352
2361
|
// we redirected to the exact same location we're currently at to avoid
|
|
2353
2362
|
// double back-buttons
|
|
2354
|
-
replace = result.
|
|
2363
|
+
replace = result.response.headers.get("Location") === state.location.pathname + state.location.search;
|
|
2355
2364
|
}
|
|
2356
|
-
await startRedirectNavigation(
|
|
2365
|
+
await startRedirectNavigation(request, result, {
|
|
2357
2366
|
submission,
|
|
2358
2367
|
replace
|
|
2359
2368
|
});
|
|
@@ -2476,7 +2485,7 @@ function createRouter(init) {
|
|
|
2476
2485
|
let {
|
|
2477
2486
|
loaderResults,
|
|
2478
2487
|
fetcherResults
|
|
2479
|
-
} = await
|
|
2488
|
+
} = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, request);
|
|
2480
2489
|
if (request.signal.aborted) {
|
|
2481
2490
|
return {
|
|
2482
2491
|
shortCircuited: true
|
|
@@ -2501,7 +2510,7 @@ function createRouter(init) {
|
|
|
2501
2510
|
let fetcherKey = revalidatingFetchers[redirect.idx - matchesToLoad.length].key;
|
|
2502
2511
|
fetchRedirectIds.add(fetcherKey);
|
|
2503
2512
|
}
|
|
2504
|
-
await startRedirectNavigation(
|
|
2513
|
+
await startRedirectNavigation(request, redirect.result, {
|
|
2505
2514
|
replace
|
|
2506
2515
|
});
|
|
2507
2516
|
return {
|
|
@@ -2610,7 +2619,8 @@ function createRouter(init) {
|
|
|
2610
2619
|
let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);
|
|
2611
2620
|
fetchControllers.set(key, abortController);
|
|
2612
2621
|
let originatingLoadId = incrementingLoadId;
|
|
2613
|
-
let
|
|
2622
|
+
let actionResults = await callDataStrategy("action", fetchRequest, [match], requestMatches);
|
|
2623
|
+
let actionResult = actionResults[0];
|
|
2614
2624
|
if (fetchRequest.signal.aborted) {
|
|
2615
2625
|
// We can delete this so long as we weren't aborted by our own fetcher
|
|
2616
2626
|
// re-submit which would have put _new_ controller is in fetchControllers
|
|
@@ -2642,7 +2652,7 @@ function createRouter(init) {
|
|
|
2642
2652
|
} else {
|
|
2643
2653
|
fetchRedirectIds.add(key);
|
|
2644
2654
|
updateFetcherState(key, getLoadingFetcher(submission));
|
|
2645
|
-
return startRedirectNavigation(
|
|
2655
|
+
return startRedirectNavigation(fetchRequest, actionResult, {
|
|
2646
2656
|
fetcherSubmission: submission
|
|
2647
2657
|
});
|
|
2648
2658
|
}
|
|
@@ -2699,7 +2709,7 @@ function createRouter(init) {
|
|
|
2699
2709
|
let {
|
|
2700
2710
|
loaderResults,
|
|
2701
2711
|
fetcherResults
|
|
2702
|
-
} = await
|
|
2712
|
+
} = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, revalidationRequest);
|
|
2703
2713
|
if (abortController.signal.aborted) {
|
|
2704
2714
|
return;
|
|
2705
2715
|
}
|
|
@@ -2716,7 +2726,7 @@ function createRouter(init) {
|
|
|
2716
2726
|
let fetcherKey = revalidatingFetchers[redirect.idx - matchesToLoad.length].key;
|
|
2717
2727
|
fetchRedirectIds.add(fetcherKey);
|
|
2718
2728
|
}
|
|
2719
|
-
return startRedirectNavigation(
|
|
2729
|
+
return startRedirectNavigation(revalidationRequest, redirect.result);
|
|
2720
2730
|
}
|
|
2721
2731
|
|
|
2722
2732
|
// Process and commit output from loaders
|
|
@@ -2770,7 +2780,8 @@ function createRouter(init) {
|
|
|
2770
2780
|
let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
|
|
2771
2781
|
fetchControllers.set(key, abortController);
|
|
2772
2782
|
let originatingLoadId = incrementingLoadId;
|
|
2773
|
-
let
|
|
2783
|
+
let results = await callDataStrategy("loader", fetchRequest, [match], matches);
|
|
2784
|
+
let result = results[0];
|
|
2774
2785
|
|
|
2775
2786
|
// Deferred isn't supported for fetcher loads, await everything and treat it
|
|
2776
2787
|
// as a normal load. resolveDeferredData will return undefined if this
|
|
@@ -2805,7 +2816,7 @@ function createRouter(init) {
|
|
|
2805
2816
|
return;
|
|
2806
2817
|
} else {
|
|
2807
2818
|
fetchRedirectIds.add(key);
|
|
2808
|
-
await startRedirectNavigation(
|
|
2819
|
+
await startRedirectNavigation(fetchRequest, result);
|
|
2809
2820
|
return;
|
|
2810
2821
|
}
|
|
2811
2822
|
}
|
|
@@ -2840,26 +2851,40 @@ function createRouter(init) {
|
|
|
2840
2851
|
* actually touch history until we've processed redirects, so we just use
|
|
2841
2852
|
* the history action from the original navigation (PUSH or REPLACE).
|
|
2842
2853
|
*/
|
|
2843
|
-
async function startRedirectNavigation(
|
|
2854
|
+
async function startRedirectNavigation(request, redirect, _temp2) {
|
|
2844
2855
|
let {
|
|
2845
2856
|
submission,
|
|
2846
2857
|
fetcherSubmission,
|
|
2847
2858
|
replace
|
|
2848
2859
|
} = _temp2 === void 0 ? {} : _temp2;
|
|
2849
|
-
if (redirect.
|
|
2860
|
+
if (redirect.response.headers.has("X-Remix-Revalidate")) {
|
|
2850
2861
|
isRevalidationRequired = true;
|
|
2851
2862
|
}
|
|
2852
|
-
let
|
|
2863
|
+
let location = redirect.response.headers.get("Location");
|
|
2864
|
+
invariant(location, "Expected a Location header on the redirect Response");
|
|
2865
|
+
let redirectLocation = createLocation(state.location, location, {
|
|
2853
2866
|
_isRedirect: true
|
|
2854
2867
|
});
|
|
2855
|
-
|
|
2868
|
+
if (ABSOLUTE_URL_REGEX.test(location)) {
|
|
2869
|
+
// Strip off the protocol+origin for same-origin + same-basename absolute redirects
|
|
2870
|
+
let normalizedLocation = location;
|
|
2871
|
+
let currentUrl = new URL(request.url);
|
|
2872
|
+
let url = normalizedLocation.startsWith("//") ? new URL(currentUrl.protocol + normalizedLocation) : new URL(normalizedLocation);
|
|
2873
|
+
let isSameBasename = stripBasename(url.pathname, basename) != null;
|
|
2874
|
+
if (url.origin === currentUrl.origin && isSameBasename) {
|
|
2875
|
+
normalizedLocation = url.pathname + url.search + url.hash;
|
|
2876
|
+
redirectLocation = createLocation(state.location, normalizedLocation, {
|
|
2877
|
+
_isRedirect: true
|
|
2878
|
+
});
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2856
2881
|
if (isBrowser) {
|
|
2857
2882
|
let isDocumentReload = false;
|
|
2858
|
-
if (redirect.
|
|
2883
|
+
if (redirect.response.headers.has("X-Remix-Reload-Document")) {
|
|
2859
2884
|
// Hard reload if the response contained X-Remix-Reload-Document
|
|
2860
2885
|
isDocumentReload = true;
|
|
2861
|
-
} else if (ABSOLUTE_URL_REGEX.test(
|
|
2862
|
-
const url = init.history.createURL(
|
|
2886
|
+
} else if (ABSOLUTE_URL_REGEX.test(location)) {
|
|
2887
|
+
const url = init.history.createURL(location);
|
|
2863
2888
|
isDocumentReload =
|
|
2864
2889
|
// Hard reload if it's an absolute URL to a new origin
|
|
2865
2890
|
url.origin !== routerWindow.location.origin ||
|
|
@@ -2868,9 +2893,9 @@ function createRouter(init) {
|
|
|
2868
2893
|
}
|
|
2869
2894
|
if (isDocumentReload) {
|
|
2870
2895
|
if (replace) {
|
|
2871
|
-
routerWindow.location.replace(
|
|
2896
|
+
routerWindow.location.replace(location);
|
|
2872
2897
|
} else {
|
|
2873
|
-
routerWindow.location.assign(
|
|
2898
|
+
routerWindow.location.assign(location);
|
|
2874
2899
|
}
|
|
2875
2900
|
return;
|
|
2876
2901
|
}
|
|
@@ -2896,10 +2921,10 @@ function createRouter(init) {
|
|
|
2896
2921
|
// re-submit the GET/POST/PUT/PATCH/DELETE as a submission navigation to the
|
|
2897
2922
|
// redirected location
|
|
2898
2923
|
let activeSubmission = submission || fetcherSubmission;
|
|
2899
|
-
if (redirectPreserveMethodStatusCodes.has(redirect.status) && activeSubmission && isMutationMethod(activeSubmission.formMethod)) {
|
|
2924
|
+
if (redirectPreserveMethodStatusCodes.has(redirect.response.status) && activeSubmission && isMutationMethod(activeSubmission.formMethod)) {
|
|
2900
2925
|
await startNavigation(redirectHistoryAction, redirectLocation, {
|
|
2901
2926
|
submission: _extends({}, activeSubmission, {
|
|
2902
|
-
formAction:
|
|
2927
|
+
formAction: location
|
|
2903
2928
|
}),
|
|
2904
2929
|
// Preserve this flag across redirects
|
|
2905
2930
|
preventScrollReset: pendingPreventScrollReset
|
|
@@ -2917,16 +2942,36 @@ function createRouter(init) {
|
|
|
2917
2942
|
});
|
|
2918
2943
|
}
|
|
2919
2944
|
}
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2945
|
+
|
|
2946
|
+
// Utility wrapper for calling dataStrategy client-side without having to
|
|
2947
|
+
// pass around the manifest, mapRouteProperties, etc.
|
|
2948
|
+
async function callDataStrategy(type, request, matchesToLoad, matches) {
|
|
2949
|
+
try {
|
|
2950
|
+
let results = await callDataStrategyImpl(dataStrategyImpl, type, request, matchesToLoad, matches, manifest, mapRouteProperties);
|
|
2951
|
+
return await Promise.all(results.map((result, i) => {
|
|
2952
|
+
if (isRedirectHandlerResult(result)) {
|
|
2953
|
+
return {
|
|
2954
|
+
type: ResultType.redirect,
|
|
2955
|
+
response: normalizeRelativeRoutingRedirectResponse(result.result, request, matchesToLoad[i].route.id, matches, basename, future.v7_relativeSplatPath)
|
|
2956
|
+
};
|
|
2957
|
+
}
|
|
2958
|
+
return convertHandlerResultToDataResult(result);
|
|
2959
|
+
}));
|
|
2960
|
+
} catch (e) {
|
|
2961
|
+
// If the outer dataStrategy method throws, just return the error for all
|
|
2962
|
+
// matches - and it'll naturally bubble to the root
|
|
2963
|
+
return matchesToLoad.map(() => ({
|
|
2964
|
+
type: ResultType.error,
|
|
2965
|
+
error: e
|
|
2966
|
+
}));
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
async function callLoadersAndMaybeResolveData(currentMatches, matches, matchesToLoad, fetchersToLoad, request) {
|
|
2970
|
+
let [loaderResults, ...fetcherResults] = await Promise.all([matchesToLoad.length ? callDataStrategy("loader", request, matchesToLoad, matches) : [], ...fetchersToLoad.map(f => {
|
|
2971
|
+
if (f.matches && f.match && f.controller) {
|
|
2972
|
+
let fetcherRequest = createClientSideRequest(init.history, f.path, f.controller.signal);
|
|
2973
|
+
return callDataStrategy("loader", fetcherRequest, [f.match], f.matches).then(r => r[0]);
|
|
2974
|
+
} else {
|
|
2930
2975
|
return Promise.resolve({
|
|
2931
2976
|
type: ResultType.error,
|
|
2932
2977
|
error: getInternalRouterError(404, {
|
|
@@ -2934,16 +2979,6 @@ function createRouter(init) {
|
|
|
2934
2979
|
})
|
|
2935
2980
|
});
|
|
2936
2981
|
}
|
|
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
2982
|
})]);
|
|
2948
2983
|
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
2984
|
return {
|
|
@@ -3257,10 +3292,9 @@ const UNSAFE_DEFERRED_SYMBOL = Symbol("deferred");
|
|
|
3257
3292
|
|
|
3258
3293
|
function createStaticHandler(routes, opts) {
|
|
3259
3294
|
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
3295
|
let manifest = {};
|
|
3263
3296
|
let basename = (opts ? opts.basename : null) || "/";
|
|
3297
|
+
let dataStrategyImpl = (opts == null ? void 0 : opts.unstable_dataStrategy) || defaultDataStrategy;
|
|
3264
3298
|
let mapRouteProperties;
|
|
3265
3299
|
if (opts != null && opts.mapRouteProperties) {
|
|
3266
3300
|
mapRouteProperties = opts.mapRouteProperties;
|
|
@@ -3275,7 +3309,8 @@ function createStaticHandler(routes, opts) {
|
|
|
3275
3309
|
}
|
|
3276
3310
|
// Config driven behavior flags
|
|
3277
3311
|
let future = _extends({
|
|
3278
|
-
v7_relativeSplatPath: false
|
|
3312
|
+
v7_relativeSplatPath: false,
|
|
3313
|
+
v7_throwAbortReason: false
|
|
3279
3314
|
}, opts ? opts.future : null);
|
|
3280
3315
|
let dataRoutes = convertRoutesToDataRoutes(routes, mapRouteProperties, undefined, manifest);
|
|
3281
3316
|
|
|
@@ -3459,14 +3494,14 @@ function createStaticHandler(routes, opts) {
|
|
|
3459
3494
|
actionHeaders: {}
|
|
3460
3495
|
});
|
|
3461
3496
|
} catch (e) {
|
|
3462
|
-
// If the user threw/returned a Response in callLoaderOrAction
|
|
3463
|
-
//
|
|
3464
|
-
//
|
|
3465
|
-
if (
|
|
3497
|
+
// If the user threw/returned a Response in callLoaderOrAction for a
|
|
3498
|
+
// `queryRoute` call, we throw the `HandlerResult` to bail out early
|
|
3499
|
+
// and then return or throw the raw Response here accordingly
|
|
3500
|
+
if (isHandlerResult(e) && isResponse(e.result)) {
|
|
3466
3501
|
if (e.type === ResultType.error) {
|
|
3467
|
-
throw e.
|
|
3502
|
+
throw e.result;
|
|
3468
3503
|
}
|
|
3469
|
-
return e.
|
|
3504
|
+
return e.result;
|
|
3470
3505
|
}
|
|
3471
3506
|
// Redirects are always returned since they don't propagate to catch
|
|
3472
3507
|
// boundaries
|
|
@@ -3492,14 +3527,10 @@ function createStaticHandler(routes, opts) {
|
|
|
3492
3527
|
error
|
|
3493
3528
|
};
|
|
3494
3529
|
} else {
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
isRouteRequest,
|
|
3498
|
-
requestContext
|
|
3499
|
-
});
|
|
3530
|
+
let results = await callDataStrategy("action", request, [actionMatch], matches, isRouteRequest, requestContext);
|
|
3531
|
+
result = results[0];
|
|
3500
3532
|
if (request.signal.aborted) {
|
|
3501
|
-
|
|
3502
|
-
throw new Error(method + "() call aborted: " + request.method + " " + request.url);
|
|
3533
|
+
throwStaticHandlerAbortedError(request, isRouteRequest, future);
|
|
3503
3534
|
}
|
|
3504
3535
|
}
|
|
3505
3536
|
if (isRedirectResult(result)) {
|
|
@@ -3508,9 +3539,9 @@ function createStaticHandler(routes, opts) {
|
|
|
3508
3539
|
// can get back on the "throw all redirect responses" train here should
|
|
3509
3540
|
// this ever happen :/
|
|
3510
3541
|
throw new Response(null, {
|
|
3511
|
-
status: result.status,
|
|
3542
|
+
status: result.response.status,
|
|
3512
3543
|
headers: {
|
|
3513
|
-
Location: result.
|
|
3544
|
+
Location: result.response.headers.get("Location")
|
|
3514
3545
|
}
|
|
3515
3546
|
});
|
|
3516
3547
|
}
|
|
@@ -3559,8 +3590,8 @@ function createStaticHandler(routes, opts) {
|
|
|
3559
3590
|
return _extends({}, context, {
|
|
3560
3591
|
statusCode: isRouteErrorResponse(result.error) ? result.error.status : 500,
|
|
3561
3592
|
actionData: null,
|
|
3562
|
-
actionHeaders: _extends({}, result.
|
|
3563
|
-
[actionMatch.route.id]: result.headers
|
|
3593
|
+
actionHeaders: _extends({}, result.response ? {
|
|
3594
|
+
[actionMatch.route.id]: result.response.headers
|
|
3564
3595
|
} : {})
|
|
3565
3596
|
});
|
|
3566
3597
|
}
|
|
@@ -3572,15 +3603,17 @@ function createStaticHandler(routes, opts) {
|
|
|
3572
3603
|
signal: request.signal
|
|
3573
3604
|
});
|
|
3574
3605
|
let context = await loadRouteData(loaderRequest, matches, requestContext);
|
|
3575
|
-
return _extends({}, context,
|
|
3576
|
-
statusCode: result.statusCode
|
|
3577
|
-
} : {}, {
|
|
3606
|
+
return _extends({}, context, {
|
|
3578
3607
|
actionData: {
|
|
3579
3608
|
[actionMatch.route.id]: result.data
|
|
3580
|
-
}
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3609
|
+
}
|
|
3610
|
+
}, result.response ? {
|
|
3611
|
+
statusCode: result.response.status,
|
|
3612
|
+
actionHeaders: {
|
|
3613
|
+
[actionMatch.route.id]: result.response.headers
|
|
3614
|
+
}
|
|
3615
|
+
} : {
|
|
3616
|
+
actionHeaders: {}
|
|
3584
3617
|
});
|
|
3585
3618
|
}
|
|
3586
3619
|
async function loadRouteData(request, matches, requestContext, routeMatch, pendingActionError) {
|
|
@@ -3611,21 +3644,9 @@ function createStaticHandler(routes, opts) {
|
|
|
3611
3644
|
activeDeferreds: null
|
|
3612
3645
|
};
|
|
3613
3646
|
}
|
|
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
|
-
});
|
|
3647
|
+
let results = await callDataStrategy("loader", request, matchesToLoad, matches, isRouteRequest, requestContext);
|
|
3626
3648
|
if (request.signal.aborted) {
|
|
3627
|
-
|
|
3628
|
-
throw new Error(method + "() call aborted: " + request.method + " " + request.url);
|
|
3649
|
+
throwStaticHandlerAbortedError(request, isRouteRequest, future);
|
|
3629
3650
|
}
|
|
3630
3651
|
|
|
3631
3652
|
// Process and commit output from loaders
|
|
@@ -3644,6 +3665,25 @@ function createStaticHandler(routes, opts) {
|
|
|
3644
3665
|
activeDeferreds: activeDeferreds.size > 0 ? Object.fromEntries(activeDeferreds.entries()) : null
|
|
3645
3666
|
});
|
|
3646
3667
|
}
|
|
3668
|
+
|
|
3669
|
+
// Utility wrapper for calling dataStrategy server-side without having to
|
|
3670
|
+
// pass around the manifest, mapRouteProperties, etc.
|
|
3671
|
+
async function callDataStrategy(type, request, matchesToLoad, matches, isRouteRequest, requestContext) {
|
|
3672
|
+
let results = await callDataStrategyImpl(dataStrategyImpl, type, request, matchesToLoad, matches, manifest, mapRouteProperties, requestContext);
|
|
3673
|
+
return await Promise.all(results.map((result, i) => {
|
|
3674
|
+
if (isRedirectHandlerResult(result)) {
|
|
3675
|
+
// Throw redirects and let the server handle them with an HTTP redirect
|
|
3676
|
+
throw normalizeRelativeRoutingRedirectResponse(result.result, request, matchesToLoad[i].route.id, matches, basename, future.v7_relativeSplatPath);
|
|
3677
|
+
}
|
|
3678
|
+
if (isResponse(result.result) && isRouteRequest) {
|
|
3679
|
+
// For SSR single-route requests, we want to hand Responses back directly
|
|
3680
|
+
// without unwrapping. We do this with the QueryRouteResponse wrapper
|
|
3681
|
+
// interface so we can know whether it was returned or thrown
|
|
3682
|
+
throw result;
|
|
3683
|
+
}
|
|
3684
|
+
return convertHandlerResultToDataResult(result);
|
|
3685
|
+
}));
|
|
3686
|
+
}
|
|
3647
3687
|
return {
|
|
3648
3688
|
dataRoutes,
|
|
3649
3689
|
query,
|
|
@@ -3657,27 +3697,26 @@ function createStaticHandler(routes, opts) {
|
|
|
3657
3697
|
//#region Helpers
|
|
3658
3698
|
////////////////////////////////////////////////////////////////////////////////
|
|
3659
3699
|
|
|
3660
|
-
function defaultDataStrategy(_ref3) {
|
|
3661
|
-
let {
|
|
3662
|
-
defaultStrategy,
|
|
3663
|
-
matches
|
|
3664
|
-
} = _ref3;
|
|
3665
|
-
return Promise.all(matches.map(match => defaultStrategy(match)));
|
|
3666
|
-
}
|
|
3667
|
-
|
|
3668
3700
|
/**
|
|
3669
3701
|
* Given an existing StaticHandlerContext and an error thrown at render time,
|
|
3670
3702
|
* provide an updated StaticHandlerContext suitable for a second SSR render
|
|
3671
3703
|
*/
|
|
3672
3704
|
function getStaticContextFromError(routes, context, error) {
|
|
3673
3705
|
let newContext = _extends({}, context, {
|
|
3674
|
-
statusCode: 500,
|
|
3706
|
+
statusCode: isRouteErrorResponse(error) ? error.status : 500,
|
|
3675
3707
|
errors: {
|
|
3676
3708
|
[context._deepestRenderedBoundaryId || routes[0].id]: error
|
|
3677
3709
|
}
|
|
3678
3710
|
});
|
|
3679
3711
|
return newContext;
|
|
3680
3712
|
}
|
|
3713
|
+
function throwStaticHandlerAbortedError(request, isRouteRequest, future) {
|
|
3714
|
+
if (future.v7_throwAbortReason && request.signal.reason !== undefined) {
|
|
3715
|
+
throw request.signal.reason;
|
|
3716
|
+
}
|
|
3717
|
+
let method = isRouteRequest ? "queryRoute" : "query";
|
|
3718
|
+
throw new Error(method + "() call aborted: " + request.method + " " + request.url);
|
|
3719
|
+
}
|
|
3681
3720
|
function isSubmissionNavigation(opts) {
|
|
3682
3721
|
return opts != null && ("formData" in opts && opts.formData != null || "body" in opts && opts.body !== undefined);
|
|
3683
3722
|
}
|
|
@@ -3762,8 +3801,8 @@ function normalizeNavigateOptions(normalizeFormMethod, isFetcher, path, opts) {
|
|
|
3762
3801
|
}
|
|
3763
3802
|
let text = typeof opts.body === "string" ? opts.body : opts.body instanceof FormData || opts.body instanceof URLSearchParams ?
|
|
3764
3803
|
// 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] =
|
|
3804
|
+
Array.from(opts.body.entries()).reduce((acc, _ref3) => {
|
|
3805
|
+
let [name, value] = _ref3;
|
|
3767
3806
|
return "" + acc + name + "=" + value + "\n";
|
|
3768
3807
|
}, "") : String(opts.body);
|
|
3769
3808
|
return {
|
|
@@ -3874,18 +3913,24 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
|
|
|
3874
3913
|
let boundaryId = pendingError ? Object.keys(pendingError)[0] : undefined;
|
|
3875
3914
|
let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
|
|
3876
3915
|
let navigationMatches = boundaryMatches.filter((match, index) => {
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
}
|
|
3882
|
-
if (match.route.lazy) {
|
|
3916
|
+
let {
|
|
3917
|
+
route
|
|
3918
|
+
} = match;
|
|
3919
|
+
if (route.lazy) {
|
|
3883
3920
|
// We haven't loaded this route yet so we don't know if it's got a loader!
|
|
3884
3921
|
return true;
|
|
3885
3922
|
}
|
|
3886
|
-
if (
|
|
3923
|
+
if (route.loader == null) {
|
|
3887
3924
|
return false;
|
|
3888
3925
|
}
|
|
3926
|
+
if (isInitialLoad) {
|
|
3927
|
+
if (route.loader.hydrate) {
|
|
3928
|
+
return true;
|
|
3929
|
+
}
|
|
3930
|
+
return state.loaderData[route.id] === undefined && (
|
|
3931
|
+
// Don't re-run if the loader ran and threw an error
|
|
3932
|
+
!state.errors || state.errors[route.id] === undefined);
|
|
3933
|
+
}
|
|
3889
3934
|
|
|
3890
3935
|
// Always call the loader on new route instances and pending defer cancellations
|
|
3891
3936
|
if (isNewLoader(state.loaderData, state.matches[index], match) || cancelledDeferredRoutes.some(id => id === match.route.id)) {
|
|
@@ -3987,20 +4032,6 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
|
|
|
3987
4032
|
});
|
|
3988
4033
|
return [navigationMatches, revalidatingFetchers];
|
|
3989
4034
|
}
|
|
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
4035
|
function isNewLoader(currentLoaderData, currentMatch, match) {
|
|
4005
4036
|
let isNew =
|
|
4006
4037
|
// [a] -> [a, b]
|
|
@@ -4088,44 +4119,57 @@ async function loadLazyRouteModule(route, mapRouteProperties, manifest) {
|
|
|
4088
4119
|
}));
|
|
4089
4120
|
return routeToUpdate;
|
|
4090
4121
|
}
|
|
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
|
-
};
|
|
4122
|
+
|
|
4123
|
+
// Default implementation of `dataStrategy` which fetches all loaders in parallel
|
|
4124
|
+
function defaultDataStrategy(opts) {
|
|
4125
|
+
return Promise.all(opts.matches.map(m => m.bikeshed_loadRoute()));
|
|
4106
4126
|
}
|
|
4107
|
-
function
|
|
4108
|
-
let
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4127
|
+
async function callDataStrategyImpl(dataStrategyImpl, type, request, matchesToLoad, matches, manifest, mapRouteProperties, requestContext) {
|
|
4128
|
+
let routeIdsToLoad = matchesToLoad.reduce((acc, m) => acc.add(m.route.id), new Set());
|
|
4129
|
+
let loadedMatches = new Set();
|
|
4130
|
+
|
|
4131
|
+
// Send all matches here to allow for a middleware-type implementation.
|
|
4132
|
+
// handler will be a no-op for unneeded routes and we filter those results
|
|
4133
|
+
// back out below.
|
|
4134
|
+
let results = await dataStrategyImpl({
|
|
4135
|
+
matches: matches.map(match => {
|
|
4136
|
+
// `bikeshed_loadRoute` encapsulates the route.lazy, executing the
|
|
4137
|
+
// loader/action, and mapping return values/thrown errors to a
|
|
4138
|
+
// HandlerResult. Users can pass a callback to take fine-grained control
|
|
4139
|
+
// over the execution of the loader/action
|
|
4140
|
+
let bikeshed_loadRoute = bikeshed_handlerOverride => {
|
|
4141
|
+
loadedMatches.add(match.route.id);
|
|
4142
|
+
return routeIdsToLoad.has(match.route.id) ? callLoaderOrAction(type, request, match, manifest, mapRouteProperties, bikeshed_handlerOverride, requestContext) :
|
|
4143
|
+
// TODO: What's the best thing to do here - return an empty "success" result?
|
|
4144
|
+
// Or return a success result with the current route loader/action data?
|
|
4145
|
+
// We strip these results out if the route didn't need to be revalidated in
|
|
4146
|
+
// `callDataStrategy` so it doesn't matter for us. It's more of a question
|
|
4147
|
+
// of whether exposing the current data to the user is useful?
|
|
4148
|
+
Promise.resolve({
|
|
4149
|
+
type: ResultType.data,
|
|
4150
|
+
result: undefined
|
|
4151
|
+
});
|
|
4152
|
+
};
|
|
4153
|
+
return _extends({}, match, {
|
|
4154
|
+
bikeshed_loadRoute
|
|
4155
|
+
});
|
|
4156
|
+
}),
|
|
4157
|
+
request,
|
|
4158
|
+
params: matches[0].params
|
|
4122
4159
|
});
|
|
4160
|
+
|
|
4161
|
+
// Throw if any loadRoute implementations not called since they are what
|
|
4162
|
+
// ensures a route is fully loaded
|
|
4163
|
+
// Throw if any loadRoute implementations not called since they are what
|
|
4164
|
+
// ensure a route is fully loaded
|
|
4165
|
+
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."));
|
|
4166
|
+
|
|
4167
|
+
// Filter out any middleware-only matches for which we didn't need to run handlers
|
|
4168
|
+
return results.filter((_, i) => routeIdsToLoad.has(matches[i].route.id));
|
|
4123
4169
|
}
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
}
|
|
4128
|
-
let resultType;
|
|
4170
|
+
|
|
4171
|
+
// Default logic for calling a loader/action is the user has no specified a dataStrategy
|
|
4172
|
+
async function callLoaderOrAction(type, request, match, manifest, mapRouteProperties, bikeshed_handlerOverride, staticContext) {
|
|
4129
4173
|
let result;
|
|
4130
4174
|
let onReject;
|
|
4131
4175
|
let runHandler = handler => {
|
|
@@ -4134,11 +4178,13 @@ async function callLoaderOrActionImplementation(type, request, match, matches, b
|
|
|
4134
4178
|
let abortPromise = new Promise((_, r) => reject = r);
|
|
4135
4179
|
onReject = () => reject();
|
|
4136
4180
|
request.signal.addEventListener("abort", onReject);
|
|
4137
|
-
|
|
4181
|
+
let handlerArg = {
|
|
4138
4182
|
request,
|
|
4139
4183
|
params: match.params,
|
|
4140
|
-
context:
|
|
4141
|
-
}
|
|
4184
|
+
context: staticContext
|
|
4185
|
+
};
|
|
4186
|
+
let handlerPromise = bikeshed_handlerOverride ? bikeshed_handlerOverride(async ctx => ctx !== undefined ? handler(handlerArg, ctx) : handler(handlerArg)) : handler(handlerArg);
|
|
4187
|
+
return Promise.race([handlerPromise, abortPromise]);
|
|
4142
4188
|
};
|
|
4143
4189
|
try {
|
|
4144
4190
|
let handler = match.route[type];
|
|
@@ -4152,17 +4198,17 @@ async function callLoaderOrActionImplementation(type, request, match, matches, b
|
|
|
4152
4198
|
// route has a boundary that can handle the error
|
|
4153
4199
|
runHandler(handler).catch(e => {
|
|
4154
4200
|
handlerError = e;
|
|
4155
|
-
}), match.route]);
|
|
4201
|
+
}), loadLazyRouteModule(match.route, mapRouteProperties, manifest)]);
|
|
4156
4202
|
if (handlerError) {
|
|
4157
4203
|
throw handlerError;
|
|
4158
4204
|
}
|
|
4159
4205
|
result = values[0];
|
|
4160
4206
|
} else {
|
|
4161
4207
|
// Load lazy route module, then run any returned handler
|
|
4162
|
-
|
|
4163
|
-
handler = route[type];
|
|
4208
|
+
await loadLazyRouteModule(match.route, mapRouteProperties, manifest);
|
|
4209
|
+
handler = match.route[type];
|
|
4164
4210
|
if (handler) {
|
|
4165
|
-
// Handler still
|
|
4211
|
+
// Handler still runs even if we got interrupted to maintain consistency
|
|
4166
4212
|
// with un-abortable behavior of handler execution on non-lazy or
|
|
4167
4213
|
// previously-lazy-loaded routes
|
|
4168
4214
|
result = await runHandler(handler);
|
|
@@ -4179,7 +4225,7 @@ async function callLoaderOrActionImplementation(type, request, match, matches, b
|
|
|
4179
4225
|
// hit the invariant below that errors on returning undefined.
|
|
4180
4226
|
return {
|
|
4181
4227
|
type: ResultType.data,
|
|
4182
|
-
|
|
4228
|
+
result: undefined
|
|
4183
4229
|
};
|
|
4184
4230
|
}
|
|
4185
4231
|
}
|
|
@@ -4194,70 +4240,37 @@ async function callLoaderOrActionImplementation(type, request, match, matches, b
|
|
|
4194
4240
|
}
|
|
4195
4241
|
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
4242
|
} catch (e) {
|
|
4197
|
-
|
|
4198
|
-
|
|
4243
|
+
return {
|
|
4244
|
+
type: ResultType.error,
|
|
4245
|
+
result: e
|
|
4246
|
+
};
|
|
4199
4247
|
} finally {
|
|
4200
4248
|
if (onReject) {
|
|
4201
4249
|
request.signal.removeEventListener("abort", onReject);
|
|
4202
4250
|
}
|
|
4203
4251
|
}
|
|
4252
|
+
return {
|
|
4253
|
+
type: ResultType.data,
|
|
4254
|
+
result
|
|
4255
|
+
};
|
|
4256
|
+
}
|
|
4257
|
+
async function convertHandlerResultToDataResult(handlerResult) {
|
|
4258
|
+
let {
|
|
4259
|
+
result,
|
|
4260
|
+
type
|
|
4261
|
+
} = handlerResult;
|
|
4204
4262
|
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
4263
|
let data;
|
|
4255
4264
|
try {
|
|
4256
4265
|
let contentType = result.headers.get("Content-Type");
|
|
4257
4266
|
// Check between word boundaries instead of startsWith() due to the last
|
|
4258
4267
|
// paragraph of https://httpwg.org/specs/rfc9110.html#field.content-type
|
|
4259
4268
|
if (contentType && /\bapplication\/json\b/.test(contentType)) {
|
|
4260
|
-
|
|
4269
|
+
if (result.body == null) {
|
|
4270
|
+
data = null;
|
|
4271
|
+
} else {
|
|
4272
|
+
data = await result.json();
|
|
4273
|
+
}
|
|
4261
4274
|
} else {
|
|
4262
4275
|
data = await result.text();
|
|
4263
4276
|
}
|
|
@@ -4267,23 +4280,22 @@ async function callLoaderOrActionImplementation(type, request, match, matches, b
|
|
|
4267
4280
|
error: e
|
|
4268
4281
|
};
|
|
4269
4282
|
}
|
|
4270
|
-
if (
|
|
4283
|
+
if (type === ResultType.error) {
|
|
4271
4284
|
return {
|
|
4272
|
-
type
|
|
4273
|
-
error: new ErrorResponseImpl(status, result.statusText, data),
|
|
4274
|
-
|
|
4285
|
+
type,
|
|
4286
|
+
error: new ErrorResponseImpl(result.status, result.statusText, data),
|
|
4287
|
+
response: result
|
|
4275
4288
|
};
|
|
4276
4289
|
}
|
|
4277
4290
|
return {
|
|
4278
4291
|
type: ResultType.data,
|
|
4279
4292
|
data,
|
|
4280
|
-
|
|
4281
|
-
headers: result.headers
|
|
4293
|
+
response: result
|
|
4282
4294
|
};
|
|
4283
4295
|
}
|
|
4284
|
-
if (
|
|
4296
|
+
if (type === ResultType.error) {
|
|
4285
4297
|
return {
|
|
4286
|
-
type
|
|
4298
|
+
type,
|
|
4287
4299
|
error: result
|
|
4288
4300
|
};
|
|
4289
4301
|
}
|
|
@@ -4302,6 +4314,18 @@ async function callLoaderOrActionImplementation(type, request, match, matches, b
|
|
|
4302
4314
|
};
|
|
4303
4315
|
}
|
|
4304
4316
|
|
|
4317
|
+
// Support relative routing in internal redirects
|
|
4318
|
+
function normalizeRelativeRoutingRedirectResponse(response, request, routeId, matches, basename, v7_relativeSplatPath) {
|
|
4319
|
+
let location = response.headers.get("Location");
|
|
4320
|
+
invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header");
|
|
4321
|
+
if (!ABSOLUTE_URL_REGEX.test(location)) {
|
|
4322
|
+
let trimmedMatches = matches.slice(0, matches.findIndex(m => m.route.id === routeId) + 1);
|
|
4323
|
+
location = normalizeTo(new URL(request.url), trimmedMatches, basename, true, location, v7_relativeSplatPath);
|
|
4324
|
+
response.headers.set("Location", location);
|
|
4325
|
+
}
|
|
4326
|
+
return response;
|
|
4327
|
+
}
|
|
4328
|
+
|
|
4305
4329
|
// Utility method for creating the Request instances for loaders/actions during
|
|
4306
4330
|
// client-side navigations and fetches. During SSR we will always have a
|
|
4307
4331
|
// Request instance from the static handler (query/queryRoute)
|
|
@@ -4392,24 +4416,31 @@ function processRouteLoaderData(matches, matchesToLoad, results, pendingError, a
|
|
|
4392
4416
|
foundError = true;
|
|
4393
4417
|
statusCode = isRouteErrorResponse(result.error) ? result.error.status : 500;
|
|
4394
4418
|
}
|
|
4395
|
-
if (result.
|
|
4396
|
-
loaderHeaders[id] = result.headers;
|
|
4419
|
+
if (result.response) {
|
|
4420
|
+
loaderHeaders[id] = result.response.headers;
|
|
4397
4421
|
}
|
|
4398
4422
|
} else {
|
|
4399
4423
|
if (isDeferredResult(result)) {
|
|
4400
4424
|
activeDeferreds.set(id, result.deferredData);
|
|
4401
4425
|
loaderData[id] = result.deferredData.data;
|
|
4426
|
+
// Error status codes always override success status codes, but if all
|
|
4427
|
+
// loaders are successful we take the deepest status code.
|
|
4428
|
+
if (result.statusCode != null && result.statusCode !== 200 && !foundError) {
|
|
4429
|
+
statusCode = result.statusCode;
|
|
4430
|
+
}
|
|
4431
|
+
if (result.headers) {
|
|
4432
|
+
loaderHeaders[id] = result.headers;
|
|
4433
|
+
}
|
|
4402
4434
|
} else {
|
|
4403
4435
|
loaderData[id] = result.data;
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
loaderHeaders[id] = result.headers;
|
|
4436
|
+
// Error status codes always override success status codes, but if all
|
|
4437
|
+
// loaders are successful we take the deepest status code.
|
|
4438
|
+
if (result.response) {
|
|
4439
|
+
if (result.response.status !== 200 && !foundError) {
|
|
4440
|
+
statusCode = result.response.status;
|
|
4441
|
+
}
|
|
4442
|
+
loaderHeaders[id] = result.response.headers;
|
|
4443
|
+
}
|
|
4413
4444
|
}
|
|
4414
4445
|
}
|
|
4415
4446
|
});
|
|
@@ -4589,6 +4620,12 @@ function isHashChangeOnly(a, b) {
|
|
|
4589
4620
|
// /page#hash -> /page
|
|
4590
4621
|
return false;
|
|
4591
4622
|
}
|
|
4623
|
+
function isHandlerResult(result) {
|
|
4624
|
+
return result != null && typeof result === "object" && "type" in result && "result" in result && (result.type === ResultType.data || result.type === ResultType.error);
|
|
4625
|
+
}
|
|
4626
|
+
function isRedirectHandlerResult(result) {
|
|
4627
|
+
return isResponse(result.result) && redirectStatusCodes.has(result.result.status);
|
|
4628
|
+
}
|
|
4592
4629
|
function isDeferredResult(result) {
|
|
4593
4630
|
return result.type === ResultType.deferred;
|
|
4594
4631
|
}
|
|
@@ -4613,9 +4650,6 @@ function isRedirectResponse(result) {
|
|
|
4613
4650
|
let location = result.headers.get("Location");
|
|
4614
4651
|
return status >= 300 && status <= 399 && location != null;
|
|
4615
4652
|
}
|
|
4616
|
-
function isQueryRouteResponse(obj) {
|
|
4617
|
-
return obj && isResponse(obj.response) && (obj.type === ResultType.data || obj.type === ResultType.error);
|
|
4618
|
-
}
|
|
4619
4653
|
function isValidMethod(method) {
|
|
4620
4654
|
return validRequestMethods.has(method.toLowerCase());
|
|
4621
4655
|
}
|
|
@@ -4859,7 +4893,6 @@ exports.Action = Action;
|
|
|
4859
4893
|
exports.IDLE_BLOCKER = IDLE_BLOCKER;
|
|
4860
4894
|
exports.IDLE_FETCHER = IDLE_FETCHER;
|
|
4861
4895
|
exports.IDLE_NAVIGATION = IDLE_NAVIGATION;
|
|
4862
|
-
exports.ResultType = ResultType;
|
|
4863
4896
|
exports.UNSAFE_DEFERRED_SYMBOL = UNSAFE_DEFERRED_SYMBOL;
|
|
4864
4897
|
exports.UNSAFE_DeferredData = DeferredData;
|
|
4865
4898
|
exports.UNSAFE_ErrorResponseImpl = ErrorResponseImpl;
|