@remix-run/router 1.3.2 → 1.3.3-pre.1
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 +14 -0
- package/dist/index.d.ts +2 -1
- package/dist/router.cjs.js +114 -51
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.d.ts +8 -0
- package/dist/router.js +114 -51
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +114 -51
- 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/index.ts +2 -1
- package/package.json +1 -1
- package/router.ts +140 -61
package/index.ts
CHANGED
|
@@ -63,7 +63,6 @@ export {
|
|
|
63
63
|
createPath,
|
|
64
64
|
createHashHistory,
|
|
65
65
|
createMemoryHistory,
|
|
66
|
-
invariant,
|
|
67
66
|
parsePath,
|
|
68
67
|
} from "./history";
|
|
69
68
|
|
|
@@ -82,3 +81,5 @@ export {
|
|
|
82
81
|
convertRoutesToDataRoutes as UNSAFE_convertRoutesToDataRoutes,
|
|
83
82
|
getPathContributingMatches as UNSAFE_getPathContributingMatches,
|
|
84
83
|
} from "./utils";
|
|
84
|
+
|
|
85
|
+
export { invariant as UNSAFE_invariant } from "./history";
|
package/package.json
CHANGED
package/router.ts
CHANGED
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
joinPaths,
|
|
34
34
|
matchRoutes,
|
|
35
35
|
resolveTo,
|
|
36
|
+
stripBasename,
|
|
36
37
|
warning,
|
|
37
38
|
} from "./utils";
|
|
38
39
|
|
|
@@ -211,6 +212,15 @@ export interface Router {
|
|
|
211
212
|
*/
|
|
212
213
|
deleteBlocker(key: string): void;
|
|
213
214
|
|
|
215
|
+
/**
|
|
216
|
+
* @internal
|
|
217
|
+
* PRIVATE - DO NOT USE
|
|
218
|
+
*
|
|
219
|
+
* HMR needs to pass in-flight route updates to React Router
|
|
220
|
+
* TODO: Replace this with granular route update APIs (addRoute, updateRoute, deleteRoute)
|
|
221
|
+
*/
|
|
222
|
+
_internalSetRoutes(routes: AgnosticRouteObject[]): void;
|
|
223
|
+
|
|
214
224
|
/**
|
|
215
225
|
* @internal
|
|
216
226
|
* PRIVATE - DO NOT USE
|
|
@@ -556,8 +566,6 @@ interface HandleLoadersResult extends ShortCircuitable {
|
|
|
556
566
|
interface FetchLoadMatch {
|
|
557
567
|
routeId: string;
|
|
558
568
|
path: string;
|
|
559
|
-
match: AgnosticDataRouteMatch;
|
|
560
|
-
matches: AgnosticDataRouteMatch[];
|
|
561
569
|
}
|
|
562
570
|
|
|
563
571
|
/**
|
|
@@ -565,6 +573,8 @@ interface FetchLoadMatch {
|
|
|
565
573
|
*/
|
|
566
574
|
interface RevalidatingFetcher extends FetchLoadMatch {
|
|
567
575
|
key: string;
|
|
576
|
+
match: AgnosticDataRouteMatch | null;
|
|
577
|
+
matches: AgnosticDataRouteMatch[] | null;
|
|
568
578
|
}
|
|
569
579
|
|
|
570
580
|
/**
|
|
@@ -644,6 +654,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
644
654
|
);
|
|
645
655
|
|
|
646
656
|
let dataRoutes = convertRoutesToDataRoutes(init.routes);
|
|
657
|
+
let inFlightDataRoutes: AgnosticDataRouteObject[] | undefined;
|
|
647
658
|
// Cleanup function for history
|
|
648
659
|
let unlistenHistory: (() => void) | null = null;
|
|
649
660
|
// Externally-provided functions to call on all state changes
|
|
@@ -921,6 +932,11 @@ export function createRouter(init: RouterInit): Router {
|
|
|
921
932
|
isMutationMethod(state.navigation.formMethod) &&
|
|
922
933
|
location.state?._isRedirect !== true);
|
|
923
934
|
|
|
935
|
+
if (inFlightDataRoutes) {
|
|
936
|
+
dataRoutes = inFlightDataRoutes;
|
|
937
|
+
inFlightDataRoutes = undefined;
|
|
938
|
+
}
|
|
939
|
+
|
|
924
940
|
updateState({
|
|
925
941
|
...newState, // matches, errors, fetchers go through as-is
|
|
926
942
|
actionData,
|
|
@@ -1108,14 +1124,15 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1108
1124
|
saveScrollPosition(state.location, state.matches);
|
|
1109
1125
|
pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
|
|
1110
1126
|
|
|
1127
|
+
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
1111
1128
|
let loadingNavigation = opts && opts.overrideNavigation;
|
|
1112
|
-
let matches = matchRoutes(
|
|
1129
|
+
let matches = matchRoutes(routesToUse, location, init.basename);
|
|
1113
1130
|
|
|
1114
1131
|
// Short circuit with a 404 on the root error boundary if we match nothing
|
|
1115
1132
|
if (!matches) {
|
|
1116
1133
|
let error = getInternalRouterError(404, { pathname: location.pathname });
|
|
1117
1134
|
let { matches: notFoundMatches, route } =
|
|
1118
|
-
getShortCircuitMatches(
|
|
1135
|
+
getShortCircuitMatches(routesToUse);
|
|
1119
1136
|
// Cancel all pending deferred on 404s since we don't keep any routes
|
|
1120
1137
|
cancelActiveDeferreds();
|
|
1121
1138
|
completeNavigation(location, {
|
|
@@ -1352,6 +1369,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1352
1369
|
}
|
|
1353
1370
|
: undefined;
|
|
1354
1371
|
|
|
1372
|
+
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
1355
1373
|
let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(
|
|
1356
1374
|
init.history,
|
|
1357
1375
|
state,
|
|
@@ -1361,9 +1379,11 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1361
1379
|
isRevalidationRequired,
|
|
1362
1380
|
cancelledDeferredRoutes,
|
|
1363
1381
|
cancelledFetcherLoads,
|
|
1382
|
+
fetchLoadMatches,
|
|
1383
|
+
routesToUse,
|
|
1384
|
+
init.basename,
|
|
1364
1385
|
pendingActionData,
|
|
1365
|
-
pendingError
|
|
1366
|
-
fetchLoadMatches
|
|
1386
|
+
pendingError
|
|
1367
1387
|
);
|
|
1368
1388
|
|
|
1369
1389
|
// Cancel pending deferreds for no-longer-matched routes or routes we're
|
|
@@ -1506,7 +1526,8 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1506
1526
|
|
|
1507
1527
|
if (fetchControllers.has(key)) abortFetcher(key);
|
|
1508
1528
|
|
|
1509
|
-
let
|
|
1529
|
+
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
1530
|
+
let matches = matchRoutes(routesToUse, href, init.basename);
|
|
1510
1531
|
if (!matches) {
|
|
1511
1532
|
setFetcherError(
|
|
1512
1533
|
key,
|
|
@@ -1528,7 +1549,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1528
1549
|
|
|
1529
1550
|
// Store off the match so we can call it's shouldRevalidate on subsequent
|
|
1530
1551
|
// revalidations
|
|
1531
|
-
fetchLoadMatches.set(key, { routeId, path
|
|
1552
|
+
fetchLoadMatches.set(key, { routeId, path });
|
|
1532
1553
|
handleFetcherLoader(key, routeId, path, match, matches, submission);
|
|
1533
1554
|
}
|
|
1534
1555
|
|
|
@@ -1629,9 +1650,10 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1629
1650
|
nextLocation,
|
|
1630
1651
|
abortController.signal
|
|
1631
1652
|
);
|
|
1653
|
+
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
1632
1654
|
let matches =
|
|
1633
1655
|
state.navigation.state !== "idle"
|
|
1634
|
-
? matchRoutes(
|
|
1656
|
+
? matchRoutes(routesToUse, state.navigation.location, init.basename)
|
|
1635
1657
|
: state.matches;
|
|
1636
1658
|
|
|
1637
1659
|
invariant(matches, "Didn't find any matches after fetcher action");
|
|
@@ -1656,9 +1678,11 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1656
1678
|
isRevalidationRequired,
|
|
1657
1679
|
cancelledDeferredRoutes,
|
|
1658
1680
|
cancelledFetcherLoads,
|
|
1681
|
+
fetchLoadMatches,
|
|
1682
|
+
routesToUse,
|
|
1683
|
+
init.basename,
|
|
1659
1684
|
{ [match.route.id]: actionResult.data },
|
|
1660
|
-
undefined
|
|
1661
|
-
fetchLoadMatches
|
|
1685
|
+
undefined // No need to send through errors since we short circuit above
|
|
1662
1686
|
);
|
|
1663
1687
|
|
|
1664
1688
|
// Put all revalidating fetchers into the loading state, except for the
|
|
@@ -1912,15 +1936,17 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1912
1936
|
redirectLocation,
|
|
1913
1937
|
"Expected a location on the redirect navigation"
|
|
1914
1938
|
);
|
|
1915
|
-
|
|
1916
1939
|
// Check if this an absolute external redirect that goes to a new origin
|
|
1917
1940
|
if (
|
|
1918
1941
|
ABSOLUTE_URL_REGEX.test(redirect.location) &&
|
|
1919
1942
|
isBrowser &&
|
|
1920
1943
|
typeof window?.location !== "undefined"
|
|
1921
1944
|
) {
|
|
1922
|
-
let
|
|
1923
|
-
|
|
1945
|
+
let url = init.history.createURL(redirect.location);
|
|
1946
|
+
let isDifferentBasename =
|
|
1947
|
+
stripBasename(url.pathname, init.basename || "/") == null;
|
|
1948
|
+
|
|
1949
|
+
if (window.location.origin !== url.origin || isDifferentBasename) {
|
|
1924
1950
|
if (replace) {
|
|
1925
1951
|
window.location.replace(redirect.location);
|
|
1926
1952
|
} else {
|
|
@@ -1997,15 +2023,23 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1997
2023
|
...matchesToLoad.map((match) =>
|
|
1998
2024
|
callLoaderOrAction("loader", request, match, matches, router.basename)
|
|
1999
2025
|
),
|
|
2000
|
-
...fetchersToLoad.map((f) =>
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2026
|
+
...fetchersToLoad.map((f) => {
|
|
2027
|
+
if (f.matches && f.match) {
|
|
2028
|
+
return callLoaderOrAction(
|
|
2029
|
+
"loader",
|
|
2030
|
+
createClientSideRequest(init.history, f.path, request.signal),
|
|
2031
|
+
f.match,
|
|
2032
|
+
f.matches,
|
|
2033
|
+
router.basename
|
|
2034
|
+
);
|
|
2035
|
+
} else {
|
|
2036
|
+
let error: ErrorResult = {
|
|
2037
|
+
type: ResultType.error,
|
|
2038
|
+
error: getInternalRouterError(404, { pathname: f.path }),
|
|
2039
|
+
};
|
|
2040
|
+
return error;
|
|
2041
|
+
}
|
|
2042
|
+
}),
|
|
2009
2043
|
]);
|
|
2010
2044
|
let loaderResults = results.slice(0, matchesToLoad.length);
|
|
2011
2045
|
let fetcherResults = results.slice(matchesToLoad.length);
|
|
@@ -2266,6 +2300,10 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2266
2300
|
return null;
|
|
2267
2301
|
}
|
|
2268
2302
|
|
|
2303
|
+
function _internalSetRoutes(newRoutes: AgnosticDataRouteObject[]) {
|
|
2304
|
+
inFlightDataRoutes = newRoutes;
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2269
2307
|
router = {
|
|
2270
2308
|
get basename() {
|
|
2271
2309
|
return init.basename;
|
|
@@ -2293,6 +2331,9 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2293
2331
|
deleteBlocker,
|
|
2294
2332
|
_internalFetchControllers: fetchControllers,
|
|
2295
2333
|
_internalActiveDeferreds: activeDeferreds,
|
|
2334
|
+
// TODO: Remove setRoutes, it's temporary to avoid dealing with
|
|
2335
|
+
// updating the tree while validating the update algorithm.
|
|
2336
|
+
_internalSetRoutes,
|
|
2296
2337
|
};
|
|
2297
2338
|
|
|
2298
2339
|
return router;
|
|
@@ -2891,9 +2932,11 @@ function getMatchesToLoad(
|
|
|
2891
2932
|
isRevalidationRequired: boolean,
|
|
2892
2933
|
cancelledDeferredRoutes: string[],
|
|
2893
2934
|
cancelledFetcherLoads: string[],
|
|
2935
|
+
fetchLoadMatches: Map<string, FetchLoadMatch>,
|
|
2936
|
+
routesToUse: AgnosticDataRouteObject[],
|
|
2937
|
+
basename: string | undefined,
|
|
2894
2938
|
pendingActionData?: RouteData,
|
|
2895
|
-
pendingError?: RouteData
|
|
2896
|
-
fetchLoadMatches?: Map<string, FetchLoadMatch>
|
|
2939
|
+
pendingError?: RouteData
|
|
2897
2940
|
): [AgnosticDataRouteMatch[], RevalidatingFetcher[]] {
|
|
2898
2941
|
let actionResult = pendingError
|
|
2899
2942
|
? Object.values(pendingError)[0]
|
|
@@ -2951,34 +2994,55 @@ function getMatchesToLoad(
|
|
|
2951
2994
|
|
|
2952
2995
|
// Pick fetcher.loads that need to be revalidated
|
|
2953
2996
|
let revalidatingFetchers: RevalidatingFetcher[] = [];
|
|
2954
|
-
fetchLoadMatches
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2997
|
+
fetchLoadMatches.forEach((f, key) => {
|
|
2998
|
+
// Don't revalidate if fetcher won't be present in the subsequent render
|
|
2999
|
+
if (!matches.some((m) => m.route.id === f.routeId)) {
|
|
3000
|
+
return;
|
|
3001
|
+
}
|
|
3002
|
+
|
|
3003
|
+
let fetcherMatches = matchRoutes(routesToUse, f.path, basename);
|
|
3004
|
+
|
|
3005
|
+
// If the fetcher path no longer matches, push it in with null matches so
|
|
3006
|
+
// we can trigger a 404 in callLoadersAndMaybeResolveData
|
|
3007
|
+
if (!fetcherMatches) {
|
|
3008
|
+
revalidatingFetchers.push({ key, ...f, matches: null, match: null });
|
|
3009
|
+
return;
|
|
3010
|
+
}
|
|
3011
|
+
|
|
3012
|
+
let fetcherMatch = getTargetMatch(fetcherMatches, f.path);
|
|
3013
|
+
|
|
3014
|
+
if (cancelledFetcherLoads.includes(key)) {
|
|
3015
|
+
revalidatingFetchers.push({
|
|
3016
|
+
key,
|
|
3017
|
+
matches: fetcherMatches,
|
|
3018
|
+
match: fetcherMatch,
|
|
3019
|
+
...f,
|
|
3020
|
+
});
|
|
3021
|
+
return;
|
|
3022
|
+
}
|
|
3023
|
+
|
|
3024
|
+
// Revalidating fetchers are decoupled from the route matches since they
|
|
3025
|
+
// hit a static href, so they _always_ check shouldRevalidate and the
|
|
3026
|
+
// default is strictly if a revalidation is explicitly required (action
|
|
3027
|
+
// submissions, useRevalidator, X-Remix-Revalidate).
|
|
3028
|
+
let shouldRevalidate = shouldRevalidateLoader(fetcherMatch, {
|
|
3029
|
+
currentUrl,
|
|
3030
|
+
currentParams: state.matches[state.matches.length - 1].params,
|
|
3031
|
+
nextUrl,
|
|
3032
|
+
nextParams: matches[matches.length - 1].params,
|
|
3033
|
+
...submission,
|
|
3034
|
+
actionResult,
|
|
3035
|
+
defaultShouldRevalidate,
|
|
2981
3036
|
});
|
|
3037
|
+
if (shouldRevalidate) {
|
|
3038
|
+
revalidatingFetchers.push({
|
|
3039
|
+
key,
|
|
3040
|
+
matches: fetcherMatches,
|
|
3041
|
+
match: fetcherMatch,
|
|
3042
|
+
...f,
|
|
3043
|
+
});
|
|
3044
|
+
}
|
|
3045
|
+
});
|
|
2982
3046
|
|
|
2983
3047
|
return [navigationMatches, revalidatingFetchers];
|
|
2984
3048
|
}
|
|
@@ -3112,14 +3176,15 @@ async function callLoaderOrAction(
|
|
|
3112
3176
|
|
|
3113
3177
|
location = createPath(resolvedLocation);
|
|
3114
3178
|
} else if (!isStaticRequest) {
|
|
3115
|
-
// Strip off the protocol+origin for same-origin absolute
|
|
3116
|
-
// If this is a static
|
|
3117
|
-
// as-is
|
|
3179
|
+
// Strip off the protocol+origin for same-origin + same-basename absolute
|
|
3180
|
+
// redirects. If this is a static request, we can let it go back to the
|
|
3181
|
+
// browser as-is
|
|
3118
3182
|
let currentUrl = new URL(request.url);
|
|
3119
3183
|
let url = location.startsWith("//")
|
|
3120
3184
|
? new URL(currentUrl.protocol + location)
|
|
3121
3185
|
: new URL(location);
|
|
3122
|
-
|
|
3186
|
+
let isSameBasename = stripBasename(url.pathname, basename) != null;
|
|
3187
|
+
if (url.origin === currentUrl.origin && isSameBasename) {
|
|
3123
3188
|
location = url.pathname + url.search + url.hash;
|
|
3124
3189
|
}
|
|
3125
3190
|
}
|
|
@@ -3183,7 +3248,12 @@ async function callLoaderOrAction(
|
|
|
3183
3248
|
}
|
|
3184
3249
|
|
|
3185
3250
|
if (result instanceof DeferredData) {
|
|
3186
|
-
return {
|
|
3251
|
+
return {
|
|
3252
|
+
type: ResultType.deferred,
|
|
3253
|
+
deferredData: result,
|
|
3254
|
+
statusCode: result.init?.status,
|
|
3255
|
+
headers: result.init?.headers && new Headers(result.init.headers),
|
|
3256
|
+
};
|
|
3187
3257
|
}
|
|
3188
3258
|
|
|
3189
3259
|
return { type: ResultType.data, data: result };
|
|
@@ -3356,7 +3426,7 @@ function processLoaderData(
|
|
|
3356
3426
|
|
|
3357
3427
|
// Process fetcher non-redirect errors
|
|
3358
3428
|
if (isErrorResult(result)) {
|
|
3359
|
-
let boundaryMatch = findNearestBoundary(state.matches, match
|
|
3429
|
+
let boundaryMatch = findNearestBoundary(state.matches, match?.route.id);
|
|
3360
3430
|
if (!(errors && errors[boundaryMatch.route.id])) {
|
|
3361
3431
|
errors = {
|
|
3362
3432
|
...errors,
|
|
@@ -3406,7 +3476,9 @@ function mergeLoaderData(
|
|
|
3406
3476
|
// incoming object with an undefined value, which is how we unset a prior
|
|
3407
3477
|
// loaderData if we encounter a loader error
|
|
3408
3478
|
}
|
|
3409
|
-
} else if (loaderData[id] !== undefined) {
|
|
3479
|
+
} else if (loaderData[id] !== undefined && match.route.loader) {
|
|
3480
|
+
// Preserve existing keys not included in newLoaderData and where a loader
|
|
3481
|
+
// wasn't removed by HMR
|
|
3410
3482
|
mergedLoaderData[id] = loaderData[id];
|
|
3411
3483
|
}
|
|
3412
3484
|
|
|
@@ -3580,7 +3652,7 @@ function isMutationMethod(method?: string): method is MutationFormMethod {
|
|
|
3580
3652
|
|
|
3581
3653
|
async function resolveDeferredResults(
|
|
3582
3654
|
currentMatches: AgnosticDataRouteMatch[],
|
|
3583
|
-
matchesToLoad: AgnosticDataRouteMatch[],
|
|
3655
|
+
matchesToLoad: (AgnosticDataRouteMatch | null)[],
|
|
3584
3656
|
results: DataResult[],
|
|
3585
3657
|
signal: AbortSignal,
|
|
3586
3658
|
isFetcher: boolean,
|
|
@@ -3589,8 +3661,15 @@ async function resolveDeferredResults(
|
|
|
3589
3661
|
for (let index = 0; index < results.length; index++) {
|
|
3590
3662
|
let result = results[index];
|
|
3591
3663
|
let match = matchesToLoad[index];
|
|
3664
|
+
// If we don't have a match, then we can have a deferred result to do
|
|
3665
|
+
// anything with. This is for revalidating fetchers where the route was
|
|
3666
|
+
// removed during HMR
|
|
3667
|
+
if (!match) {
|
|
3668
|
+
continue;
|
|
3669
|
+
}
|
|
3670
|
+
|
|
3592
3671
|
let currentMatch = currentMatches.find(
|
|
3593
|
-
(m) => m.route.id === match
|
|
3672
|
+
(m) => m.route.id === match!.route.id
|
|
3594
3673
|
);
|
|
3595
3674
|
let isRevalidatingLoader =
|
|
3596
3675
|
currentMatch != null &&
|