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