@remix-run/router 1.18.0 → 1.19.0-pre.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 +28 -0
- package/dist/index.d.ts +1 -1
- package/dist/router.cjs.js +83 -17
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.d.ts +2 -1
- package/dist/router.js +77 -18
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +83 -17
- 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 +18 -1
- package/index.ts +2 -0
- package/package.json +1 -1
- package/router.ts +69 -12
- package/utils.ts +35 -2
package/dist/utils.d.ts
CHANGED
|
@@ -55,7 +55,6 @@ export type DataResult = SuccessResult | DeferredResult | RedirectResult | Error
|
|
|
55
55
|
export interface HandlerResult {
|
|
56
56
|
type: "data" | "error";
|
|
57
57
|
result: unknown;
|
|
58
|
-
status?: number;
|
|
59
58
|
}
|
|
60
59
|
type LowerCaseFormMethod = "get" | "post" | "put" | "patch" | "delete";
|
|
61
60
|
type UpperCaseFormMethod = Uppercase<LowerCaseFormMethod>;
|
|
@@ -459,6 +458,17 @@ export type JsonFunction = <Data>(data: Data, init?: number | ResponseInit) => R
|
|
|
459
458
|
* to JSON and sets the `Content-Type` header.
|
|
460
459
|
*/
|
|
461
460
|
export declare const json: JsonFunction;
|
|
461
|
+
export declare class DataWithResponseInit<D> {
|
|
462
|
+
type: string;
|
|
463
|
+
data: D;
|
|
464
|
+
init: ResponseInit | null;
|
|
465
|
+
constructor(data: D, init?: ResponseInit);
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Create "responses" that contain `status`/`headers` without forcing
|
|
469
|
+
* serialization into an actual `Response` - used by Remix single fetch
|
|
470
|
+
*/
|
|
471
|
+
export declare function data<D>(data: D, init?: number | ResponseInit): DataWithResponseInit<D>;
|
|
462
472
|
export interface TrackedPromise extends Promise<any> {
|
|
463
473
|
_tracked?: boolean;
|
|
464
474
|
_data?: any;
|
|
@@ -500,6 +510,13 @@ export declare const redirect: RedirectFunction;
|
|
|
500
510
|
* Defaults to "302 Found".
|
|
501
511
|
*/
|
|
502
512
|
export declare const redirectDocument: RedirectFunction;
|
|
513
|
+
/**
|
|
514
|
+
* A redirect response that will perform a `history.replaceState` instead of a
|
|
515
|
+
* `history.pushState` for client-side navigation redirects.
|
|
516
|
+
* Sets the status code and the `Location` header.
|
|
517
|
+
* Defaults to "302 Found".
|
|
518
|
+
*/
|
|
519
|
+
export declare const replace: RedirectFunction;
|
|
503
520
|
export type ErrorResponse = {
|
|
504
521
|
status: number;
|
|
505
522
|
statusText: string;
|
package/index.ts
CHANGED
|
@@ -37,6 +37,7 @@ export type {
|
|
|
37
37
|
|
|
38
38
|
export {
|
|
39
39
|
AbortedDeferredError,
|
|
40
|
+
data as unstable_data,
|
|
40
41
|
defer,
|
|
41
42
|
generatePath,
|
|
42
43
|
getToPathname,
|
|
@@ -48,6 +49,7 @@ export {
|
|
|
48
49
|
normalizePathname,
|
|
49
50
|
redirect,
|
|
50
51
|
redirectDocument,
|
|
52
|
+
replace,
|
|
51
53
|
resolvePath,
|
|
52
54
|
resolveTo,
|
|
53
55
|
stripBasename,
|
package/package.json
CHANGED
package/router.ts
CHANGED
|
@@ -36,6 +36,7 @@ import type {
|
|
|
36
36
|
V7_FormMethod,
|
|
37
37
|
V7_MutationFormMethod,
|
|
38
38
|
AgnosticPatchRoutesOnMissFunction,
|
|
39
|
+
DataWithResponseInit,
|
|
39
40
|
} from "./utils";
|
|
40
41
|
import {
|
|
41
42
|
ErrorResponseImpl,
|
|
@@ -847,7 +848,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
847
848
|
// In SSR apps (with `hydrationData`), we expect that the server will send
|
|
848
849
|
// up the proper matched routes so we don't want to run lazy discovery on
|
|
849
850
|
// initial hydration and want to hydrate into the splat route.
|
|
850
|
-
if (initialMatches &&
|
|
851
|
+
if (initialMatches && !init.hydrationData) {
|
|
851
852
|
let fogOfWar = checkFogOfWar(
|
|
852
853
|
initialMatches,
|
|
853
854
|
dataRoutes,
|
|
@@ -860,9 +861,22 @@ export function createRouter(init: RouterInit): Router {
|
|
|
860
861
|
|
|
861
862
|
let initialized: boolean;
|
|
862
863
|
if (!initialMatches) {
|
|
863
|
-
// We need to run patchRoutesOnMiss in initialize()
|
|
864
864
|
initialized = false;
|
|
865
865
|
initialMatches = [];
|
|
866
|
+
|
|
867
|
+
// If partial hydration and fog of war is enabled, we will be running
|
|
868
|
+
// `patchRoutesOnMiss` during hydration so include any partial matches as
|
|
869
|
+
// the initial matches so we can properly render `HydrateFallback`'s
|
|
870
|
+
if (future.v7_partialHydration) {
|
|
871
|
+
let fogOfWar = checkFogOfWar(
|
|
872
|
+
null,
|
|
873
|
+
dataRoutes,
|
|
874
|
+
init.history.location.pathname
|
|
875
|
+
);
|
|
876
|
+
if (fogOfWar.active && fogOfWar.matches) {
|
|
877
|
+
initialMatches = fogOfWar.matches;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
866
880
|
} else if (initialMatches.some((m) => m.route.lazy)) {
|
|
867
881
|
// All initialMatches need to be loaded before we're ready. If we have lazy
|
|
868
882
|
// functions around still then we'll need to run them in initialize()
|
|
@@ -967,7 +981,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
967
981
|
|
|
968
982
|
// Use this internal array to capture fetcher loads that were cancelled by an
|
|
969
983
|
// action navigation and require revalidation
|
|
970
|
-
let cancelledFetcherLoads: string
|
|
984
|
+
let cancelledFetcherLoads: Set<string> = new Set();
|
|
971
985
|
|
|
972
986
|
// AbortControllers for any in-flight fetchers
|
|
973
987
|
let fetchControllers = new Map<string, AbortController>();
|
|
@@ -1320,7 +1334,6 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1320
1334
|
isUninterruptedRevalidation = false;
|
|
1321
1335
|
isRevalidationRequired = false;
|
|
1322
1336
|
cancelledDeferredRoutes = [];
|
|
1323
|
-
cancelledFetcherLoads = [];
|
|
1324
1337
|
}
|
|
1325
1338
|
|
|
1326
1339
|
// Trigger a navigation event, which can either be a numerical POP or a PUSH
|
|
@@ -2683,7 +2696,9 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2683
2696
|
pendingNavigationController = null;
|
|
2684
2697
|
|
|
2685
2698
|
let redirectHistoryAction =
|
|
2686
|
-
replace === true
|
|
2699
|
+
replace === true || redirect.response.headers.has("X-Remix-Replace")
|
|
2700
|
+
? HistoryAction.Replace
|
|
2701
|
+
: HistoryAction.Push;
|
|
2687
2702
|
|
|
2688
2703
|
// Use the incoming submission if provided, fallback on the active one in
|
|
2689
2704
|
// state.navigation
|
|
@@ -2851,7 +2866,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2851
2866
|
// Abort in-flight fetcher loads
|
|
2852
2867
|
fetchLoadMatches.forEach((_, key) => {
|
|
2853
2868
|
if (fetchControllers.has(key)) {
|
|
2854
|
-
cancelledFetcherLoads.
|
|
2869
|
+
cancelledFetcherLoads.add(key);
|
|
2855
2870
|
abortFetcher(key);
|
|
2856
2871
|
}
|
|
2857
2872
|
});
|
|
@@ -2915,6 +2930,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2915
2930
|
fetchReloadIds.delete(key);
|
|
2916
2931
|
fetchRedirectIds.delete(key);
|
|
2917
2932
|
deletedFetchers.delete(key);
|
|
2933
|
+
cancelledFetcherLoads.delete(key);
|
|
2918
2934
|
state.fetchers.delete(key);
|
|
2919
2935
|
}
|
|
2920
2936
|
|
|
@@ -4308,7 +4324,7 @@ function getMatchesToLoad(
|
|
|
4308
4324
|
skipActionErrorRevalidation: boolean,
|
|
4309
4325
|
isRevalidationRequired: boolean,
|
|
4310
4326
|
cancelledDeferredRoutes: string[],
|
|
4311
|
-
cancelledFetcherLoads: string
|
|
4327
|
+
cancelledFetcherLoads: Set<string>,
|
|
4312
4328
|
deletedFetchers: Set<string>,
|
|
4313
4329
|
fetchLoadMatches: Map<string, FetchLoadMatch>,
|
|
4314
4330
|
fetchRedirectIds: Set<string>,
|
|
@@ -4443,8 +4459,9 @@ function getMatchesToLoad(
|
|
|
4443
4459
|
if (fetchRedirectIds.has(key)) {
|
|
4444
4460
|
// Never trigger a revalidation of an actively redirecting fetcher
|
|
4445
4461
|
shouldRevalidate = false;
|
|
4446
|
-
} else if (cancelledFetcherLoads.
|
|
4447
|
-
// Always
|
|
4462
|
+
} else if (cancelledFetcherLoads.has(key)) {
|
|
4463
|
+
// Always mark for revalidation if the fetcher was cancelled
|
|
4464
|
+
cancelledFetcherLoads.delete(key);
|
|
4448
4465
|
shouldRevalidate = true;
|
|
4449
4466
|
} else if (
|
|
4450
4467
|
fetcher &&
|
|
@@ -4904,7 +4921,7 @@ async function callLoaderOrAction(
|
|
|
4904
4921
|
async function convertHandlerResultToDataResult(
|
|
4905
4922
|
handlerResult: HandlerResult
|
|
4906
4923
|
): Promise<DataResult> {
|
|
4907
|
-
let { result, type
|
|
4924
|
+
let { result, type } = handlerResult;
|
|
4908
4925
|
|
|
4909
4926
|
if (isResponse(result)) {
|
|
4910
4927
|
let data: any;
|
|
@@ -4944,10 +4961,26 @@ async function convertHandlerResultToDataResult(
|
|
|
4944
4961
|
}
|
|
4945
4962
|
|
|
4946
4963
|
if (type === ResultType.error) {
|
|
4964
|
+
if (isDataWithResponseInit(result)) {
|
|
4965
|
+
if (result.data instanceof Error) {
|
|
4966
|
+
return {
|
|
4967
|
+
type: ResultType.error,
|
|
4968
|
+
error: result.data,
|
|
4969
|
+
statusCode: result.init?.status,
|
|
4970
|
+
};
|
|
4971
|
+
}
|
|
4972
|
+
|
|
4973
|
+
// Convert thrown unstable_data() to ErrorResponse instances
|
|
4974
|
+
result = new ErrorResponseImpl(
|
|
4975
|
+
result.init?.status || 500,
|
|
4976
|
+
undefined,
|
|
4977
|
+
result.data
|
|
4978
|
+
);
|
|
4979
|
+
}
|
|
4947
4980
|
return {
|
|
4948
4981
|
type: ResultType.error,
|
|
4949
4982
|
error: result,
|
|
4950
|
-
statusCode: isRouteErrorResponse(result) ? result.status :
|
|
4983
|
+
statusCode: isRouteErrorResponse(result) ? result.status : undefined,
|
|
4951
4984
|
};
|
|
4952
4985
|
}
|
|
4953
4986
|
|
|
@@ -4960,7 +4993,18 @@ async function convertHandlerResultToDataResult(
|
|
|
4960
4993
|
};
|
|
4961
4994
|
}
|
|
4962
4995
|
|
|
4963
|
-
|
|
4996
|
+
if (isDataWithResponseInit(result)) {
|
|
4997
|
+
return {
|
|
4998
|
+
type: ResultType.data,
|
|
4999
|
+
data: result.data,
|
|
5000
|
+
statusCode: result.init?.status,
|
|
5001
|
+
headers: result.init?.headers
|
|
5002
|
+
? new Headers(result.init.headers)
|
|
5003
|
+
: undefined,
|
|
5004
|
+
};
|
|
5005
|
+
}
|
|
5006
|
+
|
|
5007
|
+
return { type: ResultType.data, data: result };
|
|
4964
5008
|
}
|
|
4965
5009
|
|
|
4966
5010
|
// Support relative routing in internal redirects
|
|
@@ -5474,6 +5518,19 @@ function isRedirectResult(result?: DataResult): result is RedirectResult {
|
|
|
5474
5518
|
return (result && result.type) === ResultType.redirect;
|
|
5475
5519
|
}
|
|
5476
5520
|
|
|
5521
|
+
export function isDataWithResponseInit(
|
|
5522
|
+
value: any
|
|
5523
|
+
): value is DataWithResponseInit<unknown> {
|
|
5524
|
+
return (
|
|
5525
|
+
typeof value === "object" &&
|
|
5526
|
+
value != null &&
|
|
5527
|
+
"type" in value &&
|
|
5528
|
+
"data" in value &&
|
|
5529
|
+
"init" in value &&
|
|
5530
|
+
value.type === "DataWithResponseInit"
|
|
5531
|
+
);
|
|
5532
|
+
}
|
|
5533
|
+
|
|
5477
5534
|
export function isDeferredData(value: any): value is DeferredData {
|
|
5478
5535
|
let deferred: DeferredData = value;
|
|
5479
5536
|
return (
|
package/utils.ts
CHANGED
|
@@ -68,8 +68,7 @@ export type DataResult =
|
|
|
68
68
|
*/
|
|
69
69
|
export interface HandlerResult {
|
|
70
70
|
type: "data" | "error";
|
|
71
|
-
result: unknown; // data, Error, Response, DeferredData
|
|
72
|
-
status?: number;
|
|
71
|
+
result: unknown; // data, Error, Response, DeferredData, DataWithResponseInit
|
|
73
72
|
}
|
|
74
73
|
|
|
75
74
|
type LowerCaseFormMethod = "get" | "post" | "put" | "patch" | "delete";
|
|
@@ -1375,6 +1374,28 @@ export const json: JsonFunction = (data, init = {}) => {
|
|
|
1375
1374
|
});
|
|
1376
1375
|
};
|
|
1377
1376
|
|
|
1377
|
+
export class DataWithResponseInit<D> {
|
|
1378
|
+
type: string = "DataWithResponseInit";
|
|
1379
|
+
data: D;
|
|
1380
|
+
init: ResponseInit | null;
|
|
1381
|
+
|
|
1382
|
+
constructor(data: D, init?: ResponseInit) {
|
|
1383
|
+
this.data = data;
|
|
1384
|
+
this.init = init || null;
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
/**
|
|
1389
|
+
* Create "responses" that contain `status`/`headers` without forcing
|
|
1390
|
+
* serialization into an actual `Response` - used by Remix single fetch
|
|
1391
|
+
*/
|
|
1392
|
+
export function data<D>(data: D, init?: number | ResponseInit) {
|
|
1393
|
+
return new DataWithResponseInit(
|
|
1394
|
+
data,
|
|
1395
|
+
typeof init === "number" ? { status: init } : init
|
|
1396
|
+
);
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1378
1399
|
export interface TrackedPromise extends Promise<any> {
|
|
1379
1400
|
_tracked?: boolean;
|
|
1380
1401
|
_data?: any;
|
|
@@ -1619,6 +1640,18 @@ export const redirectDocument: RedirectFunction = (url, init) => {
|
|
|
1619
1640
|
return response;
|
|
1620
1641
|
};
|
|
1621
1642
|
|
|
1643
|
+
/**
|
|
1644
|
+
* A redirect response that will perform a `history.replaceState` instead of a
|
|
1645
|
+
* `history.pushState` for client-side navigation redirects.
|
|
1646
|
+
* Sets the status code and the `Location` header.
|
|
1647
|
+
* Defaults to "302 Found".
|
|
1648
|
+
*/
|
|
1649
|
+
export const replace: RedirectFunction = (url, init) => {
|
|
1650
|
+
let response = redirect(url, init);
|
|
1651
|
+
response.headers.set("X-Remix-Replace", "true");
|
|
1652
|
+
return response;
|
|
1653
|
+
};
|
|
1654
|
+
|
|
1622
1655
|
export type ErrorResponse = {
|
|
1623
1656
|
status: number;
|
|
1624
1657
|
statusText: string;
|