@async/framework 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/README.md +93 -15
- package/{framework.d.ts → browser.d.ts} +71 -4
- package/{framework.js → browser.js} +791 -147
- package/browser.min.js +1 -0
- package/browser.ts +4781 -0
- package/{framework.umd.js → browser.umd.js} +791 -147
- package/browser.umd.min.js +1 -0
- package/package.json +45 -30
- package/server.d.ts +640 -0
- package/src/app.js +143 -12
- package/src/async-signal.js +32 -4
- package/src/browser.js +15 -0
- package/src/cache.js +27 -3
- package/src/component.js +42 -7
- package/src/index.js +5 -2
- package/src/loader.js +42 -10
- package/src/request-context.js +40 -0
- package/src/router.js +113 -16
- package/src/scheduler.js +296 -0
- package/src/server-entry.js +20 -0
- package/src/server-registry.js +97 -0
- package/src/server.js +49 -89
- package/src/signals.js +38 -6
- package/framework.min.js +0 -3648
- package/framework.ts +0 -3
- package/framework.umd.min.js +0 -3671
package/src/loader.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { renderComponent } from "./component.js";
|
|
2
2
|
import { createHandlerRegistry } from "./handlers.js";
|
|
3
|
+
import { createScheduler } from "./scheduler.js";
|
|
3
4
|
import { createSignalRegistry, isSignalRef } from "./signals.js";
|
|
4
5
|
import { matchAttribute, normalizeAttributeConfig, readAttribute } from "./attributes.js";
|
|
5
6
|
|
|
6
7
|
const inlineBindingPrefix = "__async:inline:";
|
|
7
8
|
|
|
8
|
-
export function Loader({ root, signals, handlers, server, router, cache, attributes } = {}) {
|
|
9
|
+
export function Loader({ root, signals, handlers, server, router, cache, attributes, scheduler } = {}) {
|
|
9
10
|
const documentRef = root?.ownerDocument ?? root ?? globalThis.document;
|
|
10
11
|
const rootNode = root ?? documentRef;
|
|
11
12
|
const signalRegistry = signals ?? createSignalRegistry();
|
|
12
13
|
const handlerRegistry = handlers ?? createHandlerRegistry();
|
|
14
|
+
const schedulerInstance = scheduler ?? createScheduler();
|
|
15
|
+
const ownsScheduler = !scheduler;
|
|
13
16
|
const attributeConfig = normalizeAttributeConfig(attributes);
|
|
14
17
|
const cleanups = new Set();
|
|
15
18
|
const eventBindings = new WeakMap();
|
|
@@ -30,6 +33,7 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
|
|
|
30
33
|
server,
|
|
31
34
|
router,
|
|
32
35
|
cache,
|
|
36
|
+
scheduler: schedulerInstance,
|
|
33
37
|
attributes: attributeConfig,
|
|
34
38
|
|
|
35
39
|
start() {
|
|
@@ -69,6 +73,7 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
|
|
|
69
73
|
server: api.server,
|
|
70
74
|
router: api.router,
|
|
71
75
|
cache: api.cache,
|
|
76
|
+
scheduler: schedulerInstance,
|
|
72
77
|
attributes: attributeConfig
|
|
73
78
|
});
|
|
74
79
|
cleanupChildren(target);
|
|
@@ -89,6 +94,9 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
|
|
|
89
94
|
runCleanup(cleanup);
|
|
90
95
|
}
|
|
91
96
|
cleanups.clear();
|
|
97
|
+
if (ownsScheduler) {
|
|
98
|
+
schedulerInstance.destroy();
|
|
99
|
+
}
|
|
92
100
|
},
|
|
93
101
|
|
|
94
102
|
_observeVisible(target, fn) {
|
|
@@ -106,13 +114,14 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
|
|
|
106
114
|
}
|
|
107
115
|
};
|
|
108
116
|
|
|
109
|
-
signalRegistry._setContext?.({ server: api.server, router: api.router, loader: api, cache: api.cache });
|
|
117
|
+
signalRegistry._setContext?.({ server: api.server, router: api.router, loader: api, cache: api.cache, scheduler: schedulerInstance });
|
|
110
118
|
api.server?._setContext?.({
|
|
111
119
|
signals: signalRegistry,
|
|
112
120
|
handlers: handlerRegistry,
|
|
113
121
|
loader: api,
|
|
114
122
|
router: api.router,
|
|
115
|
-
cache: api.cache
|
|
123
|
+
cache: api.cache,
|
|
124
|
+
scheduler: schedulerInstance
|
|
116
125
|
});
|
|
117
126
|
|
|
118
127
|
function bindEventAttributes(scope) {
|
|
@@ -144,18 +153,19 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
|
|
|
144
153
|
|
|
145
154
|
const listener = async (event) => {
|
|
146
155
|
try {
|
|
147
|
-
await handlerRegistry.run(ref, {
|
|
156
|
+
await schedulerInstance.batch(() => handlerRegistry.run(ref, {
|
|
148
157
|
signals: signalRegistry,
|
|
149
158
|
handlers: handlerRegistry,
|
|
150
159
|
loader: api,
|
|
151
160
|
server: api.server,
|
|
152
161
|
router: api.router,
|
|
153
162
|
cache: api.cache,
|
|
163
|
+
scheduler: schedulerInstance,
|
|
154
164
|
event,
|
|
155
165
|
element,
|
|
156
166
|
el: element,
|
|
157
167
|
root: rootNode
|
|
158
|
-
});
|
|
168
|
+
}));
|
|
159
169
|
} catch (error) {
|
|
160
170
|
dispatchAsyncError(element, error);
|
|
161
171
|
}
|
|
@@ -271,7 +281,12 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
|
|
|
271
281
|
|
|
272
282
|
const read = () => readBinding(path, options);
|
|
273
283
|
apply(read());
|
|
274
|
-
addCleanup(subscribeBinding(path, () =>
|
|
284
|
+
addCleanup(subscribeBinding(path, () => {
|
|
285
|
+
schedulerInstance.enqueue("binding", () => apply(read()), {
|
|
286
|
+
scope: element,
|
|
287
|
+
key
|
|
288
|
+
});
|
|
289
|
+
}), element);
|
|
275
290
|
}
|
|
276
291
|
|
|
277
292
|
function bindValueWriter(element, path) {
|
|
@@ -332,7 +347,12 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
|
|
|
332
347
|
const state = {
|
|
333
348
|
id,
|
|
334
349
|
templates,
|
|
335
|
-
cleanup: signalRegistry.subscribe(`${id}.$status`, () =>
|
|
350
|
+
cleanup: signalRegistry.subscribe(`${id}.$status`, () => {
|
|
351
|
+
schedulerInstance.enqueue("binding", () => renderBoundary(boundary), {
|
|
352
|
+
scope: boundary,
|
|
353
|
+
key: `boundary:${id}`
|
|
354
|
+
});
|
|
355
|
+
})
|
|
336
356
|
};
|
|
337
357
|
boundaryState.set(boundary, state);
|
|
338
358
|
addCleanup(state.cleanup, boundary);
|
|
@@ -372,7 +392,7 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
|
|
|
372
392
|
}
|
|
373
393
|
mountedElements.add(element);
|
|
374
394
|
for (const ref of refs) {
|
|
375
|
-
runPseudo(element, ref);
|
|
395
|
+
scheduleLifecycle(element, () => runPseudo(element, ref), `attach:${ref}`);
|
|
376
396
|
}
|
|
377
397
|
}
|
|
378
398
|
|
|
@@ -385,7 +405,7 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
|
|
|
385
405
|
continue;
|
|
386
406
|
}
|
|
387
407
|
visibleElements.add(element);
|
|
388
|
-
addCleanup(observeVisible(element, () => runPseudo(element, ref)), element);
|
|
408
|
+
addCleanup(observeVisible(element, () => scheduleLifecycle(element, () => runPseudo(element, ref), `visible:${ref}`)), element);
|
|
389
409
|
}
|
|
390
410
|
}
|
|
391
411
|
|
|
@@ -409,6 +429,7 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
|
|
|
409
429
|
server: api.server,
|
|
410
430
|
router: api.router,
|
|
411
431
|
cache: api.cache,
|
|
432
|
+
scheduler: schedulerInstance,
|
|
412
433
|
element,
|
|
413
434
|
el: element,
|
|
414
435
|
root: rootNode
|
|
@@ -427,10 +448,13 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
|
|
|
427
448
|
const ownerWindow = target.ownerDocument?.defaultView ?? globalThis;
|
|
428
449
|
const Observer = ownerWindow.IntersectionObserver ?? globalThis.IntersectionObserver;
|
|
429
450
|
if (!Observer) {
|
|
430
|
-
|
|
451
|
+
schedulerInstance.enqueue("lifecycle", () => {
|
|
431
452
|
if (!destroyed) {
|
|
432
453
|
fn(target);
|
|
433
454
|
}
|
|
455
|
+
}, {
|
|
456
|
+
scope: target,
|
|
457
|
+
key: "visible:fallback"
|
|
434
458
|
});
|
|
435
459
|
return () => {};
|
|
436
460
|
}
|
|
@@ -485,6 +509,7 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
|
|
|
485
509
|
}
|
|
486
510
|
for (const element of elementsIn(node)) {
|
|
487
511
|
runScopedCleanups(element);
|
|
512
|
+
schedulerInstance.markScopeDestroyed(element);
|
|
488
513
|
}
|
|
489
514
|
}
|
|
490
515
|
|
|
@@ -508,6 +533,13 @@ export function Loader({ root, signals, handlers, server, router, cache, attribu
|
|
|
508
533
|
}
|
|
509
534
|
}
|
|
510
535
|
|
|
536
|
+
function scheduleLifecycle(element, fn, key) {
|
|
537
|
+
schedulerInstance.enqueue("lifecycle", fn, {
|
|
538
|
+
scope: element,
|
|
539
|
+
key
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
|
|
511
543
|
return api;
|
|
512
544
|
}
|
|
513
545
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
|
|
3
|
+
export function createRequestContextStore() {
|
|
4
|
+
const storage = new AsyncLocalStorage();
|
|
5
|
+
|
|
6
|
+
return {
|
|
7
|
+
storage,
|
|
8
|
+
|
|
9
|
+
run(context, fn, ...args) {
|
|
10
|
+
if (typeof fn !== "function") {
|
|
11
|
+
throw new TypeError("requestContext.run(context, fn) requires a function.");
|
|
12
|
+
}
|
|
13
|
+
return storage.run(context ?? {}, fn, ...args);
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
get() {
|
|
17
|
+
return storage.getStore();
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
snapshot() {
|
|
21
|
+
return { ...(storage.getStore() ?? {}) };
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function readRequestContext(store) {
|
|
27
|
+
if (!store) {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
if (typeof store.get === "function") {
|
|
31
|
+
return store.get() ?? {};
|
|
32
|
+
}
|
|
33
|
+
if (typeof store.getStore === "function") {
|
|
34
|
+
return store.getStore() ?? {};
|
|
35
|
+
}
|
|
36
|
+
if (typeof store === "object") {
|
|
37
|
+
return store;
|
|
38
|
+
}
|
|
39
|
+
return {};
|
|
40
|
+
}
|
package/src/router.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Loader } from "./loader.js";
|
|
2
2
|
import { createHandlerRegistry } from "./handlers.js";
|
|
3
|
+
import { createScheduler } from "./scheduler.js";
|
|
3
4
|
import { createSignalRegistry } from "./signals.js";
|
|
4
5
|
import { applyServerResult } from "./server.js";
|
|
5
6
|
import { createRegistryStore } from "./registry-store.js";
|
|
@@ -31,6 +32,7 @@ export function createRouteRegistry(initialMap = {}, options = {}) {
|
|
|
31
32
|
const nextRoute = normalizeRoute(pattern, definition);
|
|
32
33
|
entries.set(pattern, nextRoute.definition);
|
|
33
34
|
routes.push(nextRoute);
|
|
35
|
+
sortRoutes(routes);
|
|
34
36
|
return nextRoute;
|
|
35
37
|
},
|
|
36
38
|
|
|
@@ -103,6 +105,7 @@ export function createRouteRegistry(initialMap = {}, options = {}) {
|
|
|
103
105
|
const nextRoute = normalizeRoute(pattern, definition);
|
|
104
106
|
entries.set(pattern, nextRoute.definition);
|
|
105
107
|
routes.push(nextRoute);
|
|
108
|
+
sortRoutes(routes);
|
|
106
109
|
}
|
|
107
110
|
}
|
|
108
111
|
|
|
@@ -119,12 +122,15 @@ export function createRouter({
|
|
|
119
122
|
partials,
|
|
120
123
|
fetch: fetchImpl = globalThis.fetch?.bind(globalThis),
|
|
121
124
|
routeEndpoint = "/__async/route",
|
|
122
|
-
attributes
|
|
125
|
+
attributes,
|
|
126
|
+
scheduler
|
|
123
127
|
} = {}) {
|
|
124
128
|
const documentRef = root?.ownerDocument ?? root ?? globalThis.document;
|
|
125
129
|
const rootNode = root ?? documentRef;
|
|
126
130
|
const signalRegistry = signals ?? loader?.signals ?? createSignalRegistry();
|
|
127
131
|
const handlerRegistry = handlers ?? loader?.handlers ?? createHandlerRegistry();
|
|
132
|
+
const schedulerInstance = scheduler ?? loader?.scheduler ?? createScheduler();
|
|
133
|
+
const ownsScheduler = !scheduler && !loader?.scheduler;
|
|
128
134
|
const attributeConfig = normalizeAttributeConfig(attributes ?? loader?.attributes);
|
|
129
135
|
const loaderInstance =
|
|
130
136
|
loader ??
|
|
@@ -134,11 +140,14 @@ export function createRouter({
|
|
|
134
140
|
handlers: handlerRegistry,
|
|
135
141
|
server,
|
|
136
142
|
cache,
|
|
143
|
+
scheduler: schedulerInstance,
|
|
137
144
|
attributes: attributeConfig
|
|
138
145
|
});
|
|
139
146
|
const ownsLoader = !loader;
|
|
140
147
|
const cleanups = new Set();
|
|
141
148
|
let destroyed = false;
|
|
149
|
+
let navigationVersion = 0;
|
|
150
|
+
let activeNavigation;
|
|
142
151
|
|
|
143
152
|
const api = {
|
|
144
153
|
mode,
|
|
@@ -151,12 +160,13 @@ export function createRouter({
|
|
|
151
160
|
server,
|
|
152
161
|
cache,
|
|
153
162
|
partials,
|
|
163
|
+
scheduler: schedulerInstance,
|
|
154
164
|
attributes: attributeConfig,
|
|
155
165
|
|
|
156
166
|
start() {
|
|
157
167
|
assertActive();
|
|
158
168
|
loaderInstance.router = api;
|
|
159
|
-
signalRegistry._setContext?.({ router: api, loader: loaderInstance, server, cache });
|
|
169
|
+
signalRegistry._setContext?.({ router: api, loader: loaderInstance, server, cache, scheduler: schedulerInstance });
|
|
160
170
|
if (ownsLoader) {
|
|
161
171
|
loaderInstance.start();
|
|
162
172
|
}
|
|
@@ -178,7 +188,7 @@ export function createRouter({
|
|
|
178
188
|
},
|
|
179
189
|
|
|
180
190
|
match(url) {
|
|
181
|
-
return routes.match(url);
|
|
191
|
+
return routes.match(resolveUrl(url));
|
|
182
192
|
},
|
|
183
193
|
|
|
184
194
|
prefetch(url) {
|
|
@@ -203,7 +213,7 @@ export function createRouter({
|
|
|
203
213
|
return null;
|
|
204
214
|
}
|
|
205
215
|
|
|
206
|
-
const target =
|
|
216
|
+
const target = resolveUrl(url);
|
|
207
217
|
if (mode === "ssr-spa") {
|
|
208
218
|
return fetchRoutePartial(target, options);
|
|
209
219
|
}
|
|
@@ -215,10 +225,14 @@ export function createRouter({
|
|
|
215
225
|
return;
|
|
216
226
|
}
|
|
217
227
|
destroyed = true;
|
|
228
|
+
activeNavigation?.controller.abort(new Error("Router has been destroyed."));
|
|
218
229
|
for (const cleanup of cleanups) {
|
|
219
230
|
cleanup();
|
|
220
231
|
}
|
|
221
232
|
cleanups.clear();
|
|
233
|
+
if (ownsScheduler) {
|
|
234
|
+
schedulerInstance.destroy();
|
|
235
|
+
}
|
|
222
236
|
}
|
|
223
237
|
};
|
|
224
238
|
|
|
@@ -254,24 +268,37 @@ export function createRouter({
|
|
|
254
268
|
async function renderLocalRoutePartial(target, options = {}) {
|
|
255
269
|
const matched = api.match(target);
|
|
256
270
|
if (!matched) {
|
|
271
|
+
beginNavigation(target, null);
|
|
257
272
|
setNoRouteError(target);
|
|
258
273
|
return null;
|
|
259
274
|
}
|
|
260
275
|
|
|
276
|
+
const navigation = beginNavigation(target, matched);
|
|
261
277
|
setMatchedRouterState(target, matched, { pending: true, error: null });
|
|
262
278
|
|
|
263
279
|
try {
|
|
264
280
|
if (!matched.route?.partial || !partials?.resolve?.(matched.route.partial)) {
|
|
265
281
|
const error = new Error(`Route "${target.pathname}" does not have a registered partial.`);
|
|
266
|
-
|
|
282
|
+
if (isActiveNavigation(navigation)) {
|
|
283
|
+
setRouterState({ pending: false, error });
|
|
284
|
+
}
|
|
267
285
|
return null;
|
|
268
286
|
}
|
|
269
287
|
|
|
270
|
-
const result = await partials.render(matched.route.partial, matched.params, contextFor(matched));
|
|
271
|
-
|
|
288
|
+
const result = await partials.render(matched.route.partial, matched.params, contextFor(matched, navigation));
|
|
289
|
+
if (!isActiveNavigation(navigation)) {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
await applyNavigationResult(result, target, options, navigation);
|
|
293
|
+
if (!isActiveNavigation(navigation)) {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
272
296
|
setRouterState({ pending: false, error: null });
|
|
273
297
|
return result;
|
|
274
298
|
} catch (error) {
|
|
299
|
+
if (!isActiveNavigation(navigation)) {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
275
302
|
setRouterState({ pending: false, error });
|
|
276
303
|
throw error;
|
|
277
304
|
}
|
|
@@ -279,28 +306,48 @@ export function createRouter({
|
|
|
279
306
|
|
|
280
307
|
async function fetchRoutePartial(target, options = {}) {
|
|
281
308
|
const matched = api.match(target);
|
|
309
|
+
const navigation = beginNavigation(target, matched);
|
|
282
310
|
setMatchedRouterState(target, matched, { pending: true, error: null });
|
|
283
311
|
|
|
284
312
|
try {
|
|
285
|
-
const result = await fetchRoute(target.href);
|
|
286
|
-
|
|
313
|
+
const result = await fetchRoute(target.href, { signal: navigation.abort });
|
|
314
|
+
if (!isActiveNavigation(navigation)) {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
await applyNavigationResult(result, target, options, navigation);
|
|
318
|
+
if (!isActiveNavigation(navigation)) {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
287
321
|
setRouterState({ pending: false, error: null });
|
|
288
322
|
return result;
|
|
289
323
|
} catch (error) {
|
|
324
|
+
if (!isActiveNavigation(navigation)) {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
290
327
|
setRouterState({ pending: false, error });
|
|
291
328
|
throw error;
|
|
292
329
|
}
|
|
293
330
|
}
|
|
294
331
|
|
|
295
|
-
async function applyNavigationResult(result, target, options) {
|
|
332
|
+
async function applyNavigationResult(result, target, options, navigation) {
|
|
333
|
+
if (!isActiveNavigation(navigation)) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
296
336
|
await applyServerResult(result, {
|
|
297
337
|
signals: signalRegistry,
|
|
298
338
|
loader: loaderInstance,
|
|
299
339
|
router: api,
|
|
300
|
-
cache
|
|
340
|
+
cache,
|
|
341
|
+
scheduler: schedulerInstance,
|
|
342
|
+
abort: navigation?.abort
|
|
301
343
|
});
|
|
344
|
+
await schedulerInstance.flush();
|
|
345
|
+
if (!isActiveNavigation(navigation)) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
302
348
|
if (result?.html != null && !result.boundary && !result.redirect) {
|
|
303
349
|
loaderInstance.swap(boundary, result.html);
|
|
350
|
+
await schedulerInstance.flush();
|
|
304
351
|
}
|
|
305
352
|
if (result?.redirect || options.history === false) {
|
|
306
353
|
return;
|
|
@@ -312,14 +359,15 @@ export function createRouter({
|
|
|
312
359
|
documentRef.defaultView?.history?.pushState?.({}, "", target.href);
|
|
313
360
|
}
|
|
314
361
|
|
|
315
|
-
async function fetchRoute(url, { prefetch = false } = {}) {
|
|
362
|
+
async function fetchRoute(url, { prefetch = false, signal } = {}) {
|
|
316
363
|
if (typeof fetchImpl !== "function") {
|
|
317
364
|
throw new Error("Router navigation requires a partial registry or fetch.");
|
|
318
365
|
}
|
|
319
366
|
const response = await fetchImpl(`${routeEndpoint}?to=${encodeURIComponent(String(url))}`, {
|
|
320
367
|
headers: {
|
|
321
368
|
accept: "application/json, text/html"
|
|
322
|
-
}
|
|
369
|
+
},
|
|
370
|
+
signal
|
|
323
371
|
});
|
|
324
372
|
if (!response.ok) {
|
|
325
373
|
throw new Error(`Route "${url}" failed with ${response.status}.`);
|
|
@@ -334,7 +382,7 @@ export function createRouter({
|
|
|
334
382
|
return { boundary, html: await response.text() };
|
|
335
383
|
}
|
|
336
384
|
|
|
337
|
-
function contextFor(matched) {
|
|
385
|
+
function contextFor(matched, navigation) {
|
|
338
386
|
return {
|
|
339
387
|
params: matched.params,
|
|
340
388
|
route: matched.route,
|
|
@@ -344,10 +392,29 @@ export function createRouter({
|
|
|
344
392
|
loader: loaderInstance,
|
|
345
393
|
server,
|
|
346
394
|
cache,
|
|
347
|
-
|
|
395
|
+
scheduler: schedulerInstance,
|
|
396
|
+
abort: navigation?.abort
|
|
348
397
|
};
|
|
349
398
|
}
|
|
350
399
|
|
|
400
|
+
function beginNavigation(target, matched) {
|
|
401
|
+
activeNavigation?.controller.abort(new Error(`Router navigation superseded by ${target.pathname}${target.search}.`));
|
|
402
|
+
const controller = new AbortController();
|
|
403
|
+
const navigation = {
|
|
404
|
+
id: ++navigationVersion,
|
|
405
|
+
controller,
|
|
406
|
+
abort: controller.signal,
|
|
407
|
+
target,
|
|
408
|
+
matched
|
|
409
|
+
};
|
|
410
|
+
activeNavigation = navigation;
|
|
411
|
+
return navigation;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function isActiveNavigation(navigation) {
|
|
415
|
+
return !destroyed && navigation && activeNavigation?.id === navigation.id && !navigation.abort.aborted;
|
|
416
|
+
}
|
|
417
|
+
|
|
351
418
|
function updateStateFromLocation() {
|
|
352
419
|
const url = currentUrl();
|
|
353
420
|
const matched = api.match(url);
|
|
@@ -382,7 +449,14 @@ export function createRouter({
|
|
|
382
449
|
}
|
|
383
450
|
|
|
384
451
|
function currentUrl() {
|
|
385
|
-
return
|
|
452
|
+
return resolveUrl(documentRef.defaultView?.location?.href ?? "http://localhost/");
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function resolveUrl(url) {
|
|
456
|
+
if (url instanceof URL) {
|
|
457
|
+
return url;
|
|
458
|
+
}
|
|
459
|
+
return new URL(String(url), documentRef.defaultView?.location?.href ?? "http://localhost/");
|
|
386
460
|
}
|
|
387
461
|
|
|
388
462
|
function assertActive() {
|
|
@@ -399,6 +473,7 @@ function normalizeRoute(pattern, definition) {
|
|
|
399
473
|
pattern,
|
|
400
474
|
regex,
|
|
401
475
|
keys,
|
|
476
|
+
score: routeScore(pattern),
|
|
402
477
|
definition: normalized
|
|
403
478
|
};
|
|
404
479
|
}
|
|
@@ -467,6 +542,28 @@ function escapeRegExp(value) {
|
|
|
467
542
|
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
468
543
|
}
|
|
469
544
|
|
|
545
|
+
function sortRoutes(routes) {
|
|
546
|
+
routes.sort((left, right) => right.score - left.score || right.pattern.length - left.pattern.length);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function routeScore(pattern) {
|
|
550
|
+
if (pattern === "*") {
|
|
551
|
+
return -1;
|
|
552
|
+
}
|
|
553
|
+
return pattern
|
|
554
|
+
.split("/")
|
|
555
|
+
.filter(Boolean)
|
|
556
|
+
.reduce((score, segment) => {
|
|
557
|
+
if (segment === "*") {
|
|
558
|
+
return score;
|
|
559
|
+
}
|
|
560
|
+
if (segment.startsWith(":")) {
|
|
561
|
+
return score + 2;
|
|
562
|
+
}
|
|
563
|
+
return score + 4;
|
|
564
|
+
}, pattern === "/" ? 3 : 0);
|
|
565
|
+
}
|
|
566
|
+
|
|
470
567
|
function assertPattern(pattern) {
|
|
471
568
|
if (typeof pattern !== "string" || (pattern !== "*" && !pattern.startsWith("/"))) {
|
|
472
569
|
throw new TypeError("Route pattern must be a path string or \"*\".");
|