@remix-run/router 1.3.3 → 1.4.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 +66 -0
- package/dist/history.d.ts +1 -0
- package/dist/index.d.ts +4 -3
- package/dist/router.cjs.js +220 -90
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.d.ts +6 -3
- package/dist/router.js +216 -90
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +220 -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 +25 -6
- package/history.ts +1 -1
- package/index.ts +6 -2
- package/package.json +1 -1
- package/router.ts +239 -30
- package/utils.ts +102 -69
package/dist/utils.d.ts
CHANGED
|
@@ -117,6 +117,27 @@ export interface ShouldRevalidateFunction {
|
|
|
117
117
|
defaultShouldRevalidate: boolean;
|
|
118
118
|
}): boolean;
|
|
119
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Function provided by the framework-aware layers to set `hasErrorBoundary`
|
|
122
|
+
* from the framework-aware `errorElement` prop
|
|
123
|
+
*/
|
|
124
|
+
export interface DetectErrorBoundaryFunction {
|
|
125
|
+
(route: AgnosticRouteObject): boolean;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Keys we cannot change from within a lazy() function. We spread all other keys
|
|
129
|
+
* onto the route. Either they're meaningful to the router, or they'll get
|
|
130
|
+
* ignored.
|
|
131
|
+
*/
|
|
132
|
+
export declare type ImmutableRouteKey = "lazy" | "caseSensitive" | "path" | "id" | "index" | "children";
|
|
133
|
+
export declare const immutableRouteKeys: Set<ImmutableRouteKey>;
|
|
134
|
+
/**
|
|
135
|
+
* lazy() function to load a route definition, which can add non-matching
|
|
136
|
+
* related properties to a route
|
|
137
|
+
*/
|
|
138
|
+
export interface LazyRouteFunction<R extends AgnosticRouteObject> {
|
|
139
|
+
(): Promise<Omit<R, ImmutableRouteKey>>;
|
|
140
|
+
}
|
|
120
141
|
/**
|
|
121
142
|
* Base RouteObject with common props shared by all types of routes
|
|
122
143
|
*/
|
|
@@ -129,6 +150,7 @@ declare type AgnosticBaseRouteObject = {
|
|
|
129
150
|
hasErrorBoundary?: boolean;
|
|
130
151
|
shouldRevalidate?: ShouldRevalidateFunction;
|
|
131
152
|
handle?: any;
|
|
153
|
+
lazy?: LazyRouteFunction<AgnosticBaseRouteObject>;
|
|
132
154
|
};
|
|
133
155
|
/**
|
|
134
156
|
* Index routes must not have children
|
|
@@ -160,6 +182,7 @@ export declare type AgnosticDataNonIndexRouteObject = AgnosticNonIndexRouteObjec
|
|
|
160
182
|
* A data route object, which is just a RouteObject with a required unique ID
|
|
161
183
|
*/
|
|
162
184
|
export declare type AgnosticDataRouteObject = AgnosticDataIndexRouteObject | AgnosticDataNonIndexRouteObject;
|
|
185
|
+
export declare type RouteManifest = Record<string, AgnosticDataRouteObject | undefined>;
|
|
163
186
|
declare type _PathParam<Path extends string> = Path extends `${infer L}/${infer R}` ? _PathParam<L> | _PathParam<R> : Path extends `:${infer Param}` ? Param extends `${infer Optional}?` ? Optional : Param : never;
|
|
164
187
|
/**
|
|
165
188
|
* Examples:
|
|
@@ -170,7 +193,7 @@ declare type _PathParam<Path extends string> = Path extends `${infer L}/${infer
|
|
|
170
193
|
* "/:a/:b" -> "a" | "b"
|
|
171
194
|
* "/:a/b/:c/*" -> "a" | "c" | "*"
|
|
172
195
|
*/
|
|
173
|
-
declare type PathParam<Path extends string> = Path extends "*" ? "*" : Path extends `${infer Rest}/*` ? "*" | _PathParam<Rest> : _PathParam<Path>;
|
|
196
|
+
declare type PathParam<Path extends string> = Path extends "*" | "/*" ? "*" : Path extends `${infer Rest}/*` ? "*" | _PathParam<Rest> : _PathParam<Path>;
|
|
174
197
|
export declare type ParamParseKey<Segment extends string> = [
|
|
175
198
|
PathParam<Segment>
|
|
176
199
|
] extends [never] ? string : PathParam<Segment>;
|
|
@@ -203,7 +226,7 @@ export interface AgnosticRouteMatch<ParamKey extends string = string, RouteObjec
|
|
|
203
226
|
}
|
|
204
227
|
export interface AgnosticDataRouteMatch extends AgnosticRouteMatch<string, AgnosticDataRouteObject> {
|
|
205
228
|
}
|
|
206
|
-
export declare function convertRoutesToDataRoutes(routes: AgnosticRouteObject[], parentPath?: number[],
|
|
229
|
+
export declare function convertRoutesToDataRoutes(routes: AgnosticRouteObject[], detectErrorBoundary: DetectErrorBoundaryFunction, parentPath?: number[], manifest?: RouteManifest): AgnosticDataRouteObject[];
|
|
207
230
|
/**
|
|
208
231
|
* Matches the given routes to a location and returns the match data.
|
|
209
232
|
*
|
|
@@ -270,10 +293,6 @@ export declare function matchPath<ParamKey extends ParamParseKey<Path>, Path ext
|
|
|
270
293
|
* @private
|
|
271
294
|
*/
|
|
272
295
|
export declare function stripBasename(pathname: string, basename: string): string | null;
|
|
273
|
-
/**
|
|
274
|
-
* @private
|
|
275
|
-
*/
|
|
276
|
-
export declare function warning(cond: any, message: string): void;
|
|
277
296
|
/**
|
|
278
297
|
* Returns a resolved path object relative to the given pathname.
|
|
279
298
|
*
|
package/history.ts
CHANGED
|
@@ -481,7 +481,7 @@ export function invariant(value: any, message?: string) {
|
|
|
481
481
|
}
|
|
482
482
|
}
|
|
483
483
|
|
|
484
|
-
function warning(cond: any, message: string) {
|
|
484
|
+
export function warning(cond: any, message: string) {
|
|
485
485
|
if (!cond) {
|
|
486
486
|
// eslint-disable-next-line no-console
|
|
487
487
|
if (typeof console !== "undefined") console.warn(message);
|
package/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ export type {
|
|
|
9
9
|
AgnosticNonIndexRouteObject,
|
|
10
10
|
AgnosticRouteMatch,
|
|
11
11
|
AgnosticRouteObject,
|
|
12
|
+
LazyRouteFunction,
|
|
12
13
|
TrackedPromise,
|
|
13
14
|
FormEncType,
|
|
14
15
|
FormMethod,
|
|
@@ -40,7 +41,6 @@ export {
|
|
|
40
41
|
resolvePath,
|
|
41
42
|
resolveTo,
|
|
42
43
|
stripBasename,
|
|
43
|
-
warning,
|
|
44
44
|
} from "./utils";
|
|
45
45
|
|
|
46
46
|
export type {
|
|
@@ -76,10 +76,14 @@ export * from "./router";
|
|
|
76
76
|
///////////////////////////////////////////////////////////////////////////////
|
|
77
77
|
|
|
78
78
|
/** @internal */
|
|
79
|
+
export type { RouteManifest as UNSAFE_RouteManifest } from "./utils";
|
|
79
80
|
export {
|
|
80
81
|
DeferredData as UNSAFE_DeferredData,
|
|
81
82
|
convertRoutesToDataRoutes as UNSAFE_convertRoutesToDataRoutes,
|
|
82
83
|
getPathContributingMatches as UNSAFE_getPathContributingMatches,
|
|
83
84
|
} from "./utils";
|
|
84
85
|
|
|
85
|
-
export {
|
|
86
|
+
export {
|
|
87
|
+
invariant as UNSAFE_invariant,
|
|
88
|
+
warning as UNSAFE_warning,
|
|
89
|
+
} from "./history";
|
package/package.json
CHANGED
package/router.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
createPath,
|
|
6
6
|
invariant,
|
|
7
7
|
parsePath,
|
|
8
|
+
warning,
|
|
8
9
|
} from "./history";
|
|
9
10
|
import type {
|
|
10
11
|
DataResult,
|
|
@@ -14,6 +15,7 @@ import type {
|
|
|
14
15
|
ErrorResult,
|
|
15
16
|
FormEncType,
|
|
16
17
|
FormMethod,
|
|
18
|
+
DetectErrorBoundaryFunction,
|
|
17
19
|
RedirectResult,
|
|
18
20
|
RouteData,
|
|
19
21
|
AgnosticRouteObject,
|
|
@@ -22,6 +24,10 @@ import type {
|
|
|
22
24
|
AgnosticRouteMatch,
|
|
23
25
|
MutationFormMethod,
|
|
24
26
|
ShouldRevalidateFunction,
|
|
27
|
+
RouteManifest,
|
|
28
|
+
ImmutableRouteKey,
|
|
29
|
+
ActionFunction,
|
|
30
|
+
LoaderFunction,
|
|
25
31
|
} from "./utils";
|
|
26
32
|
import {
|
|
27
33
|
DeferredData,
|
|
@@ -29,12 +35,12 @@ import {
|
|
|
29
35
|
ResultType,
|
|
30
36
|
convertRoutesToDataRoutes,
|
|
31
37
|
getPathContributingMatches,
|
|
38
|
+
immutableRouteKeys,
|
|
32
39
|
isRouteErrorResponse,
|
|
33
40
|
joinPaths,
|
|
34
41
|
matchRoutes,
|
|
35
42
|
resolveTo,
|
|
36
43
|
stripBasename,
|
|
37
|
-
warning,
|
|
38
44
|
} from "./utils";
|
|
39
45
|
|
|
40
46
|
////////////////////////////////////////////////////////////////////////////////
|
|
@@ -328,6 +334,7 @@ export interface RouterInit {
|
|
|
328
334
|
routes: AgnosticRouteObject[];
|
|
329
335
|
history: History;
|
|
330
336
|
hydrationData?: HydrationState;
|
|
337
|
+
detectErrorBoundary?: DetectErrorBoundaryFunction;
|
|
331
338
|
}
|
|
332
339
|
|
|
333
340
|
/**
|
|
@@ -638,6 +645,9 @@ const isBrowser =
|
|
|
638
645
|
typeof window.document !== "undefined" &&
|
|
639
646
|
typeof window.document.createElement !== "undefined";
|
|
640
647
|
const isServer = !isBrowser;
|
|
648
|
+
|
|
649
|
+
const defaultDetectErrorBoundary = (route: AgnosticRouteObject) =>
|
|
650
|
+
Boolean(route.hasErrorBoundary);
|
|
641
651
|
//#endregion
|
|
642
652
|
|
|
643
653
|
////////////////////////////////////////////////////////////////////////////////
|
|
@@ -653,7 +663,18 @@ export function createRouter(init: RouterInit): Router {
|
|
|
653
663
|
"You must provide a non-empty routes array to createRouter"
|
|
654
664
|
);
|
|
655
665
|
|
|
656
|
-
let
|
|
666
|
+
let detectErrorBoundary =
|
|
667
|
+
init.detectErrorBoundary || defaultDetectErrorBoundary;
|
|
668
|
+
|
|
669
|
+
// Routes keyed by ID
|
|
670
|
+
let manifest: RouteManifest = {};
|
|
671
|
+
// Routes in tree format for matching
|
|
672
|
+
let dataRoutes = convertRoutesToDataRoutes(
|
|
673
|
+
init.routes,
|
|
674
|
+
detectErrorBoundary,
|
|
675
|
+
undefined,
|
|
676
|
+
manifest
|
|
677
|
+
);
|
|
657
678
|
let inFlightDataRoutes: AgnosticDataRouteObject[] | undefined;
|
|
658
679
|
// Cleanup function for history
|
|
659
680
|
let unlistenHistory: (() => void) | null = null;
|
|
@@ -692,7 +713,11 @@ export function createRouter(init: RouterInit): Router {
|
|
|
692
713
|
}
|
|
693
714
|
|
|
694
715
|
let initialized =
|
|
695
|
-
|
|
716
|
+
// All initialMatches need to be loaded before we're ready. If we have lazy
|
|
717
|
+
// functions around still then we'll need to run them in initialize()
|
|
718
|
+
!initialMatches.some((m) => m.route.lazy) &&
|
|
719
|
+
// And we have to either have no loaders or have been provided hydrationData
|
|
720
|
+
(!initialMatches.some((m) => m.route.loader) || init.hydrationData != null);
|
|
696
721
|
|
|
697
722
|
let router: Router;
|
|
698
723
|
let state: RouterState = {
|
|
@@ -837,11 +862,35 @@ export function createRouter(init: RouterInit): Router {
|
|
|
837
862
|
}
|
|
838
863
|
);
|
|
839
864
|
|
|
840
|
-
|
|
841
|
-
|
|
865
|
+
if (state.initialized) {
|
|
866
|
+
return router;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
let lazyMatches = state.matches.filter((m) => m.route.lazy);
|
|
870
|
+
|
|
871
|
+
if (lazyMatches.length === 0) {
|
|
872
|
+
// Kick off initial data load if needed. Use Pop to avoid modifying history
|
|
842
873
|
startNavigation(HistoryAction.Pop, state.location);
|
|
874
|
+
return router;
|
|
843
875
|
}
|
|
844
876
|
|
|
877
|
+
// Load lazy modules, then kick off initial data load if needed
|
|
878
|
+
let lazyPromises = lazyMatches.map((m) =>
|
|
879
|
+
loadLazyRouteModule(m.route, detectErrorBoundary, manifest)
|
|
880
|
+
);
|
|
881
|
+
Promise.all(lazyPromises).then(() => {
|
|
882
|
+
let initialized =
|
|
883
|
+
!state.matches.some((m) => m.route.loader) ||
|
|
884
|
+
init.hydrationData != null;
|
|
885
|
+
if (initialized) {
|
|
886
|
+
// We already have required loaderData so we can just set initialized
|
|
887
|
+
updateState({ initialized: true });
|
|
888
|
+
} else {
|
|
889
|
+
// We still need to kick off initial data loads
|
|
890
|
+
startNavigation(HistoryAction.Pop, state.location);
|
|
891
|
+
}
|
|
892
|
+
});
|
|
893
|
+
|
|
845
894
|
return router;
|
|
846
895
|
}
|
|
847
896
|
|
|
@@ -1259,7 +1308,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1259
1308
|
let result: DataResult;
|
|
1260
1309
|
let actionMatch = getTargetMatch(matches, location);
|
|
1261
1310
|
|
|
1262
|
-
if (!actionMatch.route.action) {
|
|
1311
|
+
if (!actionMatch.route.action && !actionMatch.route.lazy) {
|
|
1263
1312
|
result = {
|
|
1264
1313
|
type: ResultType.error,
|
|
1265
1314
|
error: getInternalRouterError(405, {
|
|
@@ -1274,6 +1323,8 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1274
1323
|
request,
|
|
1275
1324
|
actionMatch,
|
|
1276
1325
|
matches,
|
|
1326
|
+
manifest,
|
|
1327
|
+
detectErrorBoundary,
|
|
1277
1328
|
router.basename
|
|
1278
1329
|
);
|
|
1279
1330
|
|
|
@@ -1566,7 +1617,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1566
1617
|
interruptActiveLoads();
|
|
1567
1618
|
fetchLoadMatches.delete(key);
|
|
1568
1619
|
|
|
1569
|
-
if (!match.route.action) {
|
|
1620
|
+
if (!match.route.action && !match.route.lazy) {
|
|
1570
1621
|
let error = getInternalRouterError(405, {
|
|
1571
1622
|
method: submission.formMethod,
|
|
1572
1623
|
pathname: path,
|
|
@@ -1602,6 +1653,8 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1602
1653
|
fetchRequest,
|
|
1603
1654
|
match,
|
|
1604
1655
|
requestMatches,
|
|
1656
|
+
manifest,
|
|
1657
|
+
detectErrorBoundary,
|
|
1605
1658
|
router.basename
|
|
1606
1659
|
);
|
|
1607
1660
|
|
|
@@ -1821,11 +1874,14 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1821
1874
|
abortController.signal
|
|
1822
1875
|
);
|
|
1823
1876
|
fetchControllers.set(key, abortController);
|
|
1877
|
+
|
|
1824
1878
|
let result: DataResult = await callLoaderOrAction(
|
|
1825
1879
|
"loader",
|
|
1826
1880
|
fetchRequest,
|
|
1827
1881
|
match,
|
|
1828
1882
|
matches,
|
|
1883
|
+
manifest,
|
|
1884
|
+
detectErrorBoundary,
|
|
1829
1885
|
router.basename
|
|
1830
1886
|
);
|
|
1831
1887
|
|
|
@@ -2021,7 +2077,15 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2021
2077
|
// accordingly
|
|
2022
2078
|
let results = await Promise.all([
|
|
2023
2079
|
...matchesToLoad.map((match) =>
|
|
2024
|
-
callLoaderOrAction(
|
|
2080
|
+
callLoaderOrAction(
|
|
2081
|
+
"loader",
|
|
2082
|
+
request,
|
|
2083
|
+
match,
|
|
2084
|
+
matches,
|
|
2085
|
+
manifest,
|
|
2086
|
+
detectErrorBoundary,
|
|
2087
|
+
router.basename
|
|
2088
|
+
)
|
|
2025
2089
|
),
|
|
2026
2090
|
...fetchersToLoad.map((f) => {
|
|
2027
2091
|
if (f.matches && f.match) {
|
|
@@ -2030,6 +2094,8 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2030
2094
|
createClientSideRequest(init.history, f.path, request.signal),
|
|
2031
2095
|
f.match,
|
|
2032
2096
|
f.matches,
|
|
2097
|
+
manifest,
|
|
2098
|
+
detectErrorBoundary,
|
|
2033
2099
|
router.basename
|
|
2034
2100
|
);
|
|
2035
2101
|
} else {
|
|
@@ -2346,18 +2412,29 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2346
2412
|
|
|
2347
2413
|
export const UNSAFE_DEFERRED_SYMBOL = Symbol("deferred");
|
|
2348
2414
|
|
|
2415
|
+
export interface CreateStaticHandlerOptions {
|
|
2416
|
+
basename?: string;
|
|
2417
|
+
detectErrorBoundary?: DetectErrorBoundaryFunction;
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2349
2420
|
export function createStaticHandler(
|
|
2350
2421
|
routes: AgnosticRouteObject[],
|
|
2351
|
-
opts?:
|
|
2352
|
-
basename?: string;
|
|
2353
|
-
}
|
|
2422
|
+
opts?: CreateStaticHandlerOptions
|
|
2354
2423
|
): StaticHandler {
|
|
2355
2424
|
invariant(
|
|
2356
2425
|
routes.length > 0,
|
|
2357
2426
|
"You must provide a non-empty routes array to createStaticHandler"
|
|
2358
2427
|
);
|
|
2359
2428
|
|
|
2360
|
-
let
|
|
2429
|
+
let manifest: RouteManifest = {};
|
|
2430
|
+
let detectErrorBoundary =
|
|
2431
|
+
opts?.detectErrorBoundary || defaultDetectErrorBoundary;
|
|
2432
|
+
let dataRoutes = convertRoutesToDataRoutes(
|
|
2433
|
+
routes,
|
|
2434
|
+
detectErrorBoundary,
|
|
2435
|
+
undefined,
|
|
2436
|
+
manifest
|
|
2437
|
+
);
|
|
2361
2438
|
let basename = (opts ? opts.basename : null) || "/";
|
|
2362
2439
|
|
|
2363
2440
|
/**
|
|
@@ -2592,7 +2669,7 @@ export function createStaticHandler(
|
|
|
2592
2669
|
): Promise<Omit<StaticHandlerContext, "location" | "basename"> | Response> {
|
|
2593
2670
|
let result: DataResult;
|
|
2594
2671
|
|
|
2595
|
-
if (!actionMatch.route.action) {
|
|
2672
|
+
if (!actionMatch.route.action && !actionMatch.route.lazy) {
|
|
2596
2673
|
let error = getInternalRouterError(405, {
|
|
2597
2674
|
method: request.method,
|
|
2598
2675
|
pathname: new URL(request.url).pathname,
|
|
@@ -2611,6 +2688,8 @@ export function createStaticHandler(
|
|
|
2611
2688
|
request,
|
|
2612
2689
|
actionMatch,
|
|
2613
2690
|
matches,
|
|
2691
|
+
manifest,
|
|
2692
|
+
detectErrorBoundary,
|
|
2614
2693
|
basename,
|
|
2615
2694
|
true,
|
|
2616
2695
|
isRouteRequest,
|
|
@@ -2732,7 +2811,11 @@ export function createStaticHandler(
|
|
|
2732
2811
|
let isRouteRequest = routeMatch != null;
|
|
2733
2812
|
|
|
2734
2813
|
// Short circuit if we have no loaders to run (queryRoute())
|
|
2735
|
-
if (
|
|
2814
|
+
if (
|
|
2815
|
+
isRouteRequest &&
|
|
2816
|
+
!routeMatch?.route.loader &&
|
|
2817
|
+
!routeMatch?.route.lazy
|
|
2818
|
+
) {
|
|
2736
2819
|
throw getInternalRouterError(400, {
|
|
2737
2820
|
method: request.method,
|
|
2738
2821
|
pathname: new URL(request.url).pathname,
|
|
@@ -2746,7 +2829,9 @@ export function createStaticHandler(
|
|
|
2746
2829
|
matches,
|
|
2747
2830
|
Object.keys(pendingActionError || {})[0]
|
|
2748
2831
|
);
|
|
2749
|
-
let matchesToLoad = requestMatches.filter(
|
|
2832
|
+
let matchesToLoad = requestMatches.filter(
|
|
2833
|
+
(m) => m.route.loader || m.route.lazy
|
|
2834
|
+
);
|
|
2750
2835
|
|
|
2751
2836
|
// Short circuit if we have no loaders to run (query())
|
|
2752
2837
|
if (matchesToLoad.length === 0) {
|
|
@@ -2771,6 +2856,8 @@ export function createStaticHandler(
|
|
|
2771
2856
|
request,
|
|
2772
2857
|
match,
|
|
2773
2858
|
matches,
|
|
2859
|
+
manifest,
|
|
2860
|
+
detectErrorBoundary,
|
|
2774
2861
|
basename,
|
|
2775
2862
|
true,
|
|
2776
2863
|
isRouteRequest,
|
|
@@ -2960,6 +3047,10 @@ function getMatchesToLoad(
|
|
|
2960
3047
|
let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
|
|
2961
3048
|
|
|
2962
3049
|
let navigationMatches = boundaryMatches.filter((match, index) => {
|
|
3050
|
+
if (match.route.lazy) {
|
|
3051
|
+
// We haven't loaded this route yet so we don't know if it's got a loader!
|
|
3052
|
+
return true;
|
|
3053
|
+
}
|
|
2963
3054
|
if (match.route.loader == null) {
|
|
2964
3055
|
return false;
|
|
2965
3056
|
}
|
|
@@ -3096,11 +3187,90 @@ function shouldRevalidateLoader(
|
|
|
3096
3187
|
return arg.defaultShouldRevalidate;
|
|
3097
3188
|
}
|
|
3098
3189
|
|
|
3190
|
+
/**
|
|
3191
|
+
* Execute route.lazy() methods to lazily load route modules (loader, action,
|
|
3192
|
+
* shouldRevalidate) and update the routeManifest in place which shares objects
|
|
3193
|
+
* with dataRoutes so those get updated as well.
|
|
3194
|
+
*/
|
|
3195
|
+
async function loadLazyRouteModule(
|
|
3196
|
+
route: AgnosticDataRouteObject,
|
|
3197
|
+
detectErrorBoundary: DetectErrorBoundaryFunction,
|
|
3198
|
+
manifest: RouteManifest
|
|
3199
|
+
) {
|
|
3200
|
+
if (!route.lazy) {
|
|
3201
|
+
return;
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
let lazyRoute = await route.lazy();
|
|
3205
|
+
|
|
3206
|
+
// If the lazy route function was executed and removed by another parallel
|
|
3207
|
+
// call then we can return - first lazy() to finish wins because the return
|
|
3208
|
+
// value of lazy is expected to be static
|
|
3209
|
+
if (!route.lazy) {
|
|
3210
|
+
return;
|
|
3211
|
+
}
|
|
3212
|
+
|
|
3213
|
+
let routeToUpdate = manifest[route.id];
|
|
3214
|
+
invariant(routeToUpdate, "No route found in manifest");
|
|
3215
|
+
|
|
3216
|
+
// Update the route in place. This should be safe because there's no way
|
|
3217
|
+
// we could yet be sitting on this route as we can't get there without
|
|
3218
|
+
// resolving lazy() first.
|
|
3219
|
+
//
|
|
3220
|
+
// This is different than the HMR "update" use-case where we may actively be
|
|
3221
|
+
// on the route being updated. The main concern boils down to "does this
|
|
3222
|
+
// mutation affect any ongoing navigations or any current state.matches
|
|
3223
|
+
// values?". If not, it should be safe to update in place.
|
|
3224
|
+
let routeUpdates: Record<string, any> = {};
|
|
3225
|
+
for (let lazyRouteProperty in lazyRoute) {
|
|
3226
|
+
let staticRouteValue =
|
|
3227
|
+
routeToUpdate[lazyRouteProperty as keyof typeof routeToUpdate];
|
|
3228
|
+
|
|
3229
|
+
let isPropertyStaticallyDefined =
|
|
3230
|
+
staticRouteValue !== undefined &&
|
|
3231
|
+
// This property isn't static since it should always be updated based
|
|
3232
|
+
// on the route updates
|
|
3233
|
+
lazyRouteProperty !== "hasErrorBoundary";
|
|
3234
|
+
|
|
3235
|
+
warning(
|
|
3236
|
+
!isPropertyStaticallyDefined,
|
|
3237
|
+
`Route "${routeToUpdate.id}" has a static property "${lazyRouteProperty}" ` +
|
|
3238
|
+
`defined but its lazy function is also returning a value for this property. ` +
|
|
3239
|
+
`The lazy route property "${lazyRouteProperty}" will be ignored.`
|
|
3240
|
+
);
|
|
3241
|
+
|
|
3242
|
+
if (
|
|
3243
|
+
!isPropertyStaticallyDefined &&
|
|
3244
|
+
!immutableRouteKeys.has(lazyRouteProperty as ImmutableRouteKey)
|
|
3245
|
+
) {
|
|
3246
|
+
routeUpdates[lazyRouteProperty] =
|
|
3247
|
+
lazyRoute[lazyRouteProperty as keyof typeof lazyRoute];
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3250
|
+
|
|
3251
|
+
// Mutate the route with the provided updates. Do this first so we pass
|
|
3252
|
+
// the updated version to detectErrorBoundary
|
|
3253
|
+
Object.assign(routeToUpdate, routeUpdates);
|
|
3254
|
+
|
|
3255
|
+
// Mutate the `hasErrorBoundary` property on the route based on the route
|
|
3256
|
+
// updates and remove the `lazy` function so we don't resolve the lazy
|
|
3257
|
+
// route again.
|
|
3258
|
+
Object.assign(routeToUpdate, {
|
|
3259
|
+
// To keep things framework agnostic, we use the provided
|
|
3260
|
+
// `detectErrorBoundary` function to set the `hasErrorBoundary` route
|
|
3261
|
+
// property since the logic will differ between frameworks.
|
|
3262
|
+
hasErrorBoundary: detectErrorBoundary({ ...routeToUpdate }),
|
|
3263
|
+
lazy: undefined,
|
|
3264
|
+
});
|
|
3265
|
+
}
|
|
3266
|
+
|
|
3099
3267
|
async function callLoaderOrAction(
|
|
3100
3268
|
type: "loader" | "action",
|
|
3101
3269
|
request: Request,
|
|
3102
3270
|
match: AgnosticDataRouteMatch,
|
|
3103
3271
|
matches: AgnosticDataRouteMatch[],
|
|
3272
|
+
manifest: RouteManifest,
|
|
3273
|
+
detectErrorBoundary: DetectErrorBoundaryFunction,
|
|
3104
3274
|
basename = "/",
|
|
3105
3275
|
isStaticRequest: boolean = false,
|
|
3106
3276
|
isRouteRequest: boolean = false,
|
|
@@ -3108,24 +3278,61 @@ async function callLoaderOrAction(
|
|
|
3108
3278
|
): Promise<DataResult> {
|
|
3109
3279
|
let resultType;
|
|
3110
3280
|
let result;
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
let
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3281
|
+
let onReject: (() => void) | undefined;
|
|
3282
|
+
|
|
3283
|
+
let runHandler = (handler: ActionFunction | LoaderFunction) => {
|
|
3284
|
+
// Setup a promise we can race against so that abort signals short circuit
|
|
3285
|
+
let reject: () => void;
|
|
3286
|
+
let abortPromise = new Promise((_, r) => (reject = r));
|
|
3287
|
+
onReject = () => reject();
|
|
3288
|
+
request.signal.addEventListener("abort", onReject);
|
|
3289
|
+
return Promise.race([
|
|
3290
|
+
handler({ request, params: match.params, context: requestContext }),
|
|
3291
|
+
abortPromise,
|
|
3292
|
+
]);
|
|
3293
|
+
};
|
|
3117
3294
|
|
|
3118
3295
|
try {
|
|
3119
3296
|
let handler = match.route[type];
|
|
3120
|
-
invariant<Function>(
|
|
3121
|
-
handler,
|
|
3122
|
-
`Could not find the ${type} to run on the "${match.route.id}" route`
|
|
3123
|
-
);
|
|
3124
3297
|
|
|
3125
|
-
|
|
3126
|
-
handler
|
|
3127
|
-
|
|
3128
|
-
|
|
3298
|
+
if (match.route.lazy) {
|
|
3299
|
+
if (handler) {
|
|
3300
|
+
// Run statically defined handler in parallel with lazy()
|
|
3301
|
+
let values = await Promise.all([
|
|
3302
|
+
runHandler(handler),
|
|
3303
|
+
loadLazyRouteModule(match.route, detectErrorBoundary, manifest),
|
|
3304
|
+
]);
|
|
3305
|
+
result = values[0];
|
|
3306
|
+
} else {
|
|
3307
|
+
// Load lazy route module, then run any returned handler
|
|
3308
|
+
await loadLazyRouteModule(match.route, detectErrorBoundary, manifest);
|
|
3309
|
+
|
|
3310
|
+
handler = match.route[type];
|
|
3311
|
+
if (handler) {
|
|
3312
|
+
// Handler still run even if we got interrupted to maintain consistency
|
|
3313
|
+
// with un-abortable behavior of handler execution on non-lazy or
|
|
3314
|
+
// previously-lazy-loaded routes
|
|
3315
|
+
result = await runHandler(handler);
|
|
3316
|
+
} else if (type === "action") {
|
|
3317
|
+
throw getInternalRouterError(405, {
|
|
3318
|
+
method: request.method,
|
|
3319
|
+
pathname: new URL(request.url).pathname,
|
|
3320
|
+
routeId: match.route.id,
|
|
3321
|
+
});
|
|
3322
|
+
} else {
|
|
3323
|
+
// lazy() route has no loader to run. Short circuit here so we don't
|
|
3324
|
+
// hit the invariant below that errors on returning undefined.
|
|
3325
|
+
return { type: ResultType.data, data: undefined };
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
} else {
|
|
3329
|
+
invariant<Function>(
|
|
3330
|
+
handler,
|
|
3331
|
+
`Could not find the ${type} to run on the "${match.route.id}" route`
|
|
3332
|
+
);
|
|
3333
|
+
|
|
3334
|
+
result = await runHandler(handler);
|
|
3335
|
+
}
|
|
3129
3336
|
|
|
3130
3337
|
invariant(
|
|
3131
3338
|
result !== undefined,
|
|
@@ -3137,7 +3344,9 @@ async function callLoaderOrAction(
|
|
|
3137
3344
|
resultType = ResultType.error;
|
|
3138
3345
|
result = e;
|
|
3139
3346
|
} finally {
|
|
3140
|
-
|
|
3347
|
+
if (onReject) {
|
|
3348
|
+
request.signal.removeEventListener("abort", onReject);
|
|
3349
|
+
}
|
|
3141
3350
|
}
|
|
3142
3351
|
|
|
3143
3352
|
if (isResponse(result)) {
|