@remix-run/router 0.0.0-experimental-c9f8a7b2 → 0.0.0-experimental-e960cf1a
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 +8 -199
- package/dist/index.d.ts +2 -2
- package/dist/router.cjs.js +138 -90
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.d.ts +3 -2
- package/dist/router.js +135 -91
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +138 -90
- 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 +13 -0
- package/index.ts +5 -0
- package/package.json +1 -1
- package/router.ts +243 -124
- package/utils.ts +25 -9
package/dist/utils.d.ts
CHANGED
|
@@ -182,6 +182,15 @@ export interface ShouldRevalidateFunction {
|
|
|
182
182
|
export interface DetectErrorBoundaryFunction {
|
|
183
183
|
(route: AgnosticRouteObject): boolean;
|
|
184
184
|
}
|
|
185
|
+
export interface DataStrategyFunctionArgs {
|
|
186
|
+
request: Request;
|
|
187
|
+
matches: AgnosticDataStrategyMatch[];
|
|
188
|
+
type: "loader" | "action";
|
|
189
|
+
defaultStrategy(match: AgnosticDataStrategyMatch): Promise<DataResult>;
|
|
190
|
+
}
|
|
191
|
+
export interface DataStrategyFunction {
|
|
192
|
+
(args: DataStrategyFunctionArgs): Promise<DataResult[]>;
|
|
193
|
+
}
|
|
185
194
|
/**
|
|
186
195
|
* Function provided by the framework-aware layers to set any framework-specific
|
|
187
196
|
* properties from framework-agnostic properties
|
|
@@ -296,6 +305,10 @@ export interface AgnosticRouteMatch<ParamKey extends string = string, RouteObjec
|
|
|
296
305
|
}
|
|
297
306
|
export interface AgnosticDataRouteMatch extends AgnosticRouteMatch<string, AgnosticDataRouteObject> {
|
|
298
307
|
}
|
|
308
|
+
export type LazyRoutePromise = PromiseLike<AgnosticDataRouteObject> & AgnosticDataRouteObject;
|
|
309
|
+
export interface AgnosticDataStrategyMatch extends Omit<AgnosticRouteMatch<string, AgnosticDataRouteObject>, "route"> {
|
|
310
|
+
route: LazyRoutePromise;
|
|
311
|
+
}
|
|
299
312
|
export declare function convertRoutesToDataRoutes(routes: AgnosticRouteObject[], mapRouteProperties: MapRoutePropertiesFunction, parentPath?: number[], manifest?: RouteManifest): AgnosticDataRouteObject[];
|
|
300
313
|
/**
|
|
301
314
|
* Matches the given routes to a location and returns the match data.
|
package/index.ts
CHANGED
|
@@ -5,10 +5,14 @@ export type {
|
|
|
5
5
|
AgnosticDataNonIndexRouteObject,
|
|
6
6
|
AgnosticDataRouteMatch,
|
|
7
7
|
AgnosticDataRouteObject,
|
|
8
|
+
AgnosticDataStrategyMatch,
|
|
8
9
|
AgnosticIndexRouteObject,
|
|
9
10
|
AgnosticNonIndexRouteObject,
|
|
10
11
|
AgnosticRouteMatch,
|
|
11
12
|
AgnosticRouteObject,
|
|
13
|
+
DataResult,
|
|
14
|
+
DataStrategyFunction,
|
|
15
|
+
DataStrategyFunctionArgs,
|
|
12
16
|
ErrorResponse,
|
|
13
17
|
FormEncType,
|
|
14
18
|
FormMethod,
|
|
@@ -45,6 +49,7 @@ export {
|
|
|
45
49
|
redirectDocument,
|
|
46
50
|
resolvePath,
|
|
47
51
|
resolveTo,
|
|
52
|
+
ResultType,
|
|
48
53
|
stripBasename,
|
|
49
54
|
} from "./utils";
|
|
50
55
|
|
package/package.json
CHANGED
package/router.ts
CHANGED
|
@@ -11,8 +11,11 @@ import type {
|
|
|
11
11
|
ActionFunction,
|
|
12
12
|
AgnosticDataRouteMatch,
|
|
13
13
|
AgnosticDataRouteObject,
|
|
14
|
+
AgnosticDataStrategyMatch,
|
|
14
15
|
AgnosticRouteObject,
|
|
15
16
|
DataResult,
|
|
17
|
+
DataStrategyFunction,
|
|
18
|
+
DataStrategyFunctionArgs,
|
|
16
19
|
DeferredData,
|
|
17
20
|
DeferredResult,
|
|
18
21
|
DetectErrorBoundaryFunction,
|
|
@@ -21,6 +24,7 @@ import type {
|
|
|
21
24
|
FormMethod,
|
|
22
25
|
HTMLFormMethod,
|
|
23
26
|
ImmutableRouteKey,
|
|
27
|
+
LazyRoutePromise,
|
|
24
28
|
LoaderFunction,
|
|
25
29
|
MapRoutePropertiesFunction,
|
|
26
30
|
MutationFormMethod,
|
|
@@ -374,6 +378,7 @@ export interface RouterInit {
|
|
|
374
378
|
future?: Partial<FutureConfig>;
|
|
375
379
|
hydrationData?: HydrationState;
|
|
376
380
|
window?: Window;
|
|
381
|
+
unstable_dataStrategy?: DataStrategyFunction;
|
|
377
382
|
}
|
|
378
383
|
|
|
379
384
|
/**
|
|
@@ -752,6 +757,9 @@ export function createRouter(init: RouterInit): Router {
|
|
|
752
757
|
"You must provide a non-empty routes array to createRouter"
|
|
753
758
|
);
|
|
754
759
|
|
|
760
|
+
const dataStrategy = init.unstable_dataStrategy || defaultDataStrategy;
|
|
761
|
+
const callLoaderOrAction = createCallLoaderOrAction(dataStrategy);
|
|
762
|
+
|
|
755
763
|
let mapRouteProperties: MapRoutePropertiesFunction;
|
|
756
764
|
if (init.mapRouteProperties) {
|
|
757
765
|
mapRouteProperties = init.mapRouteProperties;
|
|
@@ -817,34 +825,19 @@ export function createRouter(init: RouterInit): Router {
|
|
|
817
825
|
initialErrors = { [route.id]: error };
|
|
818
826
|
}
|
|
819
827
|
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
828
|
+
// "Initialized" here really means "Can `RouterProvider` render my route tree?"
|
|
829
|
+
// Prior to `route.HydrateFallback`, we only had a root `fallbackElement` so we used
|
|
830
|
+
// `state.initialized` to render that instead of `<DataRoutes>`. Now that we
|
|
831
|
+
// support route level fallbacks we can always render and we'll just render
|
|
832
|
+
// as deep as we have data for and detect the nearest ancestor HydrateFallback
|
|
833
|
+
let initialized =
|
|
834
|
+
future.v7_partialHydration ||
|
|
824
835
|
// All initialMatches need to be loaded before we're ready. If we have lazy
|
|
825
836
|
// functions around still then we'll need to run them in initialize()
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
} else if (future.v7_partialHydration) {
|
|
831
|
-
// If partial hydration is enabled, we're initialized so long as we were
|
|
832
|
-
// provided with hydrationData for every route with a loader, and no loaders
|
|
833
|
-
// were marked for explicit hydration
|
|
834
|
-
let loaderData = init.hydrationData ? init.hydrationData.loaderData : null;
|
|
835
|
-
let errors = init.hydrationData ? init.hydrationData.errors : null;
|
|
836
|
-
initialized = initialMatches.every(
|
|
837
|
-
(m) =>
|
|
838
|
-
m.route.loader &&
|
|
839
|
-
m.route.loader.hydrate !== true &&
|
|
840
|
-
((loaderData && loaderData[m.route.id] !== undefined) ||
|
|
841
|
-
(errors && errors[m.route.id] !== undefined))
|
|
842
|
-
);
|
|
843
|
-
} else {
|
|
844
|
-
// Without partial hydration - we're initialized if we were provided any
|
|
845
|
-
// hydrationData - which is expected to be complete
|
|
846
|
-
initialized = init.hydrationData != null;
|
|
847
|
-
}
|
|
837
|
+
(!initialMatches.some((m) => m.route.lazy) &&
|
|
838
|
+
// And we have to either have no loaders or have been provided hydrationData
|
|
839
|
+
(!initialMatches.some((m) => m.route.loader) ||
|
|
840
|
+
init.hydrationData != null));
|
|
848
841
|
|
|
849
842
|
let router: Router;
|
|
850
843
|
let state: RouterState = {
|
|
@@ -1025,7 +1018,11 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1025
1018
|
// in the normal navigation flow. For SSR it's expected that lazy modules are
|
|
1026
1019
|
// resolved prior to router creation since we can't go into a fallbackElement
|
|
1027
1020
|
// UI for SSR'd apps
|
|
1028
|
-
if (
|
|
1021
|
+
if (
|
|
1022
|
+
!state.initialized ||
|
|
1023
|
+
(future.v7_partialHydration &&
|
|
1024
|
+
state.matches.some((m) => isUnhydratedRoute(state, m.route)))
|
|
1025
|
+
) {
|
|
1029
1026
|
startNavigation(HistoryAction.Pop, state.location, {
|
|
1030
1027
|
initialHydration: true,
|
|
1031
1028
|
});
|
|
@@ -1772,8 +1769,8 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1772
1769
|
);
|
|
1773
1770
|
}
|
|
1774
1771
|
|
|
1775
|
-
let {
|
|
1776
|
-
await
|
|
1772
|
+
let { loaderResults, fetcherResults } =
|
|
1773
|
+
await loadDataAndMaybeResolveDeferred(
|
|
1777
1774
|
state.matches,
|
|
1778
1775
|
matches,
|
|
1779
1776
|
matchesToLoad,
|
|
@@ -1797,7 +1794,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1797
1794
|
revalidatingFetchers.forEach((rf) => fetchControllers.delete(rf.key));
|
|
1798
1795
|
|
|
1799
1796
|
// If any loaders returned a redirect Response, start a new REPLACE navigation
|
|
1800
|
-
let redirect = findRedirect(
|
|
1797
|
+
let redirect = findRedirect([...loaderResults, ...fetcherResults]);
|
|
1801
1798
|
if (redirect) {
|
|
1802
1799
|
if (redirect.idx >= matchesToLoad.length) {
|
|
1803
1800
|
// If this redirect came from a fetcher make sure we mark it in
|
|
@@ -2103,8 +2100,8 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2103
2100
|
abortPendingFetchRevalidations
|
|
2104
2101
|
);
|
|
2105
2102
|
|
|
2106
|
-
let {
|
|
2107
|
-
await
|
|
2103
|
+
let { loaderResults, fetcherResults } =
|
|
2104
|
+
await loadDataAndMaybeResolveDeferred(
|
|
2108
2105
|
state.matches,
|
|
2109
2106
|
matches,
|
|
2110
2107
|
matchesToLoad,
|
|
@@ -2125,7 +2122,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2125
2122
|
fetchControllers.delete(key);
|
|
2126
2123
|
revalidatingFetchers.forEach((r) => fetchControllers.delete(r.key));
|
|
2127
2124
|
|
|
2128
|
-
let redirect = findRedirect(
|
|
2125
|
+
let redirect = findRedirect([...loaderResults, ...fetcherResults]);
|
|
2129
2126
|
if (redirect) {
|
|
2130
2127
|
if (redirect.idx >= matchesToLoad.length) {
|
|
2131
2128
|
// If this redirect came from a fetcher make sure we mark it in
|
|
@@ -2410,52 +2407,83 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2410
2407
|
}
|
|
2411
2408
|
}
|
|
2412
2409
|
|
|
2413
|
-
async function
|
|
2410
|
+
async function loadDataAndMaybeResolveDeferred(
|
|
2414
2411
|
currentMatches: AgnosticDataRouteMatch[],
|
|
2415
2412
|
matches: AgnosticDataRouteMatch[],
|
|
2416
2413
|
matchesToLoad: AgnosticDataRouteMatch[],
|
|
2417
2414
|
fetchersToLoad: RevalidatingFetcher[],
|
|
2418
2415
|
request: Request
|
|
2419
2416
|
) {
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2417
|
+
let [loaderResults, ...fetcherResults] = await Promise.all([
|
|
2418
|
+
matchesToLoad.length
|
|
2419
|
+
? dataStrategy({
|
|
2420
|
+
matches: matchesToLoad.map((m) =>
|
|
2421
|
+
finesseToAgnosticDataStrategyMatch(
|
|
2422
|
+
m,
|
|
2423
|
+
mapRouteProperties,
|
|
2424
|
+
manifest
|
|
2425
|
+
)
|
|
2426
|
+
),
|
|
2427
|
+
request,
|
|
2428
|
+
type: "loader",
|
|
2429
|
+
defaultStrategy(match) {
|
|
2430
|
+
return callLoaderOrActionImplementation(
|
|
2431
|
+
"loader",
|
|
2432
|
+
request,
|
|
2433
|
+
match,
|
|
2434
|
+
matches,
|
|
2435
|
+
basename,
|
|
2436
|
+
future.v7_relativeSplatPath
|
|
2437
|
+
);
|
|
2438
|
+
},
|
|
2439
|
+
})
|
|
2440
|
+
: [],
|
|
2436
2441
|
...fetchersToLoad.map((f) => {
|
|
2437
|
-
if (f.matches
|
|
2438
|
-
return
|
|
2439
|
-
"loader",
|
|
2440
|
-
createClientSideRequest(init.history, f.path, f.controller.signal),
|
|
2441
|
-
f.match,
|
|
2442
|
-
f.matches,
|
|
2443
|
-
manifest,
|
|
2444
|
-
mapRouteProperties,
|
|
2445
|
-
basename,
|
|
2446
|
-
future.v7_relativeSplatPath
|
|
2447
|
-
);
|
|
2448
|
-
} else {
|
|
2449
|
-
let error: ErrorResult = {
|
|
2442
|
+
if (!f.matches || !f.match || !f.controller) {
|
|
2443
|
+
return Promise.resolve<DataResult>({
|
|
2450
2444
|
type: ResultType.error,
|
|
2451
|
-
error: getInternalRouterError(404, {
|
|
2452
|
-
|
|
2453
|
-
|
|
2445
|
+
error: getInternalRouterError(404, {
|
|
2446
|
+
pathname: f.path,
|
|
2447
|
+
}),
|
|
2448
|
+
});
|
|
2454
2449
|
}
|
|
2450
|
+
|
|
2451
|
+
return dataStrategy({
|
|
2452
|
+
matches: [
|
|
2453
|
+
finesseToAgnosticDataStrategyMatch(
|
|
2454
|
+
f.match!,
|
|
2455
|
+
mapRouteProperties,
|
|
2456
|
+
manifest
|
|
2457
|
+
),
|
|
2458
|
+
],
|
|
2459
|
+
request,
|
|
2460
|
+
type: "loader",
|
|
2461
|
+
defaultStrategy(match) {
|
|
2462
|
+
invariant(
|
|
2463
|
+
f.controller,
|
|
2464
|
+
"Expected controller for fetcher in defaultStrategy"
|
|
2465
|
+
);
|
|
2466
|
+
invariant(
|
|
2467
|
+
f.matches,
|
|
2468
|
+
"Expected matches for fetcher in defaultStrategy"
|
|
2469
|
+
);
|
|
2470
|
+
|
|
2471
|
+
return callLoaderOrActionImplementation(
|
|
2472
|
+
"loader",
|
|
2473
|
+
createClientSideRequest(
|
|
2474
|
+
init.history,
|
|
2475
|
+
f.path,
|
|
2476
|
+
f.controller.signal
|
|
2477
|
+
),
|
|
2478
|
+
match,
|
|
2479
|
+
f.matches,
|
|
2480
|
+
basename,
|
|
2481
|
+
future.v7_relativeSplatPath
|
|
2482
|
+
);
|
|
2483
|
+
},
|
|
2484
|
+
}).then((r) => r[0]);
|
|
2455
2485
|
}),
|
|
2456
2486
|
]);
|
|
2457
|
-
let loaderResults = results.slice(0, matchesToLoad.length);
|
|
2458
|
-
let fetcherResults = results.slice(matchesToLoad.length);
|
|
2459
2487
|
|
|
2460
2488
|
await Promise.all([
|
|
2461
2489
|
resolveDeferredResults(
|
|
@@ -2475,7 +2503,10 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2475
2503
|
),
|
|
2476
2504
|
]);
|
|
2477
2505
|
|
|
2478
|
-
return {
|
|
2506
|
+
return {
|
|
2507
|
+
loaderResults,
|
|
2508
|
+
fetcherResults,
|
|
2509
|
+
};
|
|
2479
2510
|
}
|
|
2480
2511
|
|
|
2481
2512
|
function interruptActiveLoads() {
|
|
@@ -2834,7 +2865,6 @@ export const UNSAFE_DEFERRED_SYMBOL = Symbol("deferred");
|
|
|
2834
2865
|
*/
|
|
2835
2866
|
export interface StaticHandlerFutureConfig {
|
|
2836
2867
|
v7_relativeSplatPath: boolean;
|
|
2837
|
-
v7_throwAbortReason: boolean;
|
|
2838
2868
|
}
|
|
2839
2869
|
|
|
2840
2870
|
export interface CreateStaticHandlerOptions {
|
|
@@ -2843,6 +2873,7 @@ export interface CreateStaticHandlerOptions {
|
|
|
2843
2873
|
* @deprecated Use `mapRouteProperties` instead
|
|
2844
2874
|
*/
|
|
2845
2875
|
detectErrorBoundary?: DetectErrorBoundaryFunction;
|
|
2876
|
+
dataStrategy?: DataStrategyFunction;
|
|
2846
2877
|
mapRouteProperties?: MapRoutePropertiesFunction;
|
|
2847
2878
|
future?: Partial<StaticHandlerFutureConfig>;
|
|
2848
2879
|
}
|
|
@@ -2856,6 +2887,9 @@ export function createStaticHandler(
|
|
|
2856
2887
|
"You must provide a non-empty routes array to createStaticHandler"
|
|
2857
2888
|
);
|
|
2858
2889
|
|
|
2890
|
+
const dataStrategy = opts?.dataStrategy || defaultDataStrategy;
|
|
2891
|
+
const callLoaderOrAction = createCallLoaderOrAction(dataStrategy);
|
|
2892
|
+
|
|
2859
2893
|
let manifest: RouteManifest = {};
|
|
2860
2894
|
let basename = (opts ? opts.basename : null) || "/";
|
|
2861
2895
|
let mapRouteProperties: MapRoutePropertiesFunction;
|
|
@@ -2873,7 +2907,6 @@ export function createStaticHandler(
|
|
|
2873
2907
|
// Config driven behavior flags
|
|
2874
2908
|
let future: StaticHandlerFutureConfig = {
|
|
2875
2909
|
v7_relativeSplatPath: false,
|
|
2876
|
-
v7_throwAbortReason: false,
|
|
2877
2910
|
...(opts ? opts.future : null),
|
|
2878
2911
|
};
|
|
2879
2912
|
|
|
@@ -3143,7 +3176,10 @@ export function createStaticHandler(
|
|
|
3143
3176
|
);
|
|
3144
3177
|
|
|
3145
3178
|
if (request.signal.aborted) {
|
|
3146
|
-
|
|
3179
|
+
let method = isRouteRequest ? "queryRoute" : "query";
|
|
3180
|
+
throw new Error(
|
|
3181
|
+
`${method}() call aborted: ${request.method} ${request.url}`
|
|
3182
|
+
);
|
|
3147
3183
|
}
|
|
3148
3184
|
}
|
|
3149
3185
|
|
|
@@ -3294,24 +3330,30 @@ export function createStaticHandler(
|
|
|
3294
3330
|
};
|
|
3295
3331
|
}
|
|
3296
3332
|
|
|
3297
|
-
let results = await
|
|
3298
|
-
|
|
3299
|
-
|
|
3333
|
+
let results = await dataStrategy({
|
|
3334
|
+
matches: matchesToLoad.map((m) =>
|
|
3335
|
+
finesseToAgnosticDataStrategyMatch(m, mapRouteProperties, manifest)
|
|
3336
|
+
),
|
|
3337
|
+
request,
|
|
3338
|
+
type: "loader",
|
|
3339
|
+
defaultStrategy(match) {
|
|
3340
|
+
return callLoaderOrActionImplementation(
|
|
3300
3341
|
"loader",
|
|
3301
3342
|
request,
|
|
3302
3343
|
match,
|
|
3303
3344
|
matches,
|
|
3304
|
-
manifest,
|
|
3305
|
-
mapRouteProperties,
|
|
3306
3345
|
basename,
|
|
3307
3346
|
future.v7_relativeSplatPath,
|
|
3308
3347
|
{ isStaticRequest: true, isRouteRequest, requestContext }
|
|
3309
|
-
)
|
|
3310
|
-
|
|
3311
|
-
|
|
3348
|
+
);
|
|
3349
|
+
},
|
|
3350
|
+
});
|
|
3312
3351
|
|
|
3313
3352
|
if (request.signal.aborted) {
|
|
3314
|
-
|
|
3353
|
+
let method = isRouteRequest ? "queryRoute" : "query";
|
|
3354
|
+
throw new Error(
|
|
3355
|
+
`${method}() call aborted: ${request.method} ${request.url}`
|
|
3356
|
+
);
|
|
3315
3357
|
}
|
|
3316
3358
|
|
|
3317
3359
|
// Process and commit output from loaders
|
|
@@ -3357,6 +3399,13 @@ export function createStaticHandler(
|
|
|
3357
3399
|
//#region Helpers
|
|
3358
3400
|
////////////////////////////////////////////////////////////////////////////////
|
|
3359
3401
|
|
|
3402
|
+
function defaultDataStrategy({
|
|
3403
|
+
defaultStrategy,
|
|
3404
|
+
matches,
|
|
3405
|
+
}: DataStrategyFunctionArgs) {
|
|
3406
|
+
return Promise.all(matches.map((match) => defaultStrategy(match)));
|
|
3407
|
+
}
|
|
3408
|
+
|
|
3360
3409
|
/**
|
|
3361
3410
|
* Given an existing StaticHandlerContext and an error thrown at render time,
|
|
3362
3411
|
* provide an updated StaticHandlerContext suitable for a second SSR render
|
|
@@ -3376,19 +3425,6 @@ export function getStaticContextFromError(
|
|
|
3376
3425
|
return newContext;
|
|
3377
3426
|
}
|
|
3378
3427
|
|
|
3379
|
-
function throwStaticHandlerAbortedError(
|
|
3380
|
-
request: Request,
|
|
3381
|
-
isRouteRequest: boolean,
|
|
3382
|
-
future: StaticHandlerFutureConfig
|
|
3383
|
-
) {
|
|
3384
|
-
if (future.v7_throwAbortReason && request.signal.reason !== undefined) {
|
|
3385
|
-
throw request.signal.reason;
|
|
3386
|
-
}
|
|
3387
|
-
|
|
3388
|
-
let method = isRouteRequest ? "queryRoute" : "query";
|
|
3389
|
-
throw new Error(`${method}() call aborted: ${request.method} ${request.url}`);
|
|
3390
|
-
}
|
|
3391
|
-
|
|
3392
3428
|
function isSubmissionNavigation(
|
|
3393
3429
|
opts: BaseNavigateOrFetchOptions
|
|
3394
3430
|
): opts is SubmissionNavigateOptions {
|
|
@@ -3664,27 +3700,21 @@ function getMatchesToLoad(
|
|
|
3664
3700
|
let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
|
|
3665
3701
|
|
|
3666
3702
|
let navigationMatches = boundaryMatches.filter((match, index) => {
|
|
3667
|
-
|
|
3668
|
-
|
|
3703
|
+
if (isInitialLoad) {
|
|
3704
|
+
// On initial hydration we don't do any shouldRevalidate stuff - we just
|
|
3705
|
+
// call the unhydrated loaders
|
|
3706
|
+
return isUnhydratedRoute(state, match.route);
|
|
3707
|
+
}
|
|
3708
|
+
|
|
3709
|
+
if (match.route.lazy) {
|
|
3669
3710
|
// We haven't loaded this route yet so we don't know if it's got a loader!
|
|
3670
3711
|
return true;
|
|
3671
3712
|
}
|
|
3672
3713
|
|
|
3673
|
-
if (route.loader == null) {
|
|
3714
|
+
if (match.route.loader == null) {
|
|
3674
3715
|
return false;
|
|
3675
3716
|
}
|
|
3676
3717
|
|
|
3677
|
-
if (isInitialLoad) {
|
|
3678
|
-
if (route.loader.hydrate) {
|
|
3679
|
-
return true;
|
|
3680
|
-
}
|
|
3681
|
-
return (
|
|
3682
|
-
state.loaderData[route.id] === undefined &&
|
|
3683
|
-
// Don't re-run if the loader ran and threw an error
|
|
3684
|
-
(!state.errors || state.errors[route.id] === undefined)
|
|
3685
|
-
);
|
|
3686
|
-
}
|
|
3687
|
-
|
|
3688
3718
|
// Always call the loader on new route instances and pending defer cancellations
|
|
3689
3719
|
if (
|
|
3690
3720
|
isNewLoader(state.loaderData, state.matches[index], match) ||
|
|
@@ -3804,6 +3834,23 @@ function getMatchesToLoad(
|
|
|
3804
3834
|
return [navigationMatches, revalidatingFetchers];
|
|
3805
3835
|
}
|
|
3806
3836
|
|
|
3837
|
+
// Is this route unhydrated (when v7_partialHydration=true) such that we need
|
|
3838
|
+
// to call it's loader on the initial router creation
|
|
3839
|
+
function isUnhydratedRoute(state: RouterState, route: AgnosticDataRouteObject) {
|
|
3840
|
+
if (!route.loader) {
|
|
3841
|
+
return false;
|
|
3842
|
+
}
|
|
3843
|
+
if (route.loader.hydrate) {
|
|
3844
|
+
return true;
|
|
3845
|
+
}
|
|
3846
|
+
return (
|
|
3847
|
+
state.loaderData[route.id] === undefined &&
|
|
3848
|
+
(!state.errors ||
|
|
3849
|
+
// Loader ran but errored - don't re-run
|
|
3850
|
+
state.errors[route.id] === undefined)
|
|
3851
|
+
);
|
|
3852
|
+
}
|
|
3853
|
+
|
|
3807
3854
|
function isNewLoader(
|
|
3808
3855
|
currentLoaderData: RouteData,
|
|
3809
3856
|
currentMatch: AgnosticDataRouteMatch,
|
|
@@ -3862,9 +3909,9 @@ async function loadLazyRouteModule(
|
|
|
3862
3909
|
route: AgnosticDataRouteObject,
|
|
3863
3910
|
mapRouteProperties: MapRoutePropertiesFunction,
|
|
3864
3911
|
manifest: RouteManifest
|
|
3865
|
-
) {
|
|
3912
|
+
): Promise<AgnosticDataRouteObject> {
|
|
3866
3913
|
if (!route.lazy) {
|
|
3867
|
-
return;
|
|
3914
|
+
return route;
|
|
3868
3915
|
}
|
|
3869
3916
|
|
|
3870
3917
|
let lazyRoute = await route.lazy();
|
|
@@ -3873,7 +3920,7 @@ async function loadLazyRouteModule(
|
|
|
3873
3920
|
// call then we can return - first lazy() to finish wins because the return
|
|
3874
3921
|
// value of lazy is expected to be static
|
|
3875
3922
|
if (!route.lazy) {
|
|
3876
|
-
return;
|
|
3923
|
+
return route;
|
|
3877
3924
|
}
|
|
3878
3925
|
|
|
3879
3926
|
let routeToUpdate = manifest[route.id];
|
|
@@ -3929,15 +3976,88 @@ async function loadLazyRouteModule(
|
|
|
3929
3976
|
...mapRouteProperties(routeToUpdate),
|
|
3930
3977
|
lazy: undefined,
|
|
3931
3978
|
});
|
|
3979
|
+
|
|
3980
|
+
return routeToUpdate;
|
|
3981
|
+
}
|
|
3982
|
+
|
|
3983
|
+
function createCallLoaderOrAction(dataStrategy: DataStrategyFunction) {
|
|
3984
|
+
return async (
|
|
3985
|
+
type: "loader" | "action",
|
|
3986
|
+
request: Request,
|
|
3987
|
+
match: AgnosticDataRouteMatch,
|
|
3988
|
+
matches: AgnosticDataRouteMatch[],
|
|
3989
|
+
manifest: RouteManifest,
|
|
3990
|
+
mapRouteProperties: MapRoutePropertiesFunction,
|
|
3991
|
+
basename: string,
|
|
3992
|
+
v7_relativeSplatPath: boolean,
|
|
3993
|
+
opts: {
|
|
3994
|
+
isStaticRequest?: boolean;
|
|
3995
|
+
isRouteRequest?: boolean;
|
|
3996
|
+
requestContext?: unknown;
|
|
3997
|
+
} = {}
|
|
3998
|
+
): Promise<DataResult> => {
|
|
3999
|
+
let [result] = await dataStrategy({
|
|
4000
|
+
matches: [
|
|
4001
|
+
finesseToAgnosticDataStrategyMatch(match, mapRouteProperties, manifest),
|
|
4002
|
+
],
|
|
4003
|
+
request,
|
|
4004
|
+
type,
|
|
4005
|
+
defaultStrategy(match) {
|
|
4006
|
+
return callLoaderOrActionImplementation(
|
|
4007
|
+
type,
|
|
4008
|
+
request,
|
|
4009
|
+
match,
|
|
4010
|
+
matches,
|
|
4011
|
+
basename,
|
|
4012
|
+
v7_relativeSplatPath,
|
|
4013
|
+
opts
|
|
4014
|
+
);
|
|
4015
|
+
},
|
|
4016
|
+
});
|
|
4017
|
+
|
|
4018
|
+
return result;
|
|
4019
|
+
};
|
|
4020
|
+
}
|
|
4021
|
+
|
|
4022
|
+
function finesseToAgnosticDataStrategyMatch(
|
|
4023
|
+
match: AgnosticDataRouteMatch,
|
|
4024
|
+
mapRouteProperties: MapRoutePropertiesFunction,
|
|
4025
|
+
manifest: RouteManifest
|
|
4026
|
+
): AgnosticDataStrategyMatch {
|
|
4027
|
+
let loadRoutePromise: Promise<AgnosticDataRouteObject> | undefined;
|
|
4028
|
+
|
|
4029
|
+
if (match.route.lazy) {
|
|
4030
|
+
try {
|
|
4031
|
+
loadRoutePromise = loadLazyRouteModule(
|
|
4032
|
+
match.route,
|
|
4033
|
+
mapRouteProperties,
|
|
4034
|
+
manifest
|
|
4035
|
+
);
|
|
4036
|
+
} catch (error) {
|
|
4037
|
+
loadRoutePromise = Promise.reject(error);
|
|
4038
|
+
}
|
|
4039
|
+
}
|
|
4040
|
+
|
|
4041
|
+
if (!loadRoutePromise) {
|
|
4042
|
+
loadRoutePromise = Promise.resolve(match.route);
|
|
4043
|
+
}
|
|
4044
|
+
|
|
4045
|
+
loadRoutePromise.catch(() => {});
|
|
4046
|
+
|
|
4047
|
+
return {
|
|
4048
|
+
...match,
|
|
4049
|
+
route: Object.assign(
|
|
4050
|
+
loadRoutePromise,
|
|
4051
|
+
match.route
|
|
4052
|
+
) as unknown as LazyRoutePromise,
|
|
4053
|
+
};
|
|
3932
4054
|
}
|
|
3933
4055
|
|
|
3934
|
-
async function
|
|
4056
|
+
async function callLoaderOrActionImplementation(
|
|
3935
4057
|
type: "loader" | "action",
|
|
3936
4058
|
request: Request,
|
|
3937
|
-
match:
|
|
4059
|
+
match: AgnosticDataStrategyMatch,
|
|
3938
4060
|
matches: AgnosticDataRouteMatch[],
|
|
3939
|
-
manifest: RouteManifest,
|
|
3940
|
-
mapRouteProperties: MapRoutePropertiesFunction,
|
|
3941
4061
|
basename: string,
|
|
3942
4062
|
v7_relativeSplatPath: boolean,
|
|
3943
4063
|
opts: {
|
|
@@ -3980,7 +4100,7 @@ async function callLoaderOrAction(
|
|
|
3980
4100
|
runHandler(handler).catch((e) => {
|
|
3981
4101
|
handlerError = e;
|
|
3982
4102
|
}),
|
|
3983
|
-
|
|
4103
|
+
match.route,
|
|
3984
4104
|
]);
|
|
3985
4105
|
if (handlerError) {
|
|
3986
4106
|
throw handlerError;
|
|
@@ -3988,9 +4108,9 @@ async function callLoaderOrAction(
|
|
|
3988
4108
|
result = values[0];
|
|
3989
4109
|
} else {
|
|
3990
4110
|
// Load lazy route module, then run any returned handler
|
|
3991
|
-
await
|
|
4111
|
+
let route = await match.route;
|
|
3992
4112
|
|
|
3993
|
-
handler =
|
|
4113
|
+
handler = route[type];
|
|
3994
4114
|
if (handler) {
|
|
3995
4115
|
// Handler still run even if we got interrupted to maintain consistency
|
|
3996
4116
|
// with un-abortable behavior of handler execution on non-lazy or
|
|
@@ -4050,7 +4170,10 @@ async function callLoaderOrAction(
|
|
|
4050
4170
|
if (!ABSOLUTE_URL_REGEX.test(location)) {
|
|
4051
4171
|
location = normalizeTo(
|
|
4052
4172
|
new URL(request.url),
|
|
4053
|
-
matches.slice(
|
|
4173
|
+
matches.slice(
|
|
4174
|
+
0,
|
|
4175
|
+
matches.findIndex((m) => m.route.id === match.route.id) + 1
|
|
4176
|
+
),
|
|
4054
4177
|
basename,
|
|
4055
4178
|
true,
|
|
4056
4179
|
location,
|
|
@@ -4107,11 +4230,7 @@ async function callLoaderOrAction(
|
|
|
4107
4230
|
// Check between word boundaries instead of startsWith() due to the last
|
|
4108
4231
|
// paragraph of https://httpwg.org/specs/rfc9110.html#field.content-type
|
|
4109
4232
|
if (contentType && /\bapplication\/json\b/.test(contentType)) {
|
|
4110
|
-
|
|
4111
|
-
data = null;
|
|
4112
|
-
} else {
|
|
4113
|
-
data = await result.json();
|
|
4114
|
-
}
|
|
4233
|
+
data = await result.json();
|
|
4115
4234
|
} else {
|
|
4116
4235
|
data = await result.text();
|
|
4117
4236
|
}
|