@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.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
|
*
|
|
@@ -417,6 +417,10 @@ function getUrlBasedHistory(getLocation, createHref, validateLocation, options)
|
|
|
417
417
|
// See https://bugzilla.mozilla.org/show_bug.cgi?id=878297
|
|
418
418
|
let base = window.location.origin !== "null" ? window.location.origin : window.location.href;
|
|
419
419
|
let href = typeof to === "string" ? to : createPath(to);
|
|
420
|
+
// Treating this as a full URL will strip any trailing spaces so we need to
|
|
421
|
+
// pre-encode them since they might be part of a matching splat param from
|
|
422
|
+
// an ancestor route
|
|
423
|
+
href = href.replace(/ $/, "%20");
|
|
420
424
|
invariant(base, "No window.location.(origin|href) available to create URL for href: " + href);
|
|
421
425
|
return new URL(href, base);
|
|
422
426
|
}
|
|
@@ -523,14 +527,14 @@ function matchRoutes(routes, locationArg, basename) {
|
|
|
523
527
|
rankRouteBranches(branches);
|
|
524
528
|
let matches = null;
|
|
525
529
|
for (let i = 0; matches == null && i < branches.length; ++i) {
|
|
526
|
-
matches = matchRouteBranch(branches[i],
|
|
527
530
|
// Incoming pathnames are generally encoded from either window.location
|
|
528
531
|
// or from router.navigate, but we want to match against the unencoded
|
|
529
532
|
// paths in the route definitions. Memory router locations won't be
|
|
530
533
|
// encoded here but there also shouldn't be anything to decode so this
|
|
531
534
|
// should be a safe operation. This avoids needing matchRoutes to be
|
|
532
535
|
// history-aware.
|
|
533
|
-
|
|
536
|
+
let decoded = decodePath(pathname);
|
|
537
|
+
matches = matchRouteBranch(branches[i], decoded);
|
|
534
538
|
}
|
|
535
539
|
return matches;
|
|
536
540
|
}
|
|
@@ -653,7 +657,7 @@ function rankRouteBranches(branches) {
|
|
|
653
657
|
branches.sort((a, b) => a.score !== b.score ? b.score - a.score // Higher score first
|
|
654
658
|
: compareIndexes(a.routesMeta.map(meta => meta.childrenIndex), b.routesMeta.map(meta => meta.childrenIndex)));
|
|
655
659
|
}
|
|
656
|
-
const paramRe =
|
|
660
|
+
const paramRe = /^:[\w-]+$/;
|
|
657
661
|
const dynamicSegmentValue = 3;
|
|
658
662
|
const indexRouteValue = 2;
|
|
659
663
|
const emptySegmentValue = 1;
|
|
@@ -740,7 +744,7 @@ function generatePath(originalPath, params) {
|
|
|
740
744
|
// Apply the splat
|
|
741
745
|
return stringify(params[star]);
|
|
742
746
|
}
|
|
743
|
-
const keyMatch = segment.match(/^:(\w+)(\??)$/);
|
|
747
|
+
const keyMatch = segment.match(/^:([\w-]+)(\??)$/);
|
|
744
748
|
if (keyMatch) {
|
|
745
749
|
const [, key, optional] = keyMatch;
|
|
746
750
|
let param = params[key];
|
|
@@ -789,7 +793,7 @@ function matchPath(pattern, pathname) {
|
|
|
789
793
|
if (isOptional && !value) {
|
|
790
794
|
memo[paramName] = undefined;
|
|
791
795
|
} else {
|
|
792
|
-
memo[paramName] =
|
|
796
|
+
memo[paramName] = (value || "").replace(/%2F/g, "/");
|
|
793
797
|
}
|
|
794
798
|
return memo;
|
|
795
799
|
}, {});
|
|
@@ -812,7 +816,7 @@ function compilePath(path, caseSensitive, end) {
|
|
|
812
816
|
let regexpSource = "^" + path.replace(/\/*\*?$/, "") // Ignore trailing / and /*, we'll handle it below
|
|
813
817
|
.replace(/^\/*/, "/") // Make sure it has a leading /
|
|
814
818
|
.replace(/[\\.*+^${}|()[\]]/g, "\\$&") // Escape special regex chars
|
|
815
|
-
.replace(/\/:(\w+)(\?)?/g, (_, paramName, isOptional) => {
|
|
819
|
+
.replace(/\/:([\w-]+)(\?)?/g, (_, paramName, isOptional) => {
|
|
816
820
|
params.push({
|
|
817
821
|
paramName,
|
|
818
822
|
isOptional: isOptional != null
|
|
@@ -841,22 +845,14 @@ function compilePath(path, caseSensitive, end) {
|
|
|
841
845
|
let matcher = new RegExp(regexpSource, caseSensitive ? undefined : "i");
|
|
842
846
|
return [matcher, params];
|
|
843
847
|
}
|
|
844
|
-
function
|
|
848
|
+
function decodePath(value) {
|
|
845
849
|
try {
|
|
846
|
-
return
|
|
850
|
+
return value.split("/").map(v => decodeURIComponent(v).replace(/\//g, "%2F")).join("/");
|
|
847
851
|
} catch (error) {
|
|
848
852
|
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 + ")."));
|
|
849
853
|
return value;
|
|
850
854
|
}
|
|
851
855
|
}
|
|
852
|
-
function safelyDecodeURIComponent(value, paramName) {
|
|
853
|
-
try {
|
|
854
|
-
return decodeURIComponent(value);
|
|
855
|
-
} catch (error) {
|
|
856
|
-
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 + ")."));
|
|
857
|
-
return value;
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
856
|
/**
|
|
861
857
|
* @private
|
|
862
858
|
*/
|
|
@@ -1306,8 +1302,6 @@ function createRouter(init) {
|
|
|
1306
1302
|
const isBrowser = typeof routerWindow !== "undefined" && typeof routerWindow.document !== "undefined" && typeof routerWindow.document.createElement !== "undefined";
|
|
1307
1303
|
const isServer = !isBrowser;
|
|
1308
1304
|
invariant(init.routes.length > 0, "You must provide a non-empty routes array to createRouter");
|
|
1309
|
-
const dataStrategy = init.unstable_dataStrategy || defaultDataStrategy;
|
|
1310
|
-
const callLoaderOrAction = createCallLoaderOrAction(dataStrategy);
|
|
1311
1305
|
let mapRouteProperties;
|
|
1312
1306
|
if (init.mapRouteProperties) {
|
|
1313
1307
|
mapRouteProperties = init.mapRouteProperties;
|
|
@@ -1326,6 +1320,7 @@ function createRouter(init) {
|
|
|
1326
1320
|
let dataRoutes = convertRoutesToDataRoutes(init.routes, mapRouteProperties, undefined, manifest);
|
|
1327
1321
|
let inFlightDataRoutes;
|
|
1328
1322
|
let basename = init.basename || "/";
|
|
1323
|
+
let dataStrategyImpl = init.unstable_dataStrategy || defaultDataStrategy;
|
|
1329
1324
|
// Config driven behavior flags
|
|
1330
1325
|
let future = _extends({
|
|
1331
1326
|
v7_fetcherPersist: false,
|
|
@@ -1368,17 +1363,28 @@ function createRouter(init) {
|
|
|
1368
1363
|
[route.id]: error
|
|
1369
1364
|
};
|
|
1370
1365
|
}
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1366
|
+
let initialized;
|
|
1367
|
+
let hasLazyRoutes = initialMatches.some(m => m.route.lazy);
|
|
1368
|
+
let hasLoaders = initialMatches.some(m => m.route.loader);
|
|
1369
|
+
if (hasLazyRoutes) {
|
|
1370
|
+
// All initialMatches need to be loaded before we're ready. If we have lazy
|
|
1371
|
+
// functions around still then we'll need to run them in initialize()
|
|
1372
|
+
initialized = false;
|
|
1373
|
+
} else if (!hasLoaders) {
|
|
1374
|
+
// If we've got no loaders to run, then we're good to go
|
|
1375
|
+
initialized = true;
|
|
1376
|
+
} else if (future.v7_partialHydration) {
|
|
1377
|
+
// If partial hydration is enabled, we're initialized so long as we were
|
|
1378
|
+
// provided with hydrationData for every route with a loader, and no loaders
|
|
1379
|
+
// were marked for explicit hydration
|
|
1380
|
+
let loaderData = init.hydrationData ? init.hydrationData.loaderData : null;
|
|
1381
|
+
let errors = init.hydrationData ? init.hydrationData.errors : null;
|
|
1382
|
+
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));
|
|
1383
|
+
} else {
|
|
1384
|
+
// Without partial hydration - we're initialized if we were provided any
|
|
1385
|
+
// hydrationData - which is expected to be complete
|
|
1386
|
+
initialized = init.hydrationData != null;
|
|
1387
|
+
}
|
|
1382
1388
|
let router;
|
|
1383
1389
|
let state = {
|
|
1384
1390
|
historyAction: init.history.action,
|
|
@@ -1521,7 +1527,7 @@ function createRouter(init) {
|
|
|
1521
1527
|
// in the normal navigation flow. For SSR it's expected that lazy modules are
|
|
1522
1528
|
// resolved prior to router creation since we can't go into a fallbackElement
|
|
1523
1529
|
// UI for SSR'd apps
|
|
1524
|
-
if (!state.initialized
|
|
1530
|
+
if (!state.initialized) {
|
|
1525
1531
|
startNavigation(Action.Pop, state.location, {
|
|
1526
1532
|
initialHydration: true
|
|
1527
1533
|
});
|
|
@@ -1932,7 +1938,8 @@ function createRouter(init) {
|
|
|
1932
1938
|
})
|
|
1933
1939
|
};
|
|
1934
1940
|
} else {
|
|
1935
|
-
|
|
1941
|
+
let results = await callDataStrategy("action", request, [actionMatch], matches);
|
|
1942
|
+
result = results[0];
|
|
1936
1943
|
if (request.signal.aborted) {
|
|
1937
1944
|
return {
|
|
1938
1945
|
shortCircuited: true
|
|
@@ -1947,9 +1954,9 @@ function createRouter(init) {
|
|
|
1947
1954
|
// If the user didn't explicity indicate replace behavior, replace if
|
|
1948
1955
|
// we redirected to the exact same location we're currently at to avoid
|
|
1949
1956
|
// double back-buttons
|
|
1950
|
-
replace = result.
|
|
1957
|
+
replace = result.response.headers.get("Location") === state.location.pathname + state.location.search;
|
|
1951
1958
|
}
|
|
1952
|
-
await startRedirectNavigation(
|
|
1959
|
+
await startRedirectNavigation(request, result, {
|
|
1953
1960
|
submission,
|
|
1954
1961
|
replace
|
|
1955
1962
|
});
|
|
@@ -2065,7 +2072,7 @@ function createRouter(init) {
|
|
|
2065
2072
|
let {
|
|
2066
2073
|
loaderResults,
|
|
2067
2074
|
fetcherResults
|
|
2068
|
-
} = await
|
|
2075
|
+
} = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, request);
|
|
2069
2076
|
if (request.signal.aborted) {
|
|
2070
2077
|
return {
|
|
2071
2078
|
shortCircuited: true
|
|
@@ -2088,7 +2095,7 @@ function createRouter(init) {
|
|
|
2088
2095
|
let fetcherKey = revalidatingFetchers[redirect.idx - matchesToLoad.length].key;
|
|
2089
2096
|
fetchRedirectIds.add(fetcherKey);
|
|
2090
2097
|
}
|
|
2091
|
-
await startRedirectNavigation(
|
|
2098
|
+
await startRedirectNavigation(request, redirect.result, {
|
|
2092
2099
|
replace
|
|
2093
2100
|
});
|
|
2094
2101
|
return {
|
|
@@ -2190,7 +2197,8 @@ function createRouter(init) {
|
|
|
2190
2197
|
let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);
|
|
2191
2198
|
fetchControllers.set(key, abortController);
|
|
2192
2199
|
let originatingLoadId = incrementingLoadId;
|
|
2193
|
-
let
|
|
2200
|
+
let actionResults = await callDataStrategy("action", fetchRequest, [match], requestMatches);
|
|
2201
|
+
let actionResult = actionResults[0];
|
|
2194
2202
|
if (fetchRequest.signal.aborted) {
|
|
2195
2203
|
// We can delete this so long as we weren't aborted by our own fetcher
|
|
2196
2204
|
// re-submit which would have put _new_ controller is in fetchControllers
|
|
@@ -2221,7 +2229,7 @@ function createRouter(init) {
|
|
|
2221
2229
|
} else {
|
|
2222
2230
|
fetchRedirectIds.add(key);
|
|
2223
2231
|
updateFetcherState(key, getLoadingFetcher(submission));
|
|
2224
|
-
return startRedirectNavigation(
|
|
2232
|
+
return startRedirectNavigation(fetchRequest, actionResult, {
|
|
2225
2233
|
fetcherSubmission: submission
|
|
2226
2234
|
});
|
|
2227
2235
|
}
|
|
@@ -2275,7 +2283,7 @@ function createRouter(init) {
|
|
|
2275
2283
|
let {
|
|
2276
2284
|
loaderResults,
|
|
2277
2285
|
fetcherResults
|
|
2278
|
-
} = await
|
|
2286
|
+
} = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, revalidationRequest);
|
|
2279
2287
|
if (abortController.signal.aborted) {
|
|
2280
2288
|
return;
|
|
2281
2289
|
}
|
|
@@ -2292,7 +2300,7 @@ function createRouter(init) {
|
|
|
2292
2300
|
let fetcherKey = revalidatingFetchers[redirect.idx - matchesToLoad.length].key;
|
|
2293
2301
|
fetchRedirectIds.add(fetcherKey);
|
|
2294
2302
|
}
|
|
2295
|
-
return startRedirectNavigation(
|
|
2303
|
+
return startRedirectNavigation(revalidationRequest, redirect.result);
|
|
2296
2304
|
}
|
|
2297
2305
|
// Process and commit output from loaders
|
|
2298
2306
|
let {
|
|
@@ -2341,7 +2349,8 @@ function createRouter(init) {
|
|
|
2341
2349
|
let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
|
|
2342
2350
|
fetchControllers.set(key, abortController);
|
|
2343
2351
|
let originatingLoadId = incrementingLoadId;
|
|
2344
|
-
let
|
|
2352
|
+
let results = await callDataStrategy("loader", fetchRequest, [match], matches);
|
|
2353
|
+
let result = results[0];
|
|
2345
2354
|
// Deferred isn't supported for fetcher loads, await everything and treat it
|
|
2346
2355
|
// as a normal load. resolveDeferredData will return undefined if this
|
|
2347
2356
|
// fetcher gets aborted, so we just leave result untouched and short circuit
|
|
@@ -2372,7 +2381,7 @@ function createRouter(init) {
|
|
|
2372
2381
|
return;
|
|
2373
2382
|
} else {
|
|
2374
2383
|
fetchRedirectIds.add(key);
|
|
2375
|
-
await startRedirectNavigation(
|
|
2384
|
+
await startRedirectNavigation(fetchRequest, result);
|
|
2376
2385
|
return;
|
|
2377
2386
|
}
|
|
2378
2387
|
}
|
|
@@ -2404,26 +2413,40 @@ function createRouter(init) {
|
|
|
2404
2413
|
* actually touch history until we've processed redirects, so we just use
|
|
2405
2414
|
* the history action from the original navigation (PUSH or REPLACE).
|
|
2406
2415
|
*/
|
|
2407
|
-
async function startRedirectNavigation(
|
|
2416
|
+
async function startRedirectNavigation(request, redirect, _temp2) {
|
|
2408
2417
|
let {
|
|
2409
2418
|
submission,
|
|
2410
2419
|
fetcherSubmission,
|
|
2411
2420
|
replace
|
|
2412
2421
|
} = _temp2 === void 0 ? {} : _temp2;
|
|
2413
|
-
if (redirect.
|
|
2422
|
+
if (redirect.response.headers.has("X-Remix-Revalidate")) {
|
|
2414
2423
|
isRevalidationRequired = true;
|
|
2415
2424
|
}
|
|
2416
|
-
let
|
|
2425
|
+
let location = redirect.response.headers.get("Location");
|
|
2426
|
+
invariant(location, "Expected a Location header on the redirect Response");
|
|
2427
|
+
let redirectLocation = createLocation(state.location, location, {
|
|
2417
2428
|
_isRedirect: true
|
|
2418
2429
|
});
|
|
2419
|
-
|
|
2430
|
+
if (ABSOLUTE_URL_REGEX.test(location)) {
|
|
2431
|
+
// Strip off the protocol+origin for same-origin + same-basename absolute redirects
|
|
2432
|
+
let normalizedLocation = location;
|
|
2433
|
+
let currentUrl = new URL(request.url);
|
|
2434
|
+
let url = normalizedLocation.startsWith("//") ? new URL(currentUrl.protocol + normalizedLocation) : new URL(normalizedLocation);
|
|
2435
|
+
let isSameBasename = stripBasename(url.pathname, basename) != null;
|
|
2436
|
+
if (url.origin === currentUrl.origin && isSameBasename) {
|
|
2437
|
+
normalizedLocation = url.pathname + url.search + url.hash;
|
|
2438
|
+
redirectLocation = createLocation(state.location, normalizedLocation, {
|
|
2439
|
+
_isRedirect: true
|
|
2440
|
+
});
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2420
2443
|
if (isBrowser) {
|
|
2421
2444
|
let isDocumentReload = false;
|
|
2422
|
-
if (redirect.
|
|
2445
|
+
if (redirect.response.headers.has("X-Remix-Reload-Document")) {
|
|
2423
2446
|
// Hard reload if the response contained X-Remix-Reload-Document
|
|
2424
2447
|
isDocumentReload = true;
|
|
2425
|
-
} else if (ABSOLUTE_URL_REGEX.test(
|
|
2426
|
-
const url = init.history.createURL(
|
|
2448
|
+
} else if (ABSOLUTE_URL_REGEX.test(location)) {
|
|
2449
|
+
const url = init.history.createURL(location);
|
|
2427
2450
|
isDocumentReload =
|
|
2428
2451
|
// Hard reload if it's an absolute URL to a new origin
|
|
2429
2452
|
url.origin !== routerWindow.location.origin ||
|
|
@@ -2432,9 +2455,9 @@ function createRouter(init) {
|
|
|
2432
2455
|
}
|
|
2433
2456
|
if (isDocumentReload) {
|
|
2434
2457
|
if (replace) {
|
|
2435
|
-
routerWindow.location.replace(
|
|
2458
|
+
routerWindow.location.replace(location);
|
|
2436
2459
|
} else {
|
|
2437
|
-
routerWindow.location.assign(
|
|
2460
|
+
routerWindow.location.assign(location);
|
|
2438
2461
|
}
|
|
2439
2462
|
return;
|
|
2440
2463
|
}
|
|
@@ -2457,10 +2480,10 @@ function createRouter(init) {
|
|
|
2457
2480
|
// re-submit the GET/POST/PUT/PATCH/DELETE as a submission navigation to the
|
|
2458
2481
|
// redirected location
|
|
2459
2482
|
let activeSubmission = submission || fetcherSubmission;
|
|
2460
|
-
if (redirectPreserveMethodStatusCodes.has(redirect.status) && activeSubmission && isMutationMethod(activeSubmission.formMethod)) {
|
|
2483
|
+
if (redirectPreserveMethodStatusCodes.has(redirect.response.status) && activeSubmission && isMutationMethod(activeSubmission.formMethod)) {
|
|
2461
2484
|
await startNavigation(redirectHistoryAction, redirectLocation, {
|
|
2462
2485
|
submission: _extends({}, activeSubmission, {
|
|
2463
|
-
formAction:
|
|
2486
|
+
formAction: location
|
|
2464
2487
|
}),
|
|
2465
2488
|
// Preserve this flag across redirects
|
|
2466
2489
|
preventScrollReset: pendingPreventScrollReset
|
|
@@ -2478,16 +2501,35 @@ function createRouter(init) {
|
|
|
2478
2501
|
});
|
|
2479
2502
|
}
|
|
2480
2503
|
}
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
type
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2504
|
+
// Utility wrapper for calling dataStrategy client-side without having to
|
|
2505
|
+
// pass around the manifest, mapRouteProperties, etc.
|
|
2506
|
+
async function callDataStrategy(type, request, matchesToLoad, matches) {
|
|
2507
|
+
try {
|
|
2508
|
+
let results = await callDataStrategyImpl(dataStrategyImpl, type, request, matchesToLoad, matches, manifest, mapRouteProperties);
|
|
2509
|
+
return await Promise.all(results.map((result, i) => {
|
|
2510
|
+
if (isRedirectHandlerResult(result)) {
|
|
2511
|
+
return {
|
|
2512
|
+
type: ResultType.redirect,
|
|
2513
|
+
response: normalizeRelativeRoutingRedirectResponse(result.result, request, matchesToLoad[i].route.id, matches, basename, future.v7_relativeSplatPath)
|
|
2514
|
+
};
|
|
2515
|
+
}
|
|
2516
|
+
return convertHandlerResultToDataResult(result);
|
|
2517
|
+
}));
|
|
2518
|
+
} catch (e) {
|
|
2519
|
+
// If the outer dataStrategy method throws, just return the error for all
|
|
2520
|
+
// matches - and it'll naturally bubble to the root
|
|
2521
|
+
return matchesToLoad.map(() => ({
|
|
2522
|
+
type: ResultType.error,
|
|
2523
|
+
error: e
|
|
2524
|
+
}));
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
async function callLoadersAndMaybeResolveData(currentMatches, matches, matchesToLoad, fetchersToLoad, request) {
|
|
2528
|
+
let [loaderResults, ...fetcherResults] = await Promise.all([matchesToLoad.length ? callDataStrategy("loader", request, matchesToLoad, matches) : [], ...fetchersToLoad.map(f => {
|
|
2529
|
+
if (f.matches && f.match && f.controller) {
|
|
2530
|
+
let fetcherRequest = createClientSideRequest(init.history, f.path, f.controller.signal);
|
|
2531
|
+
return callDataStrategy("loader", fetcherRequest, [f.match], f.matches).then(r => r[0]);
|
|
2532
|
+
} else {
|
|
2491
2533
|
return Promise.resolve({
|
|
2492
2534
|
type: ResultType.error,
|
|
2493
2535
|
error: getInternalRouterError(404, {
|
|
@@ -2495,16 +2537,6 @@ function createRouter(init) {
|
|
|
2495
2537
|
})
|
|
2496
2538
|
});
|
|
2497
2539
|
}
|
|
2498
|
-
return dataStrategy({
|
|
2499
|
-
matches: [finesseToAgnosticDataStrategyMatch(f.match, mapRouteProperties, manifest)],
|
|
2500
|
-
request,
|
|
2501
|
-
type: "loader",
|
|
2502
|
-
defaultStrategy(match) {
|
|
2503
|
-
invariant(f.controller, "Expected controller for fetcher in defaultStrategy");
|
|
2504
|
-
invariant(f.matches, "Expected matches for fetcher in defaultStrategy");
|
|
2505
|
-
return callLoaderOrActionImplementation("loader", createClientSideRequest(init.history, f.path, f.controller.signal), match, f.matches, basename, future.v7_relativeSplatPath);
|
|
2506
|
-
}
|
|
2507
|
-
}).then(r => r[0]);
|
|
2508
2540
|
})]);
|
|
2509
2541
|
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)]);
|
|
2510
2542
|
return {
|
|
@@ -2803,10 +2835,9 @@ function createRouter(init) {
|
|
|
2803
2835
|
const UNSAFE_DEFERRED_SYMBOL = Symbol("deferred");
|
|
2804
2836
|
function createStaticHandler(routes, opts) {
|
|
2805
2837
|
invariant(routes.length > 0, "You must provide a non-empty routes array to createStaticHandler");
|
|
2806
|
-
const dataStrategy = (opts == null ? void 0 : opts.dataStrategy) || defaultDataStrategy;
|
|
2807
|
-
const callLoaderOrAction = createCallLoaderOrAction(dataStrategy);
|
|
2808
2838
|
let manifest = {};
|
|
2809
2839
|
let basename = (opts ? opts.basename : null) || "/";
|
|
2840
|
+
let dataStrategyImpl = (opts == null ? void 0 : opts.unstable_dataStrategy) || defaultDataStrategy;
|
|
2810
2841
|
let mapRouteProperties;
|
|
2811
2842
|
if (opts != null && opts.mapRouteProperties) {
|
|
2812
2843
|
mapRouteProperties = opts.mapRouteProperties;
|
|
@@ -2821,7 +2852,8 @@ function createStaticHandler(routes, opts) {
|
|
|
2821
2852
|
}
|
|
2822
2853
|
// Config driven behavior flags
|
|
2823
2854
|
let future = _extends({
|
|
2824
|
-
v7_relativeSplatPath: false
|
|
2855
|
+
v7_relativeSplatPath: false,
|
|
2856
|
+
v7_throwAbortReason: false
|
|
2825
2857
|
}, opts ? opts.future : null);
|
|
2826
2858
|
let dataRoutes = convertRoutesToDataRoutes(routes, mapRouteProperties, undefined, manifest);
|
|
2827
2859
|
/**
|
|
@@ -2999,14 +3031,14 @@ function createStaticHandler(routes, opts) {
|
|
|
2999
3031
|
actionHeaders: {}
|
|
3000
3032
|
});
|
|
3001
3033
|
} catch (e) {
|
|
3002
|
-
// If the user threw/returned a Response in callLoaderOrAction
|
|
3003
|
-
//
|
|
3004
|
-
//
|
|
3005
|
-
if (
|
|
3034
|
+
// If the user threw/returned a Response in callLoaderOrAction for a
|
|
3035
|
+
// `queryRoute` call, we throw the `HandlerResult` to bail out early
|
|
3036
|
+
// and then return or throw the raw Response here accordingly
|
|
3037
|
+
if (isHandlerResult(e) && isResponse(e.result)) {
|
|
3006
3038
|
if (e.type === ResultType.error) {
|
|
3007
|
-
throw e.
|
|
3039
|
+
throw e.result;
|
|
3008
3040
|
}
|
|
3009
|
-
return e.
|
|
3041
|
+
return e.result;
|
|
3010
3042
|
}
|
|
3011
3043
|
// Redirects are always returned since they don't propagate to catch
|
|
3012
3044
|
// boundaries
|
|
@@ -3032,14 +3064,10 @@ function createStaticHandler(routes, opts) {
|
|
|
3032
3064
|
error
|
|
3033
3065
|
};
|
|
3034
3066
|
} else {
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
isRouteRequest,
|
|
3038
|
-
requestContext
|
|
3039
|
-
});
|
|
3067
|
+
let results = await callDataStrategy("action", request, [actionMatch], matches, isRouteRequest, requestContext);
|
|
3068
|
+
result = results[0];
|
|
3040
3069
|
if (request.signal.aborted) {
|
|
3041
|
-
|
|
3042
|
-
throw new Error(method + "() call aborted: " + request.method + " " + request.url);
|
|
3070
|
+
throwStaticHandlerAbortedError(request, isRouteRequest, future);
|
|
3043
3071
|
}
|
|
3044
3072
|
}
|
|
3045
3073
|
if (isRedirectResult(result)) {
|
|
@@ -3048,9 +3076,9 @@ function createStaticHandler(routes, opts) {
|
|
|
3048
3076
|
// can get back on the "throw all redirect responses" train here should
|
|
3049
3077
|
// this ever happen :/
|
|
3050
3078
|
throw new Response(null, {
|
|
3051
|
-
status: result.status,
|
|
3079
|
+
status: result.response.status,
|
|
3052
3080
|
headers: {
|
|
3053
|
-
Location: result.
|
|
3081
|
+
Location: result.response.headers.get("Location")
|
|
3054
3082
|
}
|
|
3055
3083
|
});
|
|
3056
3084
|
}
|
|
@@ -3098,8 +3126,8 @@ function createStaticHandler(routes, opts) {
|
|
|
3098
3126
|
return _extends({}, context, {
|
|
3099
3127
|
statusCode: isRouteErrorResponse(result.error) ? result.error.status : 500,
|
|
3100
3128
|
actionData: null,
|
|
3101
|
-
actionHeaders: _extends({}, result.
|
|
3102
|
-
[actionMatch.route.id]: result.headers
|
|
3129
|
+
actionHeaders: _extends({}, result.response ? {
|
|
3130
|
+
[actionMatch.route.id]: result.response.headers
|
|
3103
3131
|
} : {})
|
|
3104
3132
|
});
|
|
3105
3133
|
}
|
|
@@ -3110,15 +3138,17 @@ function createStaticHandler(routes, opts) {
|
|
|
3110
3138
|
signal: request.signal
|
|
3111
3139
|
});
|
|
3112
3140
|
let context = await loadRouteData(loaderRequest, matches, requestContext);
|
|
3113
|
-
return _extends({}, context,
|
|
3114
|
-
statusCode: result.statusCode
|
|
3115
|
-
} : {}, {
|
|
3141
|
+
return _extends({}, context, {
|
|
3116
3142
|
actionData: {
|
|
3117
3143
|
[actionMatch.route.id]: result.data
|
|
3118
|
-
}
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3144
|
+
}
|
|
3145
|
+
}, result.response ? {
|
|
3146
|
+
statusCode: result.response.status,
|
|
3147
|
+
actionHeaders: {
|
|
3148
|
+
[actionMatch.route.id]: result.response.headers
|
|
3149
|
+
}
|
|
3150
|
+
} : {
|
|
3151
|
+
actionHeaders: {}
|
|
3122
3152
|
});
|
|
3123
3153
|
}
|
|
3124
3154
|
async function loadRouteData(request, matches, requestContext, routeMatch, pendingActionError) {
|
|
@@ -3147,21 +3177,9 @@ function createStaticHandler(routes, opts) {
|
|
|
3147
3177
|
activeDeferreds: null
|
|
3148
3178
|
};
|
|
3149
3179
|
}
|
|
3150
|
-
let results = await
|
|
3151
|
-
matches: matchesToLoad.map(m => finesseToAgnosticDataStrategyMatch(m, mapRouteProperties, manifest)),
|
|
3152
|
-
request,
|
|
3153
|
-
type: "loader",
|
|
3154
|
-
defaultStrategy(match) {
|
|
3155
|
-
return callLoaderOrActionImplementation("loader", request, match, matches, basename, future.v7_relativeSplatPath, {
|
|
3156
|
-
isStaticRequest: true,
|
|
3157
|
-
isRouteRequest,
|
|
3158
|
-
requestContext
|
|
3159
|
-
});
|
|
3160
|
-
}
|
|
3161
|
-
});
|
|
3180
|
+
let results = await callDataStrategy("loader", request, matchesToLoad, matches, isRouteRequest, requestContext);
|
|
3162
3181
|
if (request.signal.aborted) {
|
|
3163
|
-
|
|
3164
|
-
throw new Error(method + "() call aborted: " + request.method + " " + request.url);
|
|
3182
|
+
throwStaticHandlerAbortedError(request, isRouteRequest, future);
|
|
3165
3183
|
}
|
|
3166
3184
|
// Process and commit output from loaders
|
|
3167
3185
|
let activeDeferreds = new Map();
|
|
@@ -3178,6 +3196,24 @@ function createStaticHandler(routes, opts) {
|
|
|
3178
3196
|
activeDeferreds: activeDeferreds.size > 0 ? Object.fromEntries(activeDeferreds.entries()) : null
|
|
3179
3197
|
});
|
|
3180
3198
|
}
|
|
3199
|
+
// Utility wrapper for calling dataStrategy server-side without having to
|
|
3200
|
+
// pass around the manifest, mapRouteProperties, etc.
|
|
3201
|
+
async function callDataStrategy(type, request, matchesToLoad, matches, isRouteRequest, requestContext) {
|
|
3202
|
+
let results = await callDataStrategyImpl(dataStrategyImpl, type, request, matchesToLoad, matches, manifest, mapRouteProperties, requestContext);
|
|
3203
|
+
return await Promise.all(results.map((result, i) => {
|
|
3204
|
+
if (isRedirectHandlerResult(result)) {
|
|
3205
|
+
// Throw redirects and let the server handle them with an HTTP redirect
|
|
3206
|
+
throw normalizeRelativeRoutingRedirectResponse(result.result, request, matchesToLoad[i].route.id, matches, basename, future.v7_relativeSplatPath);
|
|
3207
|
+
}
|
|
3208
|
+
if (isResponse(result.result) && isRouteRequest) {
|
|
3209
|
+
// For SSR single-route requests, we want to hand Responses back directly
|
|
3210
|
+
// without unwrapping. We do this with the QueryRouteResponse wrapper
|
|
3211
|
+
// interface so we can know whether it was returned or thrown
|
|
3212
|
+
throw result;
|
|
3213
|
+
}
|
|
3214
|
+
return convertHandlerResultToDataResult(result);
|
|
3215
|
+
}));
|
|
3216
|
+
}
|
|
3181
3217
|
return {
|
|
3182
3218
|
dataRoutes,
|
|
3183
3219
|
query,
|
|
@@ -3188,26 +3224,26 @@ function createStaticHandler(routes, opts) {
|
|
|
3188
3224
|
////////////////////////////////////////////////////////////////////////////////
|
|
3189
3225
|
//#region Helpers
|
|
3190
3226
|
////////////////////////////////////////////////////////////////////////////////
|
|
3191
|
-
function defaultDataStrategy(_ref3) {
|
|
3192
|
-
let {
|
|
3193
|
-
defaultStrategy,
|
|
3194
|
-
matches
|
|
3195
|
-
} = _ref3;
|
|
3196
|
-
return Promise.all(matches.map(match => defaultStrategy(match)));
|
|
3197
|
-
}
|
|
3198
3227
|
/**
|
|
3199
3228
|
* Given an existing StaticHandlerContext and an error thrown at render time,
|
|
3200
3229
|
* provide an updated StaticHandlerContext suitable for a second SSR render
|
|
3201
3230
|
*/
|
|
3202
3231
|
function getStaticContextFromError(routes, context, error) {
|
|
3203
3232
|
let newContext = _extends({}, context, {
|
|
3204
|
-
statusCode: 500,
|
|
3233
|
+
statusCode: isRouteErrorResponse(error) ? error.status : 500,
|
|
3205
3234
|
errors: {
|
|
3206
3235
|
[context._deepestRenderedBoundaryId || routes[0].id]: error
|
|
3207
3236
|
}
|
|
3208
3237
|
});
|
|
3209
3238
|
return newContext;
|
|
3210
3239
|
}
|
|
3240
|
+
function throwStaticHandlerAbortedError(request, isRouteRequest, future) {
|
|
3241
|
+
if (future.v7_throwAbortReason && request.signal.reason !== undefined) {
|
|
3242
|
+
throw request.signal.reason;
|
|
3243
|
+
}
|
|
3244
|
+
let method = isRouteRequest ? "queryRoute" : "query";
|
|
3245
|
+
throw new Error(method + "() call aborted: " + request.method + " " + request.url);
|
|
3246
|
+
}
|
|
3211
3247
|
function isSubmissionNavigation(opts) {
|
|
3212
3248
|
return opts != null && ("formData" in opts && opts.formData != null || "body" in opts && opts.body !== undefined);
|
|
3213
3249
|
}
|
|
@@ -3286,8 +3322,8 @@ function normalizeNavigateOptions(normalizeFormMethod, isFetcher, path, opts) {
|
|
|
3286
3322
|
}
|
|
3287
3323
|
let text = typeof opts.body === "string" ? opts.body : opts.body instanceof FormData || opts.body instanceof URLSearchParams ?
|
|
3288
3324
|
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#plain-text-form-data
|
|
3289
|
-
Array.from(opts.body.entries()).reduce((acc,
|
|
3290
|
-
let [name, value] =
|
|
3325
|
+
Array.from(opts.body.entries()).reduce((acc, _ref3) => {
|
|
3326
|
+
let [name, value] = _ref3;
|
|
3291
3327
|
return "" + acc + name + "=" + value + "\n";
|
|
3292
3328
|
}, "") : String(opts.body);
|
|
3293
3329
|
return {
|
|
@@ -3395,18 +3431,24 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
|
|
|
3395
3431
|
let boundaryId = pendingError ? Object.keys(pendingError)[0] : undefined;
|
|
3396
3432
|
let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
|
|
3397
3433
|
let navigationMatches = boundaryMatches.filter((match, index) => {
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
}
|
|
3403
|
-
if (match.route.lazy) {
|
|
3434
|
+
let {
|
|
3435
|
+
route
|
|
3436
|
+
} = match;
|
|
3437
|
+
if (route.lazy) {
|
|
3404
3438
|
// We haven't loaded this route yet so we don't know if it's got a loader!
|
|
3405
3439
|
return true;
|
|
3406
3440
|
}
|
|
3407
|
-
if (
|
|
3441
|
+
if (route.loader == null) {
|
|
3408
3442
|
return false;
|
|
3409
3443
|
}
|
|
3444
|
+
if (isInitialLoad) {
|
|
3445
|
+
if (typeof route.loader !== "function" || route.loader.hydrate) {
|
|
3446
|
+
return true;
|
|
3447
|
+
}
|
|
3448
|
+
return state.loaderData[route.id] === undefined && (
|
|
3449
|
+
// Don't re-run if the loader ran and threw an error
|
|
3450
|
+
!state.errors || state.errors[route.id] === undefined);
|
|
3451
|
+
}
|
|
3410
3452
|
// Always call the loader on new route instances and pending defer cancellations
|
|
3411
3453
|
if (isNewLoader(state.loaderData, state.matches[index], match) || cancelledDeferredRoutes.some(id => id === match.route.id)) {
|
|
3412
3454
|
return true;
|
|
@@ -3503,19 +3545,6 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
|
|
|
3503
3545
|
});
|
|
3504
3546
|
return [navigationMatches, revalidatingFetchers];
|
|
3505
3547
|
}
|
|
3506
|
-
// Is this route unhydrated (when v7_partialHydration=true) such that we need
|
|
3507
|
-
// to call it's loader on the initial router creation
|
|
3508
|
-
function isUnhydratedRoute(state, route) {
|
|
3509
|
-
if (!route.loader) {
|
|
3510
|
-
return false;
|
|
3511
|
-
}
|
|
3512
|
-
if (route.loader.hydrate) {
|
|
3513
|
-
return true;
|
|
3514
|
-
}
|
|
3515
|
-
return state.loaderData[route.id] === undefined && (!state.errors ||
|
|
3516
|
-
// Loader ran but errored - don't re-run
|
|
3517
|
-
state.errors[route.id] === undefined);
|
|
3518
|
-
}
|
|
3519
3548
|
function isNewLoader(currentLoaderData, currentMatch, match) {
|
|
3520
3549
|
let isNew =
|
|
3521
3550
|
// [a] -> [a, b]
|
|
@@ -3596,44 +3625,52 @@ async function loadLazyRouteModule(route, mapRouteProperties, manifest) {
|
|
|
3596
3625
|
}));
|
|
3597
3626
|
return routeToUpdate;
|
|
3598
3627
|
}
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
opts = {};
|
|
3603
|
-
}
|
|
3604
|
-
let [result] = await dataStrategy({
|
|
3605
|
-
matches: [finesseToAgnosticDataStrategyMatch(match, mapRouteProperties, manifest)],
|
|
3606
|
-
request,
|
|
3607
|
-
type,
|
|
3608
|
-
defaultStrategy(match) {
|
|
3609
|
-
return callLoaderOrActionImplementation(type, request, match, matches, basename, v7_relativeSplatPath, opts);
|
|
3610
|
-
}
|
|
3611
|
-
});
|
|
3612
|
-
return result;
|
|
3613
|
-
};
|
|
3628
|
+
// Default implementation of `dataStrategy` which fetches all loaders in parallel
|
|
3629
|
+
function defaultDataStrategy(opts) {
|
|
3630
|
+
return Promise.all(opts.matches.map(m => m.bikeshed_loadRoute()));
|
|
3614
3631
|
}
|
|
3615
|
-
function
|
|
3616
|
-
let
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3632
|
+
async function callDataStrategyImpl(dataStrategyImpl, type, request, matchesToLoad, matches, manifest, mapRouteProperties, requestContext) {
|
|
3633
|
+
let routeIdsToLoad = matchesToLoad.reduce((acc, m) => acc.add(m.route.id), new Set());
|
|
3634
|
+
let loadedMatches = new Set();
|
|
3635
|
+
// Send all matches here to allow for a middleware-type implementation.
|
|
3636
|
+
// handler will be a no-op for unneeded routes and we filter those results
|
|
3637
|
+
// back out below.
|
|
3638
|
+
let results = await dataStrategyImpl({
|
|
3639
|
+
matches: matches.map(match => {
|
|
3640
|
+
// `bikeshed_loadRoute` encapsulates the route.lazy, executing the
|
|
3641
|
+
// loader/action, and mapping return values/thrown errors to a
|
|
3642
|
+
// HandlerResult. Users can pass a callback to take fine-grained control
|
|
3643
|
+
// over the execution of the loader/action
|
|
3644
|
+
let bikeshed_loadRoute = handlerOverride => {
|
|
3645
|
+
loadedMatches.add(match.route.id);
|
|
3646
|
+
return routeIdsToLoad.has(match.route.id) ? callLoaderOrAction(type, request, match, manifest, mapRouteProperties, handlerOverride, requestContext) :
|
|
3647
|
+
// TODO: What's the best thing to do here - return an empty "success" result?
|
|
3648
|
+
// Or return a success result with the current route loader/action data?
|
|
3649
|
+
// We strip these results out if the route didn't need to be revalidated in
|
|
3650
|
+
// `callDataStrategy` so it doesn't matter for us. It's more of a question
|
|
3651
|
+
// of whether exposing the current data to the user is useful?
|
|
3652
|
+
Promise.resolve({
|
|
3653
|
+
type: ResultType.data,
|
|
3654
|
+
result: undefined
|
|
3655
|
+
});
|
|
3656
|
+
};
|
|
3657
|
+
return _extends({}, match, {
|
|
3658
|
+
bikeshed_loadRoute
|
|
3659
|
+
});
|
|
3660
|
+
}),
|
|
3661
|
+
request,
|
|
3662
|
+
params: matches[0].params
|
|
3630
3663
|
});
|
|
3664
|
+
// Throw if any loadRoute implementations not called since they are what
|
|
3665
|
+
// ensures a route is fully loaded
|
|
3666
|
+
// Throw if any loadRoute implementations not called since they are what
|
|
3667
|
+
// ensure a route is fully loaded
|
|
3668
|
+
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."));
|
|
3669
|
+
// Filter out any middleware-only matches for which we didn't need to run handlers
|
|
3670
|
+
return results.filter((_, i) => routeIdsToLoad.has(matches[i].route.id));
|
|
3631
3671
|
}
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
opts = {};
|
|
3635
|
-
}
|
|
3636
|
-
let resultType;
|
|
3672
|
+
// Default logic for calling a loader/action is the user has no specified a dataStrategy
|
|
3673
|
+
async function callLoaderOrAction(type, request, match, manifest, mapRouteProperties, handlerOverride, staticContext) {
|
|
3637
3674
|
let result;
|
|
3638
3675
|
let onReject;
|
|
3639
3676
|
let runHandler = handler => {
|
|
@@ -3642,11 +3679,18 @@ async function callLoaderOrActionImplementation(type, request, match, matches, b
|
|
|
3642
3679
|
let abortPromise = new Promise((_, r) => reject = r);
|
|
3643
3680
|
onReject = () => reject();
|
|
3644
3681
|
request.signal.addEventListener("abort", onReject);
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3682
|
+
let runHandlerForReal = ctx => {
|
|
3683
|
+
if (typeof handler !== "function") {
|
|
3684
|
+
return Promise.reject(new Error("You cannot call the handler for a route which defines a boolean " + ("\"" + type + "\" [routeId: " + match.route.id + "]")));
|
|
3685
|
+
}
|
|
3686
|
+
return handler({
|
|
3687
|
+
request,
|
|
3688
|
+
params: match.params,
|
|
3689
|
+
context: staticContext
|
|
3690
|
+
}, ...(ctx !== undefined ? [ctx] : []));
|
|
3691
|
+
};
|
|
3692
|
+
let handlerPromise = handlerOverride ? handlerOverride(async ctx => runHandlerForReal(ctx)) : runHandlerForReal();
|
|
3693
|
+
return Promise.race([handlerPromise, abortPromise]);
|
|
3650
3694
|
};
|
|
3651
3695
|
try {
|
|
3652
3696
|
let handler = match.route[type];
|
|
@@ -3660,17 +3704,17 @@ async function callLoaderOrActionImplementation(type, request, match, matches, b
|
|
|
3660
3704
|
// route has a boundary that can handle the error
|
|
3661
3705
|
runHandler(handler).catch(e => {
|
|
3662
3706
|
handlerError = e;
|
|
3663
|
-
}), match.route]);
|
|
3707
|
+
}), loadLazyRouteModule(match.route, mapRouteProperties, manifest)]);
|
|
3664
3708
|
if (handlerError) {
|
|
3665
3709
|
throw handlerError;
|
|
3666
3710
|
}
|
|
3667
3711
|
result = values[0];
|
|
3668
3712
|
} else {
|
|
3669
3713
|
// Load lazy route module, then run any returned handler
|
|
3670
|
-
|
|
3671
|
-
handler = route[type];
|
|
3714
|
+
await loadLazyRouteModule(match.route, mapRouteProperties, manifest);
|
|
3715
|
+
handler = match.route[type];
|
|
3672
3716
|
if (handler) {
|
|
3673
|
-
// Handler still
|
|
3717
|
+
// Handler still runs even if we got interrupted to maintain consistency
|
|
3674
3718
|
// with un-abortable behavior of handler execution on non-lazy or
|
|
3675
3719
|
// previously-lazy-loaded routes
|
|
3676
3720
|
result = await runHandler(handler);
|
|
@@ -3687,7 +3731,7 @@ async function callLoaderOrActionImplementation(type, request, match, matches, b
|
|
|
3687
3731
|
// hit the invariant below that errors on returning undefined.
|
|
3688
3732
|
return {
|
|
3689
3733
|
type: ResultType.data,
|
|
3690
|
-
|
|
3734
|
+
result: undefined
|
|
3691
3735
|
};
|
|
3692
3736
|
}
|
|
3693
3737
|
}
|
|
@@ -3702,66 +3746,37 @@ async function callLoaderOrActionImplementation(type, request, match, matches, b
|
|
|
3702
3746
|
}
|
|
3703
3747
|
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`.");
|
|
3704
3748
|
} catch (e) {
|
|
3705
|
-
|
|
3706
|
-
|
|
3749
|
+
return {
|
|
3750
|
+
type: ResultType.error,
|
|
3751
|
+
result: e
|
|
3752
|
+
};
|
|
3707
3753
|
} finally {
|
|
3708
3754
|
if (onReject) {
|
|
3709
3755
|
request.signal.removeEventListener("abort", onReject);
|
|
3710
3756
|
}
|
|
3711
3757
|
}
|
|
3758
|
+
return {
|
|
3759
|
+
type: ResultType.data,
|
|
3760
|
+
result
|
|
3761
|
+
};
|
|
3762
|
+
}
|
|
3763
|
+
async function convertHandlerResultToDataResult(handlerResult) {
|
|
3764
|
+
let {
|
|
3765
|
+
result,
|
|
3766
|
+
type
|
|
3767
|
+
} = handlerResult;
|
|
3712
3768
|
if (isResponse(result)) {
|
|
3713
|
-
let status = result.status;
|
|
3714
|
-
// Process redirects
|
|
3715
|
-
if (redirectStatusCodes.has(status)) {
|
|
3716
|
-
let location = result.headers.get("Location");
|
|
3717
|
-
invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header");
|
|
3718
|
-
// Support relative routing in internal redirects
|
|
3719
|
-
if (!ABSOLUTE_URL_REGEX.test(location)) {
|
|
3720
|
-
location = normalizeTo(new URL(request.url), matches.slice(0, matches.findIndex(m => m.route.id === match.route.id) + 1), basename, true, location, v7_relativeSplatPath);
|
|
3721
|
-
} else if (!opts.isStaticRequest) {
|
|
3722
|
-
// Strip off the protocol+origin for same-origin + same-basename absolute
|
|
3723
|
-
// redirects. If this is a static request, we can let it go back to the
|
|
3724
|
-
// browser as-is
|
|
3725
|
-
let currentUrl = new URL(request.url);
|
|
3726
|
-
let url = location.startsWith("//") ? new URL(currentUrl.protocol + location) : new URL(location);
|
|
3727
|
-
let isSameBasename = stripBasename(url.pathname, basename) != null;
|
|
3728
|
-
if (url.origin === currentUrl.origin && isSameBasename) {
|
|
3729
|
-
location = url.pathname + url.search + url.hash;
|
|
3730
|
-
}
|
|
3731
|
-
}
|
|
3732
|
-
// Don't process redirects in the router during static requests requests.
|
|
3733
|
-
// Instead, throw the Response and let the server handle it with an HTTP
|
|
3734
|
-
// redirect. We also update the Location header in place in this flow so
|
|
3735
|
-
// basename and relative routing is taken into account
|
|
3736
|
-
if (opts.isStaticRequest) {
|
|
3737
|
-
result.headers.set("Location", location);
|
|
3738
|
-
throw result;
|
|
3739
|
-
}
|
|
3740
|
-
return {
|
|
3741
|
-
type: ResultType.redirect,
|
|
3742
|
-
status,
|
|
3743
|
-
location,
|
|
3744
|
-
revalidate: result.headers.get("X-Remix-Revalidate") !== null,
|
|
3745
|
-
reloadDocument: result.headers.get("X-Remix-Reload-Document") !== null
|
|
3746
|
-
};
|
|
3747
|
-
}
|
|
3748
|
-
// For SSR single-route requests, we want to hand Responses back directly
|
|
3749
|
-
// without unwrapping. We do this with the QueryRouteResponse wrapper
|
|
3750
|
-
// interface so we can know whether it was returned or thrown
|
|
3751
|
-
if (opts.isRouteRequest) {
|
|
3752
|
-
let queryRouteResponse = {
|
|
3753
|
-
type: resultType === ResultType.error ? ResultType.error : ResultType.data,
|
|
3754
|
-
response: result
|
|
3755
|
-
};
|
|
3756
|
-
throw queryRouteResponse;
|
|
3757
|
-
}
|
|
3758
3769
|
let data;
|
|
3759
3770
|
try {
|
|
3760
3771
|
let contentType = result.headers.get("Content-Type");
|
|
3761
3772
|
// Check between word boundaries instead of startsWith() due to the last
|
|
3762
3773
|
// paragraph of https://httpwg.org/specs/rfc9110.html#field.content-type
|
|
3763
3774
|
if (contentType && /\bapplication\/json\b/.test(contentType)) {
|
|
3764
|
-
|
|
3775
|
+
if (result.body == null) {
|
|
3776
|
+
data = null;
|
|
3777
|
+
} else {
|
|
3778
|
+
data = await result.json();
|
|
3779
|
+
}
|
|
3765
3780
|
} else {
|
|
3766
3781
|
data = await result.text();
|
|
3767
3782
|
}
|
|
@@ -3771,23 +3786,22 @@ async function callLoaderOrActionImplementation(type, request, match, matches, b
|
|
|
3771
3786
|
error: e
|
|
3772
3787
|
};
|
|
3773
3788
|
}
|
|
3774
|
-
if (
|
|
3789
|
+
if (type === ResultType.error) {
|
|
3775
3790
|
return {
|
|
3776
|
-
type
|
|
3777
|
-
error: new ErrorResponseImpl(status, result.statusText, data),
|
|
3778
|
-
|
|
3791
|
+
type,
|
|
3792
|
+
error: new ErrorResponseImpl(result.status, result.statusText, data),
|
|
3793
|
+
response: result
|
|
3779
3794
|
};
|
|
3780
3795
|
}
|
|
3781
3796
|
return {
|
|
3782
3797
|
type: ResultType.data,
|
|
3783
3798
|
data,
|
|
3784
|
-
|
|
3785
|
-
headers: result.headers
|
|
3799
|
+
response: result
|
|
3786
3800
|
};
|
|
3787
3801
|
}
|
|
3788
|
-
if (
|
|
3802
|
+
if (type === ResultType.error) {
|
|
3789
3803
|
return {
|
|
3790
|
-
type
|
|
3804
|
+
type,
|
|
3791
3805
|
error: result
|
|
3792
3806
|
};
|
|
3793
3807
|
}
|
|
@@ -3805,6 +3819,17 @@ async function callLoaderOrActionImplementation(type, request, match, matches, b
|
|
|
3805
3819
|
data: result
|
|
3806
3820
|
};
|
|
3807
3821
|
}
|
|
3822
|
+
// Support relative routing in internal redirects
|
|
3823
|
+
function normalizeRelativeRoutingRedirectResponse(response, request, routeId, matches, basename, v7_relativeSplatPath) {
|
|
3824
|
+
let location = response.headers.get("Location");
|
|
3825
|
+
invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header");
|
|
3826
|
+
if (!ABSOLUTE_URL_REGEX.test(location)) {
|
|
3827
|
+
let trimmedMatches = matches.slice(0, matches.findIndex(m => m.route.id === routeId) + 1);
|
|
3828
|
+
location = normalizeTo(new URL(request.url), trimmedMatches, basename, true, location, v7_relativeSplatPath);
|
|
3829
|
+
response.headers.set("Location", location);
|
|
3830
|
+
}
|
|
3831
|
+
return response;
|
|
3832
|
+
}
|
|
3808
3833
|
// Utility method for creating the Request instances for loaders/actions during
|
|
3809
3834
|
// client-side navigations and fetches. During SSR we will always have a
|
|
3810
3835
|
// Request instance from the static handler (query/queryRoute)
|
|
@@ -3891,23 +3916,31 @@ function processRouteLoaderData(matches, matchesToLoad, results, pendingError, a
|
|
|
3891
3916
|
foundError = true;
|
|
3892
3917
|
statusCode = isRouteErrorResponse(result.error) ? result.error.status : 500;
|
|
3893
3918
|
}
|
|
3894
|
-
if (result.
|
|
3895
|
-
loaderHeaders[id] = result.headers;
|
|
3919
|
+
if (result.response) {
|
|
3920
|
+
loaderHeaders[id] = result.response.headers;
|
|
3896
3921
|
}
|
|
3897
3922
|
} else {
|
|
3898
3923
|
if (isDeferredResult(result)) {
|
|
3899
3924
|
activeDeferreds.set(id, result.deferredData);
|
|
3900
3925
|
loaderData[id] = result.deferredData.data;
|
|
3926
|
+
// Error status codes always override success status codes, but if all
|
|
3927
|
+
// loaders are successful we take the deepest status code.
|
|
3928
|
+
if (result.statusCode != null && result.statusCode !== 200 && !foundError) {
|
|
3929
|
+
statusCode = result.statusCode;
|
|
3930
|
+
}
|
|
3931
|
+
if (result.headers) {
|
|
3932
|
+
loaderHeaders[id] = result.headers;
|
|
3933
|
+
}
|
|
3901
3934
|
} else {
|
|
3902
3935
|
loaderData[id] = result.data;
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3936
|
+
// Error status codes always override success status codes, but if all
|
|
3937
|
+
// loaders are successful we take the deepest status code.
|
|
3938
|
+
if (result.response) {
|
|
3939
|
+
if (result.response.status !== 200 && !foundError) {
|
|
3940
|
+
statusCode = result.response.status;
|
|
3941
|
+
}
|
|
3942
|
+
loaderHeaders[id] = result.response.headers;
|
|
3943
|
+
}
|
|
3911
3944
|
}
|
|
3912
3945
|
}
|
|
3913
3946
|
});
|
|
@@ -4081,6 +4114,12 @@ function isHashChangeOnly(a, b) {
|
|
|
4081
4114
|
// /page#hash -> /page
|
|
4082
4115
|
return false;
|
|
4083
4116
|
}
|
|
4117
|
+
function isHandlerResult(result) {
|
|
4118
|
+
return result != null && typeof result === "object" && "type" in result && "result" in result && (result.type === ResultType.data || result.type === ResultType.error);
|
|
4119
|
+
}
|
|
4120
|
+
function isRedirectHandlerResult(result) {
|
|
4121
|
+
return isResponse(result.result) && redirectStatusCodes.has(result.result.status);
|
|
4122
|
+
}
|
|
4084
4123
|
function isDeferredResult(result) {
|
|
4085
4124
|
return result.type === ResultType.deferred;
|
|
4086
4125
|
}
|
|
@@ -4105,9 +4144,6 @@ function isRedirectResponse(result) {
|
|
|
4105
4144
|
let location = result.headers.get("Location");
|
|
4106
4145
|
return status >= 300 && status <= 399 && location != null;
|
|
4107
4146
|
}
|
|
4108
|
-
function isQueryRouteResponse(obj) {
|
|
4109
|
-
return obj && isResponse(obj.response) && (obj.type === ResultType.data || obj.type === ResultType.error);
|
|
4110
|
-
}
|
|
4111
4147
|
function isValidMethod(method) {
|
|
4112
4148
|
return validRequestMethods.has(method.toLowerCase());
|
|
4113
4149
|
}
|
|
@@ -4345,5 +4381,5 @@ function persistAppliedTransitions(_window, transitions) {
|
|
|
4345
4381
|
}
|
|
4346
4382
|
//#endregion
|
|
4347
4383
|
|
|
4348
|
-
export { AbortedDeferredError, Action, IDLE_BLOCKER, IDLE_FETCHER, IDLE_NAVIGATION,
|
|
4384
|
+
export { AbortedDeferredError, Action, IDLE_BLOCKER, IDLE_FETCHER, IDLE_NAVIGATION, UNSAFE_DEFERRED_SYMBOL, DeferredData as UNSAFE_DeferredData, ErrorResponseImpl as UNSAFE_ErrorResponseImpl, convertRouteMatchToUiMatch as UNSAFE_convertRouteMatchToUiMatch, convertRoutesToDataRoutes as UNSAFE_convertRoutesToDataRoutes, getResolveToMatches as UNSAFE_getResolveToMatches, invariant as UNSAFE_invariant, warning as UNSAFE_warning, createBrowserHistory, createHashHistory, createMemoryHistory, createPath, createRouter, createStaticHandler, defer, generatePath, getStaticContextFromError, getToPathname, isDeferredData, isRouteErrorResponse, joinPaths, json, matchPath, matchRoutes, normalizePathname, parsePath, redirect, redirectDocument, resolvePath, resolveTo, stripBasename };
|
|
4349
4385
|
//# sourceMappingURL=router.js.map
|