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