@remix-run/router 1.6.2 → 1.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/dist/router.cjs.js +754 -854
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.d.ts +1 -0
- package/dist/router.js +440 -946
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +754 -854
- 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/history.ts +7 -0
- package/package.json +1 -1
- package/router.ts +49 -31
package/dist/router.umd.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @remix-run/router v1.6.
|
|
2
|
+
* @remix-run/router v1.6.3
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Remix Software Inc.
|
|
5
5
|
*
|
|
@@ -18,14 +18,12 @@
|
|
|
18
18
|
_extends = Object.assign ? Object.assign.bind() : function (target) {
|
|
19
19
|
for (var i = 1; i < arguments.length; i++) {
|
|
20
20
|
var source = arguments[i];
|
|
21
|
-
|
|
22
21
|
for (var key in source) {
|
|
23
22
|
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
24
23
|
target[key] = source[key];
|
|
25
24
|
}
|
|
26
25
|
}
|
|
27
26
|
}
|
|
28
|
-
|
|
29
27
|
return target;
|
|
30
28
|
};
|
|
31
29
|
return _extends.apply(this, arguments);
|
|
@@ -39,17 +37,41 @@
|
|
|
39
37
|
* Actions represent the type of change to a location value.
|
|
40
38
|
*/
|
|
41
39
|
exports.Action = void 0;
|
|
40
|
+
|
|
42
41
|
/**
|
|
43
42
|
* The pathname, search, and hash values of a URL.
|
|
44
43
|
*/
|
|
45
|
-
|
|
46
44
|
(function (Action) {
|
|
47
45
|
Action["Pop"] = "POP";
|
|
48
46
|
Action["Push"] = "PUSH";
|
|
49
47
|
Action["Replace"] = "REPLACE";
|
|
50
48
|
})(exports.Action || (exports.Action = {}));
|
|
49
|
+
/**
|
|
50
|
+
* An entry in a history stack. A location contains information about the
|
|
51
|
+
* URL path, as well as possibly some arbitrary state and a key.
|
|
52
|
+
*/
|
|
53
|
+
/**
|
|
54
|
+
* A change to the current location.
|
|
55
|
+
*/
|
|
56
|
+
/**
|
|
57
|
+
* A function that receives notifications about location changes.
|
|
58
|
+
*/
|
|
59
|
+
/**
|
|
60
|
+
* Describes a location that is the destination of some navigation, either via
|
|
61
|
+
* `history.push` or `history.replace`. May be either a URL or the pieces of a
|
|
62
|
+
* URL path.
|
|
63
|
+
*/
|
|
64
|
+
/**
|
|
65
|
+
* A history is an interface to the navigation stack. The history serves as the
|
|
66
|
+
* source of truth for the current location, as well as provides a set of
|
|
67
|
+
* methods that may be used to change it.
|
|
68
|
+
*
|
|
69
|
+
* It is similar to the DOM's `window.history` object, but with a smaller, more
|
|
70
|
+
* focused API.
|
|
71
|
+
*/
|
|
72
|
+
const PopStateEventType = "popstate";
|
|
73
|
+
//#endregion
|
|
51
74
|
|
|
52
|
-
const PopStateEventType = "popstate"; //#endregion
|
|
53
75
|
////////////////////////////////////////////////////////////////////////////////
|
|
54
76
|
//#region Memory History
|
|
55
77
|
////////////////////////////////////////////////////////////////////////////////
|
|
@@ -58,7 +80,11 @@
|
|
|
58
80
|
* A user-supplied object that describes a location. Used when providing
|
|
59
81
|
* entries to `createMemoryHistory` via its `initialEntries` option.
|
|
60
82
|
*/
|
|
61
|
-
|
|
83
|
+
/**
|
|
84
|
+
* A memory history stores locations in memory. This is useful in stateful
|
|
85
|
+
* environments where there is no web browser, such as node tests or React
|
|
86
|
+
* Native.
|
|
87
|
+
*/
|
|
62
88
|
/**
|
|
63
89
|
* Memory history stores the current location in memory. It is designed for use
|
|
64
90
|
* in stateful non-browser environments like tests and React Native.
|
|
@@ -67,60 +93,47 @@
|
|
|
67
93
|
if (options === void 0) {
|
|
68
94
|
options = {};
|
|
69
95
|
}
|
|
70
|
-
|
|
71
96
|
let {
|
|
72
97
|
initialEntries = ["/"],
|
|
73
98
|
initialIndex,
|
|
74
99
|
v5Compat = false
|
|
75
100
|
} = options;
|
|
76
101
|
let entries; // Declare so we can access from createMemoryLocation
|
|
77
|
-
|
|
78
102
|
entries = initialEntries.map((entry, index) => createMemoryLocation(entry, typeof entry === "string" ? null : entry.state, index === 0 ? "default" : undefined));
|
|
79
103
|
let index = clampIndex(initialIndex == null ? entries.length - 1 : initialIndex);
|
|
80
104
|
let action = exports.Action.Pop;
|
|
81
105
|
let listener = null;
|
|
82
|
-
|
|
83
106
|
function clampIndex(n) {
|
|
84
107
|
return Math.min(Math.max(n, 0), entries.length - 1);
|
|
85
108
|
}
|
|
86
|
-
|
|
87
109
|
function getCurrentLocation() {
|
|
88
110
|
return entries[index];
|
|
89
111
|
}
|
|
90
|
-
|
|
91
112
|
function createMemoryLocation(to, state, key) {
|
|
92
113
|
if (state === void 0) {
|
|
93
114
|
state = null;
|
|
94
115
|
}
|
|
95
|
-
|
|
96
116
|
let location = createLocation(entries ? getCurrentLocation().pathname : "/", to, state, key);
|
|
97
117
|
warning(location.pathname.charAt(0) === "/", "relative pathnames are not supported in memory history: " + JSON.stringify(to));
|
|
98
118
|
return location;
|
|
99
119
|
}
|
|
100
|
-
|
|
101
120
|
function createHref(to) {
|
|
102
121
|
return typeof to === "string" ? to : createPath(to);
|
|
103
122
|
}
|
|
104
|
-
|
|
105
123
|
let history = {
|
|
106
124
|
get index() {
|
|
107
125
|
return index;
|
|
108
126
|
},
|
|
109
|
-
|
|
110
127
|
get action() {
|
|
111
128
|
return action;
|
|
112
129
|
},
|
|
113
|
-
|
|
114
130
|
get location() {
|
|
115
131
|
return getCurrentLocation();
|
|
116
132
|
},
|
|
117
|
-
|
|
118
133
|
createHref,
|
|
119
|
-
|
|
120
134
|
createURL(to) {
|
|
121
135
|
return new URL(createHref(to), "http://localhost");
|
|
122
136
|
},
|
|
123
|
-
|
|
124
137
|
encodeLocation(to) {
|
|
125
138
|
let path = typeof to === "string" ? parsePath(to) : to;
|
|
126
139
|
return {
|
|
@@ -129,13 +142,11 @@
|
|
|
129
142
|
hash: path.hash || ""
|
|
130
143
|
};
|
|
131
144
|
},
|
|
132
|
-
|
|
133
145
|
push(to, state) {
|
|
134
146
|
action = exports.Action.Push;
|
|
135
147
|
let nextLocation = createMemoryLocation(to, state);
|
|
136
148
|
index += 1;
|
|
137
149
|
entries.splice(index, entries.length, nextLocation);
|
|
138
|
-
|
|
139
150
|
if (v5Compat && listener) {
|
|
140
151
|
listener({
|
|
141
152
|
action,
|
|
@@ -144,12 +155,10 @@
|
|
|
144
155
|
});
|
|
145
156
|
}
|
|
146
157
|
},
|
|
147
|
-
|
|
148
158
|
replace(to, state) {
|
|
149
159
|
action = exports.Action.Replace;
|
|
150
160
|
let nextLocation = createMemoryLocation(to, state);
|
|
151
161
|
entries[index] = nextLocation;
|
|
152
|
-
|
|
153
162
|
if (v5Compat && listener) {
|
|
154
163
|
listener({
|
|
155
164
|
action,
|
|
@@ -158,13 +167,11 @@
|
|
|
158
167
|
});
|
|
159
168
|
}
|
|
160
169
|
},
|
|
161
|
-
|
|
162
170
|
go(delta) {
|
|
163
171
|
action = exports.Action.Pop;
|
|
164
172
|
let nextIndex = clampIndex(index + delta);
|
|
165
173
|
let nextLocation = entries[nextIndex];
|
|
166
174
|
index = nextIndex;
|
|
167
|
-
|
|
168
175
|
if (listener) {
|
|
169
176
|
listener({
|
|
170
177
|
action,
|
|
@@ -173,17 +180,17 @@
|
|
|
173
180
|
});
|
|
174
181
|
}
|
|
175
182
|
},
|
|
176
|
-
|
|
177
183
|
listen(fn) {
|
|
178
184
|
listener = fn;
|
|
179
185
|
return () => {
|
|
180
186
|
listener = null;
|
|
181
187
|
};
|
|
182
188
|
}
|
|
183
|
-
|
|
184
189
|
};
|
|
185
190
|
return history;
|
|
186
|
-
}
|
|
191
|
+
}
|
|
192
|
+
//#endregion
|
|
193
|
+
|
|
187
194
|
////////////////////////////////////////////////////////////////////////////////
|
|
188
195
|
//#region Browser History
|
|
189
196
|
////////////////////////////////////////////////////////////////////////////////
|
|
@@ -195,7 +202,6 @@
|
|
|
195
202
|
*
|
|
196
203
|
* @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#browserhistory
|
|
197
204
|
*/
|
|
198
|
-
|
|
199
205
|
/**
|
|
200
206
|
* Browser history stores the location in regular URLs. This is the standard for
|
|
201
207
|
* most web apps, but it requires some configuration on the server to ensure you
|
|
@@ -207,7 +213,6 @@
|
|
|
207
213
|
if (options === void 0) {
|
|
208
214
|
options = {};
|
|
209
215
|
}
|
|
210
|
-
|
|
211
216
|
function createBrowserLocation(window, globalHistory) {
|
|
212
217
|
let {
|
|
213
218
|
pathname,
|
|
@@ -218,16 +223,17 @@
|
|
|
218
223
|
pathname,
|
|
219
224
|
search,
|
|
220
225
|
hash
|
|
221
|
-
},
|
|
226
|
+
},
|
|
227
|
+
// state defaults to `null` because `window.history.state` does
|
|
222
228
|
globalHistory.state && globalHistory.state.usr || null, globalHistory.state && globalHistory.state.key || "default");
|
|
223
229
|
}
|
|
224
|
-
|
|
225
230
|
function createBrowserHref(window, to) {
|
|
226
231
|
return typeof to === "string" ? to : createPath(to);
|
|
227
232
|
}
|
|
228
|
-
|
|
229
233
|
return getUrlBasedHistory(createBrowserLocation, createBrowserHref, null, options);
|
|
230
|
-
}
|
|
234
|
+
}
|
|
235
|
+
//#endregion
|
|
236
|
+
|
|
231
237
|
////////////////////////////////////////////////////////////////////////////////
|
|
232
238
|
//#region Hash History
|
|
233
239
|
////////////////////////////////////////////////////////////////////////////////
|
|
@@ -243,7 +249,6 @@
|
|
|
243
249
|
*
|
|
244
250
|
* @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#hashhistory
|
|
245
251
|
*/
|
|
246
|
-
|
|
247
252
|
/**
|
|
248
253
|
* Hash history stores the location in window.location.hash. This makes it ideal
|
|
249
254
|
* for situations where you don't want to send the location to the server for
|
|
@@ -256,7 +261,6 @@
|
|
|
256
261
|
if (options === void 0) {
|
|
257
262
|
options = {};
|
|
258
263
|
}
|
|
259
|
-
|
|
260
264
|
function createHashLocation(window, globalHistory) {
|
|
261
265
|
let {
|
|
262
266
|
pathname = "/",
|
|
@@ -267,29 +271,27 @@
|
|
|
267
271
|
pathname,
|
|
268
272
|
search,
|
|
269
273
|
hash
|
|
270
|
-
},
|
|
274
|
+
},
|
|
275
|
+
// state defaults to `null` because `window.history.state` does
|
|
271
276
|
globalHistory.state && globalHistory.state.usr || null, globalHistory.state && globalHistory.state.key || "default");
|
|
272
277
|
}
|
|
273
|
-
|
|
274
278
|
function createHashHref(window, to) {
|
|
275
279
|
let base = window.document.querySelector("base");
|
|
276
280
|
let href = "";
|
|
277
|
-
|
|
278
281
|
if (base && base.getAttribute("href")) {
|
|
279
282
|
let url = window.location.href;
|
|
280
283
|
let hashIndex = url.indexOf("#");
|
|
281
284
|
href = hashIndex === -1 ? url : url.slice(0, hashIndex);
|
|
282
285
|
}
|
|
283
|
-
|
|
284
286
|
return href + "#" + (typeof to === "string" ? to : createPath(to));
|
|
285
287
|
}
|
|
286
|
-
|
|
287
288
|
function validateHashLocation(location, to) {
|
|
288
289
|
warning(location.pathname.charAt(0) === "/", "relative pathnames are not supported in hash history.push(" + JSON.stringify(to) + ")");
|
|
289
290
|
}
|
|
290
|
-
|
|
291
291
|
return getUrlBasedHistory(createHashLocation, createHashHref, validateHashLocation, options);
|
|
292
|
-
}
|
|
292
|
+
}
|
|
293
|
+
//#endregion
|
|
294
|
+
|
|
293
295
|
////////////////////////////////////////////////////////////////////////////////
|
|
294
296
|
//#region UTILS
|
|
295
297
|
////////////////////////////////////////////////////////////////////////////////
|
|
@@ -297,7 +299,6 @@
|
|
|
297
299
|
/**
|
|
298
300
|
* @private
|
|
299
301
|
*/
|
|
300
|
-
|
|
301
302
|
function invariant(value, message) {
|
|
302
303
|
if (value === false || value === null || typeof value === "undefined") {
|
|
303
304
|
throw new Error(message);
|
|
@@ -307,26 +308,24 @@
|
|
|
307
308
|
if (!cond) {
|
|
308
309
|
// eslint-disable-next-line no-console
|
|
309
310
|
if (typeof console !== "undefined") console.warn(message);
|
|
310
|
-
|
|
311
311
|
try {
|
|
312
312
|
// Welcome to debugging history!
|
|
313
313
|
//
|
|
314
314
|
// This error is thrown as a convenience so you can more easily
|
|
315
315
|
// find the source for a warning that appears in the console by
|
|
316
316
|
// enabling "pause on exceptions" in your JavaScript debugger.
|
|
317
|
-
throw new Error(message);
|
|
317
|
+
throw new Error(message);
|
|
318
|
+
// eslint-disable-next-line no-empty
|
|
318
319
|
} catch (e) {}
|
|
319
320
|
}
|
|
320
321
|
}
|
|
321
|
-
|
|
322
322
|
function createKey() {
|
|
323
323
|
return Math.random().toString(36).substr(2, 8);
|
|
324
324
|
}
|
|
325
|
+
|
|
325
326
|
/**
|
|
326
327
|
* For browser-based histories, we combine the state and key into an object
|
|
327
328
|
*/
|
|
328
|
-
|
|
329
|
-
|
|
330
329
|
function getHistoryState(location, index) {
|
|
331
330
|
return {
|
|
332
331
|
usr: location.state,
|
|
@@ -334,16 +333,14 @@
|
|
|
334
333
|
idx: index
|
|
335
334
|
};
|
|
336
335
|
}
|
|
336
|
+
|
|
337
337
|
/**
|
|
338
338
|
* Creates a Location object with a unique key from the given Path
|
|
339
339
|
*/
|
|
340
|
-
|
|
341
|
-
|
|
342
340
|
function createLocation(current, to, state, key) {
|
|
343
341
|
if (state === void 0) {
|
|
344
342
|
state = null;
|
|
345
343
|
}
|
|
346
|
-
|
|
347
344
|
let location = _extends({
|
|
348
345
|
pathname: typeof current === "string" ? current : current.pathname,
|
|
349
346
|
search: "",
|
|
@@ -356,13 +353,12 @@
|
|
|
356
353
|
// keep as is for the time being and just let any incoming keys take precedence
|
|
357
354
|
key: to && to.key || key || createKey()
|
|
358
355
|
});
|
|
359
|
-
|
|
360
356
|
return location;
|
|
361
357
|
}
|
|
358
|
+
|
|
362
359
|
/**
|
|
363
360
|
* Creates a string URL path from the given pathname, search, and hash components.
|
|
364
361
|
*/
|
|
365
|
-
|
|
366
362
|
function createPath(_ref) {
|
|
367
363
|
let {
|
|
368
364
|
pathname = "/",
|
|
@@ -373,41 +369,33 @@
|
|
|
373
369
|
if (hash && hash !== "#") pathname += hash.charAt(0) === "#" ? hash : "#" + hash;
|
|
374
370
|
return pathname;
|
|
375
371
|
}
|
|
372
|
+
|
|
376
373
|
/**
|
|
377
374
|
* Parses a string URL path into its separate pathname, search, and hash components.
|
|
378
375
|
*/
|
|
379
|
-
|
|
380
376
|
function parsePath(path) {
|
|
381
377
|
let parsedPath = {};
|
|
382
|
-
|
|
383
378
|
if (path) {
|
|
384
379
|
let hashIndex = path.indexOf("#");
|
|
385
|
-
|
|
386
380
|
if (hashIndex >= 0) {
|
|
387
381
|
parsedPath.hash = path.substr(hashIndex);
|
|
388
382
|
path = path.substr(0, hashIndex);
|
|
389
383
|
}
|
|
390
|
-
|
|
391
384
|
let searchIndex = path.indexOf("?");
|
|
392
|
-
|
|
393
385
|
if (searchIndex >= 0) {
|
|
394
386
|
parsedPath.search = path.substr(searchIndex);
|
|
395
387
|
path = path.substr(0, searchIndex);
|
|
396
388
|
}
|
|
397
|
-
|
|
398
389
|
if (path) {
|
|
399
390
|
parsedPath.pathname = path;
|
|
400
391
|
}
|
|
401
392
|
}
|
|
402
|
-
|
|
403
393
|
return parsedPath;
|
|
404
394
|
}
|
|
405
|
-
|
|
406
395
|
function getUrlBasedHistory(getLocation, createHref, validateLocation, options) {
|
|
407
396
|
if (options === void 0) {
|
|
408
397
|
options = {};
|
|
409
398
|
}
|
|
410
|
-
|
|
411
399
|
let {
|
|
412
400
|
window = document.defaultView,
|
|
413
401
|
v5Compat = false
|
|
@@ -415,30 +403,27 @@
|
|
|
415
403
|
let globalHistory = window.history;
|
|
416
404
|
let action = exports.Action.Pop;
|
|
417
405
|
let listener = null;
|
|
418
|
-
let index = getIndex();
|
|
406
|
+
let index = getIndex();
|
|
407
|
+
// Index should only be null when we initialize. If not, it's because the
|
|
419
408
|
// user called history.pushState or history.replaceState directly, in which
|
|
420
409
|
// case we should log a warning as it will result in bugs.
|
|
421
|
-
|
|
422
410
|
if (index == null) {
|
|
423
411
|
index = 0;
|
|
424
412
|
globalHistory.replaceState(_extends({}, globalHistory.state, {
|
|
425
413
|
idx: index
|
|
426
414
|
}), "");
|
|
427
415
|
}
|
|
428
|
-
|
|
429
416
|
function getIndex() {
|
|
430
417
|
let state = globalHistory.state || {
|
|
431
418
|
idx: null
|
|
432
419
|
};
|
|
433
420
|
return state.idx;
|
|
434
421
|
}
|
|
435
|
-
|
|
436
422
|
function handlePop() {
|
|
437
423
|
action = exports.Action.Pop;
|
|
438
424
|
let nextIndex = getIndex();
|
|
439
425
|
let delta = nextIndex == null ? null : nextIndex - index;
|
|
440
426
|
index = nextIndex;
|
|
441
|
-
|
|
442
427
|
if (listener) {
|
|
443
428
|
listener({
|
|
444
429
|
action,
|
|
@@ -447,23 +432,29 @@
|
|
|
447
432
|
});
|
|
448
433
|
}
|
|
449
434
|
}
|
|
450
|
-
|
|
451
435
|
function push(to, state) {
|
|
452
436
|
action = exports.Action.Push;
|
|
453
437
|
let location = createLocation(history.location, to, state);
|
|
454
438
|
if (validateLocation) validateLocation(location, to);
|
|
455
439
|
index = getIndex() + 1;
|
|
456
440
|
let historyState = getHistoryState(location, index);
|
|
457
|
-
let url = history.createHref(location);
|
|
441
|
+
let url = history.createHref(location);
|
|
458
442
|
|
|
443
|
+
// try...catch because iOS limits us to 100 pushState calls :/
|
|
459
444
|
try {
|
|
460
445
|
globalHistory.pushState(historyState, "", url);
|
|
461
446
|
} catch (error) {
|
|
447
|
+
// If the exception is because `state` can't be serialized, let that throw
|
|
448
|
+
// outwards just like a replace call would so the dev knows the cause
|
|
449
|
+
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#shared-history-push/replace-state-steps
|
|
450
|
+
// https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializeinternal
|
|
451
|
+
if (error instanceof DOMException && error.name === "DataCloneError") {
|
|
452
|
+
throw error;
|
|
453
|
+
}
|
|
462
454
|
// They are going to lose state here, but there is no real
|
|
463
455
|
// way to warn them about it since the page will refresh...
|
|
464
456
|
window.location.assign(url);
|
|
465
457
|
}
|
|
466
|
-
|
|
467
458
|
if (v5Compat && listener) {
|
|
468
459
|
listener({
|
|
469
460
|
action,
|
|
@@ -472,7 +463,6 @@
|
|
|
472
463
|
});
|
|
473
464
|
}
|
|
474
465
|
}
|
|
475
|
-
|
|
476
466
|
function replace(to, state) {
|
|
477
467
|
action = exports.Action.Replace;
|
|
478
468
|
let location = createLocation(history.location, to, state);
|
|
@@ -481,7 +471,6 @@
|
|
|
481
471
|
let historyState = getHistoryState(location, index);
|
|
482
472
|
let url = history.createHref(location);
|
|
483
473
|
globalHistory.replaceState(historyState, "", url);
|
|
484
|
-
|
|
485
474
|
if (v5Compat && listener) {
|
|
486
475
|
listener({
|
|
487
476
|
action,
|
|
@@ -490,7 +479,6 @@
|
|
|
490
479
|
});
|
|
491
480
|
}
|
|
492
481
|
}
|
|
493
|
-
|
|
494
482
|
function createURL(to) {
|
|
495
483
|
// window.location.origin is "null" (the literal string value) in Firefox
|
|
496
484
|
// under certain conditions, notably when serving from a local HTML file
|
|
@@ -500,21 +488,17 @@
|
|
|
500
488
|
invariant(base, "No window.location.(origin|href) available to create URL for href: " + href);
|
|
501
489
|
return new URL(href, base);
|
|
502
490
|
}
|
|
503
|
-
|
|
504
491
|
let history = {
|
|
505
492
|
get action() {
|
|
506
493
|
return action;
|
|
507
494
|
},
|
|
508
|
-
|
|
509
495
|
get location() {
|
|
510
496
|
return getLocation(window, globalHistory);
|
|
511
497
|
},
|
|
512
|
-
|
|
513
498
|
listen(fn) {
|
|
514
499
|
if (listener) {
|
|
515
500
|
throw new Error("A history only accepts one active listener");
|
|
516
501
|
}
|
|
517
|
-
|
|
518
502
|
window.addEventListener(PopStateEventType, handlePop);
|
|
519
503
|
listener = fn;
|
|
520
504
|
return () => {
|
|
@@ -522,13 +506,10 @@
|
|
|
522
506
|
listener = null;
|
|
523
507
|
};
|
|
524
508
|
},
|
|
525
|
-
|
|
526
509
|
createHref(to) {
|
|
527
510
|
return createHref(window, to);
|
|
528
511
|
},
|
|
529
|
-
|
|
530
512
|
createURL,
|
|
531
|
-
|
|
532
513
|
encodeLocation(to) {
|
|
533
514
|
// Encode a Location the same way window.location would
|
|
534
515
|
let url = createURL(to);
|
|
@@ -538,66 +519,176 @@
|
|
|
538
519
|
hash: url.hash
|
|
539
520
|
};
|
|
540
521
|
},
|
|
541
|
-
|
|
542
522
|
push,
|
|
543
523
|
replace,
|
|
544
|
-
|
|
545
524
|
go(n) {
|
|
546
525
|
return globalHistory.go(n);
|
|
547
526
|
}
|
|
548
|
-
|
|
549
527
|
};
|
|
550
528
|
return history;
|
|
551
|
-
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
//#endregion
|
|
552
532
|
|
|
553
533
|
/**
|
|
554
534
|
* Map of routeId -> data returned from a loader/action/error
|
|
555
535
|
*/
|
|
556
536
|
|
|
557
537
|
let ResultType;
|
|
538
|
+
|
|
558
539
|
/**
|
|
559
540
|
* Successful result from a loader or action
|
|
560
541
|
*/
|
|
561
|
-
|
|
562
542
|
(function (ResultType) {
|
|
563
543
|
ResultType["data"] = "data";
|
|
564
544
|
ResultType["deferred"] = "deferred";
|
|
565
545
|
ResultType["redirect"] = "redirect";
|
|
566
546
|
ResultType["error"] = "error";
|
|
567
547
|
})(ResultType || (ResultType = {}));
|
|
568
|
-
|
|
548
|
+
/**
|
|
549
|
+
* Successful defer() result from a loader or action
|
|
550
|
+
*/
|
|
551
|
+
/**
|
|
552
|
+
* Redirect result from a loader or action
|
|
553
|
+
*/
|
|
554
|
+
/**
|
|
555
|
+
* Unsuccessful result from a loader or action
|
|
556
|
+
*/
|
|
557
|
+
/**
|
|
558
|
+
* Result from a loader or action - potentially successful or unsuccessful
|
|
559
|
+
*/
|
|
560
|
+
/**
|
|
561
|
+
* Users can specify either lowercase or uppercase form methods on <Form>,
|
|
562
|
+
* useSubmit(), <fetcher.Form>, etc.
|
|
563
|
+
*/
|
|
564
|
+
/**
|
|
565
|
+
* Active navigation/fetcher form methods are exposed in lowercase on the
|
|
566
|
+
* RouterState
|
|
567
|
+
*/
|
|
568
|
+
/**
|
|
569
|
+
* In v7, active navigation/fetcher form methods are exposed in uppercase on the
|
|
570
|
+
* RouterState. This is to align with the normalization done via fetch().
|
|
571
|
+
*/
|
|
572
|
+
/**
|
|
573
|
+
* @private
|
|
574
|
+
* Internal interface to pass around for action submissions, not intended for
|
|
575
|
+
* external consumption
|
|
576
|
+
*/
|
|
577
|
+
/**
|
|
578
|
+
* @private
|
|
579
|
+
* Arguments passed to route loader/action functions. Same for now but we keep
|
|
580
|
+
* this as a private implementation detail in case they diverge in the future.
|
|
581
|
+
*/
|
|
582
|
+
/**
|
|
583
|
+
* Arguments passed to loader functions
|
|
584
|
+
*/
|
|
585
|
+
/**
|
|
586
|
+
* Arguments passed to action functions
|
|
587
|
+
*/
|
|
588
|
+
/**
|
|
589
|
+
* Loaders and actions can return anything except `undefined` (`null` is a
|
|
590
|
+
* valid return value if there is no data to return). Responses are preferred
|
|
591
|
+
* and will ease any future migration to Remix
|
|
592
|
+
*/
|
|
593
|
+
/**
|
|
594
|
+
* Route loader function signature
|
|
595
|
+
*/
|
|
596
|
+
/**
|
|
597
|
+
* Route action function signature
|
|
598
|
+
*/
|
|
599
|
+
/**
|
|
600
|
+
* Route shouldRevalidate function signature. This runs after any submission
|
|
601
|
+
* (navigation or fetcher), so we flatten the navigation/fetcher submission
|
|
602
|
+
* onto the arguments. It shouldn't matter whether it came from a navigation
|
|
603
|
+
* or a fetcher, what really matters is the URLs and the formData since loaders
|
|
604
|
+
* have to re-run based on the data models that were potentially mutated.
|
|
605
|
+
*/
|
|
606
|
+
/**
|
|
607
|
+
* Function provided by the framework-aware layers to set `hasErrorBoundary`
|
|
608
|
+
* from the framework-aware `errorElement` prop
|
|
609
|
+
*
|
|
610
|
+
* @deprecated Use `mapRouteProperties` instead
|
|
611
|
+
*/
|
|
612
|
+
/**
|
|
613
|
+
* Function provided by the framework-aware layers to set any framework-specific
|
|
614
|
+
* properties from framework-agnostic properties
|
|
615
|
+
*/
|
|
616
|
+
/**
|
|
617
|
+
* Keys we cannot change from within a lazy() function. We spread all other keys
|
|
618
|
+
* onto the route. Either they're meaningful to the router, or they'll get
|
|
619
|
+
* ignored.
|
|
620
|
+
*/
|
|
569
621
|
const immutableRouteKeys = new Set(["lazy", "caseSensitive", "path", "id", "index", "children"]);
|
|
622
|
+
|
|
570
623
|
/**
|
|
571
624
|
* lazy() function to load a route definition, which can add non-matching
|
|
572
625
|
* related properties to a route
|
|
573
626
|
*/
|
|
574
627
|
|
|
628
|
+
/**
|
|
629
|
+
* Base RouteObject with common props shared by all types of routes
|
|
630
|
+
*/
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Index routes must not have children
|
|
634
|
+
*/
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Non-index routes may have children, but cannot have index
|
|
638
|
+
*/
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* A route object represents a logical route, with (optionally) its child
|
|
642
|
+
* routes organized in a tree-like structure.
|
|
643
|
+
*/
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* A data route object, which is just a RouteObject with a required unique ID
|
|
647
|
+
*/
|
|
648
|
+
|
|
649
|
+
// Recursive helper for finding path parameters in the absence of wildcards
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Examples:
|
|
653
|
+
* "/a/b/*" -> "*"
|
|
654
|
+
* ":a" -> "a"
|
|
655
|
+
* "/a/:b" -> "b"
|
|
656
|
+
* "/a/blahblahblah:b" -> "b"
|
|
657
|
+
* "/:a/:b" -> "a" | "b"
|
|
658
|
+
* "/:a/b/:c/*" -> "a" | "c" | "*"
|
|
659
|
+
*/
|
|
660
|
+
|
|
661
|
+
// Attempt to parse the given string segment. If it fails, then just return the
|
|
662
|
+
// plain string type as a default fallback. Otherwise return the union of the
|
|
663
|
+
// parsed string literals that were referenced as dynamic segments in the route.
|
|
664
|
+
/**
|
|
665
|
+
* The parameters that were parsed from the URL path.
|
|
666
|
+
*/
|
|
667
|
+
/**
|
|
668
|
+
* A RouteMatch contains info about how a route matched a URL.
|
|
669
|
+
*/
|
|
575
670
|
function isIndexRoute(route) {
|
|
576
671
|
return route.index === true;
|
|
577
|
-
}
|
|
578
|
-
// solely with AgnosticDataRouteObject's within the Router
|
|
579
|
-
|
|
672
|
+
}
|
|
580
673
|
|
|
674
|
+
// Walk the route tree generating unique IDs where necessary so we are working
|
|
675
|
+
// solely with AgnosticDataRouteObject's within the Router
|
|
581
676
|
function convertRoutesToDataRoutes(routes, mapRouteProperties, parentPath, manifest) {
|
|
582
677
|
if (parentPath === void 0) {
|
|
583
678
|
parentPath = [];
|
|
584
679
|
}
|
|
585
|
-
|
|
586
680
|
if (manifest === void 0) {
|
|
587
681
|
manifest = {};
|
|
588
682
|
}
|
|
589
|
-
|
|
590
683
|
return routes.map((route, index) => {
|
|
591
684
|
let treePath = [...parentPath, index];
|
|
592
685
|
let id = typeof route.id === "string" ? route.id : treePath.join("-");
|
|
593
686
|
invariant(route.index !== true || !route.children, "Cannot specify children on an index route");
|
|
594
687
|
invariant(!manifest[id], "Found a route id collision on id \"" + id + "\". Route " + "id's must be globally unique within Data Router usages");
|
|
595
|
-
|
|
596
688
|
if (isIndexRoute(route)) {
|
|
597
689
|
let indexRoute = _extends({}, route, mapRouteProperties(route), {
|
|
598
690
|
id
|
|
599
691
|
});
|
|
600
|
-
|
|
601
692
|
manifest[id] = indexRoute;
|
|
602
693
|
return indexRoute;
|
|
603
694
|
} else {
|
|
@@ -605,41 +696,35 @@
|
|
|
605
696
|
id,
|
|
606
697
|
children: undefined
|
|
607
698
|
});
|
|
608
|
-
|
|
609
699
|
manifest[id] = pathOrLayoutRoute;
|
|
610
|
-
|
|
611
700
|
if (route.children) {
|
|
612
701
|
pathOrLayoutRoute.children = convertRoutesToDataRoutes(route.children, mapRouteProperties, treePath, manifest);
|
|
613
702
|
}
|
|
614
|
-
|
|
615
703
|
return pathOrLayoutRoute;
|
|
616
704
|
}
|
|
617
705
|
});
|
|
618
706
|
}
|
|
707
|
+
|
|
619
708
|
/**
|
|
620
709
|
* Matches the given routes to a location and returns the match data.
|
|
621
710
|
*
|
|
622
711
|
* @see https://reactrouter.com/utils/match-routes
|
|
623
712
|
*/
|
|
624
|
-
|
|
625
713
|
function matchRoutes(routes, locationArg, basename) {
|
|
626
714
|
if (basename === void 0) {
|
|
627
715
|
basename = "/";
|
|
628
716
|
}
|
|
629
|
-
|
|
630
717
|
let location = typeof locationArg === "string" ? parsePath(locationArg) : locationArg;
|
|
631
718
|
let pathname = stripBasename(location.pathname || "/", basename);
|
|
632
|
-
|
|
633
719
|
if (pathname == null) {
|
|
634
720
|
return null;
|
|
635
721
|
}
|
|
636
|
-
|
|
637
722
|
let branches = flattenRoutes(routes);
|
|
638
723
|
rankRouteBranches(branches);
|
|
639
724
|
let matches = null;
|
|
640
|
-
|
|
641
725
|
for (let i = 0; matches == null && i < branches.length; ++i) {
|
|
642
|
-
matches = matchRouteBranch(branches[i],
|
|
726
|
+
matches = matchRouteBranch(branches[i],
|
|
727
|
+
// Incoming pathnames are generally encoded from either window.location
|
|
643
728
|
// or from router.navigate, but we want to match against the unencoded
|
|
644
729
|
// paths in the route definitions. Memory router locations won't be
|
|
645
730
|
// encoded here but there also shouldn't be anything to decode so this
|
|
@@ -647,23 +732,18 @@
|
|
|
647
732
|
// history-aware.
|
|
648
733
|
safelyDecodeURI(pathname));
|
|
649
734
|
}
|
|
650
|
-
|
|
651
735
|
return matches;
|
|
652
736
|
}
|
|
653
|
-
|
|
654
737
|
function flattenRoutes(routes, branches, parentsMeta, parentPath) {
|
|
655
738
|
if (branches === void 0) {
|
|
656
739
|
branches = [];
|
|
657
740
|
}
|
|
658
|
-
|
|
659
741
|
if (parentsMeta === void 0) {
|
|
660
742
|
parentsMeta = [];
|
|
661
743
|
}
|
|
662
|
-
|
|
663
744
|
if (parentPath === void 0) {
|
|
664
745
|
parentPath = "";
|
|
665
746
|
}
|
|
666
|
-
|
|
667
747
|
let flattenRoute = (route, index, relativePath) => {
|
|
668
748
|
let meta = {
|
|
669
749
|
relativePath: relativePath === undefined ? route.path || "" : relativePath,
|
|
@@ -671,40 +751,37 @@
|
|
|
671
751
|
childrenIndex: index,
|
|
672
752
|
route
|
|
673
753
|
};
|
|
674
|
-
|
|
675
754
|
if (meta.relativePath.startsWith("/")) {
|
|
676
755
|
invariant(meta.relativePath.startsWith(parentPath), "Absolute route path \"" + meta.relativePath + "\" nested under path " + ("\"" + parentPath + "\" is not valid. An absolute child route path ") + "must start with the combined path of all its parent routes.");
|
|
677
756
|
meta.relativePath = meta.relativePath.slice(parentPath.length);
|
|
678
757
|
}
|
|
679
|
-
|
|
680
758
|
let path = joinPaths([parentPath, meta.relativePath]);
|
|
681
|
-
let routesMeta = parentsMeta.concat(meta);
|
|
759
|
+
let routesMeta = parentsMeta.concat(meta);
|
|
760
|
+
|
|
761
|
+
// Add the children before adding this route to the array so we traverse the
|
|
682
762
|
// route tree depth-first and child routes appear before their parents in
|
|
683
763
|
// the "flattened" version.
|
|
684
|
-
|
|
685
764
|
if (route.children && route.children.length > 0) {
|
|
686
|
-
invariant(
|
|
765
|
+
invariant(
|
|
766
|
+
// Our types know better, but runtime JS may not!
|
|
687
767
|
// @ts-expect-error
|
|
688
768
|
route.index !== true, "Index routes must not have child routes. Please remove " + ("all child routes from route path \"" + path + "\"."));
|
|
689
769
|
flattenRoutes(route.children, branches, routesMeta, path);
|
|
690
|
-
}
|
|
691
|
-
// index routes, so don't add them to the list of possible branches.
|
|
692
|
-
|
|
770
|
+
}
|
|
693
771
|
|
|
772
|
+
// Routes without a path shouldn't ever match by themselves unless they are
|
|
773
|
+
// index routes, so don't add them to the list of possible branches.
|
|
694
774
|
if (route.path == null && !route.index) {
|
|
695
775
|
return;
|
|
696
776
|
}
|
|
697
|
-
|
|
698
777
|
branches.push({
|
|
699
778
|
path,
|
|
700
779
|
score: computeScore(path, route.index),
|
|
701
780
|
routesMeta
|
|
702
781
|
});
|
|
703
782
|
};
|
|
704
|
-
|
|
705
783
|
routes.forEach((route, index) => {
|
|
706
784
|
var _route$path;
|
|
707
|
-
|
|
708
785
|
// coarse-grain check for optional params
|
|
709
786
|
if (route.path === "" || !((_route$path = route.path) != null && _route$path.includes("?"))) {
|
|
710
787
|
flattenRoute(route, index);
|
|
@@ -716,6 +793,7 @@
|
|
|
716
793
|
});
|
|
717
794
|
return branches;
|
|
718
795
|
}
|
|
796
|
+
|
|
719
797
|
/**
|
|
720
798
|
* Computes all combinations of optional path segments for a given path,
|
|
721
799
|
* excluding combinations that are ambiguous and of lower priority.
|
|
@@ -730,82 +808,74 @@
|
|
|
730
808
|
* - `/one/three/:four/:five`
|
|
731
809
|
* - `/one/:two/three/:four/:five`
|
|
732
810
|
*/
|
|
733
|
-
|
|
734
|
-
|
|
735
811
|
function explodeOptionalSegments(path) {
|
|
736
812
|
let segments = path.split("/");
|
|
737
813
|
if (segments.length === 0) return [];
|
|
738
|
-
let [first, ...rest] = segments;
|
|
739
|
-
|
|
740
|
-
let isOptional = first.endsWith("?"); // Compute the corresponding required segment: `foo?` -> `foo`
|
|
814
|
+
let [first, ...rest] = segments;
|
|
741
815
|
|
|
816
|
+
// Optional path segments are denoted by a trailing `?`
|
|
817
|
+
let isOptional = first.endsWith("?");
|
|
818
|
+
// Compute the corresponding required segment: `foo?` -> `foo`
|
|
742
819
|
let required = first.replace(/\?$/, "");
|
|
743
|
-
|
|
744
820
|
if (rest.length === 0) {
|
|
745
821
|
// Intepret empty string as omitting an optional segment
|
|
746
822
|
// `["one", "", "three"]` corresponds to omitting `:two` from `/one/:two?/three` -> `/one/three`
|
|
747
823
|
return isOptional ? [required, ""] : [required];
|
|
748
824
|
}
|
|
749
|
-
|
|
750
825
|
let restExploded = explodeOptionalSegments(rest.join("/"));
|
|
751
|
-
let result = [];
|
|
826
|
+
let result = [];
|
|
827
|
+
|
|
828
|
+
// All child paths with the prefix. Do this for all children before the
|
|
752
829
|
// optional version for all children so we get consistent ordering where the
|
|
753
830
|
// parent optional aspect is preferred as required. Otherwise, we can get
|
|
754
831
|
// child sections interspersed where deeper optional segments are higher than
|
|
755
832
|
// parent optional segments, where for example, /:two would explodes _earlier_
|
|
756
833
|
// then /:one. By always including the parent as required _for all children_
|
|
757
834
|
// first, we avoid this issue
|
|
835
|
+
result.push(...restExploded.map(subpath => subpath === "" ? required : [required, subpath].join("/")));
|
|
758
836
|
|
|
759
|
-
|
|
760
|
-
|
|
837
|
+
// Then if this is an optional value, add all child versions without
|
|
761
838
|
if (isOptional) {
|
|
762
839
|
result.push(...restExploded);
|
|
763
|
-
}
|
|
764
|
-
|
|
840
|
+
}
|
|
765
841
|
|
|
842
|
+
// for absolute paths, ensure `/` instead of empty segment
|
|
766
843
|
return result.map(exploded => path.startsWith("/") && exploded === "" ? "/" : exploded);
|
|
767
844
|
}
|
|
768
|
-
|
|
769
845
|
function rankRouteBranches(branches) {
|
|
770
846
|
branches.sort((a, b) => a.score !== b.score ? b.score - a.score // Higher score first
|
|
771
847
|
: compareIndexes(a.routesMeta.map(meta => meta.childrenIndex), b.routesMeta.map(meta => meta.childrenIndex)));
|
|
772
848
|
}
|
|
773
|
-
|
|
774
849
|
const paramRe = /^:\w+$/;
|
|
775
850
|
const dynamicSegmentValue = 3;
|
|
776
851
|
const indexRouteValue = 2;
|
|
777
852
|
const emptySegmentValue = 1;
|
|
778
853
|
const staticSegmentValue = 10;
|
|
779
854
|
const splatPenalty = -2;
|
|
780
|
-
|
|
781
855
|
const isSplat = s => s === "*";
|
|
782
|
-
|
|
783
856
|
function computeScore(path, index) {
|
|
784
857
|
let segments = path.split("/");
|
|
785
858
|
let initialScore = segments.length;
|
|
786
|
-
|
|
787
859
|
if (segments.some(isSplat)) {
|
|
788
860
|
initialScore += splatPenalty;
|
|
789
861
|
}
|
|
790
|
-
|
|
791
862
|
if (index) {
|
|
792
863
|
initialScore += indexRouteValue;
|
|
793
864
|
}
|
|
794
|
-
|
|
795
865
|
return segments.filter(s => !isSplat(s)).reduce((score, segment) => score + (paramRe.test(segment) ? dynamicSegmentValue : segment === "" ? emptySegmentValue : staticSegmentValue), initialScore);
|
|
796
866
|
}
|
|
797
|
-
|
|
798
867
|
function compareIndexes(a, b) {
|
|
799
868
|
let siblings = a.length === b.length && a.slice(0, -1).every((n, i) => n === b[i]);
|
|
800
|
-
return siblings ?
|
|
869
|
+
return siblings ?
|
|
870
|
+
// If two routes are siblings, we should try to match the earlier sibling
|
|
801
871
|
// first. This allows people to have fine-grained control over the matching
|
|
802
872
|
// behavior by simply putting routes with identical paths in the order they
|
|
803
873
|
// want them tried.
|
|
804
|
-
a[a.length - 1] - b[b.length - 1] :
|
|
874
|
+
a[a.length - 1] - b[b.length - 1] :
|
|
875
|
+
// Otherwise, it doesn't really make sense to rank non-siblings by index,
|
|
805
876
|
// so they sort equally.
|
|
806
877
|
0;
|
|
807
878
|
}
|
|
808
|
-
|
|
809
879
|
function matchRouteBranch(branch, pathname) {
|
|
810
880
|
let {
|
|
811
881
|
routesMeta
|
|
@@ -813,7 +883,6 @@
|
|
|
813
883
|
let matchedParams = {};
|
|
814
884
|
let matchedPathname = "/";
|
|
815
885
|
let matches = [];
|
|
816
|
-
|
|
817
886
|
for (let i = 0; i < routesMeta.length; ++i) {
|
|
818
887
|
let meta = routesMeta[i];
|
|
819
888
|
let end = i === routesMeta.length - 1;
|
|
@@ -833,72 +902,70 @@
|
|
|
833
902
|
pathnameBase: normalizePathname(joinPaths([matchedPathname, match.pathnameBase])),
|
|
834
903
|
route
|
|
835
904
|
});
|
|
836
|
-
|
|
837
905
|
if (match.pathnameBase !== "/") {
|
|
838
906
|
matchedPathname = joinPaths([matchedPathname, match.pathnameBase]);
|
|
839
907
|
}
|
|
840
908
|
}
|
|
841
|
-
|
|
842
909
|
return matches;
|
|
843
910
|
}
|
|
911
|
+
|
|
844
912
|
/**
|
|
845
913
|
* Returns a path with params interpolated.
|
|
846
914
|
*
|
|
847
915
|
* @see https://reactrouter.com/utils/generate-path
|
|
848
916
|
*/
|
|
849
|
-
|
|
850
|
-
|
|
851
917
|
function generatePath(originalPath, params) {
|
|
852
918
|
if (params === void 0) {
|
|
853
919
|
params = {};
|
|
854
920
|
}
|
|
855
|
-
|
|
856
921
|
let path = originalPath;
|
|
857
|
-
|
|
858
922
|
if (path.endsWith("*") && path !== "*" && !path.endsWith("/*")) {
|
|
859
923
|
warning(false, "Route path \"" + path + "\" will be treated as if it were " + ("\"" + path.replace(/\*$/, "/*") + "\" because the `*` character must ") + "always follow a `/` in the pattern. To get rid of this warning, " + ("please change the route path to \"" + path.replace(/\*$/, "/*") + "\"."));
|
|
860
924
|
path = path.replace(/\*$/, "/*");
|
|
861
|
-
}
|
|
862
|
-
|
|
925
|
+
}
|
|
863
926
|
|
|
927
|
+
// ensure `/` is added at the beginning if the path is absolute
|
|
864
928
|
const prefix = path.startsWith("/") ? "/" : "";
|
|
865
929
|
const segments = path.split(/\/+/).map((segment, index, array) => {
|
|
866
|
-
const isLastSegment = index === array.length - 1;
|
|
930
|
+
const isLastSegment = index === array.length - 1;
|
|
867
931
|
|
|
932
|
+
// only apply the splat if it's the last segment
|
|
868
933
|
if (isLastSegment && segment === "*") {
|
|
869
934
|
const star = "*";
|
|
870
|
-
const starParam = params[star];
|
|
935
|
+
const starParam = params[star];
|
|
871
936
|
|
|
937
|
+
// Apply the splat
|
|
872
938
|
return starParam;
|
|
873
939
|
}
|
|
874
|
-
|
|
875
940
|
const keyMatch = segment.match(/^:(\w+)(\??)$/);
|
|
876
|
-
|
|
877
941
|
if (keyMatch) {
|
|
878
942
|
const [, key, optional] = keyMatch;
|
|
879
943
|
let param = params[key];
|
|
880
|
-
|
|
881
944
|
if (optional === "?") {
|
|
882
945
|
return param == null ? "" : param;
|
|
883
946
|
}
|
|
884
|
-
|
|
885
947
|
if (param == null) {
|
|
886
948
|
invariant(false, "Missing \":" + key + "\" param");
|
|
887
949
|
}
|
|
888
|
-
|
|
889
950
|
return param;
|
|
890
|
-
}
|
|
891
|
-
|
|
951
|
+
}
|
|
892
952
|
|
|
953
|
+
// Remove any optional markers from optional static segments
|
|
893
954
|
return segment.replace(/\?$/g, "");
|
|
894
|
-
})
|
|
955
|
+
})
|
|
956
|
+
// Remove empty segments
|
|
895
957
|
.filter(segment => !!segment);
|
|
896
958
|
return prefix + segments.join("/");
|
|
897
959
|
}
|
|
960
|
+
|
|
898
961
|
/**
|
|
899
962
|
* A PathPattern is used to match on some portion of a URL pathname.
|
|
900
963
|
*/
|
|
901
964
|
|
|
965
|
+
/**
|
|
966
|
+
* A PathMatch contains info about how a PathPattern matched on a URL pathname.
|
|
967
|
+
*/
|
|
968
|
+
|
|
902
969
|
/**
|
|
903
970
|
* Performs pattern matching on a URL pathname and returns information about
|
|
904
971
|
* the match.
|
|
@@ -913,7 +980,6 @@
|
|
|
913
980
|
end: true
|
|
914
981
|
};
|
|
915
982
|
}
|
|
916
|
-
|
|
917
983
|
let [matcher, paramNames] = compilePath(pattern.path, pattern.caseSensitive, pattern.end);
|
|
918
984
|
let match = pathname.match(matcher);
|
|
919
985
|
if (!match) return null;
|
|
@@ -927,7 +993,6 @@
|
|
|
927
993
|
let splatValue = captureGroups[index] || "";
|
|
928
994
|
pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+$/, "$1");
|
|
929
995
|
}
|
|
930
|
-
|
|
931
996
|
memo[paramName] = safelyDecodeURIComponent(captureGroups[index] || "", paramName);
|
|
932
997
|
return memo;
|
|
933
998
|
}, {});
|
|
@@ -938,16 +1003,13 @@
|
|
|
938
1003
|
pattern
|
|
939
1004
|
};
|
|
940
1005
|
}
|
|
941
|
-
|
|
942
1006
|
function compilePath(path, caseSensitive, end) {
|
|
943
1007
|
if (caseSensitive === void 0) {
|
|
944
1008
|
caseSensitive = false;
|
|
945
1009
|
}
|
|
946
|
-
|
|
947
1010
|
if (end === void 0) {
|
|
948
1011
|
end = true;
|
|
949
1012
|
}
|
|
950
|
-
|
|
951
1013
|
warning(path === "*" || !path.endsWith("*") || path.endsWith("/*"), "Route path \"" + path + "\" will be treated as if it were " + ("\"" + path.replace(/\*$/, "/*") + "\" because the `*` character must ") + "always follow a `/` in the pattern. To get rid of this warning, " + ("please change the route path to \"" + path.replace(/\*$/, "/*") + "\"."));
|
|
952
1014
|
let paramNames = [];
|
|
953
1015
|
let regexpSource = "^" + path.replace(/\/*\*?$/, "") // Ignore trailing / and /*, we'll handle it below
|
|
@@ -957,7 +1019,6 @@
|
|
|
957
1019
|
paramNames.push(paramName);
|
|
958
1020
|
return "/([^\\/]+)";
|
|
959
1021
|
});
|
|
960
|
-
|
|
961
1022
|
if (path.endsWith("*")) {
|
|
962
1023
|
paramNames.push("*");
|
|
963
1024
|
regexpSource += path === "*" || path === "/*" ? "(.*)$" // Already matched the initial /, just match the rest
|
|
@@ -975,11 +1036,9 @@
|
|
|
975
1036
|
// /user-preferences since `-` counts as a word boundary.
|
|
976
1037
|
regexpSource += "(?:(?=\\/|$))";
|
|
977
1038
|
} else ;
|
|
978
|
-
|
|
979
1039
|
let matcher = new RegExp(regexpSource, caseSensitive ? undefined : "i");
|
|
980
1040
|
return [matcher, paramNames];
|
|
981
1041
|
}
|
|
982
|
-
|
|
983
1042
|
function safelyDecodeURI(value) {
|
|
984
1043
|
try {
|
|
985
1044
|
return decodeURI(value);
|
|
@@ -988,7 +1047,6 @@
|
|
|
988
1047
|
return value;
|
|
989
1048
|
}
|
|
990
1049
|
}
|
|
991
|
-
|
|
992
1050
|
function safelyDecodeURIComponent(value, paramName) {
|
|
993
1051
|
try {
|
|
994
1052
|
return decodeURIComponent(value);
|
|
@@ -997,41 +1055,36 @@
|
|
|
997
1055
|
return value;
|
|
998
1056
|
}
|
|
999
1057
|
}
|
|
1058
|
+
|
|
1000
1059
|
/**
|
|
1001
1060
|
* @private
|
|
1002
1061
|
*/
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
1062
|
function stripBasename(pathname, basename) {
|
|
1006
1063
|
if (basename === "/") return pathname;
|
|
1007
|
-
|
|
1008
1064
|
if (!pathname.toLowerCase().startsWith(basename.toLowerCase())) {
|
|
1009
1065
|
return null;
|
|
1010
|
-
}
|
|
1011
|
-
// specify a basename with a trailing slash, we should support it
|
|
1012
|
-
|
|
1066
|
+
}
|
|
1013
1067
|
|
|
1068
|
+
// We want to leave trailing slash behavior in the user's control, so if they
|
|
1069
|
+
// specify a basename with a trailing slash, we should support it
|
|
1014
1070
|
let startIndex = basename.endsWith("/") ? basename.length - 1 : basename.length;
|
|
1015
1071
|
let nextChar = pathname.charAt(startIndex);
|
|
1016
|
-
|
|
1017
1072
|
if (nextChar && nextChar !== "/") {
|
|
1018
1073
|
// pathname does not start with basename/
|
|
1019
1074
|
return null;
|
|
1020
1075
|
}
|
|
1021
|
-
|
|
1022
1076
|
return pathname.slice(startIndex) || "/";
|
|
1023
1077
|
}
|
|
1078
|
+
|
|
1024
1079
|
/**
|
|
1025
1080
|
* Returns a resolved path object relative to the given pathname.
|
|
1026
1081
|
*
|
|
1027
1082
|
* @see https://reactrouter.com/utils/resolve-path
|
|
1028
1083
|
*/
|
|
1029
|
-
|
|
1030
1084
|
function resolvePath(to, fromPathname) {
|
|
1031
1085
|
if (fromPathname === void 0) {
|
|
1032
1086
|
fromPathname = "/";
|
|
1033
1087
|
}
|
|
1034
|
-
|
|
1035
1088
|
let {
|
|
1036
1089
|
pathname: toPathname,
|
|
1037
1090
|
search = "",
|
|
@@ -1044,7 +1097,6 @@
|
|
|
1044
1097
|
hash: normalizeHash(hash)
|
|
1045
1098
|
};
|
|
1046
1099
|
}
|
|
1047
|
-
|
|
1048
1100
|
function resolvePathname(relativePath, fromPathname) {
|
|
1049
1101
|
let segments = fromPathname.replace(/\/+$/, "").split("/");
|
|
1050
1102
|
let relativeSegments = relativePath.split("/");
|
|
@@ -1058,10 +1110,10 @@
|
|
|
1058
1110
|
});
|
|
1059
1111
|
return segments.length > 1 ? segments.join("/") : "/";
|
|
1060
1112
|
}
|
|
1061
|
-
|
|
1062
1113
|
function getInvalidPathError(char, field, dest, path) {
|
|
1063
1114
|
return "Cannot include a '" + char + "' character in a manually specified " + ("`to." + field + "` field [" + JSON.stringify(path) + "]. Please separate it out to the ") + ("`to." + dest + "` field. Alternatively you may provide the full path as ") + "a string in <Link to=\"...\"> and the router will parse it for you.";
|
|
1064
1115
|
}
|
|
1116
|
+
|
|
1065
1117
|
/**
|
|
1066
1118
|
* @private
|
|
1067
1119
|
*
|
|
@@ -1085,22 +1137,18 @@
|
|
|
1085
1137
|
* </Route>
|
|
1086
1138
|
* </Route>
|
|
1087
1139
|
*/
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
1140
|
function getPathContributingMatches(matches) {
|
|
1091
1141
|
return matches.filter((match, index) => index === 0 || match.route.path && match.route.path.length > 0);
|
|
1092
1142
|
}
|
|
1143
|
+
|
|
1093
1144
|
/**
|
|
1094
1145
|
* @private
|
|
1095
1146
|
*/
|
|
1096
|
-
|
|
1097
1147
|
function resolveTo(toArg, routePathnames, locationPathname, isPathRelative) {
|
|
1098
1148
|
if (isPathRelative === void 0) {
|
|
1099
1149
|
isPathRelative = false;
|
|
1100
1150
|
}
|
|
1101
|
-
|
|
1102
1151
|
let to;
|
|
1103
|
-
|
|
1104
1152
|
if (typeof toArg === "string") {
|
|
1105
1153
|
to = parsePath(toArg);
|
|
1106
1154
|
} else {
|
|
@@ -1109,10 +1157,11 @@
|
|
|
1109
1157
|
invariant(!to.pathname || !to.pathname.includes("#"), getInvalidPathError("#", "pathname", "hash", to));
|
|
1110
1158
|
invariant(!to.search || !to.search.includes("#"), getInvalidPathError("#", "search", "hash", to));
|
|
1111
1159
|
}
|
|
1112
|
-
|
|
1113
1160
|
let isEmptyPath = toArg === "" || to.pathname === "";
|
|
1114
1161
|
let toPathname = isEmptyPath ? "/" : to.pathname;
|
|
1115
|
-
let from;
|
|
1162
|
+
let from;
|
|
1163
|
+
|
|
1164
|
+
// Routing is relative to the current pathname if explicitly requested.
|
|
1116
1165
|
//
|
|
1117
1166
|
// If a pathname is explicitly provided in `to`, it should be relative to the
|
|
1118
1167
|
// route context. This is explained in `Note on `<Link to>` values` in our
|
|
@@ -1121,71 +1170,66 @@
|
|
|
1121
1170
|
// `to` values that do not provide a pathname. `to` can simply be a search or
|
|
1122
1171
|
// hash string, in which case we should assume that the navigation is relative
|
|
1123
1172
|
// to the current location's pathname and *not* the route pathname.
|
|
1124
|
-
|
|
1125
1173
|
if (isPathRelative || toPathname == null) {
|
|
1126
1174
|
from = locationPathname;
|
|
1127
1175
|
} else {
|
|
1128
1176
|
let routePathnameIndex = routePathnames.length - 1;
|
|
1129
|
-
|
|
1130
1177
|
if (toPathname.startsWith("..")) {
|
|
1131
|
-
let toSegments = toPathname.split("/");
|
|
1178
|
+
let toSegments = toPathname.split("/");
|
|
1179
|
+
|
|
1180
|
+
// Each leading .. segment means "go up one route" instead of "go up one
|
|
1132
1181
|
// URL segment". This is a key difference from how <a href> works and a
|
|
1133
1182
|
// major reason we call this a "to" value instead of a "href".
|
|
1134
|
-
|
|
1135
1183
|
while (toSegments[0] === "..") {
|
|
1136
1184
|
toSegments.shift();
|
|
1137
1185
|
routePathnameIndex -= 1;
|
|
1138
1186
|
}
|
|
1139
|
-
|
|
1140
1187
|
to.pathname = toSegments.join("/");
|
|
1141
|
-
}
|
|
1142
|
-
// the root / URL.
|
|
1143
|
-
|
|
1188
|
+
}
|
|
1144
1189
|
|
|
1190
|
+
// If there are more ".." segments than parent routes, resolve relative to
|
|
1191
|
+
// the root / URL.
|
|
1145
1192
|
from = routePathnameIndex >= 0 ? routePathnames[routePathnameIndex] : "/";
|
|
1146
1193
|
}
|
|
1194
|
+
let path = resolvePath(to, from);
|
|
1147
1195
|
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1196
|
+
// Ensure the pathname has a trailing slash if the original "to" had one
|
|
1197
|
+
let hasExplicitTrailingSlash = toPathname && toPathname !== "/" && toPathname.endsWith("/");
|
|
1198
|
+
// Or if this was a link to the current path which has a trailing slash
|
|
1152
1199
|
let hasCurrentTrailingSlash = (isEmptyPath || toPathname === ".") && locationPathname.endsWith("/");
|
|
1153
|
-
|
|
1154
1200
|
if (!path.pathname.endsWith("/") && (hasExplicitTrailingSlash || hasCurrentTrailingSlash)) {
|
|
1155
1201
|
path.pathname += "/";
|
|
1156
1202
|
}
|
|
1157
|
-
|
|
1158
1203
|
return path;
|
|
1159
1204
|
}
|
|
1205
|
+
|
|
1160
1206
|
/**
|
|
1161
1207
|
* @private
|
|
1162
1208
|
*/
|
|
1163
|
-
|
|
1164
1209
|
function getToPathname(to) {
|
|
1165
1210
|
// Empty strings should be treated the same as / paths
|
|
1166
1211
|
return to === "" || to.pathname === "" ? "/" : typeof to === "string" ? parsePath(to).pathname : to.pathname;
|
|
1167
1212
|
}
|
|
1213
|
+
|
|
1168
1214
|
/**
|
|
1169
1215
|
* @private
|
|
1170
1216
|
*/
|
|
1171
|
-
|
|
1172
1217
|
const joinPaths = paths => paths.join("/").replace(/\/\/+/g, "/");
|
|
1218
|
+
|
|
1173
1219
|
/**
|
|
1174
1220
|
* @private
|
|
1175
1221
|
*/
|
|
1176
|
-
|
|
1177
1222
|
const normalizePathname = pathname => pathname.replace(/\/+$/, "").replace(/^\/*/, "/");
|
|
1223
|
+
|
|
1178
1224
|
/**
|
|
1179
1225
|
* @private
|
|
1180
1226
|
*/
|
|
1181
|
-
|
|
1182
1227
|
const normalizeSearch = search => !search || search === "?" ? "" : search.startsWith("?") ? search : "?" + search;
|
|
1228
|
+
|
|
1183
1229
|
/**
|
|
1184
1230
|
* @private
|
|
1185
1231
|
*/
|
|
1186
|
-
|
|
1187
1232
|
const normalizeHash = hash => !hash || hash === "#" ? "" : hash.startsWith("#") ? hash : "#" + hash;
|
|
1188
|
-
|
|
1189
1233
|
/**
|
|
1190
1234
|
* This is a shortcut for creating `application/json` responses. Converts `data`
|
|
1191
1235
|
* to JSON and sets the `Content-Type` header.
|
|
@@ -1194,16 +1238,13 @@
|
|
|
1194
1238
|
if (init === void 0) {
|
|
1195
1239
|
init = {};
|
|
1196
1240
|
}
|
|
1197
|
-
|
|
1198
1241
|
let responseInit = typeof init === "number" ? {
|
|
1199
1242
|
status: init
|
|
1200
1243
|
} : init;
|
|
1201
1244
|
let headers = new Headers(responseInit.headers);
|
|
1202
|
-
|
|
1203
1245
|
if (!headers.has("Content-Type")) {
|
|
1204
1246
|
headers.set("Content-Type", "application/json; charset=utf-8");
|
|
1205
1247
|
}
|
|
1206
|
-
|
|
1207
1248
|
return new Response(JSON.stringify(data), _extends({}, responseInit, {
|
|
1208
1249
|
headers
|
|
1209
1250
|
}));
|
|
@@ -1214,17 +1255,15 @@
|
|
|
1214
1255
|
this.pendingKeysSet = new Set();
|
|
1215
1256
|
this.subscribers = new Set();
|
|
1216
1257
|
this.deferredKeys = [];
|
|
1217
|
-
invariant(data && typeof data === "object" && !Array.isArray(data), "defer() only accepts plain objects");
|
|
1218
|
-
// cancellation
|
|
1258
|
+
invariant(data && typeof data === "object" && !Array.isArray(data), "defer() only accepts plain objects");
|
|
1219
1259
|
|
|
1260
|
+
// Set up an AbortController + Promise we can race against to exit early
|
|
1261
|
+
// cancellation
|
|
1220
1262
|
let reject;
|
|
1221
1263
|
this.abortPromise = new Promise((_, r) => reject = r);
|
|
1222
1264
|
this.controller = new AbortController();
|
|
1223
|
-
|
|
1224
1265
|
let onAbort = () => reject(new AbortedDeferredError("Deferred data aborted"));
|
|
1225
|
-
|
|
1226
1266
|
this.unlistenAbortSignal = () => this.controller.signal.removeEventListener("abort", onAbort);
|
|
1227
|
-
|
|
1228
1267
|
this.controller.signal.addEventListener("abort", onAbort);
|
|
1229
1268
|
this.data = Object.entries(data).reduce((acc, _ref) => {
|
|
1230
1269
|
let [key, value] = _ref;
|
|
@@ -1232,34 +1271,31 @@
|
|
|
1232
1271
|
[key]: this.trackPromise(key, value)
|
|
1233
1272
|
});
|
|
1234
1273
|
}, {});
|
|
1235
|
-
|
|
1236
1274
|
if (this.done) {
|
|
1237
1275
|
// All incoming values were resolved
|
|
1238
1276
|
this.unlistenAbortSignal();
|
|
1239
1277
|
}
|
|
1240
|
-
|
|
1241
1278
|
this.init = responseInit;
|
|
1242
1279
|
}
|
|
1243
|
-
|
|
1244
1280
|
trackPromise(key, value) {
|
|
1245
1281
|
if (!(value instanceof Promise)) {
|
|
1246
1282
|
return value;
|
|
1247
1283
|
}
|
|
1248
|
-
|
|
1249
1284
|
this.deferredKeys.push(key);
|
|
1250
|
-
this.pendingKeysSet.add(key);
|
|
1285
|
+
this.pendingKeysSet.add(key);
|
|
1286
|
+
|
|
1287
|
+
// We store a little wrapper promise that will be extended with
|
|
1251
1288
|
// _data/_error props upon resolve/reject
|
|
1289
|
+
let promise = Promise.race([value, this.abortPromise]).then(data => this.onSettle(promise, key, null, data), error => this.onSettle(promise, key, error));
|
|
1252
1290
|
|
|
1253
|
-
|
|
1291
|
+
// Register rejection listeners to avoid uncaught promise rejections on
|
|
1254
1292
|
// errors or aborted deferred values
|
|
1255
|
-
|
|
1256
1293
|
promise.catch(() => {});
|
|
1257
1294
|
Object.defineProperty(promise, "_tracked", {
|
|
1258
1295
|
get: () => true
|
|
1259
1296
|
});
|
|
1260
1297
|
return promise;
|
|
1261
1298
|
}
|
|
1262
|
-
|
|
1263
1299
|
onSettle(promise, key, error, data) {
|
|
1264
1300
|
if (this.controller.signal.aborted && error instanceof AbortedDeferredError) {
|
|
1265
1301
|
this.unlistenAbortSignal();
|
|
@@ -1268,14 +1304,11 @@
|
|
|
1268
1304
|
});
|
|
1269
1305
|
return Promise.reject(error);
|
|
1270
1306
|
}
|
|
1271
|
-
|
|
1272
1307
|
this.pendingKeysSet.delete(key);
|
|
1273
|
-
|
|
1274
1308
|
if (this.done) {
|
|
1275
1309
|
// Nothing left to abort!
|
|
1276
1310
|
this.unlistenAbortSignal();
|
|
1277
1311
|
}
|
|
1278
|
-
|
|
1279
1312
|
if (error) {
|
|
1280
1313
|
Object.defineProperty(promise, "_error", {
|
|
1281
1314
|
get: () => error
|
|
@@ -1283,54 +1316,43 @@
|
|
|
1283
1316
|
this.emit(false, key);
|
|
1284
1317
|
return Promise.reject(error);
|
|
1285
1318
|
}
|
|
1286
|
-
|
|
1287
1319
|
Object.defineProperty(promise, "_data", {
|
|
1288
1320
|
get: () => data
|
|
1289
1321
|
});
|
|
1290
1322
|
this.emit(false, key);
|
|
1291
1323
|
return data;
|
|
1292
1324
|
}
|
|
1293
|
-
|
|
1294
1325
|
emit(aborted, settledKey) {
|
|
1295
1326
|
this.subscribers.forEach(subscriber => subscriber(aborted, settledKey));
|
|
1296
1327
|
}
|
|
1297
|
-
|
|
1298
1328
|
subscribe(fn) {
|
|
1299
1329
|
this.subscribers.add(fn);
|
|
1300
1330
|
return () => this.subscribers.delete(fn);
|
|
1301
1331
|
}
|
|
1302
|
-
|
|
1303
1332
|
cancel() {
|
|
1304
1333
|
this.controller.abort();
|
|
1305
1334
|
this.pendingKeysSet.forEach((v, k) => this.pendingKeysSet.delete(k));
|
|
1306
1335
|
this.emit(true);
|
|
1307
1336
|
}
|
|
1308
|
-
|
|
1309
1337
|
async resolveData(signal) {
|
|
1310
1338
|
let aborted = false;
|
|
1311
|
-
|
|
1312
1339
|
if (!this.done) {
|
|
1313
1340
|
let onAbort = () => this.cancel();
|
|
1314
|
-
|
|
1315
1341
|
signal.addEventListener("abort", onAbort);
|
|
1316
1342
|
aborted = await new Promise(resolve => {
|
|
1317
1343
|
this.subscribe(aborted => {
|
|
1318
1344
|
signal.removeEventListener("abort", onAbort);
|
|
1319
|
-
|
|
1320
1345
|
if (aborted || this.done) {
|
|
1321
1346
|
resolve(aborted);
|
|
1322
1347
|
}
|
|
1323
1348
|
});
|
|
1324
1349
|
});
|
|
1325
1350
|
}
|
|
1326
|
-
|
|
1327
1351
|
return aborted;
|
|
1328
1352
|
}
|
|
1329
|
-
|
|
1330
1353
|
get done() {
|
|
1331
1354
|
return this.pendingKeysSet.size === 0;
|
|
1332
1355
|
}
|
|
1333
|
-
|
|
1334
1356
|
get unwrappedData() {
|
|
1335
1357
|
invariant(this.data !== null && this.done, "Can only unwrap data on initialized and settled deferreds");
|
|
1336
1358
|
return Object.entries(this.data).reduce((acc, _ref2) => {
|
|
@@ -1340,40 +1362,31 @@
|
|
|
1340
1362
|
});
|
|
1341
1363
|
}, {});
|
|
1342
1364
|
}
|
|
1343
|
-
|
|
1344
1365
|
get pendingKeys() {
|
|
1345
1366
|
return Array.from(this.pendingKeysSet);
|
|
1346
1367
|
}
|
|
1347
|
-
|
|
1348
1368
|
}
|
|
1349
|
-
|
|
1350
1369
|
function isTrackedPromise(value) {
|
|
1351
1370
|
return value instanceof Promise && value._tracked === true;
|
|
1352
1371
|
}
|
|
1353
|
-
|
|
1354
1372
|
function unwrapTrackedPromise(value) {
|
|
1355
1373
|
if (!isTrackedPromise(value)) {
|
|
1356
1374
|
return value;
|
|
1357
1375
|
}
|
|
1358
|
-
|
|
1359
1376
|
if (value._error) {
|
|
1360
1377
|
throw value._error;
|
|
1361
1378
|
}
|
|
1362
|
-
|
|
1363
1379
|
return value._data;
|
|
1364
1380
|
}
|
|
1365
|
-
|
|
1366
1381
|
const defer = function defer(data, init) {
|
|
1367
1382
|
if (init === void 0) {
|
|
1368
1383
|
init = {};
|
|
1369
1384
|
}
|
|
1370
|
-
|
|
1371
1385
|
let responseInit = typeof init === "number" ? {
|
|
1372
1386
|
status: init
|
|
1373
1387
|
} : init;
|
|
1374
1388
|
return new DeferredData(data, responseInit);
|
|
1375
1389
|
};
|
|
1376
|
-
|
|
1377
1390
|
/**
|
|
1378
1391
|
* A redirect response. Sets the status code and the `Location` header.
|
|
1379
1392
|
* Defaults to "302 Found".
|
|
@@ -1382,9 +1395,7 @@
|
|
|
1382
1395
|
if (init === void 0) {
|
|
1383
1396
|
init = 302;
|
|
1384
1397
|
}
|
|
1385
|
-
|
|
1386
1398
|
let responseInit = init;
|
|
1387
|
-
|
|
1388
1399
|
if (typeof responseInit === "number") {
|
|
1389
1400
|
responseInit = {
|
|
1390
1401
|
status: responseInit
|
|
@@ -1392,28 +1403,25 @@
|
|
|
1392
1403
|
} else if (typeof responseInit.status === "undefined") {
|
|
1393
1404
|
responseInit.status = 302;
|
|
1394
1405
|
}
|
|
1395
|
-
|
|
1396
1406
|
let headers = new Headers(responseInit.headers);
|
|
1397
1407
|
headers.set("Location", url);
|
|
1398
1408
|
return new Response(null, _extends({}, responseInit, {
|
|
1399
1409
|
headers
|
|
1400
1410
|
}));
|
|
1401
1411
|
};
|
|
1412
|
+
|
|
1402
1413
|
/**
|
|
1403
1414
|
* @private
|
|
1404
1415
|
* Utility class we use to hold auto-unwrapped 4xx/5xx Response bodies
|
|
1405
1416
|
*/
|
|
1406
|
-
|
|
1407
1417
|
class ErrorResponse {
|
|
1408
1418
|
constructor(status, statusText, data, internal) {
|
|
1409
1419
|
if (internal === void 0) {
|
|
1410
1420
|
internal = false;
|
|
1411
1421
|
}
|
|
1412
|
-
|
|
1413
1422
|
this.status = status;
|
|
1414
1423
|
this.statusText = statusText || "";
|
|
1415
1424
|
this.internal = internal;
|
|
1416
|
-
|
|
1417
1425
|
if (data instanceof Error) {
|
|
1418
1426
|
this.data = data.toString();
|
|
1419
1427
|
this.error = data;
|
|
@@ -1421,24 +1429,82 @@
|
|
|
1421
1429
|
this.data = data;
|
|
1422
1430
|
}
|
|
1423
1431
|
}
|
|
1424
|
-
|
|
1425
1432
|
}
|
|
1433
|
+
|
|
1426
1434
|
/**
|
|
1427
1435
|
* Check if the given error is an ErrorResponse generated from a 4xx/5xx
|
|
1428
1436
|
* Response thrown from an action/loader
|
|
1429
1437
|
*/
|
|
1430
|
-
|
|
1431
1438
|
function isRouteErrorResponse(error) {
|
|
1432
1439
|
return error != null && typeof error.status === "number" && typeof error.statusText === "string" && typeof error.internal === "boolean" && "data" in error;
|
|
1433
1440
|
}
|
|
1434
1441
|
|
|
1442
|
+
////////////////////////////////////////////////////////////////////////////////
|
|
1435
1443
|
//#region Types and Constants
|
|
1436
1444
|
////////////////////////////////////////////////////////////////////////////////
|
|
1437
1445
|
|
|
1438
1446
|
/**
|
|
1439
1447
|
* A Router instance manages all navigation and data loading/mutations
|
|
1440
1448
|
*/
|
|
1441
|
-
|
|
1449
|
+
/**
|
|
1450
|
+
* State maintained internally by the router. During a navigation, all states
|
|
1451
|
+
* reflect the the "old" location unless otherwise noted.
|
|
1452
|
+
*/
|
|
1453
|
+
/**
|
|
1454
|
+
* Data that can be passed into hydrate a Router from SSR
|
|
1455
|
+
*/
|
|
1456
|
+
/**
|
|
1457
|
+
* Future flags to toggle new feature behavior
|
|
1458
|
+
*/
|
|
1459
|
+
/**
|
|
1460
|
+
* Initialization options for createRouter
|
|
1461
|
+
*/
|
|
1462
|
+
/**
|
|
1463
|
+
* State returned from a server-side query() call
|
|
1464
|
+
*/
|
|
1465
|
+
/**
|
|
1466
|
+
* A StaticHandler instance manages a singular SSR navigation/fetch event
|
|
1467
|
+
*/
|
|
1468
|
+
/**
|
|
1469
|
+
* Subscriber function signature for changes to router state
|
|
1470
|
+
*/
|
|
1471
|
+
/**
|
|
1472
|
+
* Function signature for determining the key to be used in scroll restoration
|
|
1473
|
+
* for a given location
|
|
1474
|
+
*/
|
|
1475
|
+
/**
|
|
1476
|
+
* Function signature for determining the current scroll position
|
|
1477
|
+
*/
|
|
1478
|
+
/**
|
|
1479
|
+
* Options for a navigate() call for a Link navigation
|
|
1480
|
+
*/
|
|
1481
|
+
/**
|
|
1482
|
+
* Options for a navigate() call for a Form navigation
|
|
1483
|
+
*/
|
|
1484
|
+
/**
|
|
1485
|
+
* Options to pass to navigate() for either a Link or Form navigation
|
|
1486
|
+
*/
|
|
1487
|
+
/**
|
|
1488
|
+
* Options to pass to fetch()
|
|
1489
|
+
*/
|
|
1490
|
+
/**
|
|
1491
|
+
* Potential states for state.navigation
|
|
1492
|
+
*/
|
|
1493
|
+
/**
|
|
1494
|
+
* Potential states for fetchers
|
|
1495
|
+
*/
|
|
1496
|
+
/**
|
|
1497
|
+
* Cached info for active fetcher.load() instances so they can participate
|
|
1498
|
+
* in revalidation
|
|
1499
|
+
*/
|
|
1500
|
+
/**
|
|
1501
|
+
* Identified fetcher.load() calls that need to be revalidated
|
|
1502
|
+
*/
|
|
1503
|
+
/**
|
|
1504
|
+
* Wrapper object to allow us to throw any response out from callLoaderOrAction
|
|
1505
|
+
* for queryRouter while preserving whether or not it was thrown or returned
|
|
1506
|
+
* from the loader/action
|
|
1507
|
+
*/
|
|
1442
1508
|
const validMutationMethodsArr = ["post", "put", "patch", "delete"];
|
|
1443
1509
|
const validMutationMethods = new Set(validMutationMethodsArr);
|
|
1444
1510
|
const validRequestMethodsArr = ["get", ...validMutationMethodsArr];
|
|
@@ -1468,12 +1534,12 @@
|
|
|
1468
1534
|
location: undefined
|
|
1469
1535
|
};
|
|
1470
1536
|
const ABSOLUTE_URL_REGEX = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
|
|
1471
|
-
const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined";
|
|
1472
|
-
const isServer = !isBrowser;
|
|
1473
|
-
|
|
1474
1537
|
const defaultMapRouteProperties = route => ({
|
|
1475
1538
|
hasErrorBoundary: Boolean(route.hasErrorBoundary)
|
|
1476
|
-
});
|
|
1539
|
+
});
|
|
1540
|
+
|
|
1541
|
+
//#endregion
|
|
1542
|
+
|
|
1477
1543
|
////////////////////////////////////////////////////////////////////////////////
|
|
1478
1544
|
//#region createRouter
|
|
1479
1545
|
////////////////////////////////////////////////////////////////////////////////
|
|
@@ -1481,57 +1547,54 @@
|
|
|
1481
1547
|
/**
|
|
1482
1548
|
* Create a router and listen to history POP navigations
|
|
1483
1549
|
*/
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
1550
|
function createRouter(init) {
|
|
1551
|
+
const routerWindow = init.window ? init.window : typeof window !== "undefined" ? window : undefined;
|
|
1552
|
+
const isBrowser = typeof routerWindow !== "undefined" && typeof routerWindow.document !== "undefined" && typeof routerWindow.document.createElement !== "undefined";
|
|
1553
|
+
const isServer = !isBrowser;
|
|
1487
1554
|
invariant(init.routes.length > 0, "You must provide a non-empty routes array to createRouter");
|
|
1488
1555
|
let mapRouteProperties;
|
|
1489
|
-
|
|
1490
1556
|
if (init.mapRouteProperties) {
|
|
1491
1557
|
mapRouteProperties = init.mapRouteProperties;
|
|
1492
1558
|
} else if (init.detectErrorBoundary) {
|
|
1493
1559
|
// If they are still using the deprecated version, wrap it with the new API
|
|
1494
1560
|
let detectErrorBoundary = init.detectErrorBoundary;
|
|
1495
|
-
|
|
1496
1561
|
mapRouteProperties = route => ({
|
|
1497
1562
|
hasErrorBoundary: detectErrorBoundary(route)
|
|
1498
1563
|
});
|
|
1499
1564
|
} else {
|
|
1500
1565
|
mapRouteProperties = defaultMapRouteProperties;
|
|
1501
|
-
}
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
let manifest = {}; // Routes in tree format for matching
|
|
1566
|
+
}
|
|
1505
1567
|
|
|
1568
|
+
// Routes keyed by ID
|
|
1569
|
+
let manifest = {};
|
|
1570
|
+
// Routes in tree format for matching
|
|
1506
1571
|
let dataRoutes = convertRoutesToDataRoutes(init.routes, mapRouteProperties, undefined, manifest);
|
|
1507
1572
|
let inFlightDataRoutes;
|
|
1508
|
-
let basename = init.basename || "/";
|
|
1509
|
-
|
|
1573
|
+
let basename = init.basename || "/";
|
|
1574
|
+
// Config driven behavior flags
|
|
1510
1575
|
let future = _extends({
|
|
1511
1576
|
v7_normalizeFormMethod: false,
|
|
1512
1577
|
v7_prependBasename: false
|
|
1513
|
-
}, init.future);
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1578
|
+
}, init.future);
|
|
1579
|
+
// Cleanup function for history
|
|
1580
|
+
let unlistenHistory = null;
|
|
1581
|
+
// Externally-provided functions to call on all state changes
|
|
1582
|
+
let subscribers = new Set();
|
|
1583
|
+
// Externally-provided object to hold scroll restoration locations during routing
|
|
1584
|
+
let savedScrollPositions = null;
|
|
1585
|
+
// Externally-provided function to get scroll restoration keys
|
|
1586
|
+
let getScrollRestorationKey = null;
|
|
1587
|
+
// Externally-provided function to get current scroll position
|
|
1588
|
+
let getScrollPosition = null;
|
|
1589
|
+
// One-time flag to control the initial hydration scroll restoration. Because
|
|
1525
1590
|
// we don't get the saved positions from <ScrollRestoration /> until _after_
|
|
1526
1591
|
// the initial render, we need to manually trigger a separate updateState to
|
|
1527
1592
|
// send along the restoreScrollPosition
|
|
1528
1593
|
// Set to true if we have `hydrationData` since we assume we were SSR'd and that
|
|
1529
1594
|
// SSR did the initial scroll restoration.
|
|
1530
|
-
|
|
1531
1595
|
let initialScrollRestored = init.hydrationData != null;
|
|
1532
1596
|
let initialMatches = matchRoutes(dataRoutes, init.history.location, basename);
|
|
1533
1597
|
let initialErrors = null;
|
|
1534
|
-
|
|
1535
1598
|
if (initialMatches == null) {
|
|
1536
1599
|
// If we do not match a user-provided-route, fall back to the root
|
|
1537
1600
|
// to allow the error boundary to take over
|
|
@@ -1547,10 +1610,11 @@
|
|
|
1547
1610
|
[route.id]: error
|
|
1548
1611
|
};
|
|
1549
1612
|
}
|
|
1550
|
-
|
|
1551
|
-
|
|
1613
|
+
let initialized =
|
|
1614
|
+
// All initialMatches need to be loaded before we're ready. If we have lazy
|
|
1552
1615
|
// functions around still then we'll need to run them in initialize()
|
|
1553
|
-
!initialMatches.some(m => m.route.lazy) && (
|
|
1616
|
+
!initialMatches.some(m => m.route.lazy) && (
|
|
1617
|
+
// And we have to either have no loaders or have been provided hydrationData
|
|
1554
1618
|
!initialMatches.some(m => m.route.loader) || init.hydrationData != null);
|
|
1555
1619
|
let router;
|
|
1556
1620
|
let state = {
|
|
@@ -1568,57 +1632,74 @@
|
|
|
1568
1632
|
errors: init.hydrationData && init.hydrationData.errors || initialErrors,
|
|
1569
1633
|
fetchers: new Map(),
|
|
1570
1634
|
blockers: new Map()
|
|
1571
|
-
};
|
|
1635
|
+
};
|
|
1636
|
+
|
|
1637
|
+
// -- Stateful internal variables to manage navigations --
|
|
1572
1638
|
// Current navigation in progress (to be committed in completeNavigation)
|
|
1639
|
+
let pendingAction = exports.Action.Pop;
|
|
1573
1640
|
|
|
1574
|
-
|
|
1641
|
+
// Should the current navigation prevent the scroll reset if scroll cannot
|
|
1575
1642
|
// be restored?
|
|
1643
|
+
let pendingPreventScrollReset = false;
|
|
1576
1644
|
|
|
1577
|
-
|
|
1645
|
+
// AbortController for the active navigation
|
|
1646
|
+
let pendingNavigationController;
|
|
1578
1647
|
|
|
1579
|
-
|
|
1648
|
+
// We use this to avoid touching history in completeNavigation if a
|
|
1580
1649
|
// revalidation is entirely uninterrupted
|
|
1650
|
+
let isUninterruptedRevalidation = false;
|
|
1581
1651
|
|
|
1582
|
-
|
|
1652
|
+
// Use this internal flag to force revalidation of all loaders:
|
|
1583
1653
|
// - submissions (completed or interrupted)
|
|
1584
1654
|
// - useRevalidator()
|
|
1585
1655
|
// - X-Remix-Revalidate (from redirect)
|
|
1656
|
+
let isRevalidationRequired = false;
|
|
1586
1657
|
|
|
1587
|
-
|
|
1658
|
+
// Use this internal array to capture routes that require revalidation due
|
|
1588
1659
|
// to a cancelled deferred on action submission
|
|
1660
|
+
let cancelledDeferredRoutes = [];
|
|
1589
1661
|
|
|
1590
|
-
|
|
1662
|
+
// Use this internal array to capture fetcher loads that were cancelled by an
|
|
1591
1663
|
// action navigation and require revalidation
|
|
1664
|
+
let cancelledFetcherLoads = [];
|
|
1592
1665
|
|
|
1593
|
-
|
|
1666
|
+
// AbortControllers for any in-flight fetchers
|
|
1667
|
+
let fetchControllers = new Map();
|
|
1594
1668
|
|
|
1595
|
-
|
|
1669
|
+
// Track loads based on the order in which they started
|
|
1670
|
+
let incrementingLoadId = 0;
|
|
1596
1671
|
|
|
1597
|
-
|
|
1672
|
+
// Track the outstanding pending navigation data load to be compared against
|
|
1598
1673
|
// the globally incrementing load when a fetcher load lands after a completed
|
|
1599
1674
|
// navigation
|
|
1675
|
+
let pendingNavigationLoadId = -1;
|
|
1600
1676
|
|
|
1601
|
-
|
|
1677
|
+
// Fetchers that triggered data reloads as a result of their actions
|
|
1678
|
+
let fetchReloadIds = new Map();
|
|
1602
1679
|
|
|
1603
|
-
|
|
1680
|
+
// Fetchers that triggered redirect navigations
|
|
1681
|
+
let fetchRedirectIds = new Set();
|
|
1604
1682
|
|
|
1605
|
-
|
|
1683
|
+
// Most recent href/match for fetcher.load calls for fetchers
|
|
1684
|
+
let fetchLoadMatches = new Map();
|
|
1606
1685
|
|
|
1607
|
-
|
|
1686
|
+
// Store DeferredData instances for active route matches. When a
|
|
1608
1687
|
// route loader returns defer() we stick one in here. Then, when a nested
|
|
1609
1688
|
// promise resolves we update loaderData. If a new navigation starts we
|
|
1610
1689
|
// cancel active deferreds for eliminated routes.
|
|
1690
|
+
let activeDeferreds = new Map();
|
|
1611
1691
|
|
|
1612
|
-
|
|
1692
|
+
// Store blocker functions in a separate Map outside of router state since
|
|
1613
1693
|
// we don't need to update UI state if they change
|
|
1694
|
+
let blockerFunctions = new Map();
|
|
1614
1695
|
|
|
1615
|
-
|
|
1696
|
+
// Flag to ignore the next history update, so we can revert the URL change on
|
|
1616
1697
|
// a POP navigation that was blocked by the user without touching router state
|
|
1698
|
+
let ignoreNextHistoryUpdate = false;
|
|
1617
1699
|
|
|
1618
|
-
|
|
1700
|
+
// Initialize the router, all side effects should be kicked off from here.
|
|
1619
1701
|
// Implemented as a Fluent API for ease of:
|
|
1620
1702
|
// let router = createRouter(init).initialize();
|
|
1621
|
-
|
|
1622
1703
|
function initialize() {
|
|
1623
1704
|
// If history informs us of a POP navigation, start the navigation but do not update
|
|
1624
1705
|
// state. We'll update our own state once the navigation completes
|
|
@@ -1628,98 +1709,90 @@
|
|
|
1628
1709
|
location,
|
|
1629
1710
|
delta
|
|
1630
1711
|
} = _ref;
|
|
1631
|
-
|
|
1632
1712
|
// Ignore this event if it was just us resetting the URL from a
|
|
1633
1713
|
// blocked POP navigation
|
|
1634
1714
|
if (ignoreNextHistoryUpdate) {
|
|
1635
1715
|
ignoreNextHistoryUpdate = false;
|
|
1636
1716
|
return;
|
|
1637
1717
|
}
|
|
1638
|
-
|
|
1639
1718
|
warning(blockerFunctions.size === 0 || delta != null, "You are trying to use a blocker on a POP navigation to a location " + "that was not created by @remix-run/router. This will fail silently in " + "production. This can happen if you are navigating outside the router " + "via `window.history.pushState`/`window.location.hash` instead of using " + "router navigation APIs. This can also happen if you are using " + "createHashRouter and the user manually changes the URL.");
|
|
1640
1719
|
let blockerKey = shouldBlockNavigation({
|
|
1641
1720
|
currentLocation: state.location,
|
|
1642
1721
|
nextLocation: location,
|
|
1643
1722
|
historyAction
|
|
1644
1723
|
});
|
|
1645
|
-
|
|
1646
1724
|
if (blockerKey && delta != null) {
|
|
1647
1725
|
// Restore the URL to match the current UI, but don't update router state
|
|
1648
1726
|
ignoreNextHistoryUpdate = true;
|
|
1649
|
-
init.history.go(delta * -1);
|
|
1727
|
+
init.history.go(delta * -1);
|
|
1650
1728
|
|
|
1729
|
+
// Put the blocker into a blocked state
|
|
1651
1730
|
updateBlocker(blockerKey, {
|
|
1652
1731
|
state: "blocked",
|
|
1653
1732
|
location,
|
|
1654
|
-
|
|
1655
1733
|
proceed() {
|
|
1656
1734
|
updateBlocker(blockerKey, {
|
|
1657
1735
|
state: "proceeding",
|
|
1658
1736
|
proceed: undefined,
|
|
1659
1737
|
reset: undefined,
|
|
1660
1738
|
location
|
|
1661
|
-
});
|
|
1662
|
-
|
|
1739
|
+
});
|
|
1740
|
+
// Re-do the same POP navigation we just blocked
|
|
1663
1741
|
init.history.go(delta);
|
|
1664
1742
|
},
|
|
1665
|
-
|
|
1666
1743
|
reset() {
|
|
1667
1744
|
deleteBlocker(blockerKey);
|
|
1668
1745
|
updateState({
|
|
1669
1746
|
blockers: new Map(router.state.blockers)
|
|
1670
1747
|
});
|
|
1671
1748
|
}
|
|
1672
|
-
|
|
1673
1749
|
});
|
|
1674
1750
|
return;
|
|
1675
1751
|
}
|
|
1676
|
-
|
|
1677
1752
|
return startNavigation(historyAction, location);
|
|
1678
|
-
});
|
|
1753
|
+
});
|
|
1754
|
+
|
|
1755
|
+
// Kick off initial data load if needed. Use Pop to avoid modifying history
|
|
1679
1756
|
// Note we don't do any handling of lazy here. For SPA's it'll get handled
|
|
1680
1757
|
// in the normal navigation flow. For SSR it's expected that lazy modules are
|
|
1681
1758
|
// resolved prior to router creation since we can't go into a fallbackElement
|
|
1682
1759
|
// UI for SSR'd apps
|
|
1683
|
-
|
|
1684
1760
|
if (!state.initialized) {
|
|
1685
1761
|
startNavigation(exports.Action.Pop, state.location);
|
|
1686
1762
|
}
|
|
1687
|
-
|
|
1688
1763
|
return router;
|
|
1689
|
-
}
|
|
1690
|
-
|
|
1764
|
+
}
|
|
1691
1765
|
|
|
1766
|
+
// Clean up a router and it's side effects
|
|
1692
1767
|
function dispose() {
|
|
1693
1768
|
if (unlistenHistory) {
|
|
1694
1769
|
unlistenHistory();
|
|
1695
1770
|
}
|
|
1696
|
-
|
|
1697
1771
|
subscribers.clear();
|
|
1698
1772
|
pendingNavigationController && pendingNavigationController.abort();
|
|
1699
1773
|
state.fetchers.forEach((_, key) => deleteFetcher(key));
|
|
1700
1774
|
state.blockers.forEach((_, key) => deleteBlocker(key));
|
|
1701
|
-
}
|
|
1702
|
-
|
|
1775
|
+
}
|
|
1703
1776
|
|
|
1777
|
+
// Subscribe to state updates for the router
|
|
1704
1778
|
function subscribe(fn) {
|
|
1705
1779
|
subscribers.add(fn);
|
|
1706
1780
|
return () => subscribers.delete(fn);
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1781
|
+
}
|
|
1709
1782
|
|
|
1783
|
+
// Update our state and notify the calling context of the change
|
|
1710
1784
|
function updateState(newState) {
|
|
1711
1785
|
state = _extends({}, state, newState);
|
|
1712
1786
|
subscribers.forEach(subscriber => subscriber(state));
|
|
1713
|
-
}
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
// Complete a navigation returning the state.navigation back to the IDLE_NAVIGATION
|
|
1714
1790
|
// and setting state.[historyAction/location/matches] to the new route.
|
|
1715
1791
|
// - Location is a required param
|
|
1716
1792
|
// - Navigation will always be set to IDLE_NAVIGATION
|
|
1717
1793
|
// - Can pass any other state in newState
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
1794
|
function completeNavigation(location, newState) {
|
|
1721
1795
|
var _location$state, _location$state2;
|
|
1722
|
-
|
|
1723
1796
|
// Deduce if we're in a loading/actionReload state:
|
|
1724
1797
|
// - We have committed actionData in the store
|
|
1725
1798
|
// - The current navigation was a mutation submission
|
|
@@ -1727,7 +1800,6 @@
|
|
|
1727
1800
|
// - The location being loaded is not the result of a redirect
|
|
1728
1801
|
let isActionReload = state.actionData != null && state.navigation.formMethod != null && isMutationMethod(state.navigation.formMethod) && state.navigation.state === "loading" && ((_location$state = location.state) == null ? void 0 : _location$state._isRedirect) !== true;
|
|
1729
1802
|
let actionData;
|
|
1730
|
-
|
|
1731
1803
|
if (newState.actionData) {
|
|
1732
1804
|
if (Object.keys(newState.actionData).length > 0) {
|
|
1733
1805
|
actionData = newState.actionData;
|
|
@@ -1741,25 +1813,24 @@
|
|
|
1741
1813
|
} else {
|
|
1742
1814
|
// Clear actionData on any other completed navigations
|
|
1743
1815
|
actionData = null;
|
|
1744
|
-
}
|
|
1816
|
+
}
|
|
1745
1817
|
|
|
1818
|
+
// Always preserve any existing loaderData from re-used routes
|
|
1819
|
+
let loaderData = newState.loaderData ? mergeLoaderData(state.loaderData, newState.loaderData, newState.matches || [], newState.errors) : state.loaderData;
|
|
1746
1820
|
|
|
1747
|
-
|
|
1821
|
+
// On a successful navigation we can assume we got through all blockers
|
|
1748
1822
|
// so we can start fresh
|
|
1749
|
-
|
|
1750
1823
|
for (let [key] of blockerFunctions) {
|
|
1751
1824
|
deleteBlocker(key);
|
|
1752
|
-
}
|
|
1753
|
-
// submission navigations unless they redirect
|
|
1754
|
-
|
|
1825
|
+
}
|
|
1755
1826
|
|
|
1827
|
+
// Always respect the user flag. Otherwise don't reset on mutation
|
|
1828
|
+
// submission navigations unless they redirect
|
|
1756
1829
|
let preventScrollReset = pendingPreventScrollReset === true || state.navigation.formMethod != null && isMutationMethod(state.navigation.formMethod) && ((_location$state2 = location.state) == null ? void 0 : _location$state2._isRedirect) !== true;
|
|
1757
|
-
|
|
1758
1830
|
if (inFlightDataRoutes) {
|
|
1759
1831
|
dataRoutes = inFlightDataRoutes;
|
|
1760
1832
|
inFlightDataRoutes = undefined;
|
|
1761
1833
|
}
|
|
1762
|
-
|
|
1763
1834
|
updateState(_extends({}, newState, {
|
|
1764
1835
|
// matches, errors, fetchers go through as-is
|
|
1765
1836
|
actionData,
|
|
@@ -1773,30 +1844,28 @@
|
|
|
1773
1844
|
preventScrollReset,
|
|
1774
1845
|
blockers: new Map(state.blockers)
|
|
1775
1846
|
}));
|
|
1776
|
-
|
|
1777
1847
|
if (isUninterruptedRevalidation) ; else if (pendingAction === exports.Action.Pop) ; else if (pendingAction === exports.Action.Push) {
|
|
1778
1848
|
init.history.push(location, location.state);
|
|
1779
1849
|
} else if (pendingAction === exports.Action.Replace) {
|
|
1780
1850
|
init.history.replace(location, location.state);
|
|
1781
|
-
}
|
|
1782
|
-
|
|
1851
|
+
}
|
|
1783
1852
|
|
|
1853
|
+
// Reset stateful navigation vars
|
|
1784
1854
|
pendingAction = exports.Action.Pop;
|
|
1785
1855
|
pendingPreventScrollReset = false;
|
|
1786
1856
|
isUninterruptedRevalidation = false;
|
|
1787
1857
|
isRevalidationRequired = false;
|
|
1788
1858
|
cancelledDeferredRoutes = [];
|
|
1789
1859
|
cancelledFetcherLoads = [];
|
|
1790
|
-
}
|
|
1791
|
-
// replace with an optional submission
|
|
1792
|
-
|
|
1860
|
+
}
|
|
1793
1861
|
|
|
1862
|
+
// Trigger a navigation event, which can either be a numerical POP or a PUSH
|
|
1863
|
+
// replace with an optional submission
|
|
1794
1864
|
async function navigate(to, opts) {
|
|
1795
1865
|
if (typeof to === "number") {
|
|
1796
1866
|
init.history.go(to);
|
|
1797
1867
|
return;
|
|
1798
1868
|
}
|
|
1799
|
-
|
|
1800
1869
|
let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, to, opts == null ? void 0 : opts.fromRouteId, opts == null ? void 0 : opts.relative);
|
|
1801
1870
|
let {
|
|
1802
1871
|
path,
|
|
@@ -1804,16 +1873,16 @@
|
|
|
1804
1873
|
error
|
|
1805
1874
|
} = normalizeNavigateOptions(future.v7_normalizeFormMethod, false, normalizedPath, opts);
|
|
1806
1875
|
let currentLocation = state.location;
|
|
1807
|
-
let nextLocation = createLocation(state.location, path, opts && opts.state);
|
|
1876
|
+
let nextLocation = createLocation(state.location, path, opts && opts.state);
|
|
1877
|
+
|
|
1878
|
+
// When using navigate as a PUSH/REPLACE we aren't reading an already-encoded
|
|
1808
1879
|
// URL from window.location, so we need to encode it here so the behavior
|
|
1809
1880
|
// remains the same as POP and non-data-router usages. new URL() does all
|
|
1810
1881
|
// the same encoding we'd get from a history.pushState/window.location read
|
|
1811
1882
|
// without having to touch history
|
|
1812
|
-
|
|
1813
1883
|
nextLocation = _extends({}, nextLocation, init.history.encodeLocation(nextLocation));
|
|
1814
1884
|
let userReplace = opts && opts.replace != null ? opts.replace : undefined;
|
|
1815
1885
|
let historyAction = exports.Action.Push;
|
|
1816
|
-
|
|
1817
1886
|
if (userReplace === true) {
|
|
1818
1887
|
historyAction = exports.Action.Replace;
|
|
1819
1888
|
} else if (userReplace === false) ; else if (submission != null && isMutationMethod(submission.formMethod) && submission.formAction === state.location.pathname + state.location.search) {
|
|
@@ -1823,42 +1892,36 @@
|
|
|
1823
1892
|
// action/loader this will be ignored and the redirect will be a PUSH
|
|
1824
1893
|
historyAction = exports.Action.Replace;
|
|
1825
1894
|
}
|
|
1826
|
-
|
|
1827
1895
|
let preventScrollReset = opts && "preventScrollReset" in opts ? opts.preventScrollReset === true : undefined;
|
|
1828
1896
|
let blockerKey = shouldBlockNavigation({
|
|
1829
1897
|
currentLocation,
|
|
1830
1898
|
nextLocation,
|
|
1831
1899
|
historyAction
|
|
1832
1900
|
});
|
|
1833
|
-
|
|
1834
1901
|
if (blockerKey) {
|
|
1835
1902
|
// Put the blocker into a blocked state
|
|
1836
1903
|
updateBlocker(blockerKey, {
|
|
1837
1904
|
state: "blocked",
|
|
1838
1905
|
location: nextLocation,
|
|
1839
|
-
|
|
1840
1906
|
proceed() {
|
|
1841
1907
|
updateBlocker(blockerKey, {
|
|
1842
1908
|
state: "proceeding",
|
|
1843
1909
|
proceed: undefined,
|
|
1844
1910
|
reset: undefined,
|
|
1845
1911
|
location: nextLocation
|
|
1846
|
-
});
|
|
1847
|
-
|
|
1912
|
+
});
|
|
1913
|
+
// Send the same navigation through
|
|
1848
1914
|
navigate(to, opts);
|
|
1849
1915
|
},
|
|
1850
|
-
|
|
1851
1916
|
reset() {
|
|
1852
1917
|
deleteBlocker(blockerKey);
|
|
1853
1918
|
updateState({
|
|
1854
1919
|
blockers: new Map(state.blockers)
|
|
1855
1920
|
});
|
|
1856
1921
|
}
|
|
1857
|
-
|
|
1858
1922
|
});
|
|
1859
1923
|
return;
|
|
1860
1924
|
}
|
|
1861
|
-
|
|
1862
1925
|
return await startNavigation(historyAction, nextLocation, {
|
|
1863
1926
|
submission,
|
|
1864
1927
|
// Send through the formData serialization error if we have one so we can
|
|
@@ -1867,43 +1930,44 @@
|
|
|
1867
1930
|
preventScrollReset,
|
|
1868
1931
|
replace: opts && opts.replace
|
|
1869
1932
|
});
|
|
1870
|
-
}
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
// Revalidate all current loaders. If a navigation is in progress or if this
|
|
1871
1936
|
// is interrupted by a navigation, allow this to "succeed" by calling all
|
|
1872
1937
|
// loaders during the next loader round
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
1938
|
function revalidate() {
|
|
1876
1939
|
interruptActiveLoads();
|
|
1877
1940
|
updateState({
|
|
1878
1941
|
revalidation: "loading"
|
|
1879
|
-
});
|
|
1880
|
-
// navigation, we'll just let the follow up loader execution call all loaders
|
|
1942
|
+
});
|
|
1881
1943
|
|
|
1944
|
+
// If we're currently submitting an action, we don't need to start a new
|
|
1945
|
+
// navigation, we'll just let the follow up loader execution call all loaders
|
|
1882
1946
|
if (state.navigation.state === "submitting") {
|
|
1883
1947
|
return;
|
|
1884
|
-
}
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
// If we're currently in an idle state, start a new navigation for the current
|
|
1885
1951
|
// action/location and mark it as uninterrupted, which will skip the history
|
|
1886
1952
|
// update in completeNavigation
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
1953
|
if (state.navigation.state === "idle") {
|
|
1890
1954
|
startNavigation(state.historyAction, state.location, {
|
|
1891
1955
|
startUninterruptedRevalidation: true
|
|
1892
1956
|
});
|
|
1893
1957
|
return;
|
|
1894
|
-
}
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
// Otherwise, if we're currently in a loading state, just start a new
|
|
1895
1961
|
// navigation to the navigation.location but do not trigger an uninterrupted
|
|
1896
1962
|
// revalidation so that history correctly updates once the navigation completes
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
1963
|
startNavigation(pendingAction || state.historyAction, state.navigation.location, {
|
|
1900
1964
|
overrideNavigation: state.navigation
|
|
1901
1965
|
});
|
|
1902
|
-
}
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
// Start a navigation to the given action/location. Can optionally provide a
|
|
1903
1969
|
// overrideNavigation which will override the normalLoad in the case of a redirect
|
|
1904
1970
|
// navigation
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
1971
|
async function startNavigation(historyAction, location, opts) {
|
|
1908
1972
|
// Abort any in-progress navigations and start a new one. Unset any ongoing
|
|
1909
1973
|
// uninterrupted revalidations unless told otherwise, since we want this
|
|
@@ -1911,15 +1975,17 @@
|
|
|
1911
1975
|
pendingNavigationController && pendingNavigationController.abort();
|
|
1912
1976
|
pendingNavigationController = null;
|
|
1913
1977
|
pendingAction = historyAction;
|
|
1914
|
-
isUninterruptedRevalidation = (opts && opts.startUninterruptedRevalidation) === true;
|
|
1915
|
-
// and track whether we should reset scroll on completion
|
|
1978
|
+
isUninterruptedRevalidation = (opts && opts.startUninterruptedRevalidation) === true;
|
|
1916
1979
|
|
|
1980
|
+
// Save the current scroll position every time we start a new navigation,
|
|
1981
|
+
// and track whether we should reset scroll on completion
|
|
1917
1982
|
saveScrollPosition(state.location, state.matches);
|
|
1918
1983
|
pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
|
|
1919
1984
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
1920
1985
|
let loadingNavigation = opts && opts.overrideNavigation;
|
|
1921
|
-
let matches = matchRoutes(routesToUse, location, basename);
|
|
1986
|
+
let matches = matchRoutes(routesToUse, location, basename);
|
|
1922
1987
|
|
|
1988
|
+
// Short circuit with a 404 on the root error boundary if we match nothing
|
|
1923
1989
|
if (!matches) {
|
|
1924
1990
|
let error = getInternalRouterError(404, {
|
|
1925
1991
|
pathname: location.pathname
|
|
@@ -1927,8 +1993,8 @@
|
|
|
1927
1993
|
let {
|
|
1928
1994
|
matches: notFoundMatches,
|
|
1929
1995
|
route
|
|
1930
|
-
} = getShortCircuitMatches(routesToUse);
|
|
1931
|
-
|
|
1996
|
+
} = getShortCircuitMatches(routesToUse);
|
|
1997
|
+
// Cancel all pending deferred on 404s since we don't keep any routes
|
|
1932
1998
|
cancelActiveDeferreds();
|
|
1933
1999
|
completeNavigation(location, {
|
|
1934
2000
|
matches: notFoundMatches,
|
|
@@ -1938,26 +2004,26 @@
|
|
|
1938
2004
|
}
|
|
1939
2005
|
});
|
|
1940
2006
|
return;
|
|
1941
|
-
}
|
|
1942
|
-
// Ignore on initial page loads because since the initial load will always
|
|
1943
|
-
// be "same hash".
|
|
1944
|
-
// For example, on /page#hash and submit a <Form method="post"> which will
|
|
1945
|
-
// default to a navigation to /page
|
|
1946
|
-
|
|
2007
|
+
}
|
|
1947
2008
|
|
|
1948
|
-
if
|
|
2009
|
+
// Short circuit if it's only a hash change and not a revalidation or
|
|
2010
|
+
// mutation submission.
|
|
2011
|
+
//
|
|
2012
|
+
// Ignore on initial page loads because since the initial load will always
|
|
2013
|
+
// be "same hash". For example, on /page#hash and submit a <Form method="post">
|
|
2014
|
+
// which will default to a navigation to /page
|
|
2015
|
+
if (state.initialized && !isRevalidationRequired && isHashChangeOnly(state.location, location) && !(opts && opts.submission && isMutationMethod(opts.submission.formMethod))) {
|
|
1949
2016
|
completeNavigation(location, {
|
|
1950
2017
|
matches
|
|
1951
2018
|
});
|
|
1952
2019
|
return;
|
|
1953
|
-
}
|
|
1954
|
-
|
|
2020
|
+
}
|
|
1955
2021
|
|
|
2022
|
+
// Create a controller/Request for this navigation
|
|
1956
2023
|
pendingNavigationController = new AbortController();
|
|
1957
2024
|
let request = createClientSideRequest(init.history, location, pendingNavigationController.signal, opts && opts.submission);
|
|
1958
2025
|
let pendingActionData;
|
|
1959
2026
|
let pendingError;
|
|
1960
|
-
|
|
1961
2027
|
if (opts && opts.pendingError) {
|
|
1962
2028
|
// If we have a pendingError, it means the user attempted a GET submission
|
|
1963
2029
|
// with binary FormData so assign here and skip to handleLoaders. That
|
|
@@ -1971,40 +2037,36 @@
|
|
|
1971
2037
|
let actionOutput = await handleAction(request, location, opts.submission, matches, {
|
|
1972
2038
|
replace: opts.replace
|
|
1973
2039
|
});
|
|
1974
|
-
|
|
1975
2040
|
if (actionOutput.shortCircuited) {
|
|
1976
2041
|
return;
|
|
1977
2042
|
}
|
|
1978
|
-
|
|
1979
2043
|
pendingActionData = actionOutput.pendingActionData;
|
|
1980
2044
|
pendingError = actionOutput.pendingActionError;
|
|
1981
|
-
|
|
1982
2045
|
let navigation = _extends({
|
|
1983
2046
|
state: "loading",
|
|
1984
2047
|
location
|
|
1985
2048
|
}, opts.submission);
|
|
2049
|
+
loadingNavigation = navigation;
|
|
1986
2050
|
|
|
1987
|
-
|
|
1988
|
-
|
|
2051
|
+
// Create a GET request for the loaders
|
|
1989
2052
|
request = new Request(request.url, {
|
|
1990
2053
|
signal: request.signal
|
|
1991
2054
|
});
|
|
1992
|
-
}
|
|
1993
|
-
|
|
2055
|
+
}
|
|
1994
2056
|
|
|
2057
|
+
// Call loaders
|
|
1995
2058
|
let {
|
|
1996
2059
|
shortCircuited,
|
|
1997
2060
|
loaderData,
|
|
1998
2061
|
errors
|
|
1999
2062
|
} = await handleLoaders(request, location, matches, loadingNavigation, opts && opts.submission, opts && opts.fetcherSubmission, opts && opts.replace, pendingActionData, pendingError);
|
|
2000
|
-
|
|
2001
2063
|
if (shortCircuited) {
|
|
2002
2064
|
return;
|
|
2003
|
-
}
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
// Clean up now that the action/loaders have completed. Don't clean up if
|
|
2004
2068
|
// we short circuited because pendingNavigationController will have already
|
|
2005
2069
|
// been assigned to a new controller for the next navigation
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
2070
|
pendingNavigationController = null;
|
|
2009
2071
|
completeNavigation(location, _extends({
|
|
2010
2072
|
matches
|
|
@@ -2014,25 +2076,25 @@
|
|
|
2014
2076
|
loaderData,
|
|
2015
2077
|
errors
|
|
2016
2078
|
}));
|
|
2017
|
-
}
|
|
2018
|
-
// redirects/errors
|
|
2019
|
-
|
|
2079
|
+
}
|
|
2020
2080
|
|
|
2081
|
+
// Call the action matched by the leaf route for this navigation and handle
|
|
2082
|
+
// redirects/errors
|
|
2021
2083
|
async function handleAction(request, location, submission, matches, opts) {
|
|
2022
|
-
interruptActiveLoads();
|
|
2084
|
+
interruptActiveLoads();
|
|
2023
2085
|
|
|
2086
|
+
// Put us in a submitting state
|
|
2024
2087
|
let navigation = _extends({
|
|
2025
2088
|
state: "submitting",
|
|
2026
2089
|
location
|
|
2027
2090
|
}, submission);
|
|
2028
|
-
|
|
2029
2091
|
updateState({
|
|
2030
2092
|
navigation
|
|
2031
|
-
});
|
|
2093
|
+
});
|
|
2032
2094
|
|
|
2095
|
+
// Call our action and get the result
|
|
2033
2096
|
let result;
|
|
2034
2097
|
let actionMatch = getTargetMatch(matches, location);
|
|
2035
|
-
|
|
2036
2098
|
if (!actionMatch.route.action && !actionMatch.route.lazy) {
|
|
2037
2099
|
result = {
|
|
2038
2100
|
type: ResultType.error,
|
|
@@ -2044,17 +2106,14 @@
|
|
|
2044
2106
|
};
|
|
2045
2107
|
} else {
|
|
2046
2108
|
result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, mapRouteProperties, basename);
|
|
2047
|
-
|
|
2048
2109
|
if (request.signal.aborted) {
|
|
2049
2110
|
return {
|
|
2050
2111
|
shortCircuited: true
|
|
2051
2112
|
};
|
|
2052
2113
|
}
|
|
2053
2114
|
}
|
|
2054
|
-
|
|
2055
2115
|
if (isRedirectResult(result)) {
|
|
2056
2116
|
let replace;
|
|
2057
|
-
|
|
2058
2117
|
if (opts && opts.replace != null) {
|
|
2059
2118
|
replace = opts.replace;
|
|
2060
2119
|
} else {
|
|
@@ -2063,7 +2122,6 @@
|
|
|
2063
2122
|
// double back-buttons
|
|
2064
2123
|
replace = result.location === state.location.pathname + state.location.search;
|
|
2065
2124
|
}
|
|
2066
|
-
|
|
2067
2125
|
await startRedirectNavigation(state, result, {
|
|
2068
2126
|
submission,
|
|
2069
2127
|
replace
|
|
@@ -2072,19 +2130,18 @@
|
|
|
2072
2130
|
shortCircuited: true
|
|
2073
2131
|
};
|
|
2074
2132
|
}
|
|
2075
|
-
|
|
2076
2133
|
if (isErrorResult(result)) {
|
|
2077
2134
|
// Store off the pending error - we use it to determine which loaders
|
|
2078
2135
|
// to call and will commit it when we complete the navigation
|
|
2079
|
-
let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
|
|
2136
|
+
let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
|
|
2137
|
+
|
|
2138
|
+
// By default, all submissions are REPLACE navigations, but if the
|
|
2080
2139
|
// action threw an error that'll be rendered in an errorElement, we fall
|
|
2081
2140
|
// back to PUSH so that the user can use the back button to get back to
|
|
2082
2141
|
// the pre-submission form location to try again
|
|
2083
|
-
|
|
2084
2142
|
if ((opts && opts.replace) !== true) {
|
|
2085
2143
|
pendingAction = exports.Action.Push;
|
|
2086
2144
|
}
|
|
2087
|
-
|
|
2088
2145
|
return {
|
|
2089
2146
|
// Send back an empty object we can use to clear out any prior actionData
|
|
2090
2147
|
pendingActionData: {},
|
|
@@ -2093,26 +2150,23 @@
|
|
|
2093
2150
|
}
|
|
2094
2151
|
};
|
|
2095
2152
|
}
|
|
2096
|
-
|
|
2097
2153
|
if (isDeferredResult(result)) {
|
|
2098
2154
|
throw getInternalRouterError(400, {
|
|
2099
2155
|
type: "defer-action"
|
|
2100
2156
|
});
|
|
2101
2157
|
}
|
|
2102
|
-
|
|
2103
2158
|
return {
|
|
2104
2159
|
pendingActionData: {
|
|
2105
2160
|
[actionMatch.route.id]: result.data
|
|
2106
2161
|
}
|
|
2107
2162
|
};
|
|
2108
|
-
}
|
|
2109
|
-
// errors, etc.
|
|
2110
|
-
|
|
2163
|
+
}
|
|
2111
2164
|
|
|
2165
|
+
// Call all applicable loaders for the given matches, handling redirects,
|
|
2166
|
+
// errors, etc.
|
|
2112
2167
|
async function handleLoaders(request, location, matches, overrideNavigation, submission, fetcherSubmission, replace, pendingActionData, pendingError) {
|
|
2113
2168
|
// Figure out the right navigation we want to use for data loading
|
|
2114
2169
|
let loadingNavigation = overrideNavigation;
|
|
2115
|
-
|
|
2116
2170
|
if (!loadingNavigation) {
|
|
2117
2171
|
let navigation = _extends({
|
|
2118
2172
|
state: "loading",
|
|
@@ -2122,12 +2176,11 @@
|
|
|
2122
2176
|
formEncType: undefined,
|
|
2123
2177
|
formData: undefined
|
|
2124
2178
|
}, submission);
|
|
2125
|
-
|
|
2126
2179
|
loadingNavigation = navigation;
|
|
2127
|
-
}
|
|
2128
|
-
// we have it on the loading navigation so use that if available
|
|
2129
|
-
|
|
2180
|
+
}
|
|
2130
2181
|
|
|
2182
|
+
// If this was a redirect from an action we don't have a "submission" but
|
|
2183
|
+
// we have it on the loading navigation so use that if available
|
|
2131
2184
|
let activeSubmission = submission || fetcherSubmission ? submission || fetcherSubmission : loadingNavigation.formMethod && loadingNavigation.formAction && loadingNavigation.formData && loadingNavigation.formEncType ? {
|
|
2132
2185
|
formMethod: loadingNavigation.formMethod,
|
|
2133
2186
|
formAction: loadingNavigation.formAction,
|
|
@@ -2135,12 +2188,14 @@
|
|
|
2135
2188
|
formEncType: loadingNavigation.formEncType
|
|
2136
2189
|
} : undefined;
|
|
2137
2190
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
2138
|
-
let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, activeSubmission, location, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, fetchLoadMatches, routesToUse, basename, pendingActionData, pendingError);
|
|
2191
|
+
let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, activeSubmission, location, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, fetchLoadMatches, routesToUse, basename, pendingActionData, pendingError);
|
|
2192
|
+
|
|
2193
|
+
// Cancel pending deferreds for no-longer-matched routes or routes we're
|
|
2139
2194
|
// about to reload. Note that if this is an action reload we would have
|
|
2140
2195
|
// already cancelled all pending deferreds so this would be a no-op
|
|
2196
|
+
cancelActiveDeferreds(routeId => !(matches && matches.some(m => m.route.id === routeId)) || matchesToLoad && matchesToLoad.some(m => m.route.id === routeId));
|
|
2141
2197
|
|
|
2142
|
-
|
|
2143
|
-
|
|
2198
|
+
// Short circuit if we have no loaders to run
|
|
2144
2199
|
if (matchesToLoad.length === 0 && revalidatingFetchers.length === 0) {
|
|
2145
2200
|
let updatedFetchers = markFetchRedirectsDone();
|
|
2146
2201
|
completeNavigation(location, _extends({
|
|
@@ -2156,12 +2211,12 @@
|
|
|
2156
2211
|
return {
|
|
2157
2212
|
shortCircuited: true
|
|
2158
2213
|
};
|
|
2159
|
-
}
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
// If this is an uninterrupted revalidation, we remain in our current idle
|
|
2160
2217
|
// state. If not, we need to switch to our loading state and load data,
|
|
2161
2218
|
// preserving any new action data or existing action data (in the case of
|
|
2162
2219
|
// a revalidation interrupting an actionReload)
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
2220
|
if (!isUninterruptedRevalidation) {
|
|
2166
2221
|
revalidatingFetchers.forEach(rf => {
|
|
2167
2222
|
let fetcher = state.fetchers.get(rf.key);
|
|
@@ -2187,7 +2242,6 @@
|
|
|
2187
2242
|
fetchers: new Map(state.fetchers)
|
|
2188
2243
|
} : {}));
|
|
2189
2244
|
}
|
|
2190
|
-
|
|
2191
2245
|
pendingNavigationLoadId = ++incrementingLoadId;
|
|
2192
2246
|
revalidatingFetchers.forEach(rf => {
|
|
2193
2247
|
if (rf.controller) {
|
|
@@ -2196,37 +2250,34 @@
|
|
|
2196
2250
|
// triggered the revalidation
|
|
2197
2251
|
fetchControllers.set(rf.key, rf.controller);
|
|
2198
2252
|
}
|
|
2199
|
-
});
|
|
2253
|
+
});
|
|
2200
2254
|
|
|
2255
|
+
// Proxy navigation abort through to revalidation fetchers
|
|
2201
2256
|
let abortPendingFetchRevalidations = () => revalidatingFetchers.forEach(f => abortFetcher(f.key));
|
|
2202
|
-
|
|
2203
2257
|
if (pendingNavigationController) {
|
|
2204
2258
|
pendingNavigationController.signal.addEventListener("abort", abortPendingFetchRevalidations);
|
|
2205
2259
|
}
|
|
2206
|
-
|
|
2207
2260
|
let {
|
|
2208
2261
|
results,
|
|
2209
2262
|
loaderResults,
|
|
2210
2263
|
fetcherResults
|
|
2211
2264
|
} = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, request);
|
|
2212
|
-
|
|
2213
2265
|
if (request.signal.aborted) {
|
|
2214
2266
|
return {
|
|
2215
2267
|
shortCircuited: true
|
|
2216
2268
|
};
|
|
2217
|
-
}
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
// Clean up _after_ loaders have completed. Don't clean up if we short
|
|
2218
2272
|
// circuited because fetchControllers would have been aborted and
|
|
2219
2273
|
// reassigned to new controllers for the next navigation
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
2274
|
if (pendingNavigationController) {
|
|
2223
2275
|
pendingNavigationController.signal.removeEventListener("abort", abortPendingFetchRevalidations);
|
|
2224
2276
|
}
|
|
2277
|
+
revalidatingFetchers.forEach(rf => fetchControllers.delete(rf.key));
|
|
2225
2278
|
|
|
2226
|
-
|
|
2227
|
-
|
|
2279
|
+
// If any loaders returned a redirect Response, start a new REPLACE navigation
|
|
2228
2280
|
let redirect = findRedirect(results);
|
|
2229
|
-
|
|
2230
2281
|
if (redirect) {
|
|
2231
2282
|
await startRedirectNavigation(state, redirect, {
|
|
2232
2283
|
replace
|
|
@@ -2234,14 +2285,15 @@
|
|
|
2234
2285
|
return {
|
|
2235
2286
|
shortCircuited: true
|
|
2236
2287
|
};
|
|
2237
|
-
}
|
|
2238
|
-
|
|
2288
|
+
}
|
|
2239
2289
|
|
|
2290
|
+
// Process and commit output from loaders
|
|
2240
2291
|
let {
|
|
2241
2292
|
loaderData,
|
|
2242
2293
|
errors
|
|
2243
|
-
} = processLoaderData(state, matches, matchesToLoad, loaderResults, pendingError, revalidatingFetchers, fetcherResults, activeDeferreds);
|
|
2294
|
+
} = processLoaderData(state, matches, matchesToLoad, loaderResults, pendingError, revalidatingFetchers, fetcherResults, activeDeferreds);
|
|
2244
2295
|
|
|
2296
|
+
// Wire up subscribers to update loaderData as promises settle
|
|
2245
2297
|
activeDeferreds.forEach((deferredData, routeId) => {
|
|
2246
2298
|
deferredData.subscribe(aborted => {
|
|
2247
2299
|
// Note: No need to updateState here since the TrackedPromise on
|
|
@@ -2262,56 +2314,50 @@
|
|
|
2262
2314
|
fetchers: new Map(state.fetchers)
|
|
2263
2315
|
} : {});
|
|
2264
2316
|
}
|
|
2265
|
-
|
|
2266
2317
|
function getFetcher(key) {
|
|
2267
2318
|
return state.fetchers.get(key) || IDLE_FETCHER;
|
|
2268
|
-
}
|
|
2269
|
-
|
|
2319
|
+
}
|
|
2270
2320
|
|
|
2321
|
+
// Trigger a fetcher load/submit for the given fetcher key
|
|
2271
2322
|
function fetch(key, routeId, href, opts) {
|
|
2272
2323
|
if (isServer) {
|
|
2273
2324
|
throw new Error("router.fetch() was called during the server render, but it shouldn't be. " + "You are likely calling a useFetcher() method in the body of your component. " + "Try moving it to a useEffect or a callback.");
|
|
2274
2325
|
}
|
|
2275
|
-
|
|
2276
2326
|
if (fetchControllers.has(key)) abortFetcher(key);
|
|
2277
2327
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
2278
2328
|
let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, href, routeId, opts == null ? void 0 : opts.relative);
|
|
2279
2329
|
let matches = matchRoutes(routesToUse, normalizedPath, basename);
|
|
2280
|
-
|
|
2281
2330
|
if (!matches) {
|
|
2282
2331
|
setFetcherError(key, routeId, getInternalRouterError(404, {
|
|
2283
2332
|
pathname: normalizedPath
|
|
2284
2333
|
}));
|
|
2285
2334
|
return;
|
|
2286
2335
|
}
|
|
2287
|
-
|
|
2288
2336
|
let {
|
|
2289
2337
|
path,
|
|
2290
2338
|
submission
|
|
2291
2339
|
} = normalizeNavigateOptions(future.v7_normalizeFormMethod, true, normalizedPath, opts);
|
|
2292
2340
|
let match = getTargetMatch(matches, path);
|
|
2293
2341
|
pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
|
|
2294
|
-
|
|
2295
2342
|
if (submission && isMutationMethod(submission.formMethod)) {
|
|
2296
2343
|
handleFetcherAction(key, routeId, path, match, matches, submission);
|
|
2297
2344
|
return;
|
|
2298
|
-
}
|
|
2299
|
-
// revalidations
|
|
2300
|
-
|
|
2345
|
+
}
|
|
2301
2346
|
|
|
2347
|
+
// Store off the match so we can call it's shouldRevalidate on subsequent
|
|
2348
|
+
// revalidations
|
|
2302
2349
|
fetchLoadMatches.set(key, {
|
|
2303
2350
|
routeId,
|
|
2304
2351
|
path
|
|
2305
2352
|
});
|
|
2306
2353
|
handleFetcherLoader(key, routeId, path, match, matches, submission);
|
|
2307
|
-
}
|
|
2308
|
-
// errors, and revalidation
|
|
2309
|
-
|
|
2354
|
+
}
|
|
2310
2355
|
|
|
2356
|
+
// Call the action for the matched fetcher.submit(), and then handle redirects,
|
|
2357
|
+
// errors, and revalidation
|
|
2311
2358
|
async function handleFetcherAction(key, routeId, path, match, requestMatches, submission) {
|
|
2312
2359
|
interruptActiveLoads();
|
|
2313
2360
|
fetchLoadMatches.delete(key);
|
|
2314
|
-
|
|
2315
2361
|
if (!match.route.action && !match.route.lazy) {
|
|
2316
2362
|
let error = getInternalRouterError(405, {
|
|
2317
2363
|
method: submission.formMethod,
|
|
@@ -2320,49 +2366,43 @@
|
|
|
2320
2366
|
});
|
|
2321
2367
|
setFetcherError(key, routeId, error);
|
|
2322
2368
|
return;
|
|
2323
|
-
}
|
|
2324
|
-
|
|
2369
|
+
}
|
|
2325
2370
|
|
|
2371
|
+
// Put this fetcher into it's submitting state
|
|
2326
2372
|
let existingFetcher = state.fetchers.get(key);
|
|
2327
|
-
|
|
2328
2373
|
let fetcher = _extends({
|
|
2329
2374
|
state: "submitting"
|
|
2330
2375
|
}, submission, {
|
|
2331
2376
|
data: existingFetcher && existingFetcher.data,
|
|
2332
2377
|
" _hasFetcherDoneAnything ": true
|
|
2333
2378
|
});
|
|
2334
|
-
|
|
2335
2379
|
state.fetchers.set(key, fetcher);
|
|
2336
2380
|
updateState({
|
|
2337
2381
|
fetchers: new Map(state.fetchers)
|
|
2338
|
-
});
|
|
2382
|
+
});
|
|
2339
2383
|
|
|
2384
|
+
// Call the action for the fetcher
|
|
2340
2385
|
let abortController = new AbortController();
|
|
2341
2386
|
let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);
|
|
2342
2387
|
fetchControllers.set(key, abortController);
|
|
2343
2388
|
let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, manifest, mapRouteProperties, basename);
|
|
2344
|
-
|
|
2345
2389
|
if (fetchRequest.signal.aborted) {
|
|
2346
2390
|
// We can delete this so long as we weren't aborted by ou our own fetcher
|
|
2347
2391
|
// re-submit which would have put _new_ controller is in fetchControllers
|
|
2348
2392
|
if (fetchControllers.get(key) === abortController) {
|
|
2349
2393
|
fetchControllers.delete(key);
|
|
2350
2394
|
}
|
|
2351
|
-
|
|
2352
2395
|
return;
|
|
2353
2396
|
}
|
|
2354
|
-
|
|
2355
2397
|
if (isRedirectResult(actionResult)) {
|
|
2356
2398
|
fetchControllers.delete(key);
|
|
2357
2399
|
fetchRedirectIds.add(key);
|
|
2358
|
-
|
|
2359
2400
|
let loadingFetcher = _extends({
|
|
2360
2401
|
state: "loading"
|
|
2361
2402
|
}, submission, {
|
|
2362
2403
|
data: undefined,
|
|
2363
2404
|
" _hasFetcherDoneAnything ": true
|
|
2364
2405
|
});
|
|
2365
|
-
|
|
2366
2406
|
state.fetchers.set(key, loadingFetcher);
|
|
2367
2407
|
updateState({
|
|
2368
2408
|
fetchers: new Map(state.fetchers)
|
|
@@ -2371,22 +2411,21 @@
|
|
|
2371
2411
|
submission,
|
|
2372
2412
|
isFetchActionRedirect: true
|
|
2373
2413
|
});
|
|
2374
|
-
}
|
|
2375
|
-
|
|
2414
|
+
}
|
|
2376
2415
|
|
|
2416
|
+
// Process any non-redirect errors thrown
|
|
2377
2417
|
if (isErrorResult(actionResult)) {
|
|
2378
2418
|
setFetcherError(key, routeId, actionResult.error);
|
|
2379
2419
|
return;
|
|
2380
2420
|
}
|
|
2381
|
-
|
|
2382
2421
|
if (isDeferredResult(actionResult)) {
|
|
2383
2422
|
throw getInternalRouterError(400, {
|
|
2384
2423
|
type: "defer-action"
|
|
2385
2424
|
});
|
|
2386
|
-
}
|
|
2387
|
-
// in the middle of a navigation
|
|
2388
|
-
|
|
2425
|
+
}
|
|
2389
2426
|
|
|
2427
|
+
// Start the data load for current matches, or the next location if we're
|
|
2428
|
+
// in the middle of a navigation
|
|
2390
2429
|
let nextLocation = state.navigation.location || state.location;
|
|
2391
2430
|
let revalidationRequest = createClientSideRequest(init.history, nextLocation, abortController.signal);
|
|
2392
2431
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
@@ -2394,22 +2433,21 @@
|
|
|
2394
2433
|
invariant(matches, "Didn't find any matches after fetcher action");
|
|
2395
2434
|
let loadId = ++incrementingLoadId;
|
|
2396
2435
|
fetchReloadIds.set(key, loadId);
|
|
2397
|
-
|
|
2398
2436
|
let loadFetcher = _extends({
|
|
2399
2437
|
state: "loading",
|
|
2400
2438
|
data: actionResult.data
|
|
2401
2439
|
}, submission, {
|
|
2402
2440
|
" _hasFetcherDoneAnything ": true
|
|
2403
2441
|
});
|
|
2404
|
-
|
|
2405
2442
|
state.fetchers.set(key, loadFetcher);
|
|
2406
2443
|
let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, submission, nextLocation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, fetchLoadMatches, routesToUse, basename, {
|
|
2407
2444
|
[match.route.id]: actionResult.data
|
|
2408
2445
|
}, undefined // No need to send through errors since we short circuit above
|
|
2409
|
-
);
|
|
2446
|
+
);
|
|
2447
|
+
|
|
2448
|
+
// Put all revalidating fetchers into the loading state, except for the
|
|
2410
2449
|
// current fetcher which we want to keep in it's current loading state which
|
|
2411
2450
|
// contains it's action submission info + action data
|
|
2412
|
-
|
|
2413
2451
|
revalidatingFetchers.filter(rf => rf.key !== key).forEach(rf => {
|
|
2414
2452
|
let staleKey = rf.key;
|
|
2415
2453
|
let existingFetcher = state.fetchers.get(staleKey);
|
|
@@ -2423,7 +2461,6 @@
|
|
|
2423
2461
|
" _hasFetcherDoneAnything ": true
|
|
2424
2462
|
};
|
|
2425
2463
|
state.fetchers.set(staleKey, revalidatingFetcher);
|
|
2426
|
-
|
|
2427
2464
|
if (rf.controller) {
|
|
2428
2465
|
fetchControllers.set(staleKey, rf.controller);
|
|
2429
2466
|
}
|
|
@@ -2431,49 +2468,50 @@
|
|
|
2431
2468
|
updateState({
|
|
2432
2469
|
fetchers: new Map(state.fetchers)
|
|
2433
2470
|
});
|
|
2434
|
-
|
|
2435
2471
|
let abortPendingFetchRevalidations = () => revalidatingFetchers.forEach(rf => abortFetcher(rf.key));
|
|
2436
|
-
|
|
2437
2472
|
abortController.signal.addEventListener("abort", abortPendingFetchRevalidations);
|
|
2438
2473
|
let {
|
|
2439
2474
|
results,
|
|
2440
2475
|
loaderResults,
|
|
2441
2476
|
fetcherResults
|
|
2442
2477
|
} = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, revalidationRequest);
|
|
2443
|
-
|
|
2444
2478
|
if (abortController.signal.aborted) {
|
|
2445
2479
|
return;
|
|
2446
2480
|
}
|
|
2447
|
-
|
|
2448
2481
|
abortController.signal.removeEventListener("abort", abortPendingFetchRevalidations);
|
|
2449
2482
|
fetchReloadIds.delete(key);
|
|
2450
2483
|
fetchControllers.delete(key);
|
|
2451
2484
|
revalidatingFetchers.forEach(r => fetchControllers.delete(r.key));
|
|
2452
2485
|
let redirect = findRedirect(results);
|
|
2453
|
-
|
|
2454
2486
|
if (redirect) {
|
|
2455
2487
|
return startRedirectNavigation(state, redirect);
|
|
2456
|
-
}
|
|
2457
|
-
|
|
2488
|
+
}
|
|
2458
2489
|
|
|
2490
|
+
// Process and commit output from loaders
|
|
2459
2491
|
let {
|
|
2460
2492
|
loaderData,
|
|
2461
2493
|
errors
|
|
2462
2494
|
} = processLoaderData(state, state.matches, matchesToLoad, loaderResults, undefined, revalidatingFetchers, fetcherResults, activeDeferreds);
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2495
|
+
|
|
2496
|
+
// Since we let revalidations complete even if the submitting fetcher was
|
|
2497
|
+
// deleted, only put it back to idle if it hasn't been deleted
|
|
2498
|
+
if (state.fetchers.has(key)) {
|
|
2499
|
+
let doneFetcher = {
|
|
2500
|
+
state: "idle",
|
|
2501
|
+
data: actionResult.data,
|
|
2502
|
+
formMethod: undefined,
|
|
2503
|
+
formAction: undefined,
|
|
2504
|
+
formEncType: undefined,
|
|
2505
|
+
formData: undefined,
|
|
2506
|
+
" _hasFetcherDoneAnything ": true
|
|
2507
|
+
};
|
|
2508
|
+
state.fetchers.set(key, doneFetcher);
|
|
2509
|
+
}
|
|
2510
|
+
let didAbortFetchLoads = abortStaleFetchLoads(loadId);
|
|
2511
|
+
|
|
2512
|
+
// If we are currently in a navigation loading state and this fetcher is
|
|
2474
2513
|
// more recent than the navigation, we want the newer data so abort the
|
|
2475
2514
|
// navigation and complete it with the fetcher data
|
|
2476
|
-
|
|
2477
2515
|
if (state.navigation.state === "loading" && loadId > pendingNavigationLoadId) {
|
|
2478
2516
|
invariant(pendingAction, "Expected pending action");
|
|
2479
2517
|
pendingNavigationController && pendingNavigationController.abort();
|
|
@@ -2490,17 +2528,17 @@
|
|
|
2490
2528
|
updateState(_extends({
|
|
2491
2529
|
errors,
|
|
2492
2530
|
loaderData: mergeLoaderData(state.loaderData, loaderData, matches, errors)
|
|
2493
|
-
}, didAbortFetchLoads ? {
|
|
2531
|
+
}, didAbortFetchLoads || revalidatingFetchers.length > 0 ? {
|
|
2494
2532
|
fetchers: new Map(state.fetchers)
|
|
2495
2533
|
} : {}));
|
|
2496
2534
|
isRevalidationRequired = false;
|
|
2497
2535
|
}
|
|
2498
|
-
}
|
|
2499
|
-
|
|
2536
|
+
}
|
|
2500
2537
|
|
|
2538
|
+
// Call the matched loader for fetcher.load(), handling redirects, errors, etc.
|
|
2501
2539
|
async function handleFetcherLoader(key, routeId, path, match, matches, submission) {
|
|
2502
|
-
let existingFetcher = state.fetchers.get(key);
|
|
2503
|
-
|
|
2540
|
+
let existingFetcher = state.fetchers.get(key);
|
|
2541
|
+
// Put this fetcher into it's loading state
|
|
2504
2542
|
let loadingFetcher = _extends({
|
|
2505
2543
|
state: "loading",
|
|
2506
2544
|
formMethod: undefined,
|
|
@@ -2511,48 +2549,48 @@
|
|
|
2511
2549
|
data: existingFetcher && existingFetcher.data,
|
|
2512
2550
|
" _hasFetcherDoneAnything ": true
|
|
2513
2551
|
});
|
|
2514
|
-
|
|
2515
2552
|
state.fetchers.set(key, loadingFetcher);
|
|
2516
2553
|
updateState({
|
|
2517
2554
|
fetchers: new Map(state.fetchers)
|
|
2518
|
-
});
|
|
2555
|
+
});
|
|
2519
2556
|
|
|
2557
|
+
// Call the loader for this fetcher route match
|
|
2520
2558
|
let abortController = new AbortController();
|
|
2521
2559
|
let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
|
|
2522
2560
|
fetchControllers.set(key, abortController);
|
|
2523
|
-
let result = await callLoaderOrAction("loader", fetchRequest, match, matches, manifest, mapRouteProperties, basename);
|
|
2561
|
+
let result = await callLoaderOrAction("loader", fetchRequest, match, matches, manifest, mapRouteProperties, basename);
|
|
2562
|
+
|
|
2563
|
+
// Deferred isn't supported for fetcher loads, await everything and treat it
|
|
2524
2564
|
// as a normal load. resolveDeferredData will return undefined if this
|
|
2525
2565
|
// fetcher gets aborted, so we just leave result untouched and short circuit
|
|
2526
2566
|
// below if that happens
|
|
2527
|
-
|
|
2528
2567
|
if (isDeferredResult(result)) {
|
|
2529
2568
|
result = (await resolveDeferredData(result, fetchRequest.signal, true)) || result;
|
|
2530
|
-
}
|
|
2531
|
-
// re-load which would have put _new_ controller is in fetchControllers
|
|
2532
|
-
|
|
2569
|
+
}
|
|
2533
2570
|
|
|
2571
|
+
// We can delete this so long as we weren't aborted by our our own fetcher
|
|
2572
|
+
// re-load which would have put _new_ controller is in fetchControllers
|
|
2534
2573
|
if (fetchControllers.get(key) === abortController) {
|
|
2535
2574
|
fetchControllers.delete(key);
|
|
2536
2575
|
}
|
|
2537
|
-
|
|
2538
2576
|
if (fetchRequest.signal.aborted) {
|
|
2539
2577
|
return;
|
|
2540
|
-
}
|
|
2541
|
-
|
|
2578
|
+
}
|
|
2542
2579
|
|
|
2580
|
+
// If the loader threw a redirect Response, start a new REPLACE navigation
|
|
2543
2581
|
if (isRedirectResult(result)) {
|
|
2544
2582
|
fetchRedirectIds.add(key);
|
|
2545
2583
|
await startRedirectNavigation(state, result);
|
|
2546
2584
|
return;
|
|
2547
|
-
}
|
|
2548
|
-
|
|
2585
|
+
}
|
|
2549
2586
|
|
|
2587
|
+
// Process any non-redirect errors thrown
|
|
2550
2588
|
if (isErrorResult(result)) {
|
|
2551
2589
|
let boundaryMatch = findNearestBoundary(state.matches, routeId);
|
|
2552
|
-
state.fetchers.delete(key);
|
|
2590
|
+
state.fetchers.delete(key);
|
|
2591
|
+
// TODO: In remix, this would reset to IDLE_NAVIGATION if it was a catch -
|
|
2553
2592
|
// do we need to behave any differently with our non-redirect errors?
|
|
2554
2593
|
// What if it was a non-redirect Response?
|
|
2555
|
-
|
|
2556
2594
|
updateState({
|
|
2557
2595
|
fetchers: new Map(state.fetchers),
|
|
2558
2596
|
errors: {
|
|
@@ -2561,9 +2599,9 @@
|
|
|
2561
2599
|
});
|
|
2562
2600
|
return;
|
|
2563
2601
|
}
|
|
2602
|
+
invariant(!isDeferredResult(result), "Unhandled fetcher deferred data");
|
|
2564
2603
|
|
|
2565
|
-
|
|
2566
|
-
|
|
2604
|
+
// Put the fetcher back into an idle state
|
|
2567
2605
|
let doneFetcher = {
|
|
2568
2606
|
state: "idle",
|
|
2569
2607
|
data: result.data,
|
|
@@ -2578,6 +2616,7 @@
|
|
|
2578
2616
|
fetchers: new Map(state.fetchers)
|
|
2579
2617
|
});
|
|
2580
2618
|
}
|
|
2619
|
+
|
|
2581
2620
|
/**
|
|
2582
2621
|
* Utility function to handle redirects returned from an action or loader.
|
|
2583
2622
|
* Normally, a redirect "replaces" the navigation that triggered it. So, for
|
|
@@ -2597,57 +2636,49 @@
|
|
|
2597
2636
|
* actually touch history until we've processed redirects, so we just use
|
|
2598
2637
|
* the history action from the original navigation (PUSH or REPLACE).
|
|
2599
2638
|
*/
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
2639
|
async function startRedirectNavigation(state, redirect, _temp) {
|
|
2603
|
-
var _window;
|
|
2604
|
-
|
|
2605
2640
|
let {
|
|
2606
2641
|
submission,
|
|
2607
2642
|
replace,
|
|
2608
2643
|
isFetchActionRedirect
|
|
2609
2644
|
} = _temp === void 0 ? {} : _temp;
|
|
2610
|
-
|
|
2611
2645
|
if (redirect.revalidate) {
|
|
2612
2646
|
isRevalidationRequired = true;
|
|
2613
2647
|
}
|
|
2614
|
-
|
|
2615
2648
|
let redirectLocation = createLocation(state.location, redirect.location, // TODO: This can be removed once we get rid of useTransition in Remix v2
|
|
2616
2649
|
_extends({
|
|
2617
2650
|
_isRedirect: true
|
|
2618
2651
|
}, isFetchActionRedirect ? {
|
|
2619
2652
|
_isFetchActionRedirect: true
|
|
2620
2653
|
} : {}));
|
|
2621
|
-
invariant(redirectLocation, "Expected a location on the redirect navigation");
|
|
2622
|
-
|
|
2623
|
-
if (ABSOLUTE_URL_REGEX.test(redirect.location) && isBrowser
|
|
2654
|
+
invariant(redirectLocation, "Expected a location on the redirect navigation");
|
|
2655
|
+
// Check if this an absolute external redirect that goes to a new origin
|
|
2656
|
+
if (ABSOLUTE_URL_REGEX.test(redirect.location) && isBrowser) {
|
|
2624
2657
|
let url = init.history.createURL(redirect.location);
|
|
2625
2658
|
let isDifferentBasename = stripBasename(url.pathname, basename) == null;
|
|
2626
|
-
|
|
2627
|
-
if (window.location.origin !== url.origin || isDifferentBasename) {
|
|
2659
|
+
if (routerWindow.location.origin !== url.origin || isDifferentBasename) {
|
|
2628
2660
|
if (replace) {
|
|
2629
|
-
|
|
2661
|
+
routerWindow.location.replace(redirect.location);
|
|
2630
2662
|
} else {
|
|
2631
|
-
|
|
2663
|
+
routerWindow.location.assign(redirect.location);
|
|
2632
2664
|
}
|
|
2633
|
-
|
|
2634
2665
|
return;
|
|
2635
2666
|
}
|
|
2636
|
-
}
|
|
2637
|
-
// redirect until the action/loaders have settled
|
|
2638
|
-
|
|
2667
|
+
}
|
|
2639
2668
|
|
|
2669
|
+
// There's no need to abort on redirects, since we don't detect the
|
|
2670
|
+
// redirect until the action/loaders have settled
|
|
2640
2671
|
pendingNavigationController = null;
|
|
2641
|
-
let redirectHistoryAction = replace === true ? exports.Action.Replace : exports.Action.Push;
|
|
2642
|
-
// state.navigation
|
|
2672
|
+
let redirectHistoryAction = replace === true ? exports.Action.Replace : exports.Action.Push;
|
|
2643
2673
|
|
|
2674
|
+
// Use the incoming submission if provided, fallback on the active one in
|
|
2675
|
+
// state.navigation
|
|
2644
2676
|
let {
|
|
2645
2677
|
formMethod,
|
|
2646
2678
|
formAction,
|
|
2647
2679
|
formEncType,
|
|
2648
2680
|
formData
|
|
2649
2681
|
} = state.navigation;
|
|
2650
|
-
|
|
2651
2682
|
if (!submission && formMethod && formAction && formData && formEncType) {
|
|
2652
2683
|
submission = {
|
|
2653
2684
|
formMethod,
|
|
@@ -2655,11 +2686,11 @@
|
|
|
2655
2686
|
formEncType,
|
|
2656
2687
|
formData
|
|
2657
2688
|
};
|
|
2658
|
-
}
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2691
|
+
// If this was a 307/308 submission we want to preserve the HTTP method and
|
|
2659
2692
|
// re-submit the GET/POST/PUT/PATCH/DELETE as a submission navigation to the
|
|
2660
2693
|
// redirected location
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
2694
|
if (redirectPreserveMethodStatusCodes.has(redirect.status) && submission && isMutationMethod(submission.formMethod)) {
|
|
2664
2695
|
await startNavigation(redirectHistoryAction, redirectLocation, {
|
|
2665
2696
|
submission: _extends({}, submission, {
|
|
@@ -2701,7 +2732,6 @@
|
|
|
2701
2732
|
});
|
|
2702
2733
|
}
|
|
2703
2734
|
}
|
|
2704
|
-
|
|
2705
2735
|
async function callLoadersAndMaybeResolveData(currentMatches, matches, matchesToLoad, fetchersToLoad, request) {
|
|
2706
2736
|
// Call all navigation loaders and revalidating fetcher loaders in parallel,
|
|
2707
2737
|
// then slice off the results into separate arrays so we can handle them
|
|
@@ -2728,14 +2758,15 @@
|
|
|
2728
2758
|
fetcherResults
|
|
2729
2759
|
};
|
|
2730
2760
|
}
|
|
2731
|
-
|
|
2732
2761
|
function interruptActiveLoads() {
|
|
2733
2762
|
// Every interruption triggers a revalidation
|
|
2734
|
-
isRevalidationRequired = true;
|
|
2735
|
-
// revalidation
|
|
2763
|
+
isRevalidationRequired = true;
|
|
2736
2764
|
|
|
2737
|
-
|
|
2765
|
+
// Cancel pending route-level deferreds and mark cancelled routes for
|
|
2766
|
+
// revalidation
|
|
2767
|
+
cancelledDeferredRoutes.push(...cancelActiveDeferreds());
|
|
2738
2768
|
|
|
2769
|
+
// Abort in-flight fetcher loads
|
|
2739
2770
|
fetchLoadMatches.forEach((_, key) => {
|
|
2740
2771
|
if (fetchControllers.has(key)) {
|
|
2741
2772
|
cancelledFetcherLoads.push(key);
|
|
@@ -2743,7 +2774,6 @@
|
|
|
2743
2774
|
}
|
|
2744
2775
|
});
|
|
2745
2776
|
}
|
|
2746
|
-
|
|
2747
2777
|
function setFetcherError(key, routeId, error) {
|
|
2748
2778
|
let boundaryMatch = findNearestBoundary(state.matches, routeId);
|
|
2749
2779
|
deleteFetcher(key);
|
|
@@ -2754,22 +2784,25 @@
|
|
|
2754
2784
|
fetchers: new Map(state.fetchers)
|
|
2755
2785
|
});
|
|
2756
2786
|
}
|
|
2757
|
-
|
|
2758
2787
|
function deleteFetcher(key) {
|
|
2759
|
-
|
|
2788
|
+
let fetcher = state.fetchers.get(key);
|
|
2789
|
+
// Don't abort the controller if this is a deletion of a fetcher.submit()
|
|
2790
|
+
// in it's loading phase since - we don't want to abort the corresponding
|
|
2791
|
+
// revalidation and want them to complete and land
|
|
2792
|
+
if (fetchControllers.has(key) && !(fetcher && fetcher.state === "loading" && fetchReloadIds.has(key))) {
|
|
2793
|
+
abortFetcher(key);
|
|
2794
|
+
}
|
|
2760
2795
|
fetchLoadMatches.delete(key);
|
|
2761
2796
|
fetchReloadIds.delete(key);
|
|
2762
2797
|
fetchRedirectIds.delete(key);
|
|
2763
2798
|
state.fetchers.delete(key);
|
|
2764
2799
|
}
|
|
2765
|
-
|
|
2766
2800
|
function abortFetcher(key) {
|
|
2767
2801
|
let controller = fetchControllers.get(key);
|
|
2768
2802
|
invariant(controller, "Expected fetch controller: " + key);
|
|
2769
2803
|
controller.abort();
|
|
2770
2804
|
fetchControllers.delete(key);
|
|
2771
2805
|
}
|
|
2772
|
-
|
|
2773
2806
|
function markFetchersDone(keys) {
|
|
2774
2807
|
for (let key of keys) {
|
|
2775
2808
|
let fetcher = getFetcher(key);
|
|
@@ -2785,34 +2818,27 @@
|
|
|
2785
2818
|
state.fetchers.set(key, doneFetcher);
|
|
2786
2819
|
}
|
|
2787
2820
|
}
|
|
2788
|
-
|
|
2789
2821
|
function markFetchRedirectsDone() {
|
|
2790
2822
|
let doneKeys = [];
|
|
2791
2823
|
let updatedFetchers = false;
|
|
2792
|
-
|
|
2793
2824
|
for (let key of fetchRedirectIds) {
|
|
2794
2825
|
let fetcher = state.fetchers.get(key);
|
|
2795
2826
|
invariant(fetcher, "Expected fetcher: " + key);
|
|
2796
|
-
|
|
2797
2827
|
if (fetcher.state === "loading") {
|
|
2798
2828
|
fetchRedirectIds.delete(key);
|
|
2799
2829
|
doneKeys.push(key);
|
|
2800
2830
|
updatedFetchers = true;
|
|
2801
2831
|
}
|
|
2802
2832
|
}
|
|
2803
|
-
|
|
2804
2833
|
markFetchersDone(doneKeys);
|
|
2805
2834
|
return updatedFetchers;
|
|
2806
2835
|
}
|
|
2807
|
-
|
|
2808
2836
|
function abortStaleFetchLoads(landedId) {
|
|
2809
2837
|
let yeetedKeys = [];
|
|
2810
|
-
|
|
2811
2838
|
for (let [key, id] of fetchReloadIds) {
|
|
2812
2839
|
if (id < landedId) {
|
|
2813
2840
|
let fetcher = state.fetchers.get(key);
|
|
2814
2841
|
invariant(fetcher, "Expected fetcher: " + key);
|
|
2815
|
-
|
|
2816
2842
|
if (fetcher.state === "loading") {
|
|
2817
2843
|
abortFetcher(key);
|
|
2818
2844
|
fetchReloadIds.delete(key);
|
|
@@ -2820,67 +2846,59 @@
|
|
|
2820
2846
|
}
|
|
2821
2847
|
}
|
|
2822
2848
|
}
|
|
2823
|
-
|
|
2824
2849
|
markFetchersDone(yeetedKeys);
|
|
2825
2850
|
return yeetedKeys.length > 0;
|
|
2826
2851
|
}
|
|
2827
|
-
|
|
2828
2852
|
function getBlocker(key, fn) {
|
|
2829
2853
|
let blocker = state.blockers.get(key) || IDLE_BLOCKER;
|
|
2830
|
-
|
|
2831
2854
|
if (blockerFunctions.get(key) !== fn) {
|
|
2832
2855
|
blockerFunctions.set(key, fn);
|
|
2833
2856
|
}
|
|
2834
|
-
|
|
2835
2857
|
return blocker;
|
|
2836
2858
|
}
|
|
2837
|
-
|
|
2838
2859
|
function deleteBlocker(key) {
|
|
2839
2860
|
state.blockers.delete(key);
|
|
2840
2861
|
blockerFunctions.delete(key);
|
|
2841
|
-
}
|
|
2842
|
-
|
|
2862
|
+
}
|
|
2843
2863
|
|
|
2864
|
+
// Utility function to update blockers, ensuring valid state transitions
|
|
2844
2865
|
function updateBlocker(key, newBlocker) {
|
|
2845
|
-
let blocker = state.blockers.get(key) || IDLE_BLOCKER;
|
|
2846
|
-
// https://mermaid.live/edit#pako:eNqVkc9OwzAMxl8l8nnjAYrEtDIOHEBIgwvKJTReGy3_lDpIqO27k6awMG0XcrLlnz87nwdonESogKXXBuE79rq75XZO3-yHds0RJVuv70YrPlUrCEe2HfrORS3rubqZfuhtpg5C9wk5tZ4VKcRUq88q9Z8RS0-48cE1iHJkL0ugbHuFLus9L6spZy8nX9MP2CNdomVaposqu3fGayT8T8-jJQwhepo_UtpgBQaDEUom04dZhAN1aJBDlUKJBxE1ceB2Smj0Mln-IBW5AFU2dwUiktt_2Qaq2dBfaKdEup85UV7Yd-dKjlnkabl2Pvr0DTkTreM
|
|
2866
|
+
let blocker = state.blockers.get(key) || IDLE_BLOCKER;
|
|
2847
2867
|
|
|
2868
|
+
// Poor mans state machine :)
|
|
2869
|
+
// https://mermaid.live/edit#pako:eNqVkc9OwzAMxl8l8nnjAYrEtDIOHEBIgwvKJTReGy3_lDpIqO27k6awMG0XcrLlnz87nwdonESogKXXBuE79rq75XZO3-yHds0RJVuv70YrPlUrCEe2HfrORS3rubqZfuhtpg5C9wk5tZ4VKcRUq88q9Z8RS0-48cE1iHJkL0ugbHuFLus9L6spZy8nX9MP2CNdomVaposqu3fGayT8T8-jJQwhepo_UtpgBQaDEUom04dZhAN1aJBDlUKJBxE1ceB2Smj0Mln-IBW5AFU2dwUiktt_2Qaq2dBfaKdEup85UV7Yd-dKjlnkabl2Pvr0DTkTreM
|
|
2848
2870
|
invariant(blocker.state === "unblocked" && newBlocker.state === "blocked" || blocker.state === "blocked" && newBlocker.state === "blocked" || blocker.state === "blocked" && newBlocker.state === "proceeding" || blocker.state === "blocked" && newBlocker.state === "unblocked" || blocker.state === "proceeding" && newBlocker.state === "unblocked", "Invalid blocker state transition: " + blocker.state + " -> " + newBlocker.state);
|
|
2849
2871
|
state.blockers.set(key, newBlocker);
|
|
2850
2872
|
updateState({
|
|
2851
2873
|
blockers: new Map(state.blockers)
|
|
2852
2874
|
});
|
|
2853
2875
|
}
|
|
2854
|
-
|
|
2855
2876
|
function shouldBlockNavigation(_ref2) {
|
|
2856
2877
|
let {
|
|
2857
2878
|
currentLocation,
|
|
2858
2879
|
nextLocation,
|
|
2859
2880
|
historyAction
|
|
2860
2881
|
} = _ref2;
|
|
2861
|
-
|
|
2862
2882
|
if (blockerFunctions.size === 0) {
|
|
2863
2883
|
return;
|
|
2864
|
-
}
|
|
2865
|
-
// any compelling use cases for multi-blocker yet
|
|
2866
|
-
|
|
2884
|
+
}
|
|
2867
2885
|
|
|
2886
|
+
// We ony support a single active blocker at the moment since we don't have
|
|
2887
|
+
// any compelling use cases for multi-blocker yet
|
|
2868
2888
|
if (blockerFunctions.size > 1) {
|
|
2869
2889
|
warning(false, "A router only supports one blocker at a time");
|
|
2870
2890
|
}
|
|
2871
|
-
|
|
2872
2891
|
let entries = Array.from(blockerFunctions.entries());
|
|
2873
2892
|
let [blockerKey, blockerFunction] = entries[entries.length - 1];
|
|
2874
2893
|
let blocker = state.blockers.get(blockerKey);
|
|
2875
|
-
|
|
2876
2894
|
if (blocker && blocker.state === "proceeding") {
|
|
2877
2895
|
// If the blocker is currently proceeding, we don't need to re-check
|
|
2878
2896
|
// it and can let this navigation continue
|
|
2879
2897
|
return;
|
|
2880
|
-
}
|
|
2881
|
-
// user-provided blocker function
|
|
2882
|
-
|
|
2898
|
+
}
|
|
2883
2899
|
|
|
2900
|
+
// At this point, we know we're unblocked/blocked so we need to check the
|
|
2901
|
+
// user-provided blocker function
|
|
2884
2902
|
if (blockerFunction({
|
|
2885
2903
|
currentLocation,
|
|
2886
2904
|
nextLocation,
|
|
@@ -2889,7 +2907,6 @@
|
|
|
2889
2907
|
return blockerKey;
|
|
2890
2908
|
}
|
|
2891
2909
|
}
|
|
2892
|
-
|
|
2893
2910
|
function cancelActiveDeferreds(predicate) {
|
|
2894
2911
|
let cancelledRouteIds = [];
|
|
2895
2912
|
activeDeferreds.forEach((dfd, routeId) => {
|
|
@@ -2903,37 +2920,33 @@
|
|
|
2903
2920
|
}
|
|
2904
2921
|
});
|
|
2905
2922
|
return cancelledRouteIds;
|
|
2906
|
-
}
|
|
2907
|
-
// used by the <ScrollRestoration> component
|
|
2908
|
-
|
|
2923
|
+
}
|
|
2909
2924
|
|
|
2925
|
+
// Opt in to capturing and reporting scroll positions during navigations,
|
|
2926
|
+
// used by the <ScrollRestoration> component
|
|
2910
2927
|
function enableScrollRestoration(positions, getPosition, getKey) {
|
|
2911
2928
|
savedScrollPositions = positions;
|
|
2912
2929
|
getScrollPosition = getPosition;
|
|
2930
|
+
getScrollRestorationKey = getKey || (location => location.key);
|
|
2913
2931
|
|
|
2914
|
-
|
|
2932
|
+
// Perform initial hydration scroll restoration, since we miss the boat on
|
|
2915
2933
|
// the initial updateState() because we've not yet rendered <ScrollRestoration/>
|
|
2916
2934
|
// and therefore have no savedScrollPositions available
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
2935
|
if (!initialScrollRestored && state.navigation === IDLE_NAVIGATION) {
|
|
2920
2936
|
initialScrollRestored = true;
|
|
2921
2937
|
let y = getSavedScrollPosition(state.location, state.matches);
|
|
2922
|
-
|
|
2923
2938
|
if (y != null) {
|
|
2924
2939
|
updateState({
|
|
2925
2940
|
restoreScrollPosition: y
|
|
2926
2941
|
});
|
|
2927
2942
|
}
|
|
2928
2943
|
}
|
|
2929
|
-
|
|
2930
2944
|
return () => {
|
|
2931
2945
|
savedScrollPositions = null;
|
|
2932
2946
|
getScrollPosition = null;
|
|
2933
2947
|
getScrollRestorationKey = null;
|
|
2934
2948
|
};
|
|
2935
2949
|
}
|
|
2936
|
-
|
|
2937
2950
|
function saveScrollPosition(location, matches) {
|
|
2938
2951
|
if (savedScrollPositions && getScrollRestorationKey && getScrollPosition) {
|
|
2939
2952
|
let userMatches = matches.map(m => createUseMatchesMatch(m, state.loaderData));
|
|
@@ -2941,39 +2954,31 @@
|
|
|
2941
2954
|
savedScrollPositions[key] = getScrollPosition();
|
|
2942
2955
|
}
|
|
2943
2956
|
}
|
|
2944
|
-
|
|
2945
2957
|
function getSavedScrollPosition(location, matches) {
|
|
2946
2958
|
if (savedScrollPositions && getScrollRestorationKey && getScrollPosition) {
|
|
2947
2959
|
let userMatches = matches.map(m => createUseMatchesMatch(m, state.loaderData));
|
|
2948
2960
|
let key = getScrollRestorationKey(location, userMatches) || location.key;
|
|
2949
2961
|
let y = savedScrollPositions[key];
|
|
2950
|
-
|
|
2951
2962
|
if (typeof y === "number") {
|
|
2952
2963
|
return y;
|
|
2953
2964
|
}
|
|
2954
2965
|
}
|
|
2955
|
-
|
|
2956
2966
|
return null;
|
|
2957
2967
|
}
|
|
2958
|
-
|
|
2959
2968
|
function _internalSetRoutes(newRoutes) {
|
|
2960
2969
|
manifest = {};
|
|
2961
2970
|
inFlightDataRoutes = convertRoutesToDataRoutes(newRoutes, mapRouteProperties, undefined, manifest);
|
|
2962
2971
|
}
|
|
2963
|
-
|
|
2964
2972
|
router = {
|
|
2965
2973
|
get basename() {
|
|
2966
2974
|
return basename;
|
|
2967
2975
|
},
|
|
2968
|
-
|
|
2969
2976
|
get state() {
|
|
2970
2977
|
return state;
|
|
2971
2978
|
},
|
|
2972
|
-
|
|
2973
2979
|
get routes() {
|
|
2974
2980
|
return dataRoutes;
|
|
2975
2981
|
},
|
|
2976
|
-
|
|
2977
2982
|
initialize,
|
|
2978
2983
|
subscribe,
|
|
2979
2984
|
enableScrollRestoration,
|
|
@@ -2996,7 +3001,9 @@
|
|
|
2996
3001
|
_internalSetRoutes
|
|
2997
3002
|
};
|
|
2998
3003
|
return router;
|
|
2999
|
-
}
|
|
3004
|
+
}
|
|
3005
|
+
//#endregion
|
|
3006
|
+
|
|
3000
3007
|
////////////////////////////////////////////////////////////////////////////////
|
|
3001
3008
|
//#region createStaticHandler
|
|
3002
3009
|
////////////////////////////////////////////////////////////////////////////////
|
|
@@ -3007,21 +3014,19 @@
|
|
|
3007
3014
|
let manifest = {};
|
|
3008
3015
|
let basename = (opts ? opts.basename : null) || "/";
|
|
3009
3016
|
let mapRouteProperties;
|
|
3010
|
-
|
|
3011
3017
|
if (opts != null && opts.mapRouteProperties) {
|
|
3012
3018
|
mapRouteProperties = opts.mapRouteProperties;
|
|
3013
3019
|
} else if (opts != null && opts.detectErrorBoundary) {
|
|
3014
3020
|
// If they are still using the deprecated version, wrap it with the new API
|
|
3015
3021
|
let detectErrorBoundary = opts.detectErrorBoundary;
|
|
3016
|
-
|
|
3017
3022
|
mapRouteProperties = route => ({
|
|
3018
3023
|
hasErrorBoundary: detectErrorBoundary(route)
|
|
3019
3024
|
});
|
|
3020
3025
|
} else {
|
|
3021
3026
|
mapRouteProperties = defaultMapRouteProperties;
|
|
3022
3027
|
}
|
|
3023
|
-
|
|
3024
3028
|
let dataRoutes = convertRoutesToDataRoutes(routes, mapRouteProperties, undefined, manifest);
|
|
3029
|
+
|
|
3025
3030
|
/**
|
|
3026
3031
|
* The query() method is intended for document requests, in which we want to
|
|
3027
3032
|
* call an optional action and potentially multiple loaders for all nested
|
|
@@ -3041,7 +3046,6 @@
|
|
|
3041
3046
|
* propagate that out and return the raw Response so the HTTP server can
|
|
3042
3047
|
* return it directly.
|
|
3043
3048
|
*/
|
|
3044
|
-
|
|
3045
3049
|
async function query(request, _temp2) {
|
|
3046
3050
|
let {
|
|
3047
3051
|
requestContext
|
|
@@ -3049,8 +3053,9 @@
|
|
|
3049
3053
|
let url = new URL(request.url);
|
|
3050
3054
|
let method = request.method;
|
|
3051
3055
|
let location = createLocation("", createPath(url), null, "default");
|
|
3052
|
-
let matches = matchRoutes(dataRoutes, location, basename);
|
|
3056
|
+
let matches = matchRoutes(dataRoutes, location, basename);
|
|
3053
3057
|
|
|
3058
|
+
// SSR supports HEAD requests while SPA doesn't
|
|
3054
3059
|
if (!isValidMethod(method) && method !== "HEAD") {
|
|
3055
3060
|
let error = getInternalRouterError(405, {
|
|
3056
3061
|
method
|
|
@@ -3096,21 +3101,20 @@
|
|
|
3096
3101
|
activeDeferreds: null
|
|
3097
3102
|
};
|
|
3098
3103
|
}
|
|
3099
|
-
|
|
3100
3104
|
let result = await queryImpl(request, location, matches, requestContext);
|
|
3101
|
-
|
|
3102
3105
|
if (isResponse(result)) {
|
|
3103
3106
|
return result;
|
|
3104
|
-
}
|
|
3107
|
+
}
|
|
3108
|
+
|
|
3109
|
+
// When returning StaticHandlerContext, we patch back in the location here
|
|
3105
3110
|
// since we need it for React Context. But this helps keep our submit and
|
|
3106
3111
|
// loadRouteData operating on a Request instead of a Location
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
3112
|
return _extends({
|
|
3110
3113
|
location,
|
|
3111
3114
|
basename
|
|
3112
3115
|
}, result);
|
|
3113
3116
|
}
|
|
3117
|
+
|
|
3114
3118
|
/**
|
|
3115
3119
|
* The queryRoute() method is intended for targeted route requests, either
|
|
3116
3120
|
* for fetch ?_data requests or resource route requests. In this case, we
|
|
@@ -3131,8 +3135,6 @@
|
|
|
3131
3135
|
* code. Examples here are 404 and 405 errors that occur prior to reaching
|
|
3132
3136
|
* any user-defined loaders.
|
|
3133
3137
|
*/
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
3138
|
async function queryRoute(request, _temp3) {
|
|
3137
3139
|
let {
|
|
3138
3140
|
routeId,
|
|
@@ -3141,8 +3143,9 @@
|
|
|
3141
3143
|
let url = new URL(request.url);
|
|
3142
3144
|
let method = request.method;
|
|
3143
3145
|
let location = createLocation("", createPath(url), null, "default");
|
|
3144
|
-
let matches = matchRoutes(dataRoutes, location, basename);
|
|
3146
|
+
let matches = matchRoutes(dataRoutes, location, basename);
|
|
3145
3147
|
|
|
3148
|
+
// SSR supports HEAD requests while SPA doesn't
|
|
3146
3149
|
if (!isValidMethod(method) && method !== "HEAD" && method !== "OPTIONS") {
|
|
3147
3150
|
throw getInternalRouterError(405, {
|
|
3148
3151
|
method
|
|
@@ -3152,9 +3155,7 @@
|
|
|
3152
3155
|
pathname: location.pathname
|
|
3153
3156
|
});
|
|
3154
3157
|
}
|
|
3155
|
-
|
|
3156
3158
|
let match = routeId ? matches.find(m => m.route.id === routeId) : getTargetMatch(matches, location);
|
|
3157
|
-
|
|
3158
3159
|
if (routeId && !match) {
|
|
3159
3160
|
throw getInternalRouterError(403, {
|
|
3160
3161
|
pathname: location.pathname,
|
|
@@ -3166,52 +3167,40 @@
|
|
|
3166
3167
|
pathname: location.pathname
|
|
3167
3168
|
});
|
|
3168
3169
|
}
|
|
3169
|
-
|
|
3170
3170
|
let result = await queryImpl(request, location, matches, requestContext, match);
|
|
3171
|
-
|
|
3172
3171
|
if (isResponse(result)) {
|
|
3173
3172
|
return result;
|
|
3174
3173
|
}
|
|
3175
|
-
|
|
3176
3174
|
let error = result.errors ? Object.values(result.errors)[0] : undefined;
|
|
3177
|
-
|
|
3178
3175
|
if (error !== undefined) {
|
|
3179
3176
|
// If we got back result.errors, that means the loader/action threw
|
|
3180
3177
|
// _something_ that wasn't a Response, but it's not guaranteed/required
|
|
3181
3178
|
// to be an `instanceof Error` either, so we have to use throw here to
|
|
3182
3179
|
// preserve the "error" state outside of queryImpl.
|
|
3183
3180
|
throw error;
|
|
3184
|
-
}
|
|
3185
|
-
|
|
3181
|
+
}
|
|
3186
3182
|
|
|
3183
|
+
// Pick off the right state value to return
|
|
3187
3184
|
if (result.actionData) {
|
|
3188
3185
|
return Object.values(result.actionData)[0];
|
|
3189
3186
|
}
|
|
3190
|
-
|
|
3191
3187
|
if (result.loaderData) {
|
|
3192
3188
|
var _result$activeDeferre;
|
|
3193
|
-
|
|
3194
3189
|
let data = Object.values(result.loaderData)[0];
|
|
3195
|
-
|
|
3196
3190
|
if ((_result$activeDeferre = result.activeDeferreds) != null && _result$activeDeferre[match.route.id]) {
|
|
3197
3191
|
data[UNSAFE_DEFERRED_SYMBOL] = result.activeDeferreds[match.route.id];
|
|
3198
3192
|
}
|
|
3199
|
-
|
|
3200
3193
|
return data;
|
|
3201
3194
|
}
|
|
3202
|
-
|
|
3203
3195
|
return undefined;
|
|
3204
3196
|
}
|
|
3205
|
-
|
|
3206
3197
|
async function queryImpl(request, location, matches, requestContext, routeMatch) {
|
|
3207
3198
|
invariant(request.signal, "query()/queryRoute() requests must contain an AbortController signal");
|
|
3208
|
-
|
|
3209
3199
|
try {
|
|
3210
3200
|
if (isMutationMethod(request.method.toLowerCase())) {
|
|
3211
3201
|
let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, routeMatch != null);
|
|
3212
3202
|
return result;
|
|
3213
3203
|
}
|
|
3214
|
-
|
|
3215
3204
|
let result = await loadRouteData(request, matches, requestContext, routeMatch);
|
|
3216
3205
|
return isResponse(result) ? result : _extends({}, result, {
|
|
3217
3206
|
actionData: null,
|
|
@@ -3225,47 +3214,38 @@
|
|
|
3225
3214
|
if (e.type === ResultType.error && !isRedirectResponse(e.response)) {
|
|
3226
3215
|
throw e.response;
|
|
3227
3216
|
}
|
|
3228
|
-
|
|
3229
3217
|
return e.response;
|
|
3230
|
-
}
|
|
3218
|
+
}
|
|
3219
|
+
// Redirects are always returned since they don't propagate to catch
|
|
3231
3220
|
// boundaries
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
3221
|
if (isRedirectResponse(e)) {
|
|
3235
3222
|
return e;
|
|
3236
3223
|
}
|
|
3237
|
-
|
|
3238
3224
|
throw e;
|
|
3239
3225
|
}
|
|
3240
3226
|
}
|
|
3241
|
-
|
|
3242
3227
|
async function submit(request, matches, actionMatch, requestContext, isRouteRequest) {
|
|
3243
3228
|
let result;
|
|
3244
|
-
|
|
3245
3229
|
if (!actionMatch.route.action && !actionMatch.route.lazy) {
|
|
3246
3230
|
let error = getInternalRouterError(405, {
|
|
3247
3231
|
method: request.method,
|
|
3248
3232
|
pathname: new URL(request.url).pathname,
|
|
3249
3233
|
routeId: actionMatch.route.id
|
|
3250
3234
|
});
|
|
3251
|
-
|
|
3252
3235
|
if (isRouteRequest) {
|
|
3253
3236
|
throw error;
|
|
3254
3237
|
}
|
|
3255
|
-
|
|
3256
3238
|
result = {
|
|
3257
3239
|
type: ResultType.error,
|
|
3258
3240
|
error
|
|
3259
3241
|
};
|
|
3260
3242
|
} else {
|
|
3261
3243
|
result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, mapRouteProperties, basename, true, isRouteRequest, requestContext);
|
|
3262
|
-
|
|
3263
3244
|
if (request.signal.aborted) {
|
|
3264
3245
|
let method = isRouteRequest ? "queryRoute" : "query";
|
|
3265
3246
|
throw new Error(method + "() call aborted");
|
|
3266
3247
|
}
|
|
3267
3248
|
}
|
|
3268
|
-
|
|
3269
3249
|
if (isRedirectResult(result)) {
|
|
3270
3250
|
// Uhhhh - this should never happen, we should always throw these from
|
|
3271
3251
|
// callLoaderOrAction, but the type narrowing here keeps TS happy and we
|
|
@@ -3278,29 +3258,24 @@
|
|
|
3278
3258
|
}
|
|
3279
3259
|
});
|
|
3280
3260
|
}
|
|
3281
|
-
|
|
3282
3261
|
if (isDeferredResult(result)) {
|
|
3283
3262
|
let error = getInternalRouterError(400, {
|
|
3284
3263
|
type: "defer-action"
|
|
3285
3264
|
});
|
|
3286
|
-
|
|
3287
3265
|
if (isRouteRequest) {
|
|
3288
3266
|
throw error;
|
|
3289
3267
|
}
|
|
3290
|
-
|
|
3291
3268
|
result = {
|
|
3292
3269
|
type: ResultType.error,
|
|
3293
3270
|
error
|
|
3294
3271
|
};
|
|
3295
3272
|
}
|
|
3296
|
-
|
|
3297
3273
|
if (isRouteRequest) {
|
|
3298
3274
|
// Note: This should only be non-Response values if we get here, since
|
|
3299
3275
|
// isRouteRequest should throw any Response received in callLoaderOrAction
|
|
3300
3276
|
if (isErrorResult(result)) {
|
|
3301
3277
|
throw result.error;
|
|
3302
3278
|
}
|
|
3303
|
-
|
|
3304
3279
|
return {
|
|
3305
3280
|
matches: [actionMatch],
|
|
3306
3281
|
loaderData: {},
|
|
@@ -3316,15 +3291,15 @@
|
|
|
3316
3291
|
activeDeferreds: null
|
|
3317
3292
|
};
|
|
3318
3293
|
}
|
|
3319
|
-
|
|
3320
3294
|
if (isErrorResult(result)) {
|
|
3321
3295
|
// Store off the pending error - we use it to determine which loaders
|
|
3322
3296
|
// to call and will commit it when we complete the navigation
|
|
3323
3297
|
let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
|
|
3324
3298
|
let context = await loadRouteData(request, matches, requestContext, undefined, {
|
|
3325
3299
|
[boundaryMatch.route.id]: result.error
|
|
3326
|
-
});
|
|
3300
|
+
});
|
|
3327
3301
|
|
|
3302
|
+
// action status codes take precedence over loader status codes
|
|
3328
3303
|
return _extends({}, context, {
|
|
3329
3304
|
statusCode: isRouteErrorResponse(result.error) ? result.error.status : 500,
|
|
3330
3305
|
actionData: null,
|
|
@@ -3332,9 +3307,9 @@
|
|
|
3332
3307
|
[actionMatch.route.id]: result.headers
|
|
3333
3308
|
} : {})
|
|
3334
3309
|
});
|
|
3335
|
-
}
|
|
3336
|
-
|
|
3310
|
+
}
|
|
3337
3311
|
|
|
3312
|
+
// Create a GET request for the loaders
|
|
3338
3313
|
let loaderRequest = new Request(request.url, {
|
|
3339
3314
|
headers: request.headers,
|
|
3340
3315
|
redirect: request.redirect,
|
|
@@ -3352,10 +3327,10 @@
|
|
|
3352
3327
|
} : {})
|
|
3353
3328
|
});
|
|
3354
3329
|
}
|
|
3355
|
-
|
|
3356
3330
|
async function loadRouteData(request, matches, requestContext, routeMatch, pendingActionError) {
|
|
3357
|
-
let isRouteRequest = routeMatch != null;
|
|
3331
|
+
let isRouteRequest = routeMatch != null;
|
|
3358
3332
|
|
|
3333
|
+
// Short circuit if we have no loaders to run (queryRoute())
|
|
3359
3334
|
if (isRouteRequest && !(routeMatch != null && routeMatch.route.loader) && !(routeMatch != null && routeMatch.route.lazy)) {
|
|
3360
3335
|
throw getInternalRouterError(400, {
|
|
3361
3336
|
method: request.method,
|
|
@@ -3363,10 +3338,10 @@
|
|
|
3363
3338
|
routeId: routeMatch == null ? void 0 : routeMatch.route.id
|
|
3364
3339
|
});
|
|
3365
3340
|
}
|
|
3366
|
-
|
|
3367
3341
|
let requestMatches = routeMatch ? [routeMatch] : getLoaderMatchesUntilBoundary(matches, Object.keys(pendingActionError || {})[0]);
|
|
3368
|
-
let matchesToLoad = requestMatches.filter(m => m.route.loader || m.route.lazy);
|
|
3342
|
+
let matchesToLoad = requestMatches.filter(m => m.route.loader || m.route.lazy);
|
|
3369
3343
|
|
|
3344
|
+
// Short circuit if we have no loaders to run (query())
|
|
3370
3345
|
if (matchesToLoad.length === 0) {
|
|
3371
3346
|
return {
|
|
3372
3347
|
matches,
|
|
@@ -3380,18 +3355,17 @@
|
|
|
3380
3355
|
activeDeferreds: null
|
|
3381
3356
|
};
|
|
3382
3357
|
}
|
|
3383
|
-
|
|
3384
3358
|
let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, mapRouteProperties, basename, true, isRouteRequest, requestContext))]);
|
|
3385
|
-
|
|
3386
3359
|
if (request.signal.aborted) {
|
|
3387
3360
|
let method = isRouteRequest ? "queryRoute" : "query";
|
|
3388
3361
|
throw new Error(method + "() call aborted");
|
|
3389
|
-
}
|
|
3390
|
-
|
|
3362
|
+
}
|
|
3391
3363
|
|
|
3364
|
+
// Process and commit output from loaders
|
|
3392
3365
|
let activeDeferreds = new Map();
|
|
3393
|
-
let context = processRouteLoaderData(matches, matchesToLoad, results, pendingActionError, activeDeferreds);
|
|
3366
|
+
let context = processRouteLoaderData(matches, matchesToLoad, results, pendingActionError, activeDeferreds);
|
|
3394
3367
|
|
|
3368
|
+
// Add a null for any non-loader matches for proper revalidation on the client
|
|
3395
3369
|
let executedLoaders = new Set(matchesToLoad.map(match => match.route.id));
|
|
3396
3370
|
matches.forEach(match => {
|
|
3397
3371
|
if (!executedLoaders.has(match.route.id)) {
|
|
@@ -3403,13 +3377,15 @@
|
|
|
3403
3377
|
activeDeferreds: activeDeferreds.size > 0 ? Object.fromEntries(activeDeferreds.entries()) : null
|
|
3404
3378
|
});
|
|
3405
3379
|
}
|
|
3406
|
-
|
|
3407
3380
|
return {
|
|
3408
3381
|
dataRoutes,
|
|
3409
3382
|
query,
|
|
3410
3383
|
queryRoute
|
|
3411
3384
|
};
|
|
3412
|
-
}
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3387
|
+
//#endregion
|
|
3388
|
+
|
|
3413
3389
|
////////////////////////////////////////////////////////////////////////////////
|
|
3414
3390
|
//#region Helpers
|
|
3415
3391
|
////////////////////////////////////////////////////////////////////////////////
|
|
@@ -3418,7 +3394,6 @@
|
|
|
3418
3394
|
* Given an existing StaticHandlerContext and an error thrown at render time,
|
|
3419
3395
|
* provide an updated StaticHandlerContext suitable for a second SSR render
|
|
3420
3396
|
*/
|
|
3421
|
-
|
|
3422
3397
|
function getStaticContextFromError(routes, context, error) {
|
|
3423
3398
|
let newContext = _extends({}, context, {
|
|
3424
3399
|
statusCode: 500,
|
|
@@ -3426,28 +3401,22 @@
|
|
|
3426
3401
|
[context._deepestRenderedBoundaryId || routes[0].id]: error
|
|
3427
3402
|
}
|
|
3428
3403
|
});
|
|
3429
|
-
|
|
3430
3404
|
return newContext;
|
|
3431
3405
|
}
|
|
3432
|
-
|
|
3433
3406
|
function isSubmissionNavigation(opts) {
|
|
3434
3407
|
return opts != null && "formData" in opts;
|
|
3435
3408
|
}
|
|
3436
|
-
|
|
3437
3409
|
function normalizeTo(location, matches, basename, prependBasename, to, fromRouteId, relative) {
|
|
3438
3410
|
let contextualMatches;
|
|
3439
3411
|
let activeRouteMatch;
|
|
3440
|
-
|
|
3441
3412
|
if (fromRouteId != null && relative !== "path") {
|
|
3442
3413
|
// Grab matches up to the calling route so our route-relative logic is
|
|
3443
3414
|
// relative to the correct source route. When using relative:path,
|
|
3444
3415
|
// fromRouteId is ignored since that is always relative to the current
|
|
3445
3416
|
// location path
|
|
3446
3417
|
contextualMatches = [];
|
|
3447
|
-
|
|
3448
3418
|
for (let match of matches) {
|
|
3449
3419
|
contextualMatches.push(match);
|
|
3450
|
-
|
|
3451
3420
|
if (match.route.id === fromRouteId) {
|
|
3452
3421
|
activeRouteMatch = match;
|
|
3453
3422
|
break;
|
|
@@ -3456,36 +3425,36 @@
|
|
|
3456
3425
|
} else {
|
|
3457
3426
|
contextualMatches = matches;
|
|
3458
3427
|
activeRouteMatch = matches[matches.length - 1];
|
|
3459
|
-
}
|
|
3428
|
+
}
|
|
3460
3429
|
|
|
3430
|
+
// Resolve the relative path
|
|
3431
|
+
let path = resolveTo(to ? to : ".", getPathContributingMatches(contextualMatches).map(m => m.pathnameBase), stripBasename(location.pathname, basename) || location.pathname, relative === "path");
|
|
3461
3432
|
|
|
3462
|
-
|
|
3433
|
+
// When `to` is not specified we inherit search/hash from the current
|
|
3463
3434
|
// location, unlike when to="." and we just inherit the path.
|
|
3464
3435
|
// See https://github.com/remix-run/remix/issues/927
|
|
3465
|
-
|
|
3466
3436
|
if (to == null) {
|
|
3467
3437
|
path.search = location.search;
|
|
3468
3438
|
path.hash = location.hash;
|
|
3469
|
-
}
|
|
3470
|
-
|
|
3439
|
+
}
|
|
3471
3440
|
|
|
3441
|
+
// Add an ?index param for matched index routes if we don't already have one
|
|
3472
3442
|
if ((to == null || to === "" || to === ".") && activeRouteMatch && activeRouteMatch.route.index && !hasNakedIndexQuery(path.search)) {
|
|
3473
3443
|
path.search = path.search ? path.search.replace(/^\?/, "?index&") : "?index";
|
|
3474
|
-
}
|
|
3444
|
+
}
|
|
3445
|
+
|
|
3446
|
+
// If we're operating within a basename, prepend it to the pathname. If
|
|
3475
3447
|
// this is a root navigation, then just use the raw basename which allows
|
|
3476
3448
|
// the basename to have full control over the presence of a trailing slash
|
|
3477
3449
|
// on root actions
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
3450
|
if (prependBasename && basename !== "/") {
|
|
3481
3451
|
path.pathname = path.pathname === "/" ? basename : joinPaths([basename, path.pathname]);
|
|
3482
3452
|
}
|
|
3483
|
-
|
|
3484
3453
|
return createPath(path);
|
|
3485
|
-
}
|
|
3486
|
-
// URLSearchParams so they behave identically to links with query params
|
|
3487
|
-
|
|
3454
|
+
}
|
|
3488
3455
|
|
|
3456
|
+
// Normalize navigation options by converting formMethod=GET formData objects to
|
|
3457
|
+
// URLSearchParams so they behave identically to links with query params
|
|
3489
3458
|
function normalizeNavigateOptions(normalizeFormMethod, isFetcher, path, opts) {
|
|
3490
3459
|
// Return location verbatim on non-submission navigations
|
|
3491
3460
|
if (!opts || !isSubmissionNavigation(opts)) {
|
|
@@ -3493,7 +3462,6 @@
|
|
|
3493
3462
|
path
|
|
3494
3463
|
};
|
|
3495
3464
|
}
|
|
3496
|
-
|
|
3497
3465
|
if (opts.formMethod && !isValidMethod(opts.formMethod)) {
|
|
3498
3466
|
return {
|
|
3499
3467
|
path,
|
|
@@ -3501,11 +3469,10 @@
|
|
|
3501
3469
|
method: opts.formMethod
|
|
3502
3470
|
})
|
|
3503
3471
|
};
|
|
3504
|
-
}
|
|
3505
|
-
|
|
3472
|
+
}
|
|
3506
3473
|
|
|
3474
|
+
// Create a Submission on non-GET navigations
|
|
3507
3475
|
let submission;
|
|
3508
|
-
|
|
3509
3476
|
if (opts.formData) {
|
|
3510
3477
|
let formMethod = opts.formMethod || "get";
|
|
3511
3478
|
submission = {
|
|
@@ -3514,53 +3481,48 @@
|
|
|
3514
3481
|
formEncType: opts && opts.formEncType || "application/x-www-form-urlencoded",
|
|
3515
3482
|
formData: opts.formData
|
|
3516
3483
|
};
|
|
3517
|
-
|
|
3518
3484
|
if (isMutationMethod(submission.formMethod)) {
|
|
3519
3485
|
return {
|
|
3520
3486
|
path,
|
|
3521
3487
|
submission
|
|
3522
3488
|
};
|
|
3523
3489
|
}
|
|
3524
|
-
}
|
|
3525
|
-
|
|
3490
|
+
}
|
|
3526
3491
|
|
|
3492
|
+
// Flatten submission onto URLSearchParams for GET submissions
|
|
3527
3493
|
let parsedPath = parsePath(path);
|
|
3528
|
-
let searchParams = convertFormDataToSearchParams(opts.formData);
|
|
3494
|
+
let searchParams = convertFormDataToSearchParams(opts.formData);
|
|
3495
|
+
// On GET navigation submissions we can drop the ?index param from the
|
|
3529
3496
|
// resulting location since all loaders will run. But fetcher GET submissions
|
|
3530
3497
|
// only run a single loader so we need to preserve any incoming ?index params
|
|
3531
|
-
|
|
3532
3498
|
if (isFetcher && parsedPath.search && hasNakedIndexQuery(parsedPath.search)) {
|
|
3533
3499
|
searchParams.append("index", "");
|
|
3534
3500
|
}
|
|
3535
|
-
|
|
3536
3501
|
parsedPath.search = "?" + searchParams;
|
|
3537
3502
|
return {
|
|
3538
3503
|
path: createPath(parsedPath),
|
|
3539
3504
|
submission
|
|
3540
3505
|
};
|
|
3541
|
-
}
|
|
3542
|
-
// render so we don't need to load them
|
|
3543
|
-
|
|
3506
|
+
}
|
|
3544
3507
|
|
|
3508
|
+
// Filter out all routes below any caught error as they aren't going to
|
|
3509
|
+
// render so we don't need to load them
|
|
3545
3510
|
function getLoaderMatchesUntilBoundary(matches, boundaryId) {
|
|
3546
3511
|
let boundaryMatches = matches;
|
|
3547
|
-
|
|
3548
3512
|
if (boundaryId) {
|
|
3549
3513
|
let index = matches.findIndex(m => m.route.id === boundaryId);
|
|
3550
|
-
|
|
3551
3514
|
if (index >= 0) {
|
|
3552
3515
|
boundaryMatches = matches.slice(0, index);
|
|
3553
3516
|
}
|
|
3554
3517
|
}
|
|
3555
|
-
|
|
3556
3518
|
return boundaryMatches;
|
|
3557
3519
|
}
|
|
3558
|
-
|
|
3559
3520
|
function getMatchesToLoad(history, state, matches, submission, location, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, fetchLoadMatches, routesToUse, basename, pendingActionData, pendingError) {
|
|
3560
3521
|
let actionResult = pendingError ? Object.values(pendingError)[0] : pendingActionData ? Object.values(pendingActionData)[0] : undefined;
|
|
3561
3522
|
let currentUrl = history.createURL(state.location);
|
|
3562
|
-
let nextUrl = history.createURL(location);
|
|
3523
|
+
let nextUrl = history.createURL(location);
|
|
3563
3524
|
|
|
3525
|
+
// Pick navigation matches that are net-new or qualify for revalidation
|
|
3564
3526
|
let boundaryId = pendingError ? Object.keys(pendingError)[0] : undefined;
|
|
3565
3527
|
let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
|
|
3566
3528
|
let navigationMatches = boundaryMatches.filter((match, index) => {
|
|
@@ -3568,20 +3530,19 @@
|
|
|
3568
3530
|
// We haven't loaded this route yet so we don't know if it's got a loader!
|
|
3569
3531
|
return true;
|
|
3570
3532
|
}
|
|
3571
|
-
|
|
3572
3533
|
if (match.route.loader == null) {
|
|
3573
3534
|
return false;
|
|
3574
|
-
}
|
|
3575
|
-
|
|
3535
|
+
}
|
|
3576
3536
|
|
|
3537
|
+
// Always call the loader on new route instances and pending defer cancellations
|
|
3577
3538
|
if (isNewLoader(state.loaderData, state.matches[index], match) || cancelledDeferredRoutes.some(id => id === match.route.id)) {
|
|
3578
3539
|
return true;
|
|
3579
|
-
}
|
|
3540
|
+
}
|
|
3541
|
+
|
|
3542
|
+
// This is the default implementation for when we revalidate. If the route
|
|
3580
3543
|
// provides it's own implementation, then we give them full control but
|
|
3581
3544
|
// provide this value so they can leverage it if needed after they check
|
|
3582
3545
|
// their own specific use cases
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
3546
|
let currentRouteMatch = state.matches[index];
|
|
3586
3547
|
let nextRouteMatch = match;
|
|
3587
3548
|
return shouldRevalidateLoader(match, _extends({
|
|
@@ -3591,23 +3552,27 @@
|
|
|
3591
3552
|
nextParams: nextRouteMatch.params
|
|
3592
3553
|
}, submission, {
|
|
3593
3554
|
actionResult,
|
|
3594
|
-
defaultShouldRevalidate:
|
|
3595
|
-
|
|
3596
|
-
|
|
3555
|
+
defaultShouldRevalidate:
|
|
3556
|
+
// Forced revalidation due to submission, useRevalidator, or X-Remix-Revalidate
|
|
3557
|
+
isRevalidationRequired ||
|
|
3558
|
+
// Clicked the same link, resubmitted a GET form
|
|
3559
|
+
currentUrl.pathname + currentUrl.search === nextUrl.pathname + nextUrl.search ||
|
|
3560
|
+
// Search params affect all loaders
|
|
3597
3561
|
currentUrl.search !== nextUrl.search || isNewRouteInstance(currentRouteMatch, nextRouteMatch)
|
|
3598
3562
|
}));
|
|
3599
|
-
});
|
|
3563
|
+
});
|
|
3600
3564
|
|
|
3565
|
+
// Pick fetcher.loads that need to be revalidated
|
|
3601
3566
|
let revalidatingFetchers = [];
|
|
3602
3567
|
fetchLoadMatches.forEach((f, key) => {
|
|
3603
3568
|
// Don't revalidate if fetcher won't be present in the subsequent render
|
|
3604
3569
|
if (!matches.some(m => m.route.id === f.routeId)) {
|
|
3605
3570
|
return;
|
|
3606
3571
|
}
|
|
3572
|
+
let fetcherMatches = matchRoutes(routesToUse, f.path, basename);
|
|
3607
3573
|
|
|
3608
|
-
|
|
3574
|
+
// If the fetcher path no longer matches, push it in with null matches so
|
|
3609
3575
|
// we can trigger a 404 in callLoadersAndMaybeResolveData
|
|
3610
|
-
|
|
3611
3576
|
if (!fetcherMatches) {
|
|
3612
3577
|
revalidatingFetchers.push({
|
|
3613
3578
|
key,
|
|
@@ -3619,9 +3584,7 @@
|
|
|
3619
3584
|
});
|
|
3620
3585
|
return;
|
|
3621
3586
|
}
|
|
3622
|
-
|
|
3623
3587
|
let fetcherMatch = getTargetMatch(fetcherMatches, f.path);
|
|
3624
|
-
|
|
3625
3588
|
if (cancelledFetcherLoads.includes(key)) {
|
|
3626
3589
|
revalidatingFetchers.push({
|
|
3627
3590
|
key,
|
|
@@ -3632,12 +3595,12 @@
|
|
|
3632
3595
|
controller: new AbortController()
|
|
3633
3596
|
});
|
|
3634
3597
|
return;
|
|
3635
|
-
}
|
|
3598
|
+
}
|
|
3599
|
+
|
|
3600
|
+
// Revalidating fetchers are decoupled from the route matches since they
|
|
3636
3601
|
// hit a static href, so they _always_ check shouldRevalidate and the
|
|
3637
3602
|
// default is strictly if a revalidation is explicitly required (action
|
|
3638
3603
|
// submissions, useRevalidator, X-Remix-Revalidate).
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
3604
|
let shouldRevalidate = shouldRevalidateLoader(fetcherMatch, _extends({
|
|
3642
3605
|
currentUrl,
|
|
3643
3606
|
currentParams: state.matches[state.matches.length - 1].params,
|
|
@@ -3648,7 +3611,6 @@
|
|
|
3648
3611
|
// Forced revalidation due to submission, useRevalidator, or X-Remix-Revalidate
|
|
3649
3612
|
defaultShouldRevalidate: isRevalidationRequired
|
|
3650
3613
|
}));
|
|
3651
|
-
|
|
3652
3614
|
if (shouldRevalidate) {
|
|
3653
3615
|
revalidatingFetchers.push({
|
|
3654
3616
|
key,
|
|
@@ -3662,60 +3624,61 @@
|
|
|
3662
3624
|
});
|
|
3663
3625
|
return [navigationMatches, revalidatingFetchers];
|
|
3664
3626
|
}
|
|
3665
|
-
|
|
3666
3627
|
function isNewLoader(currentLoaderData, currentMatch, match) {
|
|
3667
|
-
let isNew =
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
//
|
|
3628
|
+
let isNew =
|
|
3629
|
+
// [a] -> [a, b]
|
|
3630
|
+
!currentMatch ||
|
|
3631
|
+
// [a, b] -> [a, c]
|
|
3632
|
+
match.route.id !== currentMatch.route.id;
|
|
3671
3633
|
|
|
3672
|
-
|
|
3634
|
+
// Handle the case that we don't have data for a re-used route, potentially
|
|
3635
|
+
// from a prior error or from a cancelled pending deferred
|
|
3636
|
+
let isMissingData = currentLoaderData[match.route.id] === undefined;
|
|
3673
3637
|
|
|
3638
|
+
// Always load if this is a net-new route or we don't yet have data
|
|
3674
3639
|
return isNew || isMissingData;
|
|
3675
3640
|
}
|
|
3676
|
-
|
|
3677
3641
|
function isNewRouteInstance(currentMatch, match) {
|
|
3678
3642
|
let currentPath = currentMatch.route.path;
|
|
3679
|
-
return (
|
|
3680
|
-
|
|
3643
|
+
return (
|
|
3644
|
+
// param change for this match, /users/123 -> /users/456
|
|
3645
|
+
currentMatch.pathname !== match.pathname ||
|
|
3646
|
+
// splat param changed, which is not present in match.path
|
|
3681
3647
|
// e.g. /files/images/avatar.jpg -> files/finances.xls
|
|
3682
3648
|
currentPath != null && currentPath.endsWith("*") && currentMatch.params["*"] !== match.params["*"]
|
|
3683
3649
|
);
|
|
3684
3650
|
}
|
|
3685
|
-
|
|
3686
3651
|
function shouldRevalidateLoader(loaderMatch, arg) {
|
|
3687
3652
|
if (loaderMatch.route.shouldRevalidate) {
|
|
3688
3653
|
let routeChoice = loaderMatch.route.shouldRevalidate(arg);
|
|
3689
|
-
|
|
3690
3654
|
if (typeof routeChoice === "boolean") {
|
|
3691
3655
|
return routeChoice;
|
|
3692
3656
|
}
|
|
3693
3657
|
}
|
|
3694
|
-
|
|
3695
3658
|
return arg.defaultShouldRevalidate;
|
|
3696
3659
|
}
|
|
3660
|
+
|
|
3697
3661
|
/**
|
|
3698
3662
|
* Execute route.lazy() methods to lazily load route modules (loader, action,
|
|
3699
3663
|
* shouldRevalidate) and update the routeManifest in place which shares objects
|
|
3700
3664
|
* with dataRoutes so those get updated as well.
|
|
3701
3665
|
*/
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
3666
|
async function loadLazyRouteModule(route, mapRouteProperties, manifest) {
|
|
3705
3667
|
if (!route.lazy) {
|
|
3706
3668
|
return;
|
|
3707
3669
|
}
|
|
3670
|
+
let lazyRoute = await route.lazy();
|
|
3708
3671
|
|
|
3709
|
-
|
|
3672
|
+
// If the lazy route function was executed and removed by another parallel
|
|
3710
3673
|
// call then we can return - first lazy() to finish wins because the return
|
|
3711
3674
|
// value of lazy is expected to be static
|
|
3712
|
-
|
|
3713
3675
|
if (!route.lazy) {
|
|
3714
3676
|
return;
|
|
3715
3677
|
}
|
|
3716
|
-
|
|
3717
3678
|
let routeToUpdate = manifest[route.id];
|
|
3718
|
-
invariant(routeToUpdate, "No route found in manifest");
|
|
3679
|
+
invariant(routeToUpdate, "No route found in manifest");
|
|
3680
|
+
|
|
3681
|
+
// Update the route in place. This should be safe because there's no way
|
|
3719
3682
|
// we could yet be sitting on this route as we can't get there without
|
|
3720
3683
|
// resolving lazy() first.
|
|
3721
3684
|
//
|
|
@@ -3723,52 +3686,45 @@
|
|
|
3723
3686
|
// on the route being updated. The main concern boils down to "does this
|
|
3724
3687
|
// mutation affect any ongoing navigations or any current state.matches
|
|
3725
3688
|
// values?". If not, it should be safe to update in place.
|
|
3726
|
-
|
|
3727
3689
|
let routeUpdates = {};
|
|
3728
|
-
|
|
3729
3690
|
for (let lazyRouteProperty in lazyRoute) {
|
|
3730
3691
|
let staticRouteValue = routeToUpdate[lazyRouteProperty];
|
|
3731
|
-
let isPropertyStaticallyDefined = staticRouteValue !== undefined &&
|
|
3692
|
+
let isPropertyStaticallyDefined = staticRouteValue !== undefined &&
|
|
3693
|
+
// This property isn't static since it should always be updated based
|
|
3732
3694
|
// on the route updates
|
|
3733
3695
|
lazyRouteProperty !== "hasErrorBoundary";
|
|
3734
3696
|
warning(!isPropertyStaticallyDefined, "Route \"" + routeToUpdate.id + "\" has a static property \"" + lazyRouteProperty + "\" " + "defined but its lazy function is also returning a value for this property. " + ("The lazy route property \"" + lazyRouteProperty + "\" will be ignored."));
|
|
3735
|
-
|
|
3736
3697
|
if (!isPropertyStaticallyDefined && !immutableRouteKeys.has(lazyRouteProperty)) {
|
|
3737
3698
|
routeUpdates[lazyRouteProperty] = lazyRoute[lazyRouteProperty];
|
|
3738
3699
|
}
|
|
3739
|
-
}
|
|
3740
|
-
// the updated version to mapRouteProperties
|
|
3700
|
+
}
|
|
3741
3701
|
|
|
3702
|
+
// Mutate the route with the provided updates. Do this first so we pass
|
|
3703
|
+
// the updated version to mapRouteProperties
|
|
3704
|
+
Object.assign(routeToUpdate, routeUpdates);
|
|
3742
3705
|
|
|
3743
|
-
|
|
3706
|
+
// Mutate the `hasErrorBoundary` property on the route based on the route
|
|
3744
3707
|
// updates and remove the `lazy` function so we don't resolve the lazy
|
|
3745
3708
|
// route again.
|
|
3746
|
-
|
|
3747
3709
|
Object.assign(routeToUpdate, _extends({}, mapRouteProperties(routeToUpdate), {
|
|
3748
3710
|
lazy: undefined
|
|
3749
3711
|
}));
|
|
3750
3712
|
}
|
|
3751
|
-
|
|
3752
3713
|
async function callLoaderOrAction(type, request, match, matches, manifest, mapRouteProperties, basename, isStaticRequest, isRouteRequest, requestContext) {
|
|
3753
3714
|
if (isStaticRequest === void 0) {
|
|
3754
3715
|
isStaticRequest = false;
|
|
3755
3716
|
}
|
|
3756
|
-
|
|
3757
3717
|
if (isRouteRequest === void 0) {
|
|
3758
3718
|
isRouteRequest = false;
|
|
3759
3719
|
}
|
|
3760
|
-
|
|
3761
3720
|
let resultType;
|
|
3762
3721
|
let result;
|
|
3763
3722
|
let onReject;
|
|
3764
|
-
|
|
3765
3723
|
let runHandler = handler => {
|
|
3766
3724
|
// Setup a promise we can race against so that abort signals short circuit
|
|
3767
3725
|
let reject;
|
|
3768
3726
|
let abortPromise = new Promise((_, r) => reject = r);
|
|
3769
|
-
|
|
3770
3727
|
onReject = () => reject();
|
|
3771
|
-
|
|
3772
3728
|
request.signal.addEventListener("abort", onReject);
|
|
3773
3729
|
return Promise.race([handler({
|
|
3774
3730
|
request,
|
|
@@ -3776,10 +3732,8 @@
|
|
|
3776
3732
|
context: requestContext
|
|
3777
3733
|
}), abortPromise]);
|
|
3778
3734
|
};
|
|
3779
|
-
|
|
3780
3735
|
try {
|
|
3781
3736
|
let handler = match.route[type];
|
|
3782
|
-
|
|
3783
3737
|
if (match.route.lazy) {
|
|
3784
3738
|
if (handler) {
|
|
3785
3739
|
// Run statically defined handler in parallel with lazy()
|
|
@@ -3789,7 +3743,6 @@
|
|
|
3789
3743
|
// Load lazy route module, then run any returned handler
|
|
3790
3744
|
await loadLazyRouteModule(match.route, mapRouteProperties, manifest);
|
|
3791
3745
|
handler = match.route[type];
|
|
3792
|
-
|
|
3793
3746
|
if (handler) {
|
|
3794
3747
|
// Handler still run even if we got interrupted to maintain consistency
|
|
3795
3748
|
// with un-abortable behavior of handler execution on non-lazy or
|
|
@@ -3821,7 +3774,6 @@
|
|
|
3821
3774
|
} else {
|
|
3822
3775
|
result = await runHandler(handler);
|
|
3823
3776
|
}
|
|
3824
|
-
|
|
3825
3777
|
invariant(result !== undefined, "You defined " + (type === "action" ? "an action" : "a loader") + " for route " + ("\"" + match.route.id + "\" but didn't return anything from your `" + type + "` ") + "function. Please return a value or `null`.");
|
|
3826
3778
|
} catch (e) {
|
|
3827
3779
|
resultType = ResultType.error;
|
|
@@ -3831,14 +3783,15 @@
|
|
|
3831
3783
|
request.signal.removeEventListener("abort", onReject);
|
|
3832
3784
|
}
|
|
3833
3785
|
}
|
|
3834
|
-
|
|
3835
3786
|
if (isResponse(result)) {
|
|
3836
|
-
let status = result.status;
|
|
3787
|
+
let status = result.status;
|
|
3837
3788
|
|
|
3789
|
+
// Process redirects
|
|
3838
3790
|
if (redirectStatusCodes.has(status)) {
|
|
3839
3791
|
let location = result.headers.get("Location");
|
|
3840
|
-
invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header");
|
|
3792
|
+
invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header");
|
|
3841
3793
|
|
|
3794
|
+
// Support relative routing in internal redirects
|
|
3842
3795
|
if (!ABSOLUTE_URL_REGEX.test(location)) {
|
|
3843
3796
|
location = normalizeTo(new URL(request.url), matches.slice(0, matches.indexOf(match) + 1), basename, true, location);
|
|
3844
3797
|
} else if (!isStaticRequest) {
|
|
@@ -3848,32 +3801,30 @@
|
|
|
3848
3801
|
let currentUrl = new URL(request.url);
|
|
3849
3802
|
let url = location.startsWith("//") ? new URL(currentUrl.protocol + location) : new URL(location);
|
|
3850
3803
|
let isSameBasename = stripBasename(url.pathname, basename) != null;
|
|
3851
|
-
|
|
3852
3804
|
if (url.origin === currentUrl.origin && isSameBasename) {
|
|
3853
3805
|
location = url.pathname + url.search + url.hash;
|
|
3854
3806
|
}
|
|
3855
|
-
}
|
|
3807
|
+
}
|
|
3808
|
+
|
|
3809
|
+
// Don't process redirects in the router during static requests requests.
|
|
3856
3810
|
// Instead, throw the Response and let the server handle it with an HTTP
|
|
3857
3811
|
// redirect. We also update the Location header in place in this flow so
|
|
3858
3812
|
// basename and relative routing is taken into account
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
3813
|
if (isStaticRequest) {
|
|
3862
3814
|
result.headers.set("Location", location);
|
|
3863
3815
|
throw result;
|
|
3864
3816
|
}
|
|
3865
|
-
|
|
3866
3817
|
return {
|
|
3867
3818
|
type: ResultType.redirect,
|
|
3868
3819
|
status,
|
|
3869
3820
|
location,
|
|
3870
3821
|
revalidate: result.headers.get("X-Remix-Revalidate") !== null
|
|
3871
3822
|
};
|
|
3872
|
-
}
|
|
3823
|
+
}
|
|
3824
|
+
|
|
3825
|
+
// For SSR single-route requests, we want to hand Responses back directly
|
|
3873
3826
|
// without unwrapping. We do this with the QueryRouteResponse wrapper
|
|
3874
3827
|
// interface so we can know whether it was returned or thrown
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
3828
|
if (isRouteRequest) {
|
|
3878
3829
|
// eslint-disable-next-line no-throw-literal
|
|
3879
3830
|
throw {
|
|
@@ -3881,17 +3832,15 @@
|
|
|
3881
3832
|
response: result
|
|
3882
3833
|
};
|
|
3883
3834
|
}
|
|
3884
|
-
|
|
3885
3835
|
let data;
|
|
3886
|
-
let contentType = result.headers.get("Content-Type");
|
|
3836
|
+
let contentType = result.headers.get("Content-Type");
|
|
3837
|
+
// Check between word boundaries instead of startsWith() due to the last
|
|
3887
3838
|
// paragraph of https://httpwg.org/specs/rfc9110.html#field.content-type
|
|
3888
|
-
|
|
3889
3839
|
if (contentType && /\bapplication\/json\b/.test(contentType)) {
|
|
3890
3840
|
data = await result.json();
|
|
3891
3841
|
} else {
|
|
3892
3842
|
data = await result.text();
|
|
3893
3843
|
}
|
|
3894
|
-
|
|
3895
3844
|
if (resultType === ResultType.error) {
|
|
3896
3845
|
return {
|
|
3897
3846
|
type: resultType,
|
|
@@ -3899,7 +3848,6 @@
|
|
|
3899
3848
|
headers: result.headers
|
|
3900
3849
|
};
|
|
3901
3850
|
}
|
|
3902
|
-
|
|
3903
3851
|
return {
|
|
3904
3852
|
type: ResultType.data,
|
|
3905
3853
|
data,
|
|
@@ -3907,17 +3855,14 @@
|
|
|
3907
3855
|
headers: result.headers
|
|
3908
3856
|
};
|
|
3909
3857
|
}
|
|
3910
|
-
|
|
3911
3858
|
if (resultType === ResultType.error) {
|
|
3912
3859
|
return {
|
|
3913
3860
|
type: resultType,
|
|
3914
3861
|
error: result
|
|
3915
3862
|
};
|
|
3916
3863
|
}
|
|
3917
|
-
|
|
3918
3864
|
if (isDeferredData(result)) {
|
|
3919
3865
|
var _result$init, _result$init2;
|
|
3920
|
-
|
|
3921
3866
|
return {
|
|
3922
3867
|
type: ResultType.deferred,
|
|
3923
3868
|
deferredData: result,
|
|
@@ -3925,90 +3870,84 @@
|
|
|
3925
3870
|
headers: ((_result$init2 = result.init) == null ? void 0 : _result$init2.headers) && new Headers(result.init.headers)
|
|
3926
3871
|
};
|
|
3927
3872
|
}
|
|
3928
|
-
|
|
3929
3873
|
return {
|
|
3930
3874
|
type: ResultType.data,
|
|
3931
3875
|
data: result
|
|
3932
3876
|
};
|
|
3933
|
-
}
|
|
3877
|
+
}
|
|
3878
|
+
|
|
3879
|
+
// Utility method for creating the Request instances for loaders/actions during
|
|
3934
3880
|
// client-side navigations and fetches. During SSR we will always have a
|
|
3935
3881
|
// Request instance from the static handler (query/queryRoute)
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
3882
|
function createClientSideRequest(history, location, signal, submission) {
|
|
3939
3883
|
let url = history.createURL(stripHashFromPath(location)).toString();
|
|
3940
3884
|
let init = {
|
|
3941
3885
|
signal
|
|
3942
3886
|
};
|
|
3943
|
-
|
|
3944
3887
|
if (submission && isMutationMethod(submission.formMethod)) {
|
|
3945
3888
|
let {
|
|
3946
3889
|
formMethod,
|
|
3947
3890
|
formEncType,
|
|
3948
3891
|
formData
|
|
3949
|
-
} = submission;
|
|
3892
|
+
} = submission;
|
|
3893
|
+
// Didn't think we needed this but it turns out unlike other methods, patch
|
|
3950
3894
|
// won't be properly normalized to uppercase and results in a 405 error.
|
|
3951
3895
|
// See: https://fetch.spec.whatwg.org/#concept-method
|
|
3952
|
-
|
|
3953
3896
|
init.method = formMethod.toUpperCase();
|
|
3954
3897
|
init.body = formEncType === "application/x-www-form-urlencoded" ? convertFormDataToSearchParams(formData) : formData;
|
|
3955
|
-
}
|
|
3956
|
-
|
|
3898
|
+
}
|
|
3957
3899
|
|
|
3900
|
+
// Content-Type is inferred (https://fetch.spec.whatwg.org/#dom-request)
|
|
3958
3901
|
return new Request(url, init);
|
|
3959
3902
|
}
|
|
3960
|
-
|
|
3961
3903
|
function convertFormDataToSearchParams(formData) {
|
|
3962
3904
|
let searchParams = new URLSearchParams();
|
|
3963
|
-
|
|
3964
3905
|
for (let [key, value] of formData.entries()) {
|
|
3965
3906
|
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#converting-an-entry-list-to-a-list-of-name-value-pairs
|
|
3966
3907
|
searchParams.append(key, value instanceof File ? value.name : value);
|
|
3967
3908
|
}
|
|
3968
|
-
|
|
3969
3909
|
return searchParams;
|
|
3970
3910
|
}
|
|
3971
|
-
|
|
3972
3911
|
function processRouteLoaderData(matches, matchesToLoad, results, pendingError, activeDeferreds) {
|
|
3973
3912
|
// Fill in loaderData/errors from our loaders
|
|
3974
3913
|
let loaderData = {};
|
|
3975
3914
|
let errors = null;
|
|
3976
3915
|
let statusCode;
|
|
3977
3916
|
let foundError = false;
|
|
3978
|
-
let loaderHeaders = {};
|
|
3917
|
+
let loaderHeaders = {};
|
|
3979
3918
|
|
|
3919
|
+
// Process loader results into state.loaderData/state.errors
|
|
3980
3920
|
results.forEach((result, index) => {
|
|
3981
3921
|
let id = matchesToLoad[index].route.id;
|
|
3982
3922
|
invariant(!isRedirectResult(result), "Cannot handle redirect results in processLoaderData");
|
|
3983
|
-
|
|
3984
3923
|
if (isErrorResult(result)) {
|
|
3985
3924
|
// Look upwards from the matched route for the closest ancestor
|
|
3986
3925
|
// error boundary, defaulting to the root match
|
|
3987
3926
|
let boundaryMatch = findNearestBoundary(matches, id);
|
|
3988
|
-
let error = result.error;
|
|
3927
|
+
let error = result.error;
|
|
3928
|
+
// If we have a pending action error, we report it at the highest-route
|
|
3989
3929
|
// that throws a loader error, and then clear it out to indicate that
|
|
3990
3930
|
// it was consumed
|
|
3991
|
-
|
|
3992
3931
|
if (pendingError) {
|
|
3993
3932
|
error = Object.values(pendingError)[0];
|
|
3994
3933
|
pendingError = undefined;
|
|
3995
3934
|
}
|
|
3935
|
+
errors = errors || {};
|
|
3996
3936
|
|
|
3997
|
-
|
|
3998
|
-
|
|
3937
|
+
// Prefer higher error values if lower errors bubble to the same boundary
|
|
3999
3938
|
if (errors[boundaryMatch.route.id] == null) {
|
|
4000
3939
|
errors[boundaryMatch.route.id] = error;
|
|
4001
|
-
}
|
|
3940
|
+
}
|
|
4002
3941
|
|
|
3942
|
+
// Clear our any prior loaderData for the throwing route
|
|
3943
|
+
loaderData[id] = undefined;
|
|
4003
3944
|
|
|
4004
|
-
|
|
3945
|
+
// Once we find our first (highest) error, we set the status code and
|
|
4005
3946
|
// prevent deeper status codes from overriding
|
|
4006
|
-
|
|
4007
3947
|
if (!foundError) {
|
|
4008
3948
|
foundError = true;
|
|
4009
3949
|
statusCode = isRouteErrorResponse(result.error) ? result.error.status : 500;
|
|
4010
3950
|
}
|
|
4011
|
-
|
|
4012
3951
|
if (result.headers) {
|
|
4013
3952
|
loaderHeaders[id] = result.headers;
|
|
4014
3953
|
}
|
|
@@ -4018,27 +3957,26 @@
|
|
|
4018
3957
|
loaderData[id] = result.deferredData.data;
|
|
4019
3958
|
} else {
|
|
4020
3959
|
loaderData[id] = result.data;
|
|
4021
|
-
}
|
|
4022
|
-
// loaders are successful we take the deepest status code.
|
|
4023
|
-
|
|
3960
|
+
}
|
|
4024
3961
|
|
|
3962
|
+
// Error status codes always override success status codes, but if all
|
|
3963
|
+
// loaders are successful we take the deepest status code.
|
|
4025
3964
|
if (result.statusCode != null && result.statusCode !== 200 && !foundError) {
|
|
4026
3965
|
statusCode = result.statusCode;
|
|
4027
3966
|
}
|
|
4028
|
-
|
|
4029
3967
|
if (result.headers) {
|
|
4030
3968
|
loaderHeaders[id] = result.headers;
|
|
4031
3969
|
}
|
|
4032
3970
|
}
|
|
4033
|
-
});
|
|
3971
|
+
});
|
|
3972
|
+
|
|
3973
|
+
// If we didn't consume the pending action error (i.e., all loaders
|
|
4034
3974
|
// resolved), then consume it here. Also clear out any loaderData for the
|
|
4035
3975
|
// throwing route
|
|
4036
|
-
|
|
4037
3976
|
if (pendingError) {
|
|
4038
3977
|
errors = pendingError;
|
|
4039
3978
|
loaderData[Object.keys(pendingError)[0]] = undefined;
|
|
4040
3979
|
}
|
|
4041
|
-
|
|
4042
3980
|
return {
|
|
4043
3981
|
loaderData,
|
|
4044
3982
|
errors,
|
|
@@ -4046,13 +3984,13 @@
|
|
|
4046
3984
|
loaderHeaders
|
|
4047
3985
|
};
|
|
4048
3986
|
}
|
|
4049
|
-
|
|
4050
3987
|
function processLoaderData(state, matches, matchesToLoad, results, pendingError, revalidatingFetchers, fetcherResults, activeDeferreds) {
|
|
4051
3988
|
let {
|
|
4052
3989
|
loaderData,
|
|
4053
3990
|
errors
|
|
4054
|
-
} = processRouteLoaderData(matches, matchesToLoad, results, pendingError, activeDeferreds);
|
|
3991
|
+
} = processRouteLoaderData(matches, matchesToLoad, results, pendingError, activeDeferreds);
|
|
4055
3992
|
|
|
3993
|
+
// Process results from our revalidating fetchers
|
|
4056
3994
|
for (let index = 0; index < revalidatingFetchers.length; index++) {
|
|
4057
3995
|
let {
|
|
4058
3996
|
key,
|
|
@@ -4060,20 +3998,19 @@
|
|
|
4060
3998
|
controller
|
|
4061
3999
|
} = revalidatingFetchers[index];
|
|
4062
4000
|
invariant(fetcherResults !== undefined && fetcherResults[index] !== undefined, "Did not find corresponding fetcher result");
|
|
4063
|
-
let result = fetcherResults[index];
|
|
4001
|
+
let result = fetcherResults[index];
|
|
4064
4002
|
|
|
4003
|
+
// Process fetcher non-redirect errors
|
|
4065
4004
|
if (controller && controller.signal.aborted) {
|
|
4066
4005
|
// Nothing to do for aborted fetchers
|
|
4067
4006
|
continue;
|
|
4068
4007
|
} else if (isErrorResult(result)) {
|
|
4069
4008
|
let boundaryMatch = findNearestBoundary(state.matches, match == null ? void 0 : match.route.id);
|
|
4070
|
-
|
|
4071
4009
|
if (!(errors && errors[boundaryMatch.route.id])) {
|
|
4072
4010
|
errors = _extends({}, errors, {
|
|
4073
4011
|
[boundaryMatch.route.id]: result.error
|
|
4074
4012
|
});
|
|
4075
4013
|
}
|
|
4076
|
-
|
|
4077
4014
|
state.fetchers.delete(key);
|
|
4078
4015
|
} else if (isRedirectResult(result)) {
|
|
4079
4016
|
// Should never get here, redirects should get processed above, but we
|
|
@@ -4096,19 +4033,15 @@
|
|
|
4096
4033
|
state.fetchers.set(key, doneFetcher);
|
|
4097
4034
|
}
|
|
4098
4035
|
}
|
|
4099
|
-
|
|
4100
4036
|
return {
|
|
4101
4037
|
loaderData,
|
|
4102
4038
|
errors
|
|
4103
4039
|
};
|
|
4104
4040
|
}
|
|
4105
|
-
|
|
4106
4041
|
function mergeLoaderData(loaderData, newLoaderData, matches, errors) {
|
|
4107
4042
|
let mergedLoaderData = _extends({}, newLoaderData);
|
|
4108
|
-
|
|
4109
4043
|
for (let match of matches) {
|
|
4110
4044
|
let id = match.route.id;
|
|
4111
|
-
|
|
4112
4045
|
if (newLoaderData.hasOwnProperty(id)) {
|
|
4113
4046
|
if (newLoaderData[id] !== undefined) {
|
|
4114
4047
|
mergedLoaderData[id] = newLoaderData[id];
|
|
@@ -4118,24 +4051,21 @@
|
|
|
4118
4051
|
// wasn't removed by HMR
|
|
4119
4052
|
mergedLoaderData[id] = loaderData[id];
|
|
4120
4053
|
}
|
|
4121
|
-
|
|
4122
4054
|
if (errors && errors.hasOwnProperty(id)) {
|
|
4123
4055
|
// Don't keep any loader data below the boundary
|
|
4124
4056
|
break;
|
|
4125
4057
|
}
|
|
4126
4058
|
}
|
|
4127
|
-
|
|
4128
4059
|
return mergedLoaderData;
|
|
4129
|
-
}
|
|
4060
|
+
}
|
|
4061
|
+
|
|
4062
|
+
// Find the nearest error boundary, looking upwards from the leaf route (or the
|
|
4130
4063
|
// route specified by routeId) for the closest ancestor error boundary,
|
|
4131
4064
|
// defaulting to the root match
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
4065
|
function findNearestBoundary(matches, routeId) {
|
|
4135
4066
|
let eligibleMatches = routeId ? matches.slice(0, matches.findIndex(m => m.route.id === routeId) + 1) : [...matches];
|
|
4136
4067
|
return eligibleMatches.reverse().find(m => m.route.hasErrorBoundary === true) || matches[0];
|
|
4137
4068
|
}
|
|
4138
|
-
|
|
4139
4069
|
function getShortCircuitMatches(routes) {
|
|
4140
4070
|
// Prefer a root layout route if present, otherwise shim in a route object
|
|
4141
4071
|
let route = routes.find(r => r.index || !r.path || r.path === "/") || {
|
|
@@ -4151,7 +4081,6 @@
|
|
|
4151
4081
|
route
|
|
4152
4082
|
};
|
|
4153
4083
|
}
|
|
4154
|
-
|
|
4155
4084
|
function getInternalRouterError(status, _temp4) {
|
|
4156
4085
|
let {
|
|
4157
4086
|
pathname,
|
|
@@ -4161,10 +4090,8 @@
|
|
|
4161
4090
|
} = _temp4 === void 0 ? {} : _temp4;
|
|
4162
4091
|
let statusText = "Unknown Server Error";
|
|
4163
4092
|
let errorMessage = "Unknown @remix-run/router error";
|
|
4164
|
-
|
|
4165
4093
|
if (status === 400) {
|
|
4166
4094
|
statusText = "Bad Request";
|
|
4167
|
-
|
|
4168
4095
|
if (method && pathname && routeId) {
|
|
4169
4096
|
errorMessage = "You made a " + method + " request to \"" + pathname + "\" but " + ("did not provide a `loader` for route \"" + routeId + "\", ") + "so there is no way to handle the request.";
|
|
4170
4097
|
} else if (type === "defer-action") {
|
|
@@ -4178,40 +4105,34 @@
|
|
|
4178
4105
|
errorMessage = "No route matches URL \"" + pathname + "\"";
|
|
4179
4106
|
} else if (status === 405) {
|
|
4180
4107
|
statusText = "Method Not Allowed";
|
|
4181
|
-
|
|
4182
4108
|
if (method && pathname && routeId) {
|
|
4183
4109
|
errorMessage = "You made a " + method.toUpperCase() + " request to \"" + pathname + "\" but " + ("did not provide an `action` for route \"" + routeId + "\", ") + "so there is no way to handle the request.";
|
|
4184
4110
|
} else if (method) {
|
|
4185
4111
|
errorMessage = "Invalid request method \"" + method.toUpperCase() + "\"";
|
|
4186
4112
|
}
|
|
4187
4113
|
}
|
|
4188
|
-
|
|
4189
4114
|
return new ErrorResponse(status || 500, statusText, new Error(errorMessage), true);
|
|
4190
|
-
}
|
|
4191
|
-
|
|
4115
|
+
}
|
|
4192
4116
|
|
|
4117
|
+
// Find any returned redirect errors, starting from the lowest match
|
|
4193
4118
|
function findRedirect(results) {
|
|
4194
4119
|
for (let i = results.length - 1; i >= 0; i--) {
|
|
4195
4120
|
let result = results[i];
|
|
4196
|
-
|
|
4197
4121
|
if (isRedirectResult(result)) {
|
|
4198
4122
|
return result;
|
|
4199
4123
|
}
|
|
4200
4124
|
}
|
|
4201
4125
|
}
|
|
4202
|
-
|
|
4203
4126
|
function stripHashFromPath(path) {
|
|
4204
4127
|
let parsedPath = typeof path === "string" ? parsePath(path) : path;
|
|
4205
4128
|
return createPath(_extends({}, parsedPath, {
|
|
4206
4129
|
hash: ""
|
|
4207
4130
|
}));
|
|
4208
4131
|
}
|
|
4209
|
-
|
|
4210
4132
|
function isHashChangeOnly(a, b) {
|
|
4211
4133
|
if (a.pathname !== b.pathname || a.search !== b.search) {
|
|
4212
4134
|
return false;
|
|
4213
4135
|
}
|
|
4214
|
-
|
|
4215
4136
|
if (a.hash === "") {
|
|
4216
4137
|
// /page -> /page#hash
|
|
4217
4138
|
return b.hash !== "";
|
|
@@ -4221,70 +4142,57 @@
|
|
|
4221
4142
|
} else if (b.hash !== "") {
|
|
4222
4143
|
// /page#hash -> /page#other
|
|
4223
4144
|
return true;
|
|
4224
|
-
}
|
|
4225
|
-
// /page#hash -> /page
|
|
4226
|
-
|
|
4145
|
+
}
|
|
4227
4146
|
|
|
4147
|
+
// If the hash is removed the browser will re-perform a request to the server
|
|
4148
|
+
// /page#hash -> /page
|
|
4228
4149
|
return false;
|
|
4229
4150
|
}
|
|
4230
|
-
|
|
4231
4151
|
function isDeferredResult(result) {
|
|
4232
4152
|
return result.type === ResultType.deferred;
|
|
4233
4153
|
}
|
|
4234
|
-
|
|
4235
4154
|
function isErrorResult(result) {
|
|
4236
4155
|
return result.type === ResultType.error;
|
|
4237
4156
|
}
|
|
4238
|
-
|
|
4239
4157
|
function isRedirectResult(result) {
|
|
4240
4158
|
return (result && result.type) === ResultType.redirect;
|
|
4241
4159
|
}
|
|
4242
|
-
|
|
4243
4160
|
function isDeferredData(value) {
|
|
4244
4161
|
let deferred = value;
|
|
4245
4162
|
return deferred && typeof deferred === "object" && typeof deferred.data === "object" && typeof deferred.subscribe === "function" && typeof deferred.cancel === "function" && typeof deferred.resolveData === "function";
|
|
4246
4163
|
}
|
|
4247
|
-
|
|
4248
4164
|
function isResponse(value) {
|
|
4249
4165
|
return value != null && typeof value.status === "number" && typeof value.statusText === "string" && typeof value.headers === "object" && typeof value.body !== "undefined";
|
|
4250
4166
|
}
|
|
4251
|
-
|
|
4252
4167
|
function isRedirectResponse(result) {
|
|
4253
4168
|
if (!isResponse(result)) {
|
|
4254
4169
|
return false;
|
|
4255
4170
|
}
|
|
4256
|
-
|
|
4257
4171
|
let status = result.status;
|
|
4258
4172
|
let location = result.headers.get("Location");
|
|
4259
4173
|
return status >= 300 && status <= 399 && location != null;
|
|
4260
4174
|
}
|
|
4261
|
-
|
|
4262
4175
|
function isQueryRouteResponse(obj) {
|
|
4263
4176
|
return obj && isResponse(obj.response) && (obj.type === ResultType.data || ResultType.error);
|
|
4264
4177
|
}
|
|
4265
|
-
|
|
4266
4178
|
function isValidMethod(method) {
|
|
4267
4179
|
return validRequestMethods.has(method.toLowerCase());
|
|
4268
4180
|
}
|
|
4269
|
-
|
|
4270
4181
|
function isMutationMethod(method) {
|
|
4271
4182
|
return validMutationMethods.has(method.toLowerCase());
|
|
4272
4183
|
}
|
|
4273
|
-
|
|
4274
4184
|
async function resolveDeferredResults(currentMatches, matchesToLoad, results, signals, isFetcher, currentLoaderData) {
|
|
4275
4185
|
for (let index = 0; index < results.length; index++) {
|
|
4276
4186
|
let result = results[index];
|
|
4277
|
-
let match = matchesToLoad[index];
|
|
4187
|
+
let match = matchesToLoad[index];
|
|
4188
|
+
// If we don't have a match, then we can have a deferred result to do
|
|
4278
4189
|
// anything with. This is for revalidating fetchers where the route was
|
|
4279
4190
|
// removed during HMR
|
|
4280
|
-
|
|
4281
4191
|
if (!match) {
|
|
4282
4192
|
continue;
|
|
4283
4193
|
}
|
|
4284
|
-
|
|
4285
4194
|
let currentMatch = currentMatches.find(m => m.route.id === match.route.id);
|
|
4286
4195
|
let isRevalidatingLoader = currentMatch != null && !isNewRouteInstance(currentMatch, match) && (currentLoaderData && currentLoaderData[match.route.id]) !== undefined;
|
|
4287
|
-
|
|
4288
4196
|
if (isDeferredResult(result) && (isFetcher || isRevalidatingLoader)) {
|
|
4289
4197
|
// Note: we do not have to touch activeDeferreds here since we race them
|
|
4290
4198
|
// against the signal in resolveDeferredData and they'll get aborted
|
|
@@ -4299,18 +4207,14 @@
|
|
|
4299
4207
|
}
|
|
4300
4208
|
}
|
|
4301
4209
|
}
|
|
4302
|
-
|
|
4303
4210
|
async function resolveDeferredData(result, signal, unwrap) {
|
|
4304
4211
|
if (unwrap === void 0) {
|
|
4305
4212
|
unwrap = false;
|
|
4306
4213
|
}
|
|
4307
|
-
|
|
4308
4214
|
let aborted = await result.deferredData.resolveData(signal);
|
|
4309
|
-
|
|
4310
4215
|
if (aborted) {
|
|
4311
4216
|
return;
|
|
4312
4217
|
}
|
|
4313
|
-
|
|
4314
4218
|
if (unwrap) {
|
|
4315
4219
|
try {
|
|
4316
4220
|
return {
|
|
@@ -4325,19 +4229,17 @@
|
|
|
4325
4229
|
};
|
|
4326
4230
|
}
|
|
4327
4231
|
}
|
|
4328
|
-
|
|
4329
4232
|
return {
|
|
4330
4233
|
type: ResultType.data,
|
|
4331
4234
|
data: result.deferredData.data
|
|
4332
4235
|
};
|
|
4333
4236
|
}
|
|
4334
|
-
|
|
4335
4237
|
function hasNakedIndexQuery(search) {
|
|
4336
4238
|
return new URLSearchParams(search).getAll("index").some(v => v === "");
|
|
4337
|
-
}
|
|
4338
|
-
// this please also change that :) Eventually we'll DRY this up
|
|
4339
|
-
|
|
4239
|
+
}
|
|
4340
4240
|
|
|
4241
|
+
// Note: This should match the format exported by useMatches, so if you change
|
|
4242
|
+
// this please also change that :) Eventually we'll DRY this up
|
|
4341
4243
|
function createUseMatchesMatch(match, loaderData) {
|
|
4342
4244
|
let {
|
|
4343
4245
|
route,
|
|
@@ -4352,20 +4254,18 @@
|
|
|
4352
4254
|
handle: route.handle
|
|
4353
4255
|
};
|
|
4354
4256
|
}
|
|
4355
|
-
|
|
4356
4257
|
function getTargetMatch(matches, location) {
|
|
4357
4258
|
let search = typeof location === "string" ? parsePath(location).search : location.search;
|
|
4358
|
-
|
|
4359
4259
|
if (matches[matches.length - 1].route.index && hasNakedIndexQuery(search || "")) {
|
|
4360
4260
|
// Return the leaf index route when index is present
|
|
4361
4261
|
return matches[matches.length - 1];
|
|
4362
|
-
}
|
|
4262
|
+
}
|
|
4263
|
+
// Otherwise grab the deepest "path contributing" match (ignoring index and
|
|
4363
4264
|
// pathless layout routes)
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
4265
|
let pathMatches = getPathContributingMatches(matches);
|
|
4367
4266
|
return pathMatches[pathMatches.length - 1];
|
|
4368
|
-
}
|
|
4267
|
+
}
|
|
4268
|
+
//#endregion
|
|
4369
4269
|
|
|
4370
4270
|
exports.AbortedDeferredError = AbortedDeferredError;
|
|
4371
4271
|
exports.ErrorResponse = ErrorResponse;
|