@zenithbuild/router 0.6.17 → 0.7.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/README.md +13 -129
- package/dist/ZenLink.zen +66 -3
- package/dist/events.d.ts +7 -3
- package/dist/events.js +62 -3
- package/index.d.ts +47 -5
- package/package.json +5 -1
- package/template-core.js +422 -0
- package/template-lifecycle.js +127 -0
- package/template-navigation.js +468 -0
- package/template.js +5 -412
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
export function renderRouterNavigationSource() {
|
|
2
|
+
return `function extractSsrData(parsed) {
|
|
3
|
+
if (!parsed || typeof parsed.getElementById !== "function") return {};
|
|
4
|
+
const ssrScript = parsed.getElementById("zenith-ssr-data");
|
|
5
|
+
if (!ssrScript) return {};
|
|
6
|
+
const source = typeof ssrScript.textContent === "string" ? ssrScript.textContent : "";
|
|
7
|
+
const marker = "window.__zenith_ssr_data =";
|
|
8
|
+
const markerIndex = source.indexOf(marker);
|
|
9
|
+
if (markerIndex === -1) return {};
|
|
10
|
+
const jsonText = source.slice(markerIndex + marker.length).trim().replace(/;$/, "");
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(jsonText);
|
|
13
|
+
} catch {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parseDocumentPayload(html) {
|
|
19
|
+
if (typeof DOMParser === "undefined") return null;
|
|
20
|
+
const parsed = new DOMParser().parseFromString(html, "text/html");
|
|
21
|
+
return {
|
|
22
|
+
html,
|
|
23
|
+
title: parsed.title || "",
|
|
24
|
+
ssrData: extractSsrData(parsed)
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isHtmlResponse(response) {
|
|
29
|
+
const contentType = String(response.headers.get("content-type") || "");
|
|
30
|
+
return /text\\/html|application\\/xhtml\\+xml/i.test(contentType);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function createDocumentDetail(payload, response) {
|
|
34
|
+
return {
|
|
35
|
+
title: payload && typeof payload.title === "string" ? payload.title : "",
|
|
36
|
+
hasSsrData: !!(payload && payload.ssrData && typeof payload.ssrData === "object"),
|
|
37
|
+
status: response && typeof response.status === "number" ? response.status : 200
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function createScrollDetail(targetUrl, scrollTarget) {
|
|
42
|
+
return {
|
|
43
|
+
mode: scrollTarget.mode,
|
|
44
|
+
x: scrollTarget.x,
|
|
45
|
+
y: scrollTarget.y,
|
|
46
|
+
hash: targetUrl.hash || ""
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function requestRouteCheck(context, resolved, targetUrl, signal) {
|
|
51
|
+
if (!requiresServerReload(resolved.route)) {
|
|
52
|
+
return { kind: "allow" };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
dispatchRouteEvent("route-check:start", {
|
|
56
|
+
navigationId: context.token,
|
|
57
|
+
navigationType: context.navigationType,
|
|
58
|
+
to: cloneUrl(targetUrl),
|
|
59
|
+
from: cloneUrl(context.fromUrl),
|
|
60
|
+
routeId: resolved.route,
|
|
61
|
+
params: copyParams(resolved.params),
|
|
62
|
+
stage: "route-check"
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const response = await fetch("/__zenith/route-check?path=" + encodeURIComponent(toNavigationPath(targetUrl)), {
|
|
67
|
+
headers: { "x-zenith-route-check": "1" },
|
|
68
|
+
credentials: "include",
|
|
69
|
+
signal
|
|
70
|
+
});
|
|
71
|
+
const data = await response.json();
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
throw new Error("route-check failed");
|
|
74
|
+
}
|
|
75
|
+
const result = data && data.result ? data.result : { kind: "allow" };
|
|
76
|
+
dispatchRouteEvent("route-check:end", {
|
|
77
|
+
navigationId: context.token,
|
|
78
|
+
navigationType: context.navigationType,
|
|
79
|
+
to: cloneUrl(targetUrl),
|
|
80
|
+
from: cloneUrl(context.fromUrl),
|
|
81
|
+
routeId: resolved.route,
|
|
82
|
+
params: copyParams(resolved.params),
|
|
83
|
+
stage: "route-check",
|
|
84
|
+
result
|
|
85
|
+
});
|
|
86
|
+
if (result.kind === "redirect") {
|
|
87
|
+
dispatchRouteEvent("route:redirect", {
|
|
88
|
+
navigationId: context.token,
|
|
89
|
+
navigationType: context.navigationType,
|
|
90
|
+
to: cloneUrl(targetUrl),
|
|
91
|
+
from: cloneUrl(context.fromUrl),
|
|
92
|
+
routeId: resolved.route,
|
|
93
|
+
params: copyParams(resolved.params),
|
|
94
|
+
stage: "route-check",
|
|
95
|
+
result
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
if (result.kind === "deny") {
|
|
99
|
+
dispatchRouteEvent("route:deny", {
|
|
100
|
+
navigationId: context.token,
|
|
101
|
+
navigationType: context.navigationType,
|
|
102
|
+
to: cloneUrl(targetUrl),
|
|
103
|
+
from: cloneUrl(context.fromUrl),
|
|
104
|
+
routeId: resolved.route,
|
|
105
|
+
params: copyParams(resolved.params),
|
|
106
|
+
stage: "route-check",
|
|
107
|
+
result
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
} catch (error) {
|
|
112
|
+
if (isAbortError(error)) {
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
dispatchRouteEvent("route-check:error", {
|
|
116
|
+
navigationId: context.token,
|
|
117
|
+
navigationType: context.navigationType,
|
|
118
|
+
to: cloneUrl(targetUrl),
|
|
119
|
+
from: cloneUrl(context.fromUrl),
|
|
120
|
+
routeId: resolved.route,
|
|
121
|
+
params: copyParams(resolved.params),
|
|
122
|
+
stage: "route-check",
|
|
123
|
+
error
|
|
124
|
+
});
|
|
125
|
+
return { kind: "allow" };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function resolveRedirectUrl(location, fallbackUrl) {
|
|
130
|
+
try {
|
|
131
|
+
return new URL(location || fallbackUrl.href, fallbackUrl.href);
|
|
132
|
+
} catch {
|
|
133
|
+
return new URL(fallbackUrl.href);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function navigateViaBrowser(targetUrl, replace) {
|
|
138
|
+
if (!targetUrl || typeof targetUrl.href !== "string") return;
|
|
139
|
+
if (replace && typeof window.location.replace === "function") {
|
|
140
|
+
window.location.replace(targetUrl.href);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
window.location.assign(targetUrl.href);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function dispatchNavigationFallback(context, detail) {
|
|
147
|
+
emitNavigationAbort(context, detail);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function performNavigation(targetUrl, historyMode, popstateState) {
|
|
151
|
+
const resolved = resolveRoute(targetUrl.pathname);
|
|
152
|
+
if (!resolved) {
|
|
153
|
+
if (historyMode === "pop") {
|
|
154
|
+
navigateViaBrowser(new URL(window.location.href), true);
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const context = beginNavigation(targetUrl, resolved, historyMode);
|
|
161
|
+
let historyCommitted = false;
|
|
162
|
+
let documentDetail = null;
|
|
163
|
+
try {
|
|
164
|
+
dispatchRouteEvent("navigation:request", buildNavigationPayload(context));
|
|
165
|
+
|
|
166
|
+
context.stage = "route-check";
|
|
167
|
+
const checkResult = await requestRouteCheck(context, resolved, targetUrl, context.signal);
|
|
168
|
+
if (!ensureCurrentNavigation(context)) return false;
|
|
169
|
+
if (checkResult.kind === "redirect") {
|
|
170
|
+
const redirectUrl = resolveRedirectUrl(checkResult.location, targetUrl);
|
|
171
|
+
dispatchNavigationFallback(context, {
|
|
172
|
+
reason: "server-redirect",
|
|
173
|
+
location: redirectUrl.href,
|
|
174
|
+
status: checkResult.status
|
|
175
|
+
});
|
|
176
|
+
navigateViaBrowser(redirectUrl, historyMode === "pop");
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
if (checkResult.kind === "deny") {
|
|
180
|
+
dispatchNavigationFallback(context, {
|
|
181
|
+
reason: "server-deny",
|
|
182
|
+
status: checkResult.status
|
|
183
|
+
});
|
|
184
|
+
navigateViaBrowser(targetUrl, historyMode === "pop");
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
context.stage = "fetch";
|
|
189
|
+
const response = await fetch(targetUrl.href, {
|
|
190
|
+
credentials: "include",
|
|
191
|
+
headers: { Accept: "text/html,application/xhtml+xml" },
|
|
192
|
+
redirect: "manual",
|
|
193
|
+
signal: context.signal
|
|
194
|
+
});
|
|
195
|
+
if (!ensureCurrentNavigation(context)) return false;
|
|
196
|
+
|
|
197
|
+
if (response.type === "opaqueredirect" || (response.status >= 300 && response.status < 400)) {
|
|
198
|
+
const redirectUrl = resolveRedirectUrl(response.headers.get("location"), targetUrl);
|
|
199
|
+
dispatchNavigationFallback(context, {
|
|
200
|
+
reason: "server-redirect",
|
|
201
|
+
location: redirectUrl.href,
|
|
202
|
+
status: response.status
|
|
203
|
+
});
|
|
204
|
+
navigateViaBrowser(redirectUrl, historyMode === "pop");
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const html = await response.text();
|
|
209
|
+
if (!ensureCurrentNavigation(context)) return false;
|
|
210
|
+
if (response.status !== 200) {
|
|
211
|
+
dispatchNavigationFallback(context, {
|
|
212
|
+
reason: "http-status",
|
|
213
|
+
status: response.status
|
|
214
|
+
});
|
|
215
|
+
navigateViaBrowser(targetUrl, historyMode === "pop");
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
if (!isHtmlResponse(response)) {
|
|
219
|
+
dispatchNavigationFallback(context, {
|
|
220
|
+
reason: "non-html",
|
|
221
|
+
status: response.status
|
|
222
|
+
});
|
|
223
|
+
navigateViaBrowser(targetUrl, historyMode === "pop");
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const payload = parseDocumentPayload(html);
|
|
228
|
+
if (!payload) {
|
|
229
|
+
dispatchNavigationFallback(context, {
|
|
230
|
+
reason: "document-parse"
|
|
231
|
+
});
|
|
232
|
+
navigateViaBrowser(targetUrl, historyMode === "pop");
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
documentDetail = createDocumentDetail(payload, response);
|
|
237
|
+
|
|
238
|
+
context.stage = "data-ready";
|
|
239
|
+
emitNavigationEvent(context, "navigation:data-ready", {
|
|
240
|
+
document: documentDetail
|
|
241
|
+
}, false);
|
|
242
|
+
|
|
243
|
+
dispatchScrollEvent("before", {
|
|
244
|
+
navigationType: historyMode,
|
|
245
|
+
to: targetUrl.href,
|
|
246
|
+
from: context.fromUrl ? context.fromUrl.href : window.location.href
|
|
247
|
+
}, false);
|
|
248
|
+
|
|
249
|
+
context.stage = "before-leave";
|
|
250
|
+
await emitNavigationEvent(context, "navigation:before-leave", {
|
|
251
|
+
document: documentDetail
|
|
252
|
+
}, true);
|
|
253
|
+
if (!ensureCurrentNavigation(context)) return false;
|
|
254
|
+
|
|
255
|
+
context.stage = "leave-complete";
|
|
256
|
+
emitNavigationEvent(context, "navigation:leave-complete", {
|
|
257
|
+
document: documentDetail
|
|
258
|
+
}, false);
|
|
259
|
+
|
|
260
|
+
context.stage = "before-swap";
|
|
261
|
+
await emitNavigationEvent(context, "navigation:before-swap", {
|
|
262
|
+
document: documentDetail
|
|
263
|
+
}, true);
|
|
264
|
+
if (!ensureCurrentNavigation(context)) return false;
|
|
265
|
+
|
|
266
|
+
if (historyMode === "push") {
|
|
267
|
+
rememberScrollForKey(currentHistoryKey);
|
|
268
|
+
pushHistoryEntry(targetUrl);
|
|
269
|
+
historyCommitted = true;
|
|
270
|
+
} else if (historyMode === "pop") {
|
|
271
|
+
syncHistoryEntry(popstateState);
|
|
272
|
+
historyCommitted = true;
|
|
273
|
+
}
|
|
274
|
+
currentUrl = new URL(targetUrl.href);
|
|
275
|
+
|
|
276
|
+
if (payload.title) {
|
|
277
|
+
document.title = payload.title;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const mounted = await mountRoute(resolved.route, resolved.params, context.token, payload);
|
|
281
|
+
if (!mounted || !ensureCurrentNavigation(context)) return false;
|
|
282
|
+
|
|
283
|
+
context.stage = "content-swapped";
|
|
284
|
+
emitNavigationEvent(context, "navigation:content-swapped", {
|
|
285
|
+
document: documentDetail,
|
|
286
|
+
historyCommitted
|
|
287
|
+
}, false);
|
|
288
|
+
|
|
289
|
+
await nextFrame();
|
|
290
|
+
if (!ensureCurrentNavigation(context)) return false;
|
|
291
|
+
|
|
292
|
+
const scrollTarget = resolveScrollTarget(targetUrl, historyMode, popstateState);
|
|
293
|
+
const scrollDetail = createScrollDetail(targetUrl, scrollTarget);
|
|
294
|
+
const defaultScrollAllowed = dispatchScrollEvent("apply", {
|
|
295
|
+
navigationType: historyMode,
|
|
296
|
+
mode: scrollDetail.mode,
|
|
297
|
+
x: scrollDetail.x,
|
|
298
|
+
y: scrollDetail.y,
|
|
299
|
+
hash: scrollDetail.hash
|
|
300
|
+
}, true);
|
|
301
|
+
if (defaultScrollAllowed) {
|
|
302
|
+
applyNativeScroll(scrollTarget);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
focusAfterNavigation(scrollTarget);
|
|
306
|
+
rememberScrollForKey(currentHistoryKey, { x: scrollTarget.x, y: scrollTarget.y });
|
|
307
|
+
|
|
308
|
+
context.stage = "before-enter";
|
|
309
|
+
await emitNavigationEvent(context, "navigation:before-enter", {
|
|
310
|
+
document: documentDetail,
|
|
311
|
+
scroll: scrollDetail
|
|
312
|
+
}, true);
|
|
313
|
+
if (!ensureCurrentNavigation(context)) return false;
|
|
314
|
+
|
|
315
|
+
dispatchScrollEvent("after", {
|
|
316
|
+
navigationType: historyMode,
|
|
317
|
+
mode: scrollDetail.mode,
|
|
318
|
+
x: scrollDetail.x,
|
|
319
|
+
y: scrollDetail.y,
|
|
320
|
+
hash: scrollDetail.hash
|
|
321
|
+
}, false);
|
|
322
|
+
|
|
323
|
+
await nextFrame();
|
|
324
|
+
if (!ensureCurrentNavigation(context)) return false;
|
|
325
|
+
|
|
326
|
+
context.stage = "enter-complete";
|
|
327
|
+
emitNavigationEvent(context, "navigation:enter-complete", {
|
|
328
|
+
document: documentDetail,
|
|
329
|
+
scroll: scrollDetail
|
|
330
|
+
}, false);
|
|
331
|
+
|
|
332
|
+
return true;
|
|
333
|
+
} catch (error) {
|
|
334
|
+
if (!isAbortError(error)) {
|
|
335
|
+
emitNavigationError(context, {
|
|
336
|
+
reason: "runtime-failure",
|
|
337
|
+
error,
|
|
338
|
+
historyCommitted,
|
|
339
|
+
document: documentDetail
|
|
340
|
+
});
|
|
341
|
+
console.error("[Zenith Router] navigation failed", error);
|
|
342
|
+
dispatchNavigationFallback(context, {
|
|
343
|
+
reason: "runtime-failure",
|
|
344
|
+
historyCommitted
|
|
345
|
+
});
|
|
346
|
+
navigateViaBrowser(targetUrl, historyMode === "pop" || historyCommitted);
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
dispatchNavigationFallback(context, context.abortReason || {
|
|
350
|
+
reason: "superseded",
|
|
351
|
+
abortedStage: context.stage
|
|
352
|
+
});
|
|
353
|
+
return false;
|
|
354
|
+
} finally {
|
|
355
|
+
completeNavigation(context);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function isInternalLink(anchor) {
|
|
360
|
+
if (!anchor || anchor.target || anchor.hasAttribute("download")) return false;
|
|
361
|
+
const href = anchor.getAttribute("href");
|
|
362
|
+
if (!href || href.startsWith("#") || href.startsWith("mailto:") || href.startsWith("tel:")) {
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
const url = new URL(anchor.href, window.location.href);
|
|
366
|
+
return url.origin === window.location.origin;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async function mountInitialRoute() {
|
|
370
|
+
const resolved = resolveRoute(window.location.pathname);
|
|
371
|
+
if (!resolved) return;
|
|
372
|
+
navigationToken += 1;
|
|
373
|
+
currentUrl = new URL(window.location.href);
|
|
374
|
+
await mountRoute(resolved.route, resolved.params, navigationToken, null);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function start() {
|
|
378
|
+
if (typeof history === "object" && history && "scrollRestoration" in history) {
|
|
379
|
+
history.scrollRestoration = "manual";
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
ensureHistoryEntry();
|
|
383
|
+
currentUrl = new URL(window.location.href);
|
|
384
|
+
rememberScrollForKey(currentHistoryKey);
|
|
385
|
+
window.addEventListener("scroll", queueScrollSnapshot, { passive: true });
|
|
386
|
+
window.addEventListener("hashchange", function() {
|
|
387
|
+
currentUrl = new URL(window.location.href);
|
|
388
|
+
queueScrollSnapshot();
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
document.addEventListener("click", function(event) {
|
|
392
|
+
if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
393
|
+
const target = event.target && event.target.closest ? event.target.closest("a[data-zen-link]") : null;
|
|
394
|
+
if (!isInternalLink(target)) return;
|
|
395
|
+
|
|
396
|
+
const targetUrl = new URL(target.href, window.location.href);
|
|
397
|
+
const hashOnly =
|
|
398
|
+
currentUrl &&
|
|
399
|
+
targetUrl.pathname === currentUrl.pathname &&
|
|
400
|
+
targetUrl.search === currentUrl.search &&
|
|
401
|
+
targetUrl.hash !== currentUrl.hash;
|
|
402
|
+
if (hashOnly) return;
|
|
403
|
+
if (currentUrl && targetUrl.pathname === currentUrl.pathname && targetUrl.search === currentUrl.search && targetUrl.hash === currentUrl.hash) {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
if (!resolveRoute(targetUrl.pathname)) return;
|
|
407
|
+
|
|
408
|
+
event.preventDefault();
|
|
409
|
+
performNavigation(targetUrl, "push", null).catch(function(error) {
|
|
410
|
+
if (!isAbortError(error)) {
|
|
411
|
+
console.error("[Zenith Router] click navigation failed", error);
|
|
412
|
+
navigateViaBrowser(targetUrl, false);
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
window.addEventListener("popstate", function(event) {
|
|
418
|
+
const targetUrl = new URL(window.location.href);
|
|
419
|
+
const hashOnly =
|
|
420
|
+
currentUrl &&
|
|
421
|
+
targetUrl.pathname === currentUrl.pathname &&
|
|
422
|
+
targetUrl.search === currentUrl.search &&
|
|
423
|
+
targetUrl.hash !== currentUrl.hash;
|
|
424
|
+
if (hashOnly) {
|
|
425
|
+
syncHistoryEntry(event.state);
|
|
426
|
+
currentUrl = targetUrl;
|
|
427
|
+
const scrollTarget = resolveScrollTarget(targetUrl, "pop", event.state);
|
|
428
|
+
const defaultScrollAllowed = dispatchScrollEvent("apply", {
|
|
429
|
+
navigationType: "pop",
|
|
430
|
+
mode: scrollTarget.mode,
|
|
431
|
+
x: scrollTarget.x,
|
|
432
|
+
y: scrollTarget.y,
|
|
433
|
+
hash: targetUrl.hash || ""
|
|
434
|
+
}, true);
|
|
435
|
+
if (defaultScrollAllowed) {
|
|
436
|
+
applyNativeScroll(scrollTarget);
|
|
437
|
+
}
|
|
438
|
+
focusAfterNavigation(scrollTarget);
|
|
439
|
+
rememberScrollForKey(currentHistoryKey, { x: scrollTarget.x, y: scrollTarget.y });
|
|
440
|
+
dispatchScrollEvent("after", {
|
|
441
|
+
navigationType: "pop",
|
|
442
|
+
mode: scrollTarget.mode,
|
|
443
|
+
x: scrollTarget.x,
|
|
444
|
+
y: scrollTarget.y,
|
|
445
|
+
hash: targetUrl.hash || ""
|
|
446
|
+
}, false);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
performNavigation(targetUrl, "pop", event.state).catch(function(error) {
|
|
451
|
+
if (!isAbortError(error)) {
|
|
452
|
+
console.error("[Zenith Router] popstate navigation failed", error);
|
|
453
|
+
navigateViaBrowser(targetUrl, true);
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
mountInitialRoute().catch(function(error) {
|
|
459
|
+
console.error("[Zenith Router] initial navigation failed", error);
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (document.readyState === "loading") {
|
|
464
|
+
document.addEventListener("DOMContentLoaded", start, { once: true });
|
|
465
|
+
} else {
|
|
466
|
+
start();
|
|
467
|
+
}`;
|
|
468
|
+
}
|