@async/framework 0.10.2 → 0.11.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 +20 -0
- package/README.md +23 -7
- package/browser.d.ts +4 -7
- package/browser.js +17 -66
- package/browser.min.js +1 -1
- package/browser.ts +17 -66
- package/browser.umd.js +17 -66
- package/browser.umd.min.js +1 -1
- package/{server.d.ts → framework.d.ts} +4 -7
- package/framework.ts +5946 -0
- package/package.json +25 -17
- package/server.js +5945 -0
- package/examples/cache/index.html +0 -16
- package/examples/cache/main.js +0 -47
- package/examples/components/index.html +0 -11
- package/examples/components/main.js +0 -26
- package/examples/counter/index.html +0 -15
- package/examples/counter/main.js +0 -17
- package/examples/partials/index.html +0 -15
- package/examples/partials/main.js +0 -43
- package/examples/product/index.html +0 -32
- package/examples/product/main.js +0 -24
- package/examples/router/index.html +0 -18
- package/examples/router/main.js +0 -52
- package/examples/server-call/index.html +0 -21
- package/examples/server-call/main.js +0 -22
- package/examples/ssr/index.html +0 -12
- package/examples/ssr/main.js +0 -89
- package/examples/streaming/index.html +0 -16
- package/examples/streaming/main.js +0 -30
- package/src/app.js +0 -802
- package/src/async-signal.js +0 -277
- package/src/attributes.js +0 -52
- package/src/boundary-receiver.js +0 -302
- package/src/browser.js +0 -18
- package/src/cache.js +0 -193
- package/src/component.js +0 -373
- package/src/delay.js +0 -30
- package/src/elements.js +0 -63
- package/src/handlers.js +0 -219
- package/src/html.js +0 -158
- package/src/index.js +0 -20
- package/src/lazy-registry.js +0 -218
- package/src/loader.js +0 -772
- package/src/partials.js +0 -133
- package/src/registry-store.js +0 -267
- package/src/request-context.js +0 -40
- package/src/router.js +0 -617
- package/src/scheduler.js +0 -300
- package/src/server-entry.js +0 -20
- package/src/server-registry.js +0 -97
- package/src/server.js +0 -362
- package/src/signals.js +0 -592
package/src/router.js
DELETED
|
@@ -1,617 +0,0 @@
|
|
|
1
|
-
import { Loader } from "./loader.js";
|
|
2
|
-
import { createHandlerRegistry } from "./handlers.js";
|
|
3
|
-
import { createScheduler } from "./scheduler.js";
|
|
4
|
-
import { createSignalRegistry } from "./signals.js";
|
|
5
|
-
import { applyServerResult } from "./server.js";
|
|
6
|
-
import { createRegistryStore } from "./registry-store.js";
|
|
7
|
-
import { normalizeAttributeConfig } from "./attributes.js";
|
|
8
|
-
|
|
9
|
-
export function defineRoute(partial, options = {}) {
|
|
10
|
-
return {
|
|
11
|
-
...options,
|
|
12
|
-
partial
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export const route = defineRoute;
|
|
17
|
-
|
|
18
|
-
export function createRouteRegistry(initialMap = {}, options = {}) {
|
|
19
|
-
const registryStore = options.registry ?? createRegistryStore();
|
|
20
|
-
const type = options.type ?? "route";
|
|
21
|
-
const entries = registryStore._map(type);
|
|
22
|
-
const routes = [];
|
|
23
|
-
|
|
24
|
-
const registry = {
|
|
25
|
-
registry: registryStore,
|
|
26
|
-
|
|
27
|
-
register(pattern, definition) {
|
|
28
|
-
assertPattern(pattern);
|
|
29
|
-
if (routes.some((candidate) => candidate.pattern === pattern)) {
|
|
30
|
-
throw new Error(`Route "${pattern}" is already registered.`);
|
|
31
|
-
}
|
|
32
|
-
const nextRoute = normalizeRoute(pattern, definition);
|
|
33
|
-
entries.set(pattern, nextRoute.definition);
|
|
34
|
-
routes.push(nextRoute);
|
|
35
|
-
sortRoutes(routes);
|
|
36
|
-
return nextRoute;
|
|
37
|
-
},
|
|
38
|
-
|
|
39
|
-
registerMany(map) {
|
|
40
|
-
for (const [pattern, definition] of Object.entries(map ?? {})) {
|
|
41
|
-
registry.register(pattern, definition);
|
|
42
|
-
}
|
|
43
|
-
return registry;
|
|
44
|
-
},
|
|
45
|
-
|
|
46
|
-
unregister(pattern) {
|
|
47
|
-
assertPattern(pattern);
|
|
48
|
-
const index = routes.findIndex((candidate) => candidate.pattern === pattern);
|
|
49
|
-
if (index !== -1) {
|
|
50
|
-
routes.splice(index, 1);
|
|
51
|
-
}
|
|
52
|
-
return entries.delete(pattern);
|
|
53
|
-
},
|
|
54
|
-
|
|
55
|
-
match(url) {
|
|
56
|
-
const path = toUrl(url).pathname;
|
|
57
|
-
for (const candidate of routes) {
|
|
58
|
-
const match = candidate.regex.exec(path);
|
|
59
|
-
if (!match) {
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
const params = {};
|
|
63
|
-
candidate.keys.forEach((key, index) => {
|
|
64
|
-
params[key] = safeDecodeURIComponent(match[index + 1] ?? "");
|
|
65
|
-
});
|
|
66
|
-
return {
|
|
67
|
-
pattern: candidate.pattern,
|
|
68
|
-
params,
|
|
69
|
-
route: candidate.definition
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
return null;
|
|
73
|
-
},
|
|
74
|
-
|
|
75
|
-
entries() {
|
|
76
|
-
return routes.map(({ pattern, definition }) => ({ pattern, route: definition }));
|
|
77
|
-
},
|
|
78
|
-
|
|
79
|
-
keys() {
|
|
80
|
-
return [...entries.keys()];
|
|
81
|
-
},
|
|
82
|
-
|
|
83
|
-
inspect() {
|
|
84
|
-
return registryStore.entries(type);
|
|
85
|
-
},
|
|
86
|
-
|
|
87
|
-
_adoptMany(map = {}) {
|
|
88
|
-
for (const pattern of Object.keys(map ?? {})) {
|
|
89
|
-
adoptRoute(pattern, entries.get(pattern));
|
|
90
|
-
}
|
|
91
|
-
return registry;
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
for (const [pattern, definition] of entries) {
|
|
96
|
-
adoptRoute(pattern, definition);
|
|
97
|
-
}
|
|
98
|
-
registry.registerMany(initialMap);
|
|
99
|
-
return registry;
|
|
100
|
-
|
|
101
|
-
function adoptRoute(pattern, definition) {
|
|
102
|
-
if (routes.some((candidate) => candidate.pattern === pattern)) {
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
const nextRoute = normalizeRoute(pattern, definition);
|
|
106
|
-
entries.set(pattern, nextRoute.definition);
|
|
107
|
-
routes.push(nextRoute);
|
|
108
|
-
sortRoutes(routes);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export function createRouter({
|
|
113
|
-
mode = "ssr-spa",
|
|
114
|
-
root,
|
|
115
|
-
boundary = "route",
|
|
116
|
-
routes = createRouteRegistry(),
|
|
117
|
-
loader,
|
|
118
|
-
signals,
|
|
119
|
-
handlers,
|
|
120
|
-
server,
|
|
121
|
-
cache,
|
|
122
|
-
partials,
|
|
123
|
-
fetch: fetchImpl = globalThis.fetch?.bind(globalThis),
|
|
124
|
-
routeEndpoint = "/__async/route",
|
|
125
|
-
attributes,
|
|
126
|
-
scheduler
|
|
127
|
-
} = {}) {
|
|
128
|
-
const documentRef = root?.ownerDocument ?? root ?? globalThis.document;
|
|
129
|
-
const rootNode = root ?? documentRef;
|
|
130
|
-
const signalRegistry = signals ?? loader?.signals ?? createSignalRegistry();
|
|
131
|
-
const handlerRegistry = handlers ?? loader?.handlers ?? createHandlerRegistry();
|
|
132
|
-
const schedulerInstance = scheduler ?? loader?.scheduler ?? createScheduler();
|
|
133
|
-
const ownsScheduler = !scheduler && !loader?.scheduler;
|
|
134
|
-
const attributeConfig = normalizeAttributeConfig(attributes ?? loader?.attributes);
|
|
135
|
-
const loaderInstance =
|
|
136
|
-
loader ??
|
|
137
|
-
Loader({
|
|
138
|
-
root: rootNode,
|
|
139
|
-
signals: signalRegistry,
|
|
140
|
-
handlers: handlerRegistry,
|
|
141
|
-
server,
|
|
142
|
-
cache,
|
|
143
|
-
scheduler: schedulerInstance,
|
|
144
|
-
attributes: attributeConfig
|
|
145
|
-
});
|
|
146
|
-
const ownsLoader = !loader;
|
|
147
|
-
const cleanups = new Set();
|
|
148
|
-
let destroyed = false;
|
|
149
|
-
let navigationVersion = 0;
|
|
150
|
-
let activeNavigation;
|
|
151
|
-
|
|
152
|
-
const api = {
|
|
153
|
-
mode,
|
|
154
|
-
root: rootNode,
|
|
155
|
-
boundary,
|
|
156
|
-
routes,
|
|
157
|
-
loader: loaderInstance,
|
|
158
|
-
signals: signalRegistry,
|
|
159
|
-
handlers: handlerRegistry,
|
|
160
|
-
server,
|
|
161
|
-
cache,
|
|
162
|
-
partials,
|
|
163
|
-
scheduler: schedulerInstance,
|
|
164
|
-
attributes: attributeConfig,
|
|
165
|
-
|
|
166
|
-
start() {
|
|
167
|
-
assertActive();
|
|
168
|
-
loaderInstance.router = api;
|
|
169
|
-
signalRegistry._setContext?.({ router: api, loader: loaderInstance, server, cache, scheduler: schedulerInstance });
|
|
170
|
-
if (ownsLoader) {
|
|
171
|
-
loaderInstance.start();
|
|
172
|
-
}
|
|
173
|
-
if (mode === "mpa" || mode === "ssr") {
|
|
174
|
-
updateStateFromLocation();
|
|
175
|
-
return api;
|
|
176
|
-
}
|
|
177
|
-
bindNavigation();
|
|
178
|
-
if (mode === "csr") {
|
|
179
|
-
handleNavigation(api.navigate(currentUrl(), {
|
|
180
|
-
replace: true,
|
|
181
|
-
initial: true,
|
|
182
|
-
source: "client"
|
|
183
|
-
}));
|
|
184
|
-
return api;
|
|
185
|
-
}
|
|
186
|
-
updateStateFromLocation();
|
|
187
|
-
return api;
|
|
188
|
-
},
|
|
189
|
-
|
|
190
|
-
match(url) {
|
|
191
|
-
return routes.match(resolveUrl(url));
|
|
192
|
-
},
|
|
193
|
-
|
|
194
|
-
prefetch(url) {
|
|
195
|
-
assertActive();
|
|
196
|
-
if (mode === "ssr-spa" && typeof fetchImpl === "function") {
|
|
197
|
-
return fetchRoute(url, { prefetch: true });
|
|
198
|
-
}
|
|
199
|
-
const matched = api.match(url);
|
|
200
|
-
if (matched?.route?.partial && partials?.resolve?.(matched.route.partial)) {
|
|
201
|
-
return partials.render(matched.route.partial, matched.params, contextFor(matched));
|
|
202
|
-
}
|
|
203
|
-
if (typeof fetchImpl === "function") {
|
|
204
|
-
return fetchRoute(url, { prefetch: true });
|
|
205
|
-
}
|
|
206
|
-
return Promise.resolve(null);
|
|
207
|
-
},
|
|
208
|
-
|
|
209
|
-
async navigate(url, options = {}) {
|
|
210
|
-
assertActive();
|
|
211
|
-
if (mode === "mpa" || mode === "ssr") {
|
|
212
|
-
documentRef.defaultView?.location?.assign?.(url);
|
|
213
|
-
return null;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const target = resolveUrl(url);
|
|
217
|
-
if (mode === "ssr-spa") {
|
|
218
|
-
return fetchRoutePartial(target, options);
|
|
219
|
-
}
|
|
220
|
-
return renderLocalRoutePartial(target, options);
|
|
221
|
-
},
|
|
222
|
-
|
|
223
|
-
destroy() {
|
|
224
|
-
if (destroyed) {
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
destroyed = true;
|
|
228
|
-
activeNavigation?.controller.abort(new Error("Router has been destroyed."));
|
|
229
|
-
for (const cleanup of cleanups) {
|
|
230
|
-
cleanup();
|
|
231
|
-
}
|
|
232
|
-
cleanups.clear();
|
|
233
|
-
if (ownsScheduler) {
|
|
234
|
-
schedulerInstance.destroy();
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
};
|
|
238
|
-
|
|
239
|
-
return api;
|
|
240
|
-
|
|
241
|
-
function bindNavigation() {
|
|
242
|
-
const click = (event) => {
|
|
243
|
-
const anchor = closest(event.target, "a[href]");
|
|
244
|
-
if (!anchor || shouldIgnoreLink(event, anchor)) {
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
event.preventDefault();
|
|
248
|
-
handleNavigation(api.navigate(anchor.href));
|
|
249
|
-
};
|
|
250
|
-
const submit = (event) => {
|
|
251
|
-
const form = closest(event.target, "form");
|
|
252
|
-
if (!form || shouldIgnoreForm(form)) {
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
event.preventDefault();
|
|
256
|
-
handleNavigation(api.navigate(formActionUrl(form)));
|
|
257
|
-
};
|
|
258
|
-
const popstate = () => handleNavigation(api.navigate(currentUrl(), { history: false }));
|
|
259
|
-
|
|
260
|
-
rootNode.addEventListener?.("click", click);
|
|
261
|
-
rootNode.addEventListener?.("submit", submit);
|
|
262
|
-
documentRef.defaultView?.addEventListener?.("popstate", popstate);
|
|
263
|
-
cleanups.add(() => rootNode.removeEventListener?.("click", click));
|
|
264
|
-
cleanups.add(() => rootNode.removeEventListener?.("submit", submit));
|
|
265
|
-
cleanups.add(() => documentRef.defaultView?.removeEventListener?.("popstate", popstate));
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
async function renderLocalRoutePartial(target, options = {}) {
|
|
269
|
-
const matched = api.match(target);
|
|
270
|
-
if (!matched) {
|
|
271
|
-
beginNavigation(target, null);
|
|
272
|
-
setNoRouteError(target);
|
|
273
|
-
return null;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
const navigation = beginNavigation(target, matched);
|
|
277
|
-
setMatchedRouterState(target, matched, { pending: true, error: null });
|
|
278
|
-
|
|
279
|
-
try {
|
|
280
|
-
if (!matched.route?.partial || !partials?.resolve?.(matched.route.partial)) {
|
|
281
|
-
const error = new Error(`Route "${target.pathname}" does not have a registered partial.`);
|
|
282
|
-
if (isActiveNavigation(navigation)) {
|
|
283
|
-
setRouterState({ pending: false, error });
|
|
284
|
-
}
|
|
285
|
-
return null;
|
|
286
|
-
}
|
|
287
|
-
|
|
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
|
-
}
|
|
296
|
-
setRouterState({ pending: false, error: null });
|
|
297
|
-
return result;
|
|
298
|
-
} catch (error) {
|
|
299
|
-
if (!isActiveNavigation(navigation)) {
|
|
300
|
-
return null;
|
|
301
|
-
}
|
|
302
|
-
setRouterState({ pending: false, error });
|
|
303
|
-
throw error;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
async function fetchRoutePartial(target, options = {}) {
|
|
308
|
-
const matched = api.match(target);
|
|
309
|
-
const navigation = beginNavigation(target, matched);
|
|
310
|
-
setMatchedRouterState(target, matched, { pending: true, error: null });
|
|
311
|
-
|
|
312
|
-
try {
|
|
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
|
-
}
|
|
321
|
-
setRouterState({ pending: false, error: null });
|
|
322
|
-
return result;
|
|
323
|
-
} catch (error) {
|
|
324
|
-
if (!isActiveNavigation(navigation)) {
|
|
325
|
-
return null;
|
|
326
|
-
}
|
|
327
|
-
setRouterState({ pending: false, error });
|
|
328
|
-
throw error;
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
async function applyNavigationResult(result, target, options, navigation) {
|
|
333
|
-
if (!isActiveNavigation(navigation)) {
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
await applyServerResult(result, {
|
|
337
|
-
signals: signalRegistry,
|
|
338
|
-
loader: loaderInstance,
|
|
339
|
-
router: api,
|
|
340
|
-
cache,
|
|
341
|
-
scheduler: schedulerInstance,
|
|
342
|
-
abort: navigation?.abort
|
|
343
|
-
});
|
|
344
|
-
await schedulerInstance.flush();
|
|
345
|
-
if (!isActiveNavigation(navigation)) {
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
if (result?.html != null && !result.boundary && !result.redirect) {
|
|
349
|
-
loaderInstance.swap(boundary, result.html);
|
|
350
|
-
await schedulerInstance.flush();
|
|
351
|
-
}
|
|
352
|
-
if (result?.redirect || options.history === false) {
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
if (options.replace) {
|
|
356
|
-
documentRef.defaultView?.history?.replaceState?.({}, "", target.href);
|
|
357
|
-
return;
|
|
358
|
-
}
|
|
359
|
-
documentRef.defaultView?.history?.pushState?.({}, "", target.href);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
async function fetchRoute(url, { prefetch = false, signal } = {}) {
|
|
363
|
-
if (typeof fetchImpl !== "function") {
|
|
364
|
-
throw new Error("Router navigation requires a partial registry or fetch.");
|
|
365
|
-
}
|
|
366
|
-
const response = await fetchImpl(`${routeEndpoint}?to=${encodeURIComponent(String(url))}`, {
|
|
367
|
-
headers: {
|
|
368
|
-
accept: "application/json, text/html"
|
|
369
|
-
},
|
|
370
|
-
signal
|
|
371
|
-
});
|
|
372
|
-
if (!response.ok) {
|
|
373
|
-
throw new Error(`Route "${url}" failed with ${response.status}.`);
|
|
374
|
-
}
|
|
375
|
-
if (prefetch) {
|
|
376
|
-
return response;
|
|
377
|
-
}
|
|
378
|
-
const type = response.headers.get("content-type") ?? "";
|
|
379
|
-
if (type.includes("application/json")) {
|
|
380
|
-
return response.json();
|
|
381
|
-
}
|
|
382
|
-
return { boundary, html: await response.text() };
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
function contextFor(matched, navigation) {
|
|
386
|
-
return {
|
|
387
|
-
params: matched.params,
|
|
388
|
-
route: matched.route,
|
|
389
|
-
router: api,
|
|
390
|
-
signals: signalRegistry,
|
|
391
|
-
handlers: handlerRegistry,
|
|
392
|
-
loader: loaderInstance,
|
|
393
|
-
server,
|
|
394
|
-
cache,
|
|
395
|
-
scheduler: schedulerInstance,
|
|
396
|
-
abort: navigation?.abort
|
|
397
|
-
};
|
|
398
|
-
}
|
|
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
|
-
|
|
418
|
-
function updateStateFromLocation() {
|
|
419
|
-
const url = currentUrl();
|
|
420
|
-
const matched = api.match(url);
|
|
421
|
-
setMatchedRouterState(url, matched, { pending: false, error: null });
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
function setMatchedRouterState(url, matched, patch = {}) {
|
|
425
|
-
signalRegistry.ensure("router", {});
|
|
426
|
-
setRouterState({
|
|
427
|
-
url: url.href,
|
|
428
|
-
path: url.pathname,
|
|
429
|
-
query: queryObject(url),
|
|
430
|
-
params: matched?.params ?? {},
|
|
431
|
-
route: matched?.route ?? null,
|
|
432
|
-
...patch
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
function setNoRouteError(url) {
|
|
437
|
-
const error = new Error(`No route matched ${url.pathname}${url.search}`);
|
|
438
|
-
setMatchedRouterState(url, null, {
|
|
439
|
-
pending: false,
|
|
440
|
-
error
|
|
441
|
-
});
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
function setRouterState(patch) {
|
|
445
|
-
signalRegistry.ensure("router", {});
|
|
446
|
-
for (const [key, value] of Object.entries(patch)) {
|
|
447
|
-
signalRegistry.set(`router.${key}`, value);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
function handleNavigation(promise) {
|
|
452
|
-
void promise.catch((error) => {
|
|
453
|
-
if (destroyed) {
|
|
454
|
-
return;
|
|
455
|
-
}
|
|
456
|
-
setRouterState({
|
|
457
|
-
pending: false,
|
|
458
|
-
error
|
|
459
|
-
});
|
|
460
|
-
dispatchAsyncError(rootNode, error);
|
|
461
|
-
});
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
function currentUrl() {
|
|
465
|
-
return resolveUrl(documentRef.defaultView?.location?.href ?? "http://localhost/");
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
function resolveUrl(url) {
|
|
469
|
-
if (url instanceof URL) {
|
|
470
|
-
return url;
|
|
471
|
-
}
|
|
472
|
-
return new URL(String(url), documentRef.defaultView?.location?.href ?? "http://localhost/");
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
function assertActive() {
|
|
476
|
-
if (destroyed) {
|
|
477
|
-
throw new Error("Router has been destroyed.");
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
function shouldIgnoreLink(event, anchor) {
|
|
482
|
-
if (event.defaultPrevented || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
|
|
483
|
-
return true;
|
|
484
|
-
}
|
|
485
|
-
if (anchor.target || anchor.hasAttribute("download")) {
|
|
486
|
-
return true;
|
|
487
|
-
}
|
|
488
|
-
const target = resolveUrl(anchor.href);
|
|
489
|
-
const current = currentUrl();
|
|
490
|
-
if (target.origin !== current.origin) {
|
|
491
|
-
return true;
|
|
492
|
-
}
|
|
493
|
-
return isHashOnlyNavigation(target, current, anchor);
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
function shouldIgnoreForm(form) {
|
|
497
|
-
const method = String(form.method || "get").toLowerCase();
|
|
498
|
-
return method !== "get" || resolveUrl(form.action).origin !== currentUrl().origin;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
function formActionUrl(form) {
|
|
502
|
-
const url = resolveUrl(form.action || form.ownerDocument.defaultView.location.href);
|
|
503
|
-
const formData = new form.ownerDocument.defaultView.FormData(form);
|
|
504
|
-
url.search = new URLSearchParams(formData).toString();
|
|
505
|
-
return url.href;
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
function normalizeRoute(pattern, definition) {
|
|
510
|
-
const normalized = typeof definition === "string" ? defineRoute(definition) : definition;
|
|
511
|
-
const { regex, keys } = compilePattern(pattern);
|
|
512
|
-
return {
|
|
513
|
-
pattern,
|
|
514
|
-
regex,
|
|
515
|
-
keys,
|
|
516
|
-
score: routeScore(pattern),
|
|
517
|
-
definition: normalized
|
|
518
|
-
};
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
function compilePattern(pattern) {
|
|
522
|
-
const keys = [];
|
|
523
|
-
if (pattern === "*") {
|
|
524
|
-
return { regex: /^.*$/, keys };
|
|
525
|
-
}
|
|
526
|
-
if (pattern === "/") {
|
|
527
|
-
return { regex: /^\/$/, keys };
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
const source = pattern
|
|
531
|
-
.split("/")
|
|
532
|
-
.map((segment) => {
|
|
533
|
-
if (segment.startsWith(":")) {
|
|
534
|
-
keys.push(segment.slice(1));
|
|
535
|
-
return "([^/]+)";
|
|
536
|
-
}
|
|
537
|
-
return escapeRegExp(segment);
|
|
538
|
-
})
|
|
539
|
-
.join("/");
|
|
540
|
-
|
|
541
|
-
return { regex: new RegExp(`^${source}$`), keys };
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
function closest(target, selector) {
|
|
545
|
-
return target?.closest?.(selector);
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
function toUrl(url) {
|
|
549
|
-
if (url instanceof URL) {
|
|
550
|
-
return url;
|
|
551
|
-
}
|
|
552
|
-
return new URL(String(url), globalThis.location?.href ?? "http://localhost/");
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
function queryObject(url) {
|
|
556
|
-
return Object.fromEntries(url.searchParams.entries());
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
function safeDecodeURIComponent(value) {
|
|
560
|
-
try {
|
|
561
|
-
return decodeURIComponent(value);
|
|
562
|
-
} catch {
|
|
563
|
-
return value;
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
function isHashOnlyNavigation(target, current, anchor) {
|
|
568
|
-
if (target.origin !== current.origin || target.pathname !== current.pathname || target.search !== current.search) {
|
|
569
|
-
return false;
|
|
570
|
-
}
|
|
571
|
-
return target.hash !== current.hash || anchor.getAttribute?.("href")?.startsWith("#") === true;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
function dispatchAsyncError(element, error) {
|
|
575
|
-
const EventCtor = element.ownerDocument?.defaultView?.CustomEvent ?? globalThis.CustomEvent;
|
|
576
|
-
if (typeof EventCtor !== "function") {
|
|
577
|
-
return;
|
|
578
|
-
}
|
|
579
|
-
element.dispatchEvent?.(
|
|
580
|
-
new EventCtor("async:error", {
|
|
581
|
-
bubbles: true,
|
|
582
|
-
detail: { error }
|
|
583
|
-
})
|
|
584
|
-
);
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
function escapeRegExp(value) {
|
|
588
|
-
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
function sortRoutes(routes) {
|
|
592
|
-
routes.sort((left, right) => right.score - left.score || right.pattern.length - left.pattern.length);
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
function routeScore(pattern) {
|
|
596
|
-
if (pattern === "*") {
|
|
597
|
-
return -1;
|
|
598
|
-
}
|
|
599
|
-
return pattern
|
|
600
|
-
.split("/")
|
|
601
|
-
.filter(Boolean)
|
|
602
|
-
.reduce((score, segment) => {
|
|
603
|
-
if (segment === "*") {
|
|
604
|
-
return score;
|
|
605
|
-
}
|
|
606
|
-
if (segment.startsWith(":")) {
|
|
607
|
-
return score + 2;
|
|
608
|
-
}
|
|
609
|
-
return score + 4;
|
|
610
|
-
}, pattern === "/" ? 3 : 0);
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
function assertPattern(pattern) {
|
|
614
|
-
if (typeof pattern !== "string" || (pattern !== "*" && !pattern.startsWith("/"))) {
|
|
615
|
-
throw new TypeError("Route pattern must be a path string or \"*\".");
|
|
616
|
-
}
|
|
617
|
-
}
|