@remix-run/router 1.2.1 → 1.3.0-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 +18 -0
- package/dist/history.d.ts +6 -1
- package/dist/index.d.ts +1 -2
- package/dist/router.cjs.js +177 -90
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.d.ts +3 -0
- package/dist/router.js +176 -91
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +177 -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 +14 -7
- package/history.ts +33 -23
- package/index.ts +3 -3
- package/package.json +1 -1
- package/router.ts +95 -39
- package/utils.ts +88 -42
package/dist/utils.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ export interface SuccessResult {
|
|
|
26
26
|
export interface DeferredResult {
|
|
27
27
|
type: ResultType.deferred;
|
|
28
28
|
deferredData: DeferredData;
|
|
29
|
+
statusCode?: number;
|
|
30
|
+
headers?: Headers;
|
|
29
31
|
}
|
|
30
32
|
/**
|
|
31
33
|
* Redirect result from a loader or action
|
|
@@ -158,7 +160,7 @@ export declare type AgnosticDataNonIndexRouteObject = AgnosticNonIndexRouteObjec
|
|
|
158
160
|
* A data route object, which is just a RouteObject with a required unique ID
|
|
159
161
|
*/
|
|
160
162
|
export declare type AgnosticDataRouteObject = AgnosticDataIndexRouteObject | AgnosticDataNonIndexRouteObject;
|
|
161
|
-
declare type _PathParam<Path extends string> = Path extends `${infer L}/${infer R}` ? _PathParam<L> | _PathParam<R> : Path extends `:${infer Param}` ? Param : never;
|
|
163
|
+
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;
|
|
162
164
|
/**
|
|
163
165
|
* Examples:
|
|
164
166
|
* "/a/b/*" -> "*"
|
|
@@ -214,7 +216,7 @@ export declare function matchRoutes<RouteObjectType extends AgnosticRouteObject
|
|
|
214
216
|
* @see https://reactrouter.com/utils/generate-path
|
|
215
217
|
*/
|
|
216
218
|
export declare function generatePath<Path extends string>(originalPath: Path, params?: {
|
|
217
|
-
[key in PathParam<Path>]: string;
|
|
219
|
+
[key in PathParam<Path>]: string | null;
|
|
218
220
|
}): string;
|
|
219
221
|
/**
|
|
220
222
|
* A PathPattern is used to match on some portion of a URL pathname.
|
|
@@ -340,22 +342,27 @@ export interface TrackedPromise extends Promise<any> {
|
|
|
340
342
|
export declare class AbortedDeferredError extends Error {
|
|
341
343
|
}
|
|
342
344
|
export declare class DeferredData {
|
|
343
|
-
private
|
|
345
|
+
private pendingKeysSet;
|
|
344
346
|
private controller;
|
|
345
347
|
private abortPromise;
|
|
346
348
|
private unlistenAbortSignal;
|
|
347
|
-
private
|
|
349
|
+
private subscribers;
|
|
348
350
|
data: Record<string, unknown>;
|
|
349
|
-
|
|
351
|
+
init?: ResponseInit;
|
|
352
|
+
deferredKeys: string[];
|
|
353
|
+
constructor(data: Record<string, unknown>, responseInit?: ResponseInit);
|
|
350
354
|
private trackPromise;
|
|
351
355
|
private onSettle;
|
|
352
|
-
|
|
356
|
+
private emit;
|
|
357
|
+
subscribe(fn: (aborted: boolean, settledKey?: string) => void): () => boolean;
|
|
353
358
|
cancel(): void;
|
|
354
359
|
resolveData(signal: AbortSignal): Promise<boolean>;
|
|
355
360
|
get done(): boolean;
|
|
356
361
|
get unwrappedData(): {};
|
|
362
|
+
get pendingKeys(): string[];
|
|
357
363
|
}
|
|
358
|
-
export declare
|
|
364
|
+
export declare type DeferFunction = (data: Record<string, unknown>, init?: number | ResponseInit) => DeferredData;
|
|
365
|
+
export declare const defer: DeferFunction;
|
|
359
366
|
export declare type RedirectFunction = (url: string, init?: number | ResponseInit) => Response;
|
|
360
367
|
/**
|
|
361
368
|
* A redirect response. Sets the status code and the `Location` header.
|
package/history.ts
CHANGED
|
@@ -125,6 +125,13 @@ export interface History {
|
|
|
125
125
|
*/
|
|
126
126
|
createHref(to: To): string;
|
|
127
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Returns a URL for the given `to` value
|
|
130
|
+
*
|
|
131
|
+
* @param to - The destination URL
|
|
132
|
+
*/
|
|
133
|
+
createURL(to: To): URL;
|
|
134
|
+
|
|
128
135
|
/**
|
|
129
136
|
* Encode a location the same way window.history would do (no-op for memory
|
|
130
137
|
* history) so we ensure our PUSH/REPLACE navigations for data routers
|
|
@@ -255,6 +262,10 @@ export function createMemoryHistory(
|
|
|
255
262
|
return location;
|
|
256
263
|
}
|
|
257
264
|
|
|
265
|
+
function createHref(to: To) {
|
|
266
|
+
return typeof to === "string" ? to : createPath(to);
|
|
267
|
+
}
|
|
268
|
+
|
|
258
269
|
let history: MemoryHistory = {
|
|
259
270
|
get index() {
|
|
260
271
|
return index;
|
|
@@ -265,8 +276,9 @@ export function createMemoryHistory(
|
|
|
265
276
|
get location() {
|
|
266
277
|
return getCurrentLocation();
|
|
267
278
|
},
|
|
268
|
-
createHref
|
|
269
|
-
|
|
279
|
+
createHref,
|
|
280
|
+
createURL(to) {
|
|
281
|
+
return new URL(createHref(to), "http://localhost");
|
|
270
282
|
},
|
|
271
283
|
encodeLocation(to: To) {
|
|
272
284
|
let path = typeof to === "string" ? parsePath(to) : to;
|
|
@@ -558,24 +570,6 @@ export function parsePath(path: string): Partial<Path> {
|
|
|
558
570
|
return parsedPath;
|
|
559
571
|
}
|
|
560
572
|
|
|
561
|
-
export function createClientSideURL(location: Location | string): URL {
|
|
562
|
-
// window.location.origin is "null" (the literal string value) in Firefox
|
|
563
|
-
// under certain conditions, notably when serving from a local HTML file
|
|
564
|
-
// See https://bugzilla.mozilla.org/show_bug.cgi?id=878297
|
|
565
|
-
let base =
|
|
566
|
-
typeof window !== "undefined" &&
|
|
567
|
-
typeof window.location !== "undefined" &&
|
|
568
|
-
window.location.origin !== "null"
|
|
569
|
-
? window.location.origin
|
|
570
|
-
: window.location.href;
|
|
571
|
-
let href = typeof location === "string" ? location : createPath(location);
|
|
572
|
-
invariant(
|
|
573
|
-
base,
|
|
574
|
-
`No window.location.(origin|href) available to create URL for href: ${href}`
|
|
575
|
-
);
|
|
576
|
-
return new URL(href, base);
|
|
577
|
-
}
|
|
578
|
-
|
|
579
573
|
export interface UrlHistory extends History {}
|
|
580
574
|
|
|
581
575
|
export type UrlHistoryOptions = {
|
|
@@ -637,6 +631,23 @@ function getUrlBasedHistory(
|
|
|
637
631
|
}
|
|
638
632
|
}
|
|
639
633
|
|
|
634
|
+
function createURL(to: To): URL {
|
|
635
|
+
// window.location.origin is "null" (the literal string value) in Firefox
|
|
636
|
+
// under certain conditions, notably when serving from a local HTML file
|
|
637
|
+
// See https://bugzilla.mozilla.org/show_bug.cgi?id=878297
|
|
638
|
+
let base =
|
|
639
|
+
window.location.origin !== "null"
|
|
640
|
+
? window.location.origin
|
|
641
|
+
: window.location.href;
|
|
642
|
+
|
|
643
|
+
let href = typeof to === "string" ? to : createPath(to);
|
|
644
|
+
invariant(
|
|
645
|
+
base,
|
|
646
|
+
`No window.location.(origin|href) available to create URL for href: ${href}`
|
|
647
|
+
);
|
|
648
|
+
return new URL(href, base);
|
|
649
|
+
}
|
|
650
|
+
|
|
640
651
|
let history: History = {
|
|
641
652
|
get action() {
|
|
642
653
|
return action;
|
|
@@ -659,11 +670,10 @@ function getUrlBasedHistory(
|
|
|
659
670
|
createHref(to) {
|
|
660
671
|
return createHref(window, to);
|
|
661
672
|
},
|
|
673
|
+
createURL,
|
|
662
674
|
encodeLocation(to) {
|
|
663
675
|
// Encode a Location the same way window.location would
|
|
664
|
-
let url =
|
|
665
|
-
typeof to === "string" ? to : createPath(to)
|
|
666
|
-
);
|
|
676
|
+
let url = createURL(to);
|
|
667
677
|
return {
|
|
668
678
|
pathname: url.pathname,
|
|
669
679
|
search: url.search,
|
package/index.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { convertRoutesToDataRoutes, getPathContributingMatches } from "./utils";
|
|
2
|
-
|
|
3
1
|
export type {
|
|
4
2
|
ActionFunction,
|
|
5
3
|
ActionFunctionArgs,
|
|
@@ -58,6 +56,7 @@ export type {
|
|
|
58
56
|
Path,
|
|
59
57
|
To,
|
|
60
58
|
} from "./history";
|
|
59
|
+
|
|
61
60
|
export {
|
|
62
61
|
Action,
|
|
63
62
|
createBrowserHistory,
|
|
@@ -79,6 +78,7 @@ export * from "./router";
|
|
|
79
78
|
|
|
80
79
|
/** @internal */
|
|
81
80
|
export {
|
|
81
|
+
DeferredData as UNSAFE_DeferredData,
|
|
82
82
|
convertRoutesToDataRoutes as UNSAFE_convertRoutesToDataRoutes,
|
|
83
83
|
getPathContributingMatches as UNSAFE_getPathContributingMatches,
|
|
84
|
-
};
|
|
84
|
+
} from "./utils";
|
package/package.json
CHANGED
package/router.ts
CHANGED
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
Action as HistoryAction,
|
|
4
4
|
createLocation,
|
|
5
5
|
createPath,
|
|
6
|
-
createClientSideURL,
|
|
7
6
|
invariant,
|
|
8
7
|
parsePath,
|
|
9
8
|
} from "./history";
|
|
@@ -308,6 +307,7 @@ export interface StaticHandlerContext {
|
|
|
308
307
|
statusCode: number;
|
|
309
308
|
loaderHeaders: Record<string, Headers>;
|
|
310
309
|
actionHeaders: Record<string, Headers>;
|
|
310
|
+
activeDeferreds: Record<string, DeferredData> | null;
|
|
311
311
|
_deepestRenderedBoundaryId?: string | null;
|
|
312
312
|
}
|
|
313
313
|
|
|
@@ -371,6 +371,7 @@ type LinkNavigateOptions = {
|
|
|
371
371
|
type SubmissionNavigateOptions = {
|
|
372
372
|
replace?: boolean;
|
|
373
373
|
state?: any;
|
|
374
|
+
preventScrollReset?: boolean;
|
|
374
375
|
formMethod?: FormMethod;
|
|
375
376
|
formEncType?: FormEncType;
|
|
376
377
|
formData: FormData;
|
|
@@ -771,6 +772,14 @@ export function createRouter(init: RouterInit): Router {
|
|
|
771
772
|
)
|
|
772
773
|
: state.loaderData;
|
|
773
774
|
|
|
775
|
+
// Always respect the user flag. Otherwise don't reset on mutation
|
|
776
|
+
// submission navigations unless they redirect
|
|
777
|
+
let preventScrollReset =
|
|
778
|
+
pendingPreventScrollReset === true ||
|
|
779
|
+
(state.navigation.formMethod != null &&
|
|
780
|
+
isMutationMethod(state.navigation.formMethod) &&
|
|
781
|
+
location.state?._isRedirect !== true);
|
|
782
|
+
|
|
774
783
|
updateState({
|
|
775
784
|
...newState, // matches, errors, fetchers go through as-is
|
|
776
785
|
actionData,
|
|
@@ -780,11 +789,11 @@ export function createRouter(init: RouterInit): Router {
|
|
|
780
789
|
initialized: true,
|
|
781
790
|
navigation: IDLE_NAVIGATION,
|
|
782
791
|
revalidation: "idle",
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
preventScrollReset
|
|
792
|
+
restoreScrollPosition: getSavedScrollPosition(
|
|
793
|
+
location,
|
|
794
|
+
newState.matches || state.matches
|
|
795
|
+
),
|
|
796
|
+
preventScrollReset,
|
|
788
797
|
});
|
|
789
798
|
|
|
790
799
|
if (isUninterruptedRevalidation) {
|
|
@@ -957,6 +966,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
957
966
|
// Create a controller/Request for this navigation
|
|
958
967
|
pendingNavigationController = new AbortController();
|
|
959
968
|
let request = createClientSideRequest(
|
|
969
|
+
init.history,
|
|
960
970
|
location,
|
|
961
971
|
pendingNavigationController.signal,
|
|
962
972
|
opts && opts.submission
|
|
@@ -1115,7 +1125,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1115
1125
|
}
|
|
1116
1126
|
|
|
1117
1127
|
if (isDeferredResult(result)) {
|
|
1118
|
-
throw
|
|
1128
|
+
throw getInternalRouterError(400, { type: "defer-action" });
|
|
1119
1129
|
}
|
|
1120
1130
|
|
|
1121
1131
|
return {
|
|
@@ -1167,6 +1177,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1167
1177
|
: undefined;
|
|
1168
1178
|
|
|
1169
1179
|
let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(
|
|
1180
|
+
init.history,
|
|
1170
1181
|
state,
|
|
1171
1182
|
matches,
|
|
1172
1183
|
activeSubmission,
|
|
@@ -1380,6 +1391,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1380
1391
|
// Call the action for the fetcher
|
|
1381
1392
|
let abortController = new AbortController();
|
|
1382
1393
|
let fetchRequest = createClientSideRequest(
|
|
1394
|
+
init.history,
|
|
1383
1395
|
path,
|
|
1384
1396
|
abortController.signal,
|
|
1385
1397
|
submission
|
|
@@ -1427,13 +1439,15 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1427
1439
|
}
|
|
1428
1440
|
|
|
1429
1441
|
if (isDeferredResult(actionResult)) {
|
|
1430
|
-
|
|
1442
|
+
throw getInternalRouterError(400, { type: "defer-action" });
|
|
1431
1443
|
}
|
|
1432
1444
|
|
|
1433
1445
|
// Start the data load for current matches, or the next location if we're
|
|
1434
1446
|
// in the middle of a navigation
|
|
1435
1447
|
let nextLocation = state.navigation.location || state.location;
|
|
1436
1448
|
let revalidationRequest = createClientSideRequest(
|
|
1449
|
+
init.history,
|
|
1450
|
+
|
|
1437
1451
|
nextLocation,
|
|
1438
1452
|
abortController.signal
|
|
1439
1453
|
);
|
|
@@ -1456,6 +1470,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1456
1470
|
state.fetchers.set(key, loadFetcher);
|
|
1457
1471
|
|
|
1458
1472
|
let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(
|
|
1473
|
+
init.history,
|
|
1459
1474
|
state,
|
|
1460
1475
|
matches,
|
|
1461
1476
|
submission,
|
|
@@ -1599,7 +1614,11 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1599
1614
|
|
|
1600
1615
|
// Call the loader for this fetcher route match
|
|
1601
1616
|
let abortController = new AbortController();
|
|
1602
|
-
let fetchRequest = createClientSideRequest(
|
|
1617
|
+
let fetchRequest = createClientSideRequest(
|
|
1618
|
+
init.history,
|
|
1619
|
+
path,
|
|
1620
|
+
abortController.signal
|
|
1621
|
+
);
|
|
1603
1622
|
fetchControllers.set(key, abortController);
|
|
1604
1623
|
let result: DataResult = await callLoaderOrAction(
|
|
1605
1624
|
"loader",
|
|
@@ -1609,7 +1628,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1609
1628
|
router.basename
|
|
1610
1629
|
);
|
|
1611
1630
|
|
|
1612
|
-
// Deferred isn't supported
|
|
1631
|
+
// Deferred isn't supported for fetcher loads, await everything and treat it
|
|
1613
1632
|
// as a normal load. resolveDeferredData will return undefined if this
|
|
1614
1633
|
// fetcher gets aborted, so we just leave result untouched and short circuit
|
|
1615
1634
|
// below if that happens
|
|
@@ -1719,7 +1738,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1719
1738
|
|
|
1720
1739
|
// Check if this an external redirect that goes to a new origin
|
|
1721
1740
|
if (typeof window?.location !== "undefined") {
|
|
1722
|
-
let newOrigin =
|
|
1741
|
+
let newOrigin = init.history.createURL(redirect.location).origin;
|
|
1723
1742
|
if (window.location.origin !== newOrigin) {
|
|
1724
1743
|
if (replace) {
|
|
1725
1744
|
window.location.replace(redirect.location);
|
|
@@ -1762,6 +1781,8 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1762
1781
|
...submission,
|
|
1763
1782
|
formAction: redirect.location,
|
|
1764
1783
|
},
|
|
1784
|
+
// Preserve this flag across redirects
|
|
1785
|
+
preventScrollReset: pendingPreventScrollReset,
|
|
1765
1786
|
});
|
|
1766
1787
|
} else {
|
|
1767
1788
|
// Otherwise, we kick off a new loading navigation, preserving the
|
|
@@ -1775,6 +1796,8 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1775
1796
|
formEncType: submission ? submission.formEncType : undefined,
|
|
1776
1797
|
formData: submission ? submission.formData : undefined,
|
|
1777
1798
|
},
|
|
1799
|
+
// Preserve this flag across redirects
|
|
1800
|
+
preventScrollReset: pendingPreventScrollReset,
|
|
1778
1801
|
});
|
|
1779
1802
|
}
|
|
1780
1803
|
}
|
|
@@ -1796,7 +1819,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1796
1819
|
...fetchersToLoad.map(([, href, match, fetchMatches]) =>
|
|
1797
1820
|
callLoaderOrAction(
|
|
1798
1821
|
"loader",
|
|
1799
|
-
createClientSideRequest(href, request.signal),
|
|
1822
|
+
createClientSideRequest(init.history, href, request.signal),
|
|
1800
1823
|
match,
|
|
1801
1824
|
fetchMatches,
|
|
1802
1825
|
router.basename
|
|
@@ -2027,6 +2050,8 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2027
2050
|
//#region createStaticHandler
|
|
2028
2051
|
////////////////////////////////////////////////////////////////////////////////
|
|
2029
2052
|
|
|
2053
|
+
export const UNSAFE_DEFERRED_SYMBOL = Symbol("deferred");
|
|
2054
|
+
|
|
2030
2055
|
export function createStaticHandler(
|
|
2031
2056
|
routes: AgnosticRouteObject[],
|
|
2032
2057
|
opts?: {
|
|
@@ -2086,6 +2111,7 @@ export function createStaticHandler(
|
|
|
2086
2111
|
statusCode: error.status,
|
|
2087
2112
|
loaderHeaders: {},
|
|
2088
2113
|
actionHeaders: {},
|
|
2114
|
+
activeDeferreds: null,
|
|
2089
2115
|
};
|
|
2090
2116
|
} else if (!matches) {
|
|
2091
2117
|
let error = getInternalRouterError(404, { pathname: location.pathname });
|
|
@@ -2103,6 +2129,7 @@ export function createStaticHandler(
|
|
|
2103
2129
|
statusCode: error.status,
|
|
2104
2130
|
loaderHeaders: {},
|
|
2105
2131
|
actionHeaders: {},
|
|
2132
|
+
activeDeferreds: null,
|
|
2106
2133
|
};
|
|
2107
2134
|
}
|
|
2108
2135
|
|
|
@@ -2191,8 +2218,19 @@ export function createStaticHandler(
|
|
|
2191
2218
|
}
|
|
2192
2219
|
|
|
2193
2220
|
// Pick off the right state value to return
|
|
2194
|
-
|
|
2195
|
-
|
|
2221
|
+
if (result.actionData) {
|
|
2222
|
+
return Object.values(result.actionData)[0];
|
|
2223
|
+
}
|
|
2224
|
+
|
|
2225
|
+
if (result.loaderData) {
|
|
2226
|
+
let data = Object.values(result.loaderData)[0];
|
|
2227
|
+
if (result.activeDeferreds?.[match.route.id]) {
|
|
2228
|
+
data[UNSAFE_DEFERRED_SYMBOL] = result.activeDeferreds[match.route.id];
|
|
2229
|
+
}
|
|
2230
|
+
return data;
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
return undefined;
|
|
2196
2234
|
}
|
|
2197
2235
|
|
|
2198
2236
|
async function queryImpl(
|
|
@@ -2305,7 +2343,14 @@ export function createStaticHandler(
|
|
|
2305
2343
|
}
|
|
2306
2344
|
|
|
2307
2345
|
if (isDeferredResult(result)) {
|
|
2308
|
-
|
|
2346
|
+
let error = getInternalRouterError(400, { type: "defer-action" });
|
|
2347
|
+
if (isRouteRequest) {
|
|
2348
|
+
throw error;
|
|
2349
|
+
}
|
|
2350
|
+
result = {
|
|
2351
|
+
type: ResultType.error,
|
|
2352
|
+
error,
|
|
2353
|
+
};
|
|
2309
2354
|
}
|
|
2310
2355
|
|
|
2311
2356
|
if (isRouteRequest) {
|
|
@@ -2325,6 +2370,7 @@ export function createStaticHandler(
|
|
|
2325
2370
|
statusCode: 200,
|
|
2326
2371
|
loaderHeaders: {},
|
|
2327
2372
|
actionHeaders: {},
|
|
2373
|
+
activeDeferreds: null,
|
|
2328
2374
|
};
|
|
2329
2375
|
}
|
|
2330
2376
|
|
|
@@ -2420,6 +2466,7 @@ export function createStaticHandler(
|
|
|
2420
2466
|
errors: pendingActionError || null,
|
|
2421
2467
|
statusCode: 200,
|
|
2422
2468
|
loaderHeaders: {},
|
|
2469
|
+
activeDeferreds: null,
|
|
2423
2470
|
};
|
|
2424
2471
|
}
|
|
2425
2472
|
|
|
@@ -2443,25 +2490,20 @@ export function createStaticHandler(
|
|
|
2443
2490
|
throw new Error(`${method}() call aborted`);
|
|
2444
2491
|
}
|
|
2445
2492
|
|
|
2446
|
-
let executedLoaders = new Set<string>();
|
|
2447
|
-
results.forEach((result, i) => {
|
|
2448
|
-
executedLoaders.add(matchesToLoad[i].route.id);
|
|
2449
|
-
// Can't do anything with these without the Remix side of things, so just
|
|
2450
|
-
// cancel them for now
|
|
2451
|
-
if (isDeferredResult(result)) {
|
|
2452
|
-
result.deferredData.cancel();
|
|
2453
|
-
}
|
|
2454
|
-
});
|
|
2455
|
-
|
|
2456
2493
|
// Process and commit output from loaders
|
|
2494
|
+
let activeDeferreds = new Map<string, DeferredData>();
|
|
2457
2495
|
let context = processRouteLoaderData(
|
|
2458
2496
|
matches,
|
|
2459
2497
|
matchesToLoad,
|
|
2460
2498
|
results,
|
|
2461
|
-
pendingActionError
|
|
2499
|
+
pendingActionError,
|
|
2500
|
+
activeDeferreds
|
|
2462
2501
|
);
|
|
2463
2502
|
|
|
2464
2503
|
// Add a null for any non-loader matches for proper revalidation on the client
|
|
2504
|
+
let executedLoaders = new Set<string>(
|
|
2505
|
+
matchesToLoad.map((match) => match.route.id)
|
|
2506
|
+
);
|
|
2465
2507
|
matches.forEach((match) => {
|
|
2466
2508
|
if (!executedLoaders.has(match.route.id)) {
|
|
2467
2509
|
context.loaderData[match.route.id] = null;
|
|
@@ -2471,6 +2513,10 @@ export function createStaticHandler(
|
|
|
2471
2513
|
return {
|
|
2472
2514
|
...context,
|
|
2473
2515
|
matches,
|
|
2516
|
+
activeDeferreds:
|
|
2517
|
+
activeDeferreds.size > 0
|
|
2518
|
+
? Object.fromEntries(activeDeferreds.entries())
|
|
2519
|
+
: null,
|
|
2474
2520
|
};
|
|
2475
2521
|
}
|
|
2476
2522
|
|
|
@@ -2595,6 +2641,7 @@ function getLoaderMatchesUntilBoundary(
|
|
|
2595
2641
|
}
|
|
2596
2642
|
|
|
2597
2643
|
function getMatchesToLoad(
|
|
2644
|
+
history: History,
|
|
2598
2645
|
state: RouterState,
|
|
2599
2646
|
matches: AgnosticDataRouteMatch[],
|
|
2600
2647
|
submission: Submission | undefined,
|
|
@@ -2622,6 +2669,7 @@ function getMatchesToLoad(
|
|
|
2622
2669
|
// If this route had a pending deferred cancelled it must be revalidated
|
|
2623
2670
|
cancelledDeferredRoutes.some((id) => id === match.route.id) ||
|
|
2624
2671
|
shouldRevalidateLoader(
|
|
2672
|
+
history,
|
|
2625
2673
|
state.location,
|
|
2626
2674
|
state.matches[index],
|
|
2627
2675
|
submission,
|
|
@@ -2641,6 +2689,7 @@ function getMatchesToLoad(
|
|
|
2641
2689
|
revalidatingFetchers.push([key, href, match, fetchMatches]);
|
|
2642
2690
|
} else if (isRevalidationRequired) {
|
|
2643
2691
|
let shouldRevalidate = shouldRevalidateLoader(
|
|
2692
|
+
history,
|
|
2644
2693
|
href,
|
|
2645
2694
|
match,
|
|
2646
2695
|
submission,
|
|
@@ -2694,6 +2743,7 @@ function isNewRouteInstance(
|
|
|
2694
2743
|
}
|
|
2695
2744
|
|
|
2696
2745
|
function shouldRevalidateLoader(
|
|
2746
|
+
history: History,
|
|
2697
2747
|
currentLocation: string | Location,
|
|
2698
2748
|
currentMatch: AgnosticDataRouteMatch,
|
|
2699
2749
|
submission: Submission | undefined,
|
|
@@ -2702,9 +2752,9 @@ function shouldRevalidateLoader(
|
|
|
2702
2752
|
isRevalidationRequired: boolean,
|
|
2703
2753
|
actionResult: DataResult | undefined
|
|
2704
2754
|
) {
|
|
2705
|
-
let currentUrl =
|
|
2755
|
+
let currentUrl = history.createURL(currentLocation);
|
|
2706
2756
|
let currentParams = currentMatch.params;
|
|
2707
|
-
let nextUrl =
|
|
2757
|
+
let nextUrl = history.createURL(location);
|
|
2708
2758
|
let nextParams = match.params;
|
|
2709
2759
|
|
|
2710
2760
|
// This is the default implementation as to when we revalidate. If the route
|
|
@@ -2795,8 +2845,7 @@ async function callLoaderOrAction(
|
|
|
2795
2845
|
"Redirects returned/thrown from loaders/actions must have a Location header"
|
|
2796
2846
|
);
|
|
2797
2847
|
|
|
2798
|
-
let isAbsolute =
|
|
2799
|
-
/^[a-z+]+:\/\//i.test(location) || location.startsWith("//");
|
|
2848
|
+
let isAbsolute = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(location);
|
|
2800
2849
|
|
|
2801
2850
|
// Support relative routing in internal redirects
|
|
2802
2851
|
if (!isAbsolute) {
|
|
@@ -2893,11 +2942,12 @@ async function callLoaderOrAction(
|
|
|
2893
2942
|
// client-side navigations and fetches. During SSR we will always have a
|
|
2894
2943
|
// Request instance from the static handler (query/queryRoute)
|
|
2895
2944
|
function createClientSideRequest(
|
|
2945
|
+
history: History,
|
|
2896
2946
|
location: string | Location,
|
|
2897
2947
|
signal: AbortSignal,
|
|
2898
2948
|
submission?: Submission
|
|
2899
2949
|
): Request {
|
|
2900
|
-
let url =
|
|
2950
|
+
let url = history.createURL(stripHashFromPath(location)).toString();
|
|
2901
2951
|
let init: RequestInit = { signal };
|
|
2902
2952
|
|
|
2903
2953
|
if (submission && isMutationMethod(submission.formMethod)) {
|
|
@@ -2933,7 +2983,7 @@ function processRouteLoaderData(
|
|
|
2933
2983
|
matchesToLoad: AgnosticDataRouteMatch[],
|
|
2934
2984
|
results: DataResult[],
|
|
2935
2985
|
pendingError: RouteData | undefined,
|
|
2936
|
-
activeDeferreds
|
|
2986
|
+
activeDeferreds: Map<string, DeferredData>
|
|
2937
2987
|
): {
|
|
2938
2988
|
loaderData: RouterState["loaderData"];
|
|
2939
2989
|
errors: RouterState["errors"] | null;
|
|
@@ -2988,12 +3038,14 @@ function processRouteLoaderData(
|
|
|
2988
3038
|
if (result.headers) {
|
|
2989
3039
|
loaderHeaders[id] = result.headers;
|
|
2990
3040
|
}
|
|
2991
|
-
} else if (isDeferredResult(result)) {
|
|
2992
|
-
activeDeferreds && activeDeferreds.set(id, result.deferredData);
|
|
2993
|
-
loaderData[id] = result.deferredData.data;
|
|
2994
|
-
// TODO: Add statusCode/headers once we wire up streaming in Remix
|
|
2995
3041
|
} else {
|
|
2996
|
-
|
|
3042
|
+
if (isDeferredResult(result)) {
|
|
3043
|
+
activeDeferreds.set(id, result.deferredData);
|
|
3044
|
+
loaderData[id] = result.deferredData.data;
|
|
3045
|
+
} else {
|
|
3046
|
+
loaderData[id] = result.data;
|
|
3047
|
+
}
|
|
3048
|
+
|
|
2997
3049
|
// Error status codes always override success status codes, but if all
|
|
2998
3050
|
// loaders are successful we take the deepest status code.
|
|
2999
3051
|
if (
|
|
@@ -3068,11 +3120,11 @@ function processLoaderData(
|
|
|
3068
3120
|
} else if (isRedirectResult(result)) {
|
|
3069
3121
|
// Should never get here, redirects should get processed above, but we
|
|
3070
3122
|
// keep this to type narrow to a success result in the else
|
|
3071
|
-
|
|
3123
|
+
invariant(false, "Unhandled fetcher revalidation redirect");
|
|
3072
3124
|
} else if (isDeferredResult(result)) {
|
|
3073
3125
|
// Should never get here, deferred data should be awaited for fetchers
|
|
3074
3126
|
// in resolveDeferredResults
|
|
3075
|
-
|
|
3127
|
+
invariant(false, "Unhandled fetcher deferred data");
|
|
3076
3128
|
} else {
|
|
3077
3129
|
let doneFetcher: FetcherStates["Idle"] = {
|
|
3078
3130
|
state: "idle",
|
|
@@ -3163,10 +3215,12 @@ function getInternalRouterError(
|
|
|
3163
3215
|
pathname,
|
|
3164
3216
|
routeId,
|
|
3165
3217
|
method,
|
|
3218
|
+
type,
|
|
3166
3219
|
}: {
|
|
3167
3220
|
pathname?: string;
|
|
3168
3221
|
routeId?: string;
|
|
3169
3222
|
method?: string;
|
|
3223
|
+
type?: "defer-action";
|
|
3170
3224
|
} = {}
|
|
3171
3225
|
) {
|
|
3172
3226
|
let statusText = "Unknown Server Error";
|
|
@@ -3179,6 +3233,8 @@ function getInternalRouterError(
|
|
|
3179
3233
|
`You made a ${method} request to "${pathname}" but ` +
|
|
3180
3234
|
`did not provide a \`loader\` for route "${routeId}", ` +
|
|
3181
3235
|
`so there is no way to handle the request.`;
|
|
3236
|
+
} else if (type === "defer-action") {
|
|
3237
|
+
errorMessage = "defer() is not supported in actions";
|
|
3182
3238
|
} else {
|
|
3183
3239
|
errorMessage = "Cannot submit binary form data using GET";
|
|
3184
3240
|
}
|