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