@remix-run/router 1.16.1 → 1.17.0
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 +9 -0
- package/dist/index.d.ts +1 -1
- package/dist/router.cjs.js +467 -80
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.d.ts +11 -1
- package/dist/router.js +457 -76
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +467 -80
- 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 +9 -1
- package/index.ts +1 -0
- package/package.json +1 -1
- package/router.ts +586 -73
- package/utils.ts +51 -8
package/dist/router.umd.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @remix-run/router v1.
|
|
2
|
+
* @remix-run/router v1.17.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Remix Software Inc.
|
|
5
5
|
*
|
|
@@ -720,7 +720,7 @@
|
|
|
720
720
|
manifest = {};
|
|
721
721
|
}
|
|
722
722
|
return routes.map((route, index) => {
|
|
723
|
-
let treePath = [...parentPath, index];
|
|
723
|
+
let treePath = [...parentPath, String(index)];
|
|
724
724
|
let id = typeof route.id === "string" ? route.id : treePath.join("-");
|
|
725
725
|
invariant(route.index !== true || !route.children, "Cannot specify children on an index route");
|
|
726
726
|
invariant(!manifest[id], "Found a route id collision on id \"" + id + "\". Route " + "id's must be globally unique within Data Router usages");
|
|
@@ -753,6 +753,9 @@
|
|
|
753
753
|
if (basename === void 0) {
|
|
754
754
|
basename = "/";
|
|
755
755
|
}
|
|
756
|
+
return matchRoutesImpl(routes, locationArg, basename, false);
|
|
757
|
+
}
|
|
758
|
+
function matchRoutesImpl(routes, locationArg, basename, allowPartial) {
|
|
756
759
|
let location = typeof locationArg === "string" ? parsePath(locationArg) : locationArg;
|
|
757
760
|
let pathname = stripBasename(location.pathname || "/", basename);
|
|
758
761
|
if (pathname == null) {
|
|
@@ -769,7 +772,7 @@
|
|
|
769
772
|
// should be a safe operation. This avoids needing matchRoutes to be
|
|
770
773
|
// history-aware.
|
|
771
774
|
let decoded = decodePath(pathname);
|
|
772
|
-
matches = matchRouteBranch(branches[i], decoded);
|
|
775
|
+
matches = matchRouteBranch(branches[i], decoded, allowPartial);
|
|
773
776
|
}
|
|
774
777
|
return matches;
|
|
775
778
|
}
|
|
@@ -929,7 +932,10 @@
|
|
|
929
932
|
// so they sort equally.
|
|
930
933
|
0;
|
|
931
934
|
}
|
|
932
|
-
function matchRouteBranch(branch, pathname) {
|
|
935
|
+
function matchRouteBranch(branch, pathname, allowPartial) {
|
|
936
|
+
if (allowPartial === void 0) {
|
|
937
|
+
allowPartial = false;
|
|
938
|
+
}
|
|
933
939
|
let {
|
|
934
940
|
routesMeta
|
|
935
941
|
} = branch;
|
|
@@ -945,9 +951,18 @@
|
|
|
945
951
|
caseSensitive: meta.caseSensitive,
|
|
946
952
|
end
|
|
947
953
|
}, remainingPathname);
|
|
948
|
-
if (!match) return null;
|
|
949
|
-
Object.assign(matchedParams, match.params);
|
|
950
954
|
let route = meta.route;
|
|
955
|
+
if (!match && end && allowPartial && !routesMeta[routesMeta.length - 1].route.index) {
|
|
956
|
+
match = matchPath({
|
|
957
|
+
path: meta.relativePath,
|
|
958
|
+
caseSensitive: meta.caseSensitive,
|
|
959
|
+
end: false
|
|
960
|
+
}, remainingPathname);
|
|
961
|
+
}
|
|
962
|
+
if (!match) {
|
|
963
|
+
return null;
|
|
964
|
+
}
|
|
965
|
+
Object.assign(matchedParams, match.params);
|
|
951
966
|
matches.push({
|
|
952
967
|
// TODO: Can this as be avoided?
|
|
953
968
|
params: matchedParams,
|
|
@@ -1671,6 +1686,8 @@
|
|
|
1671
1686
|
let inFlightDataRoutes;
|
|
1672
1687
|
let basename = init.basename || "/";
|
|
1673
1688
|
let dataStrategyImpl = init.unstable_dataStrategy || defaultDataStrategy;
|
|
1689
|
+
let patchRoutesOnMissImpl = init.unstable_patchRoutesOnMiss;
|
|
1690
|
+
|
|
1674
1691
|
// Config driven behavior flags
|
|
1675
1692
|
let future = _extends({
|
|
1676
1693
|
v7_fetcherPersist: false,
|
|
@@ -1699,7 +1716,7 @@
|
|
|
1699
1716
|
let initialScrollRestored = init.hydrationData != null;
|
|
1700
1717
|
let initialMatches = matchRoutes(dataRoutes, init.history.location, basename);
|
|
1701
1718
|
let initialErrors = null;
|
|
1702
|
-
if (initialMatches == null) {
|
|
1719
|
+
if (initialMatches == null && !patchRoutesOnMissImpl) {
|
|
1703
1720
|
// If we do not match a user-provided-route, fall back to the root
|
|
1704
1721
|
// to allow the error boundary to take over
|
|
1705
1722
|
let error = getInternalRouterError(404, {
|
|
@@ -1715,13 +1732,15 @@
|
|
|
1715
1732
|
};
|
|
1716
1733
|
}
|
|
1717
1734
|
let initialized;
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1735
|
+
if (!initialMatches) {
|
|
1736
|
+
// We need to run patchRoutesOnMiss in initialize()
|
|
1737
|
+
initialized = false;
|
|
1738
|
+
initialMatches = [];
|
|
1739
|
+
} else if (initialMatches.some(m => m.route.lazy)) {
|
|
1721
1740
|
// All initialMatches need to be loaded before we're ready. If we have lazy
|
|
1722
1741
|
// functions around still then we'll need to run them in initialize()
|
|
1723
1742
|
initialized = false;
|
|
1724
|
-
} else if (!
|
|
1743
|
+
} else if (!initialMatches.some(m => m.route.loader)) {
|
|
1725
1744
|
// If we've got no loaders to run, then we're good to go
|
|
1726
1745
|
initialized = true;
|
|
1727
1746
|
} else if (future.v7_partialHydration) {
|
|
@@ -1848,6 +1867,10 @@
|
|
|
1848
1867
|
// we don't need to update UI state if they change
|
|
1849
1868
|
let blockerFunctions = new Map();
|
|
1850
1869
|
|
|
1870
|
+
// Map of pending patchRoutesOnMiss() promises (keyed by path/matches) so
|
|
1871
|
+
// that we only kick them off once for a given combo
|
|
1872
|
+
let pendingPatchRoutes = new Map();
|
|
1873
|
+
|
|
1851
1874
|
// Flag to ignore the next history update, so we can revert the URL change on
|
|
1852
1875
|
// a POP navigation that was blocked by the user without touching router state
|
|
1853
1876
|
let ignoreNextHistoryUpdate = false;
|
|
@@ -2237,18 +2260,18 @@
|
|
|
2237
2260
|
let loadingNavigation = opts && opts.overrideNavigation;
|
|
2238
2261
|
let matches = matchRoutes(routesToUse, location, basename);
|
|
2239
2262
|
let flushSync = (opts && opts.flushSync) === true;
|
|
2263
|
+
let fogOfWar = checkFogOfWar(matches, routesToUse, location.pathname);
|
|
2264
|
+
if (fogOfWar.active && fogOfWar.matches) {
|
|
2265
|
+
matches = fogOfWar.matches;
|
|
2266
|
+
}
|
|
2240
2267
|
|
|
2241
2268
|
// Short circuit with a 404 on the root error boundary if we match nothing
|
|
2242
2269
|
if (!matches) {
|
|
2243
|
-
let error = getInternalRouterError(404, {
|
|
2244
|
-
pathname: location.pathname
|
|
2245
|
-
});
|
|
2246
2270
|
let {
|
|
2247
|
-
|
|
2271
|
+
error,
|
|
2272
|
+
notFoundMatches,
|
|
2248
2273
|
route
|
|
2249
|
-
} =
|
|
2250
|
-
// Cancel all pending deferred on 404s since we don't keep any routes
|
|
2251
|
-
cancelActiveDeferreds();
|
|
2274
|
+
} = handleNavigational404(location.pathname);
|
|
2252
2275
|
completeNavigation(location, {
|
|
2253
2276
|
matches: notFoundMatches,
|
|
2254
2277
|
loaderData: {},
|
|
@@ -2291,16 +2314,36 @@
|
|
|
2291
2314
|
}];
|
|
2292
2315
|
} else if (opts && opts.submission && isMutationMethod(opts.submission.formMethod)) {
|
|
2293
2316
|
// Call action if we received an action submission
|
|
2294
|
-
let actionResult = await handleAction(request, location, opts.submission, matches, {
|
|
2317
|
+
let actionResult = await handleAction(request, location, opts.submission, matches, fogOfWar.active, {
|
|
2295
2318
|
replace: opts.replace,
|
|
2296
2319
|
flushSync
|
|
2297
2320
|
});
|
|
2298
2321
|
if (actionResult.shortCircuited) {
|
|
2299
2322
|
return;
|
|
2300
2323
|
}
|
|
2324
|
+
|
|
2325
|
+
// If we received a 404 from handleAction, it's because we couldn't lazily
|
|
2326
|
+
// discover the destination route so we don't want to call loaders
|
|
2327
|
+
if (actionResult.pendingActionResult) {
|
|
2328
|
+
let [routeId, result] = actionResult.pendingActionResult;
|
|
2329
|
+
if (isErrorResult(result) && isRouteErrorResponse(result.error) && result.error.status === 404) {
|
|
2330
|
+
pendingNavigationController = null;
|
|
2331
|
+
completeNavigation(location, {
|
|
2332
|
+
matches: actionResult.matches,
|
|
2333
|
+
loaderData: {},
|
|
2334
|
+
errors: {
|
|
2335
|
+
[routeId]: result.error
|
|
2336
|
+
}
|
|
2337
|
+
});
|
|
2338
|
+
return;
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
matches = actionResult.matches || matches;
|
|
2301
2342
|
pendingActionResult = actionResult.pendingActionResult;
|
|
2302
2343
|
loadingNavigation = getLoadingNavigation(location, opts.submission);
|
|
2303
2344
|
flushSync = false;
|
|
2345
|
+
// No need to do fog of war matching again on loader execution
|
|
2346
|
+
fogOfWar.active = false;
|
|
2304
2347
|
|
|
2305
2348
|
// Create a GET request for the loaders
|
|
2306
2349
|
request = createClientSideRequest(init.history, request.url, request.signal);
|
|
@@ -2309,9 +2352,10 @@
|
|
|
2309
2352
|
// Call loaders
|
|
2310
2353
|
let {
|
|
2311
2354
|
shortCircuited,
|
|
2355
|
+
matches: updatedMatches,
|
|
2312
2356
|
loaderData,
|
|
2313
2357
|
errors
|
|
2314
|
-
} = await handleLoaders(request, location, matches, loadingNavigation, opts && opts.submission, opts && opts.fetcherSubmission, opts && opts.replace, opts && opts.initialHydration === true, flushSync, pendingActionResult);
|
|
2358
|
+
} = await handleLoaders(request, location, matches, fogOfWar.active, loadingNavigation, opts && opts.submission, opts && opts.fetcherSubmission, opts && opts.replace, opts && opts.initialHydration === true, flushSync, pendingActionResult);
|
|
2315
2359
|
if (shortCircuited) {
|
|
2316
2360
|
return;
|
|
2317
2361
|
}
|
|
@@ -2321,7 +2365,7 @@
|
|
|
2321
2365
|
// been assigned to a new controller for the next navigation
|
|
2322
2366
|
pendingNavigationController = null;
|
|
2323
2367
|
completeNavigation(location, _extends({
|
|
2324
|
-
matches
|
|
2368
|
+
matches: updatedMatches || matches
|
|
2325
2369
|
}, getActionDataForCommit(pendingActionResult), {
|
|
2326
2370
|
loaderData,
|
|
2327
2371
|
errors
|
|
@@ -2330,7 +2374,7 @@
|
|
|
2330
2374
|
|
|
2331
2375
|
// Call the action matched by the leaf route for this navigation and handle
|
|
2332
2376
|
// redirects/errors
|
|
2333
|
-
async function handleAction(request, location, submission, matches, opts) {
|
|
2377
|
+
async function handleAction(request, location, submission, matches, isFogOfWar, opts) {
|
|
2334
2378
|
if (opts === void 0) {
|
|
2335
2379
|
opts = {};
|
|
2336
2380
|
}
|
|
@@ -2343,6 +2387,42 @@
|
|
|
2343
2387
|
}, {
|
|
2344
2388
|
flushSync: opts.flushSync === true
|
|
2345
2389
|
});
|
|
2390
|
+
if (isFogOfWar) {
|
|
2391
|
+
let discoverResult = await discoverRoutes(matches, location.pathname, request.signal);
|
|
2392
|
+
if (discoverResult.type === "aborted") {
|
|
2393
|
+
return {
|
|
2394
|
+
shortCircuited: true
|
|
2395
|
+
};
|
|
2396
|
+
} else if (discoverResult.type === "error") {
|
|
2397
|
+
let {
|
|
2398
|
+
error,
|
|
2399
|
+
notFoundMatches,
|
|
2400
|
+
route
|
|
2401
|
+
} = handleDiscoverRouteError(location.pathname, discoverResult);
|
|
2402
|
+
return {
|
|
2403
|
+
matches: notFoundMatches,
|
|
2404
|
+
pendingActionResult: [route.id, {
|
|
2405
|
+
type: ResultType.error,
|
|
2406
|
+
error
|
|
2407
|
+
}]
|
|
2408
|
+
};
|
|
2409
|
+
} else if (!discoverResult.matches) {
|
|
2410
|
+
let {
|
|
2411
|
+
notFoundMatches,
|
|
2412
|
+
error,
|
|
2413
|
+
route
|
|
2414
|
+
} = handleNavigational404(location.pathname);
|
|
2415
|
+
return {
|
|
2416
|
+
matches: notFoundMatches,
|
|
2417
|
+
pendingActionResult: [route.id, {
|
|
2418
|
+
type: ResultType.error,
|
|
2419
|
+
error
|
|
2420
|
+
}]
|
|
2421
|
+
};
|
|
2422
|
+
} else {
|
|
2423
|
+
matches = discoverResult.matches;
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2346
2426
|
|
|
2347
2427
|
// Call our action and get the result
|
|
2348
2428
|
let result;
|
|
@@ -2394,31 +2474,94 @@
|
|
|
2394
2474
|
// to call and will commit it when we complete the navigation
|
|
2395
2475
|
let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
|
|
2396
2476
|
|
|
2397
|
-
// By default, all submissions
|
|
2398
|
-
// action threw an error that'll be rendered in
|
|
2399
|
-
// back to PUSH so that the user can use the
|
|
2400
|
-
// the pre-submission form location to try
|
|
2477
|
+
// By default, all submissions to the current location are REPLACE
|
|
2478
|
+
// navigations, but if the action threw an error that'll be rendered in
|
|
2479
|
+
// an errorElement, we fall back to PUSH so that the user can use the
|
|
2480
|
+
// back button to get back to the pre-submission form location to try
|
|
2481
|
+
// again
|
|
2401
2482
|
if ((opts && opts.replace) !== true) {
|
|
2402
2483
|
pendingAction = Action.Push;
|
|
2403
2484
|
}
|
|
2404
2485
|
return {
|
|
2486
|
+
matches,
|
|
2405
2487
|
pendingActionResult: [boundaryMatch.route.id, result]
|
|
2406
2488
|
};
|
|
2407
2489
|
}
|
|
2408
2490
|
return {
|
|
2491
|
+
matches,
|
|
2409
2492
|
pendingActionResult: [actionMatch.route.id, result]
|
|
2410
2493
|
};
|
|
2411
2494
|
}
|
|
2412
2495
|
|
|
2413
2496
|
// Call all applicable loaders for the given matches, handling redirects,
|
|
2414
2497
|
// errors, etc.
|
|
2415
|
-
async function handleLoaders(request, location, matches, overrideNavigation, submission, fetcherSubmission, replace, initialHydration, flushSync, pendingActionResult) {
|
|
2498
|
+
async function handleLoaders(request, location, matches, isFogOfWar, overrideNavigation, submission, fetcherSubmission, replace, initialHydration, flushSync, pendingActionResult) {
|
|
2416
2499
|
// Figure out the right navigation we want to use for data loading
|
|
2417
2500
|
let loadingNavigation = overrideNavigation || getLoadingNavigation(location, submission);
|
|
2418
2501
|
|
|
2419
2502
|
// If this was a redirect from an action we don't have a "submission" but
|
|
2420
2503
|
// we have it on the loading navigation so use that if available
|
|
2421
2504
|
let activeSubmission = submission || fetcherSubmission || getSubmissionFromNavigation(loadingNavigation);
|
|
2505
|
+
|
|
2506
|
+
// If this is an uninterrupted revalidation, we remain in our current idle
|
|
2507
|
+
// state. If not, we need to switch to our loading state and load data,
|
|
2508
|
+
// preserving any new action data or existing action data (in the case of
|
|
2509
|
+
// a revalidation interrupting an actionReload)
|
|
2510
|
+
// If we have partialHydration enabled, then don't update the state for the
|
|
2511
|
+
// initial data load since it's not a "navigation"
|
|
2512
|
+
let shouldUpdateNavigationState = !isUninterruptedRevalidation && (!future.v7_partialHydration || !initialHydration);
|
|
2513
|
+
|
|
2514
|
+
// When fog of war is enabled, we enter our `loading` state earlier so we
|
|
2515
|
+
// can discover new routes during the `loading` state. We skip this if
|
|
2516
|
+
// we've already run actions since we would have done our matching already.
|
|
2517
|
+
// If the children() function threw then, we want to proceed with the
|
|
2518
|
+
// partial matches it discovered.
|
|
2519
|
+
if (isFogOfWar) {
|
|
2520
|
+
if (shouldUpdateNavigationState) {
|
|
2521
|
+
let actionData = getUpdatedActionData(pendingActionResult);
|
|
2522
|
+
updateState(_extends({
|
|
2523
|
+
navigation: loadingNavigation
|
|
2524
|
+
}, actionData !== undefined ? {
|
|
2525
|
+
actionData
|
|
2526
|
+
} : {}), {
|
|
2527
|
+
flushSync
|
|
2528
|
+
});
|
|
2529
|
+
}
|
|
2530
|
+
let discoverResult = await discoverRoutes(matches, location.pathname, request.signal);
|
|
2531
|
+
if (discoverResult.type === "aborted") {
|
|
2532
|
+
return {
|
|
2533
|
+
shortCircuited: true
|
|
2534
|
+
};
|
|
2535
|
+
} else if (discoverResult.type === "error") {
|
|
2536
|
+
let {
|
|
2537
|
+
error,
|
|
2538
|
+
notFoundMatches,
|
|
2539
|
+
route
|
|
2540
|
+
} = handleDiscoverRouteError(location.pathname, discoverResult);
|
|
2541
|
+
return {
|
|
2542
|
+
matches: notFoundMatches,
|
|
2543
|
+
loaderData: {},
|
|
2544
|
+
errors: {
|
|
2545
|
+
[route.id]: error
|
|
2546
|
+
}
|
|
2547
|
+
};
|
|
2548
|
+
} else if (!discoverResult.matches) {
|
|
2549
|
+
let {
|
|
2550
|
+
error,
|
|
2551
|
+
notFoundMatches,
|
|
2552
|
+
route
|
|
2553
|
+
} = handleNavigational404(location.pathname);
|
|
2554
|
+
return {
|
|
2555
|
+
matches: notFoundMatches,
|
|
2556
|
+
loaderData: {},
|
|
2557
|
+
errors: {
|
|
2558
|
+
[route.id]: error
|
|
2559
|
+
}
|
|
2560
|
+
};
|
|
2561
|
+
} else {
|
|
2562
|
+
matches = discoverResult.matches;
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2422
2565
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
2423
2566
|
let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, activeSubmission, location, future.v7_partialHydration && initialHydration === true, future.unstable_skipActionErrorRevalidation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, deletedFetchers, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionResult);
|
|
2424
2567
|
|
|
@@ -2447,41 +2590,20 @@
|
|
|
2447
2590
|
shortCircuited: true
|
|
2448
2591
|
};
|
|
2449
2592
|
}
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
revalidatingFetchers.forEach(rf => {
|
|
2459
|
-
let fetcher = state.fetchers.get(rf.key);
|
|
2460
|
-
let revalidatingFetcher = getLoadingFetcher(undefined, fetcher ? fetcher.data : undefined);
|
|
2461
|
-
state.fetchers.set(rf.key, revalidatingFetcher);
|
|
2462
|
-
});
|
|
2463
|
-
let actionData;
|
|
2464
|
-
if (pendingActionResult && !isErrorResult(pendingActionResult[1])) {
|
|
2465
|
-
// This is cast to `any` currently because `RouteData`uses any and it
|
|
2466
|
-
// would be a breaking change to use any.
|
|
2467
|
-
// TODO: v7 - change `RouteData` to use `unknown` instead of `any`
|
|
2468
|
-
actionData = {
|
|
2469
|
-
[pendingActionResult[0]]: pendingActionResult[1].data
|
|
2470
|
-
};
|
|
2471
|
-
} else if (state.actionData) {
|
|
2472
|
-
if (Object.keys(state.actionData).length === 0) {
|
|
2473
|
-
actionData = null;
|
|
2474
|
-
} else {
|
|
2475
|
-
actionData = state.actionData;
|
|
2593
|
+
if (shouldUpdateNavigationState) {
|
|
2594
|
+
let updates = {};
|
|
2595
|
+
if (!isFogOfWar) {
|
|
2596
|
+
// Only update navigation/actionNData if we didn't already do it above
|
|
2597
|
+
updates.navigation = loadingNavigation;
|
|
2598
|
+
let actionData = getUpdatedActionData(pendingActionResult);
|
|
2599
|
+
if (actionData !== undefined) {
|
|
2600
|
+
updates.actionData = actionData;
|
|
2476
2601
|
}
|
|
2477
2602
|
}
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
}
|
|
2481
|
-
|
|
2482
|
-
} : {}, revalidatingFetchers.length > 0 ? {
|
|
2483
|
-
fetchers: new Map(state.fetchers)
|
|
2484
|
-
} : {}), {
|
|
2603
|
+
if (revalidatingFetchers.length > 0) {
|
|
2604
|
+
updates.fetchers = getUpdatedRevalidatingFetchers(revalidatingFetchers);
|
|
2605
|
+
}
|
|
2606
|
+
updateState(updates, {
|
|
2485
2607
|
flushSync
|
|
2486
2608
|
});
|
|
2487
2609
|
}
|
|
@@ -2572,12 +2694,37 @@
|
|
|
2572
2694
|
let didAbortFetchLoads = abortStaleFetchLoads(pendingNavigationLoadId);
|
|
2573
2695
|
let shouldUpdateFetchers = updatedFetchers || didAbortFetchLoads || revalidatingFetchers.length > 0;
|
|
2574
2696
|
return _extends({
|
|
2697
|
+
matches,
|
|
2575
2698
|
loaderData,
|
|
2576
2699
|
errors
|
|
2577
2700
|
}, shouldUpdateFetchers ? {
|
|
2578
2701
|
fetchers: new Map(state.fetchers)
|
|
2579
2702
|
} : {});
|
|
2580
2703
|
}
|
|
2704
|
+
function getUpdatedActionData(pendingActionResult) {
|
|
2705
|
+
if (pendingActionResult && !isErrorResult(pendingActionResult[1])) {
|
|
2706
|
+
// This is cast to `any` currently because `RouteData`uses any and it
|
|
2707
|
+
// would be a breaking change to use any.
|
|
2708
|
+
// TODO: v7 - change `RouteData` to use `unknown` instead of `any`
|
|
2709
|
+
return {
|
|
2710
|
+
[pendingActionResult[0]]: pendingActionResult[1].data
|
|
2711
|
+
};
|
|
2712
|
+
} else if (state.actionData) {
|
|
2713
|
+
if (Object.keys(state.actionData).length === 0) {
|
|
2714
|
+
return null;
|
|
2715
|
+
} else {
|
|
2716
|
+
return state.actionData;
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
function getUpdatedRevalidatingFetchers(revalidatingFetchers) {
|
|
2721
|
+
revalidatingFetchers.forEach(rf => {
|
|
2722
|
+
let fetcher = state.fetchers.get(rf.key);
|
|
2723
|
+
let revalidatingFetcher = getLoadingFetcher(undefined, fetcher ? fetcher.data : undefined);
|
|
2724
|
+
state.fetchers.set(rf.key, revalidatingFetcher);
|
|
2725
|
+
});
|
|
2726
|
+
return new Map(state.fetchers);
|
|
2727
|
+
}
|
|
2581
2728
|
|
|
2582
2729
|
// Trigger a fetcher load/submit for the given fetcher key
|
|
2583
2730
|
function fetch(key, routeId, href, opts) {
|
|
@@ -2589,6 +2736,10 @@
|
|
|
2589
2736
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
2590
2737
|
let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, href, future.v7_relativeSplatPath, routeId, opts == null ? void 0 : opts.relative);
|
|
2591
2738
|
let matches = matchRoutes(routesToUse, normalizedPath, basename);
|
|
2739
|
+
let fogOfWar = checkFogOfWar(matches, routesToUse, normalizedPath);
|
|
2740
|
+
if (fogOfWar.active && fogOfWar.matches) {
|
|
2741
|
+
matches = fogOfWar.matches;
|
|
2742
|
+
}
|
|
2592
2743
|
if (!matches) {
|
|
2593
2744
|
setFetcherError(key, routeId, getInternalRouterError(404, {
|
|
2594
2745
|
pathname: normalizedPath
|
|
@@ -2611,7 +2762,7 @@
|
|
|
2611
2762
|
let match = getTargetMatch(matches, path);
|
|
2612
2763
|
pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
|
|
2613
2764
|
if (submission && isMutationMethod(submission.formMethod)) {
|
|
2614
|
-
handleFetcherAction(key, routeId, path, match, matches, flushSync, submission);
|
|
2765
|
+
handleFetcherAction(key, routeId, path, match, matches, fogOfWar.active, flushSync, submission);
|
|
2615
2766
|
return;
|
|
2616
2767
|
}
|
|
2617
2768
|
|
|
@@ -2621,23 +2772,29 @@
|
|
|
2621
2772
|
routeId,
|
|
2622
2773
|
path
|
|
2623
2774
|
});
|
|
2624
|
-
handleFetcherLoader(key, routeId, path, match, matches, flushSync, submission);
|
|
2775
|
+
handleFetcherLoader(key, routeId, path, match, matches, fogOfWar.active, flushSync, submission);
|
|
2625
2776
|
}
|
|
2626
2777
|
|
|
2627
2778
|
// Call the action for the matched fetcher.submit(), and then handle redirects,
|
|
2628
2779
|
// errors, and revalidation
|
|
2629
|
-
async function handleFetcherAction(key, routeId, path, match, requestMatches, flushSync, submission) {
|
|
2780
|
+
async function handleFetcherAction(key, routeId, path, match, requestMatches, isFogOfWar, flushSync, submission) {
|
|
2630
2781
|
interruptActiveLoads();
|
|
2631
2782
|
fetchLoadMatches.delete(key);
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2783
|
+
function detectAndHandle405Error(m) {
|
|
2784
|
+
if (!m.route.action && !m.route.lazy) {
|
|
2785
|
+
let error = getInternalRouterError(405, {
|
|
2786
|
+
method: submission.formMethod,
|
|
2787
|
+
pathname: path,
|
|
2788
|
+
routeId: routeId
|
|
2789
|
+
});
|
|
2790
|
+
setFetcherError(key, routeId, error, {
|
|
2791
|
+
flushSync
|
|
2792
|
+
});
|
|
2793
|
+
return true;
|
|
2794
|
+
}
|
|
2795
|
+
return false;
|
|
2796
|
+
}
|
|
2797
|
+
if (!isFogOfWar && detectAndHandle405Error(match)) {
|
|
2641
2798
|
return;
|
|
2642
2799
|
}
|
|
2643
2800
|
|
|
@@ -2646,10 +2803,37 @@
|
|
|
2646
2803
|
updateFetcherState(key, getSubmittingFetcher(submission, existingFetcher), {
|
|
2647
2804
|
flushSync
|
|
2648
2805
|
});
|
|
2649
|
-
|
|
2650
|
-
// Call the action for the fetcher
|
|
2651
2806
|
let abortController = new AbortController();
|
|
2652
2807
|
let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);
|
|
2808
|
+
if (isFogOfWar) {
|
|
2809
|
+
let discoverResult = await discoverRoutes(requestMatches, path, fetchRequest.signal);
|
|
2810
|
+
if (discoverResult.type === "aborted") {
|
|
2811
|
+
return;
|
|
2812
|
+
} else if (discoverResult.type === "error") {
|
|
2813
|
+
let {
|
|
2814
|
+
error
|
|
2815
|
+
} = handleDiscoverRouteError(path, discoverResult);
|
|
2816
|
+
setFetcherError(key, routeId, error, {
|
|
2817
|
+
flushSync
|
|
2818
|
+
});
|
|
2819
|
+
return;
|
|
2820
|
+
} else if (!discoverResult.matches) {
|
|
2821
|
+
setFetcherError(key, routeId, getInternalRouterError(404, {
|
|
2822
|
+
pathname: path
|
|
2823
|
+
}), {
|
|
2824
|
+
flushSync
|
|
2825
|
+
});
|
|
2826
|
+
return;
|
|
2827
|
+
} else {
|
|
2828
|
+
requestMatches = discoverResult.matches;
|
|
2829
|
+
match = getTargetMatch(requestMatches, path);
|
|
2830
|
+
if (detectAndHandle405Error(match)) {
|
|
2831
|
+
return;
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2836
|
+
// Call the action for the fetcher
|
|
2653
2837
|
fetchControllers.set(key, abortController);
|
|
2654
2838
|
let originatingLoadId = incrementingLoadId;
|
|
2655
2839
|
let actionResults = await callDataStrategy("action", fetchRequest, [match], requestMatches);
|
|
@@ -2799,15 +2983,39 @@
|
|
|
2799
2983
|
}
|
|
2800
2984
|
|
|
2801
2985
|
// Call the matched loader for fetcher.load(), handling redirects, errors, etc.
|
|
2802
|
-
async function handleFetcherLoader(key, routeId, path, match, matches, flushSync, submission) {
|
|
2986
|
+
async function handleFetcherLoader(key, routeId, path, match, matches, isFogOfWar, flushSync, submission) {
|
|
2803
2987
|
let existingFetcher = state.fetchers.get(key);
|
|
2804
2988
|
updateFetcherState(key, getLoadingFetcher(submission, existingFetcher ? existingFetcher.data : undefined), {
|
|
2805
2989
|
flushSync
|
|
2806
2990
|
});
|
|
2807
|
-
|
|
2808
|
-
// Call the loader for this fetcher route match
|
|
2809
2991
|
let abortController = new AbortController();
|
|
2810
2992
|
let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
|
|
2993
|
+
if (isFogOfWar) {
|
|
2994
|
+
let discoverResult = await discoverRoutes(matches, path, fetchRequest.signal);
|
|
2995
|
+
if (discoverResult.type === "aborted") {
|
|
2996
|
+
return;
|
|
2997
|
+
} else if (discoverResult.type === "error") {
|
|
2998
|
+
let {
|
|
2999
|
+
error
|
|
3000
|
+
} = handleDiscoverRouteError(path, discoverResult);
|
|
3001
|
+
setFetcherError(key, routeId, error, {
|
|
3002
|
+
flushSync
|
|
3003
|
+
});
|
|
3004
|
+
return;
|
|
3005
|
+
} else if (!discoverResult.matches) {
|
|
3006
|
+
setFetcherError(key, routeId, getInternalRouterError(404, {
|
|
3007
|
+
pathname: path
|
|
3008
|
+
}), {
|
|
3009
|
+
flushSync
|
|
3010
|
+
});
|
|
3011
|
+
return;
|
|
3012
|
+
} else {
|
|
3013
|
+
matches = discoverResult.matches;
|
|
3014
|
+
match = getTargetMatch(matches, path);
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
|
|
3018
|
+
// Call the loader for this fetcher route match
|
|
2811
3019
|
fetchControllers.set(key, abortController);
|
|
2812
3020
|
let originatingLoadId = incrementingLoadId;
|
|
2813
3021
|
let results = await callDataStrategy("loader", fetchRequest, [match], matches);
|
|
@@ -3191,6 +3399,39 @@
|
|
|
3191
3399
|
return blockerKey;
|
|
3192
3400
|
}
|
|
3193
3401
|
}
|
|
3402
|
+
function handleNavigational404(pathname) {
|
|
3403
|
+
let error = getInternalRouterError(404, {
|
|
3404
|
+
pathname
|
|
3405
|
+
});
|
|
3406
|
+
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
3407
|
+
let {
|
|
3408
|
+
matches,
|
|
3409
|
+
route
|
|
3410
|
+
} = getShortCircuitMatches(routesToUse);
|
|
3411
|
+
|
|
3412
|
+
// Cancel all pending deferred on 404s since we don't keep any routes
|
|
3413
|
+
cancelActiveDeferreds();
|
|
3414
|
+
return {
|
|
3415
|
+
notFoundMatches: matches,
|
|
3416
|
+
route,
|
|
3417
|
+
error
|
|
3418
|
+
};
|
|
3419
|
+
}
|
|
3420
|
+
function handleDiscoverRouteError(pathname, discoverResult) {
|
|
3421
|
+
let matches = discoverResult.partialMatches;
|
|
3422
|
+
let route = matches[matches.length - 1].route;
|
|
3423
|
+
let error = getInternalRouterError(400, {
|
|
3424
|
+
type: "route-discovery",
|
|
3425
|
+
routeId: route.id,
|
|
3426
|
+
pathname,
|
|
3427
|
+
message: discoverResult.error != null && "message" in discoverResult.error ? discoverResult.error : String(discoverResult.error)
|
|
3428
|
+
});
|
|
3429
|
+
return {
|
|
3430
|
+
notFoundMatches: matches,
|
|
3431
|
+
route,
|
|
3432
|
+
error
|
|
3433
|
+
};
|
|
3434
|
+
}
|
|
3194
3435
|
function cancelActiveDeferreds(predicate) {
|
|
3195
3436
|
let cancelledRouteIds = [];
|
|
3196
3437
|
activeDeferreds.forEach((dfd, routeId) => {
|
|
@@ -3254,6 +3495,100 @@
|
|
|
3254
3495
|
}
|
|
3255
3496
|
return null;
|
|
3256
3497
|
}
|
|
3498
|
+
function checkFogOfWar(matches, routesToUse, pathname) {
|
|
3499
|
+
if (patchRoutesOnMissImpl) {
|
|
3500
|
+
if (!matches) {
|
|
3501
|
+
let fogMatches = matchRoutesImpl(routesToUse, pathname, basename, true);
|
|
3502
|
+
return {
|
|
3503
|
+
active: true,
|
|
3504
|
+
matches: fogMatches || []
|
|
3505
|
+
};
|
|
3506
|
+
} else {
|
|
3507
|
+
let leafRoute = matches[matches.length - 1].route;
|
|
3508
|
+
if (leafRoute.path === "*") {
|
|
3509
|
+
// If we matched a splat, it might only be because we haven't yet fetched
|
|
3510
|
+
// the children that would match with a higher score, so let's fetch
|
|
3511
|
+
// around and find out
|
|
3512
|
+
let partialMatches = matchRoutesImpl(routesToUse, pathname, basename, true);
|
|
3513
|
+
return {
|
|
3514
|
+
active: true,
|
|
3515
|
+
matches: partialMatches
|
|
3516
|
+
};
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
3520
|
+
return {
|
|
3521
|
+
active: false,
|
|
3522
|
+
matches: null
|
|
3523
|
+
};
|
|
3524
|
+
}
|
|
3525
|
+
async function discoverRoutes(matches, pathname, signal) {
|
|
3526
|
+
let partialMatches = matches;
|
|
3527
|
+
let route = partialMatches.length > 0 ? partialMatches[partialMatches.length - 1].route : null;
|
|
3528
|
+
while (true) {
|
|
3529
|
+
try {
|
|
3530
|
+
await loadLazyRouteChildren(patchRoutesOnMissImpl, pathname, partialMatches, dataRoutes || inFlightDataRoutes, manifest, mapRouteProperties, pendingPatchRoutes, signal);
|
|
3531
|
+
} catch (e) {
|
|
3532
|
+
return {
|
|
3533
|
+
type: "error",
|
|
3534
|
+
error: e,
|
|
3535
|
+
partialMatches
|
|
3536
|
+
};
|
|
3537
|
+
}
|
|
3538
|
+
if (signal.aborted) {
|
|
3539
|
+
return {
|
|
3540
|
+
type: "aborted"
|
|
3541
|
+
};
|
|
3542
|
+
}
|
|
3543
|
+
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
3544
|
+
let newMatches = matchRoutes(routesToUse, pathname, basename);
|
|
3545
|
+
let matchedSplat = false;
|
|
3546
|
+
if (newMatches) {
|
|
3547
|
+
let leafRoute = newMatches[newMatches.length - 1].route;
|
|
3548
|
+
if (leafRoute.index) {
|
|
3549
|
+
// If we found an index route, we can stop
|
|
3550
|
+
return {
|
|
3551
|
+
type: "success",
|
|
3552
|
+
matches: newMatches
|
|
3553
|
+
};
|
|
3554
|
+
}
|
|
3555
|
+
if (leafRoute.path && leafRoute.path.length > 0) {
|
|
3556
|
+
if (leafRoute.path === "*") {
|
|
3557
|
+
// If we found a splat route, we can't be sure there's not a
|
|
3558
|
+
// higher-scoring route down some partial matches trail so we need
|
|
3559
|
+
// to check that out
|
|
3560
|
+
matchedSplat = true;
|
|
3561
|
+
} else {
|
|
3562
|
+
// If we found a non-splat route, we can stop
|
|
3563
|
+
return {
|
|
3564
|
+
type: "success",
|
|
3565
|
+
matches: newMatches
|
|
3566
|
+
};
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
}
|
|
3570
|
+
let newPartialMatches = matchRoutesImpl(routesToUse, pathname, basename, true);
|
|
3571
|
+
|
|
3572
|
+
// If we are no longer partially matching anything, this was either a
|
|
3573
|
+
// legit splat match above, or it's a 404. Also avoid loops if the
|
|
3574
|
+
// second pass results in the same partial matches
|
|
3575
|
+
if (!newPartialMatches || partialMatches.map(m => m.route.id).join("-") === newPartialMatches.map(m => m.route.id).join("-")) {
|
|
3576
|
+
return {
|
|
3577
|
+
type: "success",
|
|
3578
|
+
matches: matchedSplat ? newMatches : null
|
|
3579
|
+
};
|
|
3580
|
+
}
|
|
3581
|
+
partialMatches = newPartialMatches;
|
|
3582
|
+
route = partialMatches[partialMatches.length - 1].route;
|
|
3583
|
+
if (route.path === "*") {
|
|
3584
|
+
// The splat is still our most accurate partial, so run with it
|
|
3585
|
+
return {
|
|
3586
|
+
type: "success",
|
|
3587
|
+
matches: partialMatches
|
|
3588
|
+
};
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
}
|
|
3257
3592
|
function _internalSetRoutes(newRoutes) {
|
|
3258
3593
|
manifest = {};
|
|
3259
3594
|
inFlightDataRoutes = convertRoutesToDataRoutes(newRoutes, mapRouteProperties, undefined, manifest);
|
|
@@ -3289,6 +3624,9 @@
|
|
|
3289
3624
|
dispose,
|
|
3290
3625
|
getBlocker,
|
|
3291
3626
|
deleteBlocker,
|
|
3627
|
+
patchRoutes(routeId, children) {
|
|
3628
|
+
return patchRoutes(routeId, children, dataRoutes || inFlightDataRoutes, manifest, mapRouteProperties);
|
|
3629
|
+
},
|
|
3292
3630
|
_internalFetchControllers: fetchControllers,
|
|
3293
3631
|
_internalActiveDeferreds: activeDeferreds,
|
|
3294
3632
|
// TODO: Remove setRoutes, it's temporary to avoid dealing with
|
|
@@ -4105,6 +4443,50 @@
|
|
|
4105
4443
|
return arg.defaultShouldRevalidate;
|
|
4106
4444
|
}
|
|
4107
4445
|
|
|
4446
|
+
/**
|
|
4447
|
+
* Idempotent utility to execute route.children() method to lazily load route
|
|
4448
|
+
* definitions and update the routes/routeManifest
|
|
4449
|
+
*/
|
|
4450
|
+
async function loadLazyRouteChildren(patchRoutesOnMissImpl, path, matches, routes, manifest, mapRouteProperties, pendingRouteChildren, signal) {
|
|
4451
|
+
let key = [path, ...matches.map(m => m.route.id)].join("-");
|
|
4452
|
+
try {
|
|
4453
|
+
let pending = pendingRouteChildren.get(key);
|
|
4454
|
+
if (!pending) {
|
|
4455
|
+
pending = patchRoutesOnMissImpl({
|
|
4456
|
+
path,
|
|
4457
|
+
matches,
|
|
4458
|
+
patch: (routeId, children) => {
|
|
4459
|
+
if (!signal.aborted) {
|
|
4460
|
+
patchRoutes(routeId, children, routes, manifest, mapRouteProperties);
|
|
4461
|
+
}
|
|
4462
|
+
}
|
|
4463
|
+
});
|
|
4464
|
+
pendingRouteChildren.set(key, pending);
|
|
4465
|
+
}
|
|
4466
|
+
if (pending && isPromise(pending)) {
|
|
4467
|
+
await pending;
|
|
4468
|
+
}
|
|
4469
|
+
} finally {
|
|
4470
|
+
pendingRouteChildren.delete(key);
|
|
4471
|
+
}
|
|
4472
|
+
}
|
|
4473
|
+
function patchRoutes(routeId, children, routes, manifest, mapRouteProperties) {
|
|
4474
|
+
if (routeId) {
|
|
4475
|
+
var _route$children;
|
|
4476
|
+
let route = manifest[routeId];
|
|
4477
|
+
invariant(route, "No route found to patch children into: routeId = " + routeId);
|
|
4478
|
+
let dataChildren = convertRoutesToDataRoutes(children, mapRouteProperties, [routeId, "patch", String(((_route$children = route.children) == null ? void 0 : _route$children.length) || "0")], manifest);
|
|
4479
|
+
if (route.children) {
|
|
4480
|
+
route.children.push(...dataChildren);
|
|
4481
|
+
} else {
|
|
4482
|
+
route.children = dataChildren;
|
|
4483
|
+
}
|
|
4484
|
+
} else {
|
|
4485
|
+
let dataChildren = convertRoutesToDataRoutes(children, mapRouteProperties, ["patch", String(routes.length || "0")], manifest);
|
|
4486
|
+
routes.push(...dataChildren);
|
|
4487
|
+
}
|
|
4488
|
+
}
|
|
4489
|
+
|
|
4108
4490
|
/**
|
|
4109
4491
|
* Execute route.lazy() methods to lazily load route modules (loader, action,
|
|
4110
4492
|
* shouldRevalidate) and update the routeManifest in place which shares objects
|
|
@@ -4648,13 +5030,16 @@
|
|
|
4648
5030
|
pathname,
|
|
4649
5031
|
routeId,
|
|
4650
5032
|
method,
|
|
4651
|
-
type
|
|
5033
|
+
type,
|
|
5034
|
+
message
|
|
4652
5035
|
} = _temp5 === void 0 ? {} : _temp5;
|
|
4653
5036
|
let statusText = "Unknown Server Error";
|
|
4654
5037
|
let errorMessage = "Unknown @remix-run/router error";
|
|
4655
5038
|
if (status === 400) {
|
|
4656
5039
|
statusText = "Bad Request";
|
|
4657
|
-
if (
|
|
5040
|
+
if (type === "route-discovery") {
|
|
5041
|
+
errorMessage = "Unable to match URL \"" + pathname + "\" - the `children()` function for " + ("route `" + routeId + "` threw the following error:\n" + message);
|
|
5042
|
+
} else if (method && pathname && routeId) {
|
|
4658
5043
|
errorMessage = "You made a " + method + " request to \"" + pathname + "\" but " + ("did not provide a `loader` for route \"" + routeId + "\", ") + "so there is no way to handle the request.";
|
|
4659
5044
|
} else if (type === "defer-action") {
|
|
4660
5045
|
errorMessage = "defer() is not supported in actions";
|
|
@@ -4715,6 +5100,9 @@
|
|
|
4715
5100
|
// /page#hash -> /page
|
|
4716
5101
|
return false;
|
|
4717
5102
|
}
|
|
5103
|
+
function isPromise(val) {
|
|
5104
|
+
return typeof val === "object" && val != null && "then" in val;
|
|
5105
|
+
}
|
|
4718
5106
|
function isHandlerResult(result) {
|
|
4719
5107
|
return result != null && typeof result === "object" && "type" in result && "result" in result && (result.type === ResultType.data || result.type === ResultType.error);
|
|
4720
5108
|
}
|
|
@@ -4980,7 +5368,6 @@
|
|
|
4980
5368
|
}
|
|
4981
5369
|
}
|
|
4982
5370
|
}
|
|
4983
|
-
|
|
4984
5371
|
//#endregion
|
|
4985
5372
|
|
|
4986
5373
|
exports.AbortedDeferredError = AbortedDeferredError;
|