@markless/router 0.5.0 → 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.
@@ -0,0 +1,1333 @@
1
+ import { a as ROUTE_SCRIPT_TYPE, c as servedHtmlAttributesScript, d as REPLACE_ATTRIBUTE, f as SCROLL_ATTRIBUTE, i as HTML_ATTRIBUTES_SLOT, l as LINK_ATTRIBUTE, n as requestLocals, o as SERVED_HTML_ATTRIBUTES_PROPERTY, r as takeMiddlewareHeaders, u as PREFETCH_ATTRIBUTE } from "./request-locals-BDAhNpEa.js";
2
+ import { i as matchRouteManifest, n as buildRouteManifestFromFileIds, o as normalizeRouteFileId } from "./route-manifest-DMizh42B.js";
3
+ import { n as __marklessDebugBootstrapSource } from "./debug-channel-Di6SppZv.js";
4
+ import { CONTAINER_ATTRIBUTE, PROTOCOL_CONTAINER_END_COMMENT, PROTOCOL_CONTAINER_START_COMMENT, PROTOCOL_LOCATOR_PATHS_ATTRIBUTE } from "@markless/web/fns/container-region";
5
+ import { CONTAINER_RETIRE_PROPERTY, renderToString, splitSsrContainer } from "@markless/web/render-to-string";
6
+ import { STREAMED_ARM_SELECTOR, STREAM_ARM_EXECUTOR_GLOBAL, renderToStream } from "@markless/web/render-to-stream";
7
+ import { normalizeMarklessDevError, renderMarklessDevErrorDocument } from "@markless/bundler/dev-error";
8
+ //#region src/vite/link-intent.ts
9
+ function listenForLinkIntent(root, linkAttribute, preload, prefetchAttribute) {
10
+ const listener = (event) => {
11
+ if (event.defaultPrevented) return;
12
+ const target = event.composedPath()[0] ?? event.target;
13
+ const anchor = (target instanceof Element ? target : target?.parentElement)?.closest("a[href]");
14
+ if (!anchor?.hasAttribute(linkAttribute) || anchor.hasAttribute("download")) return;
15
+ if (prefetchAttribute && anchor.getAttribute(prefetchAttribute) === "none") return;
16
+ if (anchor.target && anchor.target !== "_self") return;
17
+ if (anchor.relList.contains("external")) return;
18
+ const url = new URL(anchor.href, anchor.ownerDocument.baseURI);
19
+ if (url.origin !== new URL(anchor.ownerDocument.URL).origin) return;
20
+ preload(url);
21
+ };
22
+ const events = [
23
+ "pointerover",
24
+ "focusin",
25
+ "pointerdown"
26
+ ];
27
+ for (const event of events) root.addEventListener(event, listener, { passive: true });
28
+ return () => {
29
+ for (const event of events) root.removeEventListener(event, listener);
30
+ };
31
+ }
32
+ function appendModulePreloads(document, items) {
33
+ if (!document?.head || !items?.length) return [];
34
+ const absolute = (href) => new URL(href, document.baseURI).href;
35
+ const seen = new Set([...document.querySelectorAll("link[rel=\"modulepreload\"]")].map((link) => absolute(link.getAttribute("href") || link.href)));
36
+ const appended = [];
37
+ for (const item of items) {
38
+ const href = typeof item === "string" ? item : item.href;
39
+ if (!href || seen.has(absolute(href))) continue;
40
+ const link = document.createElement("link");
41
+ link.rel = "modulepreload";
42
+ link.href = href;
43
+ link.crossOrigin = typeof item === "string" ? "anonymous" : item.crossOrigin ?? "anonymous";
44
+ if (typeof item !== "string" && item.fetchPriority) link.fetchPriority = item.fetchPriority;
45
+ document.head.appendChild(link);
46
+ seen.add(absolute(href));
47
+ appended.push(href);
48
+ }
49
+ return appended;
50
+ }
51
+ //#endregion
52
+ //#region src/vite/fragment-navigation.ts
53
+ function startFragmentNavigation(d, linkAttributeOf, routes, importModule, loadRun, intentOf) {
54
+ const w = d.defaultView;
55
+ if (!w) return;
56
+ const host = d;
57
+ const known = host.__marklessFragmentNav?.routes ?? host.__marklessFragmentWake;
58
+ if (known) return void Object.assign(known, routes);
59
+ host.__marklessFragmentWake = routes;
60
+ const scrolled = /* @__PURE__ */ new Set();
61
+ const arriving = /* @__PURE__ */ new Map();
62
+ let running;
63
+ let starting;
64
+ let pressed;
65
+ const preload = (item) => {
66
+ const href = new URL(typeof item === "string" ? item : item.href, d.baseURI).href;
67
+ if (arriving.has(href)) return;
68
+ const link = d.createElement("link");
69
+ link.rel = "modulepreload";
70
+ link.href = href;
71
+ link.crossOrigin = "anonymous";
72
+ arriving.set(href, new Promise((done) => link.onload = link.onerror = done));
73
+ d.head.appendChild(link);
74
+ };
75
+ const anchorOf = (event) => {
76
+ const target = event.composedPath()[0] ?? event.target;
77
+ return (target.nodeType === 1 ? target : target.parentElement)?.closest("a[href]");
78
+ };
79
+ const followed = (anchor) => {
80
+ if (anchor.hasAttribute("download") || anchor.relList.contains("external")) return;
81
+ if (anchor.target && anchor.target !== "_self") return;
82
+ const url = new URL(anchor.href, d.baseURI);
83
+ return url.origin === w.location.origin ? url : void 0;
84
+ };
85
+ const here = (url) => url.pathname + url.search === w.location.pathname + w.location.search;
86
+ const press = (anchor) => {
87
+ const intent = intentOf();
88
+ if (!intent || pressed || anchor.getAttribute(intent.prefetchAttribute) === "none") return;
89
+ const url = followed(anchor);
90
+ const code = url && routes[url.pathname];
91
+ if (!code || here(url)) return;
92
+ const target = new URL(url.href);
93
+ target.hash = "";
94
+ target.pathname = intent.requestPrefix + target.pathname;
95
+ const controller = new AbortController();
96
+ const response = w.fetch(target.href, {
97
+ credentials: "same-origin",
98
+ headers: { purpose: "prefetch" },
99
+ signal: controller.signal
100
+ });
101
+ response.catch(() => {});
102
+ for (const item of [...intent.code, ...code]) preload(item);
103
+ pressed = {
104
+ url: url.href,
105
+ at: w.performance.now(),
106
+ controller,
107
+ response
108
+ };
109
+ };
110
+ const wake = (event) => {
111
+ starting ??= loadRun().then((run) => {
112
+ delete host.__marklessFragmentWake;
113
+ return running = run(d, routes, importModule, scrolled, arriving, pressed);
114
+ });
115
+ starting.then((api) => api.replay(event), () => starting = void 0);
116
+ return starting;
117
+ };
118
+ let rest = 0;
119
+ let at = "";
120
+ const listen = (event) => {
121
+ const target = event.composedPath()[0] ?? event.target;
122
+ if (event.type === "pointermove") {
123
+ const pointer = event;
124
+ w.clearTimeout(rest);
125
+ if (at === (at = `${pointer.clientX},${pointer.clientY}`)) return;
126
+ const anchor = !running && pointer.pointerType === "mouse" && anchorOf(event);
127
+ const dwell = anchor && intentOf()?.hoverDwellMs;
128
+ if (dwell) rest = w.setTimeout(() => running || press(anchor), dwell);
129
+ } else if (event.type === "scroll") {
130
+ if (target !== d) scrolled.add(target);
131
+ } else if (event.type === "popstate") wake().then((api) => api.traverse(event));
132
+ else if (!running) {
133
+ const anchor = anchorOf(event);
134
+ if (!anchor) return;
135
+ if (event.type !== "click") {
136
+ if (event.type !== "pointerover") press(anchor);
137
+ wake(event);
138
+ } else if (anchor.hasAttribute(linkAttributeOf())) early(event);
139
+ else d.addEventListener("click", (later) => later === event && early(later), { once: true });
140
+ }
141
+ };
142
+ for (const type of [
143
+ "pointermove",
144
+ "scroll",
145
+ "pointerover",
146
+ "pointerdown",
147
+ "touchstart",
148
+ "focusin",
149
+ "popstate"
150
+ ]) w.addEventListener(type, listen, {
151
+ capture: true,
152
+ passive: true
153
+ });
154
+ w.addEventListener("click", listen, true);
155
+ const early = (event) => {
156
+ const mouse = event;
157
+ if (running || mouse.defaultPrevented || mouse.button !== 0) return;
158
+ if (mouse.metaKey || mouse.altKey || mouse.ctrlKey || mouse.shiftKey) return;
159
+ const anchor = anchorOf(event);
160
+ const url = followed(anchor);
161
+ if (!url || url.hash.startsWith("#/") || url.hash && here(url)) return;
162
+ event.preventDefault();
163
+ wake().then((api) => api.go(anchor) || w.location.assign(url.href), () => w.location.assign(url.href));
164
+ };
165
+ if (Object.keys(routes).length) {
166
+ const idle = () => w.requestIdleCallback ? w.requestIdleCallback(() => wake(), { timeout: 3e3 }) : w.setTimeout(() => wake(), 500);
167
+ if (d.readyState === "complete") idle();
168
+ else w.addEventListener("load", idle, { once: true });
169
+ }
170
+ }
171
+ function runFragmentNavigation(d, config, routes, importModule, scrolled, arriving, early) {
172
+ const w = d.defaultView;
173
+ const host = d;
174
+ const location = w.location;
175
+ const connection = w.navigator.connection;
176
+ const pageKey = (url) => url.pathname + url.search;
177
+ let module;
178
+ let loaded;
179
+ const load = () => {
180
+ if (!module) {
181
+ module = importModule(config.entry);
182
+ module.then((entry) => loaded = entry, () => module = void 0);
183
+ }
184
+ return module;
185
+ };
186
+ const underEndpoints = (pathname) => config.endpointPrefixes.some((prefix) => (pathname + "/").startsWith(prefix + "/"));
187
+ const pages = config.pages.map((page) => [underEndpoints(page), page.split("/").filter(Boolean)]);
188
+ const isPage = (pathname) => {
189
+ const endpoint = underEndpoints(pathname);
190
+ const parts = pathname.split("/").filter(Boolean);
191
+ return pages.some(([under, page]) => {
192
+ if (endpoint && !under) return false;
193
+ const rest = page.indexOf("**");
194
+ if (rest === -1 ? page.length !== parts.length : parts.length <= rest) return false;
195
+ return page.every((segment, index) => segment === "**" || segment === ":" || segment === parts[index]);
196
+ });
197
+ };
198
+ const eligible = (anchor, forClick = false) => {
199
+ if (!anchor || anchor.localName !== "a") return;
200
+ const link = anchor;
201
+ if (link.hasAttribute("download") || link.relList.contains("external")) return;
202
+ if (link.target && link.target !== "_self") return;
203
+ if (!forClick && link.getAttribute(config.prefetchAttribute) === "none") return;
204
+ let url;
205
+ try {
206
+ url = new URL(link.href, d.baseURI);
207
+ } catch {
208
+ return;
209
+ }
210
+ if (url.origin !== location.origin || url.hash.startsWith("#/")) return;
211
+ if (!isPage(url.pathname)) return;
212
+ const here = forClick ? pageKey(new URL(location.href)) : state.shown;
213
+ if (pageKey(url) === here && (!forClick || url.href.includes("#"))) return;
214
+ return url;
215
+ };
216
+ const anchorOf = (event) => {
217
+ const target = event.composedPath?.()[0] ?? event.target;
218
+ return (target && target.nodeType === 1 ? target : target?.parentElement)?.closest("a[href]");
219
+ };
220
+ const preloadCode = (url, low) => {
221
+ const present = new Set([...d.querySelectorAll("link[rel=\"modulepreload\"]")].map((link) => link.href));
222
+ for (const item of [
223
+ config.entry,
224
+ ...config.entryImports,
225
+ ...state.routes[url.pathname] ?? []
226
+ ]) {
227
+ const entry = typeof item === "string" ? { href: item } : item;
228
+ const href = new URL(entry.href, d.baseURI).href;
229
+ if (present.has(href) || arriving.has(href)) continue;
230
+ present.add(href);
231
+ const link = d.createElement("link");
232
+ link.rel = "modulepreload";
233
+ link.href = href;
234
+ link.crossOrigin = entry.crossOrigin ?? "anonymous";
235
+ if (low) link.fetchPriority = "low";
236
+ arriving.set(href, new Promise((done) => {
237
+ link.addEventListener("load", done, { once: true });
238
+ link.addEventListener("error", done, { once: true });
239
+ }));
240
+ d.head.appendChild(link);
241
+ }
242
+ };
243
+ const parse = (url, text) => {
244
+ const at = text.indexOf(config.regionEndMarker);
245
+ const prefix = at === -1 ? text : text.slice(0, at);
246
+ return {
247
+ url,
248
+ prefix,
249
+ doc: new DOMParser().parseFromString(prefix, "text/html"),
250
+ tail: at === -1 ? "" : text.slice(at + config.regionEndMarker.length)
251
+ };
252
+ };
253
+ const drain = (reader, closed) => {
254
+ const chunks = [];
255
+ let ended = false;
256
+ let wake = () => {};
257
+ const end = () => {
258
+ ended = true;
259
+ closed();
260
+ wake();
261
+ };
262
+ (async () => {
263
+ for (let chunk = await reader.read(); !chunk.done; chunk = await reader.read()) {
264
+ chunks.push(chunk.value);
265
+ wake();
266
+ }
267
+ })().then(end, end);
268
+ return {
269
+ read: async () => {
270
+ while (!chunks.length && !ended) await new Promise((done) => wake = done);
271
+ return chunks.length ? {
272
+ done: false,
273
+ value: chunks.shift()
274
+ } : {
275
+ done: true,
276
+ value: void 0
277
+ };
278
+ },
279
+ cancel: () => reader.cancel()
280
+ };
281
+ };
282
+ const request = (url, speculative, started) => {
283
+ const controller = started?.controller ?? new AbortController();
284
+ const read = async () => {
285
+ const prefix = config.requestPrefix;
286
+ const target = new URL(url.href);
287
+ target.hash = "";
288
+ target.pathname = prefix + target.pathname;
289
+ const response = await (started?.response ?? w.fetch(target.href, {
290
+ credentials: "same-origin",
291
+ headers: speculative ? { purpose: "prefetch" } : {},
292
+ priority: speculative ? "auto" : "high",
293
+ signal: controller.signal
294
+ }));
295
+ const type = response.headers.get("content-type") ?? "";
296
+ if (!response.ok || !type.includes("text/html") || !response.body) throw new Error(`MARKLESS_ROUTER_FRAGMENT_UNAVAILABLE: ${response.status}`);
297
+ const finalUrl = new URL(response.url || target.href);
298
+ if (finalUrl.origin !== location.origin) throw new Error("MARKLESS_ROUTER_FRAGMENT_OFF_ORIGIN");
299
+ if (!finalUrl.pathname.startsWith(`${prefix}/`)) throw new Error("MARKLESS_ROUTER_FRAGMENT_UNAVAILABLE: redirected");
300
+ finalUrl.pathname = finalUrl.pathname.slice(prefix.length);
301
+ finalUrl.hash = url.hash;
302
+ const reader = response.body.getReader();
303
+ const decoder = new TextDecoder();
304
+ let text = "";
305
+ for (;;) {
306
+ if (text.includes(config.regionEndMarker)) return {
307
+ ...parse(finalUrl.href, text),
308
+ rest: speculative ? drain(reader, closed) : reader
309
+ };
310
+ const chunk = await reader.read();
311
+ if (chunk.done) {
312
+ closed();
313
+ return parse(finalUrl.href, text + decoder.decode());
314
+ }
315
+ text += decoder.decode(chunk.value, { stream: true });
316
+ }
317
+ };
318
+ const closed = () => {
319
+ entry.open = false;
320
+ queueMicrotask(pumpIdle);
321
+ };
322
+ const entry = {
323
+ at: started?.at ?? w.performance.now(),
324
+ result: read(),
325
+ open: true,
326
+ abort: () => {
327
+ closed();
328
+ controller.abort();
329
+ }
330
+ };
331
+ entry.result.catch(closed);
332
+ return entry;
333
+ };
334
+ const prefetch = (url, code = true, started) => {
335
+ const key = pageKey(url);
336
+ if (!config.prefetch || key === state.shown) return;
337
+ const existing = state.prefetches.get(key);
338
+ if (existing && w.performance.now() - existing.at < config.prefetchTtlMs) return void (code && preloadCode(url, false));
339
+ existing?.abort();
340
+ const open = [...state.prefetches].filter(([, entry]) => entry.open);
341
+ for (const [stale, entry] of open.slice(0, Math.max(0, open.length + 1 - config.prefetchConnections))) {
342
+ state.prefetches.delete(stale);
343
+ entry.abort();
344
+ }
345
+ const entry = request(url, true, started);
346
+ state.prefetches.set(key, entry);
347
+ w.setTimeout(() => {
348
+ if (state.prefetches.get(key) !== entry) return;
349
+ state.prefetches.delete(key);
350
+ entry.abort();
351
+ }, config.prefetchTtlMs);
352
+ if (code) preloadCode(url, false);
353
+ };
354
+ let scanning = false;
355
+ let idleQueue = [];
356
+ const pumpIdle = () => {
357
+ while (idleQueue.length) {
358
+ let open = 0;
359
+ for (const entry of state.prefetches.values()) if (entry.open) open += 1;
360
+ if (open >= config.prefetchConnections) return;
361
+ const url = idleQueue.shift();
362
+ if (state.prefetches.has(pageKey(url))) continue;
363
+ prefetch(url, false);
364
+ preloadCode(url, true);
365
+ }
366
+ };
367
+ const rescan = () => {
368
+ if (!state.idleDownloads || scanning) return;
369
+ scanning = true;
370
+ const run = () => {
371
+ scanning = false;
372
+ const visible = /* @__PURE__ */ new Map();
373
+ for (const anchor of d.querySelectorAll("a[href]")) {
374
+ const url = eligible(anchor);
375
+ if (!url || !state.routes[url.pathname]) continue;
376
+ const box = anchor.getBoundingClientRect();
377
+ if (!box.width || box.bottom < 0 || box.top > w.innerHeight) continue;
378
+ if (box.right < 0 || box.left > w.innerWidth) continue;
379
+ const hit = d.elementFromPoint(box.left + box.width / 2, box.top + box.height / 2);
380
+ if (!hit || hit !== anchor && !anchor.contains(hit)) continue;
381
+ visible.set(pageKey(url), url);
382
+ }
383
+ idleQueue = [];
384
+ for (const [key, url] of visible) if (idleQueue.length < config.idleFragments && !state.prefetches.has(key)) idleQueue.push(url);
385
+ pumpIdle();
386
+ };
387
+ const idle = () => w.requestIdleCallback ? w.requestIdleCallback(run, { timeout: 3e3 }) : w.setTimeout(run, 500);
388
+ if (d.readyState === "complete") idle();
389
+ else w.addEventListener("load", idle, { once: true });
390
+ };
391
+ const state = {
392
+ config,
393
+ routes,
394
+ prefetches: /* @__PURE__ */ new Map(),
395
+ visited: /* @__PURE__ */ new Map(),
396
+ shown: pageKey(new URL(location.href)),
397
+ eligible,
398
+ preloadCode,
399
+ request,
400
+ prefetch,
401
+ parse,
402
+ idleDownloads: config.prefetch && (connection ? connection.effectiveType === "4g" && connection.saveData !== true : config.idleWithoutConnectionInfo),
403
+ rescan,
404
+ scrolled,
405
+ arriving
406
+ };
407
+ host.__marklessFragmentNav = state;
408
+ if (early) prefetch(new URL(early.url), true, early);
409
+ let restTimer = 0;
410
+ let previous;
411
+ const hover = (event) => {
412
+ const pointer = event;
413
+ if (pointer.pointerType !== "mouse") return;
414
+ const point = {
415
+ x: pointer.clientX,
416
+ y: pointer.clientY,
417
+ t: pointer.timeStamp
418
+ };
419
+ if (previous && point.x === previous.x && point.y === previous.y) return void w.clearTimeout(restTimer);
420
+ const gap = previous ? point.t - previous.t : 0;
421
+ const creeping = !!previous && gap > 0 && Math.hypot(point.x - previous.x, point.y - previous.y) / gap < .1;
422
+ previous = point;
423
+ w.clearTimeout(restTimer);
424
+ const url = eligible(anchorOf(event));
425
+ if (!url) return;
426
+ if (creeping) prefetch(url);
427
+ else restTimer = w.setTimeout(() => prefetch(url), Math.max(config.hoverDwellMs, gap * 1.5));
428
+ };
429
+ d.addEventListener("pointermove", hover, { passive: true });
430
+ d.addEventListener("pointerout", () => w.clearTimeout(restTimer), { passive: true });
431
+ const press = (event) => {
432
+ w.clearTimeout(restTimer);
433
+ const anchor = anchorOf(event);
434
+ const url = eligible(anchor);
435
+ if (url) prefetch(url);
436
+ if (url || eligible(anchor, true)) load();
437
+ };
438
+ for (const type of [
439
+ "pointerdown",
440
+ "touchstart",
441
+ "focusin"
442
+ ]) d.addEventListener(type, press, { passive: true });
443
+ const go = (anchor) => {
444
+ const url = eligible(anchor, true);
445
+ if (!url) return false;
446
+ const current = new URL(location.href);
447
+ w.clearTimeout(restTimer);
448
+ const replace = anchor.hasAttribute(config.replaceAttribute) || url.href === current.href;
449
+ const manual = anchor.getAttribute(config.scrollAttribute) === "manual";
450
+ const swap = (entry) => entry.navigate(d, url, replace ? "replace" : "push", manual ? "manual" : "reset");
451
+ if (loaded) swap(loaded);
452
+ else load().then(swap, () => location.assign(url.href));
453
+ return true;
454
+ };
455
+ const follow = (routedPhase) => (event) => {
456
+ const mouse = event;
457
+ if (mouse.defaultPrevented || mouse.button !== 0) return;
458
+ if (mouse.metaKey || mouse.altKey || mouse.ctrlKey || mouse.shiftKey) return;
459
+ const anchor = anchorOf(event);
460
+ if (!anchor || anchor.hasAttribute(config.linkAttribute) !== routedPhase) return;
461
+ if (go(anchor)) event.preventDefault();
462
+ };
463
+ d.addEventListener("click", follow(true), true);
464
+ d.addEventListener("click", follow(false));
465
+ rescan();
466
+ const traverse = (event) => {
467
+ const url = new URL(location.href);
468
+ if (url.hash.startsWith("#/") || pageKey(url) === state.shown) return;
469
+ const saved = event.state?.__marklessFragment;
470
+ load().then((entry) => entry.navigate(d, url, "traverse", saved ?? {
471
+ x: 0,
472
+ y: 0
473
+ }), () => location.reload());
474
+ };
475
+ const replay = (event) => {
476
+ if (event?.type === "pointerover") hover(event);
477
+ else if (event && event.type !== "popstate") press(event);
478
+ };
479
+ return {
480
+ traverse,
481
+ replay,
482
+ go
483
+ };
484
+ }
485
+ const FRAGMENT_PATH_PREFIX = "/_markless/fragment";
486
+ function fragmentPageUrl(url) {
487
+ if (!url.pathname.startsWith(`/_markless/fragment/`)) return void 0;
488
+ const page = new URL(url);
489
+ page.pathname = url.pathname.slice(19);
490
+ return page;
491
+ }
492
+ const FRAGMENT_REGION_END = "<!--markless:region-end-->";
493
+ const FRAGMENT_APPEND_END = "<!--markless:append-end-->";
494
+ const FRAGMENT_PREFETCH_TTL_MS = 3e4;
495
+ //#endregion
496
+ //#region src/vite/runtime/link-bridge-module.ts
497
+ /** Marks the page's link bridge script: inline in dev, a module stub importing the shared bridge in a build. */
498
+ const LINK_BRIDGE_SCRIPT_ATTRIBUTE = "data-markless-router-link-resumer";
499
+ /** The client output folder a build writes its link bridge files into, served as immutable static assets. */
500
+ const LINK_BRIDGE_DIR = "_markless/links";
501
+ function linkBridgeModulePath(base, hash) {
502
+ return `${base.replace(/\/?$/, "/")}${LINK_BRIDGE_DIR}/${hash}.js`;
503
+ }
504
+ //#endregion
505
+ //#region src/vite/runtime/page-http.ts
506
+ const RESPONSE_COMMITTED_CODE = "MARKLESS_ROUTER_RESPONSE_COMMITTED";
507
+ function createPageHttp(request, url) {
508
+ let committed;
509
+ let status;
510
+ let statusText;
511
+ const refuse = (write) => {
512
+ throw new Error(`${RESPONSE_COMMITTED_CODE}: ${write} ran after the page's response started streaming to the browser, so its status and headers were already sent. Set http.response before the first byte goes out: in the page body, before any await that outlives the first flush.`);
513
+ };
514
+ const headers = new PageHeaders((write, unchanged) => {
515
+ if (committed && !unchanged(committed.headers)) refuse(write);
516
+ });
517
+ const response = {
518
+ headers,
519
+ get status() {
520
+ return status;
521
+ },
522
+ set status(value) {
523
+ if (committed && value !== committed.status) refuse(`http.response.status = ${value}`);
524
+ status = value;
525
+ },
526
+ get statusText() {
527
+ return statusText;
528
+ },
529
+ set statusText(value) {
530
+ if (committed && value !== committed.statusText) refuse(`http.response.statusText = ${JSON.stringify(value)}`);
531
+ statusText = value;
532
+ }
533
+ };
534
+ return {
535
+ context: {
536
+ locals: requestLocals(request) ?? {},
537
+ request,
538
+ response,
539
+ url
540
+ },
541
+ commit(fallbackStatus, routerHeaders) {
542
+ const sent = new Headers(routerHeaders);
543
+ const middleware = takeMiddlewareHeaders(request);
544
+ for (const source of middleware ? [middleware, headers] : [headers]) {
545
+ for (const [name, value] of source) if (name !== "set-cookie") sent.set(name, value);
546
+ for (const cookie of source.getSetCookie()) sent.append("set-cookie", cookie);
547
+ }
548
+ committed = {
549
+ status: status ?? fallbackStatus,
550
+ statusText,
551
+ headers: sent
552
+ };
553
+ return {
554
+ status: committed.status,
555
+ ...statusText === void 0 ? {} : { statusText },
556
+ headers: sent
557
+ };
558
+ }
559
+ };
560
+ }
561
+ var PageHeaders = class extends Headers {
562
+ #guard;
563
+ constructor(guard) {
564
+ super();
565
+ this.#guard = guard;
566
+ }
567
+ set(name, value) {
568
+ this.#guard(`http.response.headers.set(${JSON.stringify(name)})`, (sent) => name.toLowerCase() === "set-cookie" ? sent.getSetCookie().includes(value) : sent.get(name) === value);
569
+ super.set(name, value);
570
+ }
571
+ append(name, value) {
572
+ this.#guard(`http.response.headers.append(${JSON.stringify(name)})`, (sent) => name.toLowerCase() === "set-cookie" ? sent.getSetCookie().includes(value) : (sent.get(name)?.split(", ") ?? []).includes(value));
573
+ if (!headerValues(this, name).includes(value)) super.append(name, value);
574
+ }
575
+ delete(name) {
576
+ this.#guard(`http.response.headers.delete(${JSON.stringify(name)})`, (sent) => !sent.has(name));
577
+ super.delete(name);
578
+ }
579
+ };
580
+ function headerValues(headers, name) {
581
+ return name.toLowerCase() === "set-cookie" ? headers.getSetCookie() : headers.get(name)?.split(", ") ?? [];
582
+ }
583
+ //#endregion
584
+ //#region src/vite/runtime/page-region.ts
585
+ const REGION_HOSTS = /* @__PURE__ */ new Set([
586
+ "article",
587
+ "aside",
588
+ "blockquote",
589
+ "body",
590
+ "dd",
591
+ "details",
592
+ "dialog",
593
+ "div",
594
+ "fieldset",
595
+ "figure",
596
+ "footer",
597
+ "form",
598
+ "header",
599
+ "li",
600
+ "main",
601
+ "nav",
602
+ "section",
603
+ "td",
604
+ "th"
605
+ ]);
606
+ const PAGE_END_MARKER = `<!--${PROTOCOL_CONTAINER_END_COMMENT}-->`;
607
+ const PAGE_START_MARKER = `<!--${PROTOCOL_CONTAINER_START_COMMENT}-->`;
608
+ const VOID_TAGS = /* @__PURE__ */ new Set([
609
+ "area",
610
+ "base",
611
+ "br",
612
+ "col",
613
+ "embed",
614
+ "hr",
615
+ "img",
616
+ "input",
617
+ "link",
618
+ "meta",
619
+ "source",
620
+ "track",
621
+ "wbr"
622
+ ]);
623
+ const RAW_TEXT_TAGS = /* @__PURE__ */ new Set([
624
+ "script",
625
+ "style",
626
+ "textarea",
627
+ "title"
628
+ ]);
629
+ function splitPageHtml(html) {
630
+ const parts = splitSsrContainer(html);
631
+ if (!parts) return {
632
+ headHtml: "",
633
+ bodyHtml: html
634
+ };
635
+ return {
636
+ headHtml: parts.before,
637
+ bodyHtml: html.slice(parts.before.length),
638
+ region: {
639
+ attributes: parts.attributes,
640
+ inner: parts.inner
641
+ }
642
+ };
643
+ }
644
+ /**
645
+ * The document with the page in its children slot, split where streamed appends go:
646
+ * `prefix + suffix` is the whole document. When the slot sits inside an element that can hold any
647
+ * content, that element becomes the container: the page's markup lands as its children, a comment
648
+ * closes the page's nodes and, when document content precedes the slot, another opens them, so
649
+ * the document's content on either side stays where the author wrote it.
650
+ */
651
+ function placePage(shell, slot, page) {
652
+ const at = shell.indexOf(slot);
653
+ if (at === -1) return {
654
+ prefix: shell,
655
+ suffix: ""
656
+ };
657
+ const rest = shell.slice(at + slot.length);
658
+ const host = regionHost(shell, at, page);
659
+ if (!host) return {
660
+ prefix: shell.slice(0, at) + page.bodyHtml,
661
+ suffix: rest
662
+ };
663
+ return {
664
+ prefix: `${shell.slice(0, host.start)}${host.open}${host.before}${page.region.inner}`,
665
+ suffix: `${PAGE_END_MARKER}${rest}`
666
+ };
667
+ }
668
+ function fragmentPage(shell, slot, page) {
669
+ const at = shell.indexOf(slot);
670
+ const bodyAt = shell.search(/<body[\s>]/i);
671
+ const bodyOpenEnd = bodyAt === -1 ? -1 : shell.indexOf(">", bodyAt);
672
+ if (at === -1 || bodyOpenEnd === -1) {
673
+ const placed = placePage(shell, slot, page);
674
+ return placed.prefix + placed.suffix;
675
+ }
676
+ const host = regionHost(shell, at, page);
677
+ const inner = (host?.before ? PAGE_START_MARKER : "") + (page.region?.inner ?? "");
678
+ if (host?.tag === "body") return `${shell.slice(0, host.start)}${host.open}${inner}</body></html>`;
679
+ const region = host ? `${host.open}${inner}</${host.tag}>` : page.bodyHtml;
680
+ return `${shell.slice(0, bodyOpenEnd + 1)}${region}</body></html>`;
681
+ }
682
+ function regionHost(shell, at, page) {
683
+ if (!page.region) return void 0;
684
+ const open = innermostOpenTag(shell, at);
685
+ if (!open || !REGION_HOSTS.has(open.tag)) return void 0;
686
+ const before = shell.slice(open.end + 1, at);
687
+ return {
688
+ start: open.start,
689
+ open: `${shell.slice(open.start, open.end)}${page.region.attributes}>`,
690
+ tag: open.tag,
691
+ before: before.trim() ? before + PAGE_START_MARKER : ""
692
+ };
693
+ }
694
+ function innermostOpenTag(html, at) {
695
+ const stack = [];
696
+ let cursor = 0;
697
+ let lower;
698
+ while (cursor < at) {
699
+ const start = html.indexOf("<", cursor);
700
+ if (start === -1 || start >= at) break;
701
+ if (html.startsWith("<!--", start)) {
702
+ const close = html.indexOf("-->", start + 4);
703
+ cursor = close === -1 ? at : close + 3;
704
+ continue;
705
+ }
706
+ const end = tagEnd(html, start);
707
+ if (end === -1 || end >= at) break;
708
+ cursor = end + 1;
709
+ const name = /^<(\/?)([a-zA-Z][a-zA-Z0-9-]*)/.exec(html.slice(start, end + 1));
710
+ if (!name) continue;
711
+ const tag = name[2].toLowerCase();
712
+ if (name[1]) {
713
+ const match = stack.findLastIndex((entry) => entry.tag === tag);
714
+ if (match !== -1) stack.length = match;
715
+ continue;
716
+ }
717
+ if (VOID_TAGS.has(tag) || html[end - 1] === "/") continue;
718
+ if (RAW_TEXT_TAGS.has(tag)) {
719
+ const close = (lower ??= html.toLowerCase()).indexOf(`</${tag}`, cursor);
720
+ cursor = close === -1 ? at : close;
721
+ continue;
722
+ }
723
+ stack.push({
724
+ tag,
725
+ start,
726
+ end
727
+ });
728
+ }
729
+ return stack.at(-1);
730
+ }
731
+ function tagEnd(html, from) {
732
+ let quote = "";
733
+ for (let at = from; at < html.length; at++) {
734
+ const char = html[at];
735
+ if (quote) {
736
+ if (char === quote) quote = "";
737
+ } else if (char === "\"" || char === "'") quote = char;
738
+ else if (char === ">") return at;
739
+ }
740
+ return -1;
741
+ }
742
+ //#endregion
743
+ //#region src/vite/runtime/create-server-entry.ts
744
+ const DOCUMENT_CHILDREN_PLACEHOLDER = "__markless_router_document_children__";
745
+ function fragmentBridgeFor(options, manifest) {
746
+ return options.fragmentEntryPath !== void 0 && options.navigation !== "render" ? {
747
+ entry: options.fragmentEntryPath,
748
+ entryImports: options.fragmentEntryImports ?? [],
749
+ source: options.fragmentNavigationSource ?? startFragmentNavigation.toString(),
750
+ runSource: options.fragmentNavigationRunSource ?? runFragmentNavigation.toString(),
751
+ prefetch: options.prefetch !== false,
752
+ pages: manifest.routes.map((route) => route.pathname.replace(/:[^/]+/g, ":"))
753
+ } : void 0;
754
+ }
755
+ /** The link bridge modules a built app's pages import; the build writes them as static client files. */
756
+ function routerLinkBridgeModules(options, manifest = buildRouteManifestFromFileIds(options.routeFileIds)) {
757
+ if (!options.navigationEntryPath) return void 0;
758
+ const fragmentBridge = fragmentBridgeFor(options, manifest);
759
+ return linkBridgeModules(options.base ?? "/", options.navigationEntryPath, fragmentBridge !== void 0 || options.prefetch !== false, options.documentNavigation === "document", fragmentBridge, options.debug);
760
+ }
761
+ function createServerEntry(options) {
762
+ const manifest = buildRouteManifestFromFileIds(options.routeFileIds);
763
+ assertCurrentRouteAssets(options);
764
+ const fragmentBridge = fragmentBridgeFor(options, manifest);
765
+ const fragmentNavigation = fragmentBridge !== void 0;
766
+ const linkBridgeModules = options.dev || options.linkBridgePaths ? void 0 : routerLinkBridgeModules(options, manifest);
767
+ const linkBridges = options.dev ? void 0 : options.linkBridgePaths ?? (linkBridgeModules && [linkBridgeModules[0].path, linkBridgeModules[1].path]);
768
+ async function fetch(request) {
769
+ const requestUrl = new URL(request.url);
770
+ const fragmentUrl = fragmentNavigation ? fragmentPageUrl(requestUrl) : void 0;
771
+ const fragment = fragmentUrl !== void 0;
772
+ const url = fragmentUrl ?? requestUrl;
773
+ const match = matchRouteManifest(url.pathname, manifest);
774
+ if (isNitroApiPathname(url.pathname) && !(match && isNitroApiPathname(match.route.pathname))) return new Response("Not found", createPageHttp(request, url).commit(404, {}));
775
+ if (!match) return renderStatusPage(url, manifest.statusPages.notFound, 404, "Not found", fragment, request);
776
+ try {
777
+ return await renderPage(url, match.route.file, match.params, 200, fragment, request);
778
+ } catch (error) {
779
+ console.error("[markless-router] page render failed:", error);
780
+ if (options.dev) return renderDevelopmentError(url, error);
781
+ try {
782
+ return await renderStatusPage(url, manifest.statusPages.error, 500, "Internal Server Error", fragment, request);
783
+ } catch (errorPageError) {
784
+ console.error("[markless-router] error page render failed:", errorPageError);
785
+ return new Response("Internal Server Error", {
786
+ status: 500,
787
+ headers: { "content-type": "text/plain;charset=utf-8" }
788
+ });
789
+ }
790
+ }
791
+ }
792
+ function renderDevelopmentError(url, error) {
793
+ const headers = {
794
+ "cache-control": "no-store",
795
+ "content-type": "text/html;charset=utf-8"
796
+ };
797
+ try {
798
+ const payload = normalizeMarklessDevError(error, { id: `navigation:${url.pathname}` });
799
+ return new Response(renderMarklessDevErrorDocument(payload), {
800
+ status: 500,
801
+ headers
802
+ });
803
+ } catch {
804
+ return new Response(escapeHtml(flattenError(error)), {
805
+ status: 500,
806
+ headers: {
807
+ ...headers,
808
+ "content-type": "text/plain;charset=utf-8"
809
+ }
810
+ });
811
+ }
812
+ }
813
+ async function renderStatusPage(url, file, status, fallbackText, fragment, request) {
814
+ if (!file) return new Response(fallbackText, createPageHttp(request, url).commit(status, {}));
815
+ return renderPage(url, file, {}, status, fragment, request);
816
+ }
817
+ async function renderPage(url, file, params, status, fragment, request) {
818
+ const loadPageModule = options.pageModuleLoaders[file];
819
+ if (!loadPageModule) return new Response(`Page module not found: ${file}`, { status: 500 });
820
+ const cookie = request.headers.get("cookie");
821
+ const pageModule = await loadPageModule();
822
+ const http = createPageHttp(request, url);
823
+ const pageProps = {
824
+ http: http.context,
825
+ params,
826
+ url: {
827
+ href: url.href,
828
+ pathname: url.pathname,
829
+ search: url.search
830
+ },
831
+ status
832
+ };
833
+ const documentModule = options.documentModuleLoader ? await options.documentModuleLoader() : void 0;
834
+ const headers = { "content-type": "text/html;charset=utf-8" };
835
+ const baseArtifact = pageModule.default;
836
+ const renderSsr = baseArtifact?.renderSsr ?? pageModule.marklessRenderSsr;
837
+ if (!renderSsr) {
838
+ const shell = await renderDocumentShell(documentModule, pageProps, "");
839
+ const message = `Page module must export an Markless compiled artifact: ${escapeHtml(file)}`;
840
+ return new Response(fillDocumentChildren(shell, message), http.commit(status, headers));
841
+ }
842
+ const documentLoads = options.documentNavigation === "document";
843
+ const linkIntent = fragmentNavigation || options.prefetch !== false;
844
+ const destinationPreloads = documentLoads || fragmentNavigation ? options.routeSsrModulePreloads : options.routeModulePreloads;
845
+ const pageArtifact = routedPageArtifact(renderSsr, baseArtifact, pageProps, file, options.navigationEntryPath, pageStylesheets(options, file), documentModule?.default?.headInjections ?? [], linkIntent ? (html) => linkIntentPreloads(html, pageProps.url.href, manifest, destinationPreloads, fragmentNavigation) : void 0, documentLoads, fragmentBridge, linkBridges);
846
+ let rendersStorage = false;
847
+ const routedArtifact = {
848
+ ...pageArtifact,
849
+ ...options.prerenderWakeEntryPath ? { prerenderWakeModuleUrl: options.prerenderWakeEntryPath } : {},
850
+ async renderSsr(renderProps, renderContext) {
851
+ const output = await pageArtifact.renderSsr(renderProps, renderContext);
852
+ if (documentStorageRecords(output.state).length > 0) rendersStorage = true;
853
+ return output;
854
+ }
855
+ };
856
+ const privateToVisitor = () => {
857
+ if (!rendersStorage) return;
858
+ headers["cache-control"] = "private";
859
+ headers.vary = headers.vary ? `${headers.vary}, cookie` : "cookie";
860
+ };
861
+ const renderOptions = {
862
+ cookie,
863
+ props: pageProps,
864
+ resumeModuleUrl: options.resumeEntryPath ?? baseArtifact?.resumeModuleUrl,
865
+ modulePreloads: (html, resumes) => landingModulePreloads(resumes ? options.routeSsrModulePreloads?.[file] : void 0, linkBridges?.[html.includes("href=\"#/") ? 1 : 0])
866
+ };
867
+ const headStart = (documentLoads && fragmentNavigation && !fragment ? servedHtmlAttributesScript() : "") + (options.importMap ?? "");
868
+ if (options.render === "blocking") {
869
+ const pageHtml = splitPageHtml(await renderToString(routedArtifact, renderOptions));
870
+ privateToVisitor();
871
+ const shell = await renderDocumentShell(documentModule, pageProps, pageHtml.headHtml, headStart);
872
+ return new Response(wholeDocument(shell, pageHtml, fragment, documentLoads), http.commit(status, headers));
873
+ }
874
+ const stream = await renderToStream(routedArtifact, renderOptions);
875
+ privateToVisitor();
876
+ const pageHtml = splitPageHtml(stream.shell);
877
+ const shell = await renderDocumentShell(documentModule, pageProps, pageHtml.headHtml, headStart);
878
+ if (stream.pendingArmCount === 0) return new Response(wholeDocument(shell, pageHtml, fragment, documentLoads), http.commit(status, headers));
879
+ const placed = placePage(shell, DOCUMENT_CHILDREN_PLACEHOLDER, pageHtml);
880
+ const prefix = fragment ? fragmentDocument(shell, pageHtml, documentLoads) + FRAGMENT_REGION_END : placed.prefix;
881
+ const suffix = fragment ? "" : placed.suffix;
882
+ const appendEnd = fragment ? FRAGMENT_APPEND_END : "";
883
+ const encoder = new TextEncoder();
884
+ const sent = http.commit(status, headers);
885
+ let cancelled = false;
886
+ let completed = false;
887
+ const body = new ReadableStream({
888
+ async start(controller) {
889
+ controller.enqueue(encoder.encode(prefix));
890
+ try {
891
+ for await (const chunk of stream.appends()) {
892
+ if (cancelled) return;
893
+ controller.enqueue(encoder.encode(chunk + appendEnd));
894
+ }
895
+ } catch (error) {
896
+ if (cancelled) return;
897
+ console.error("[markless-router] streaming settle failed:", error);
898
+ controller.error(error);
899
+ completed = true;
900
+ return;
901
+ }
902
+ if (cancelled) return;
903
+ if (suffix) controller.enqueue(encoder.encode(suffix));
904
+ controller.close();
905
+ completed = true;
906
+ },
907
+ cancel(reason) {
908
+ if (completed) return;
909
+ cancelled = true;
910
+ stream.cancel(reason);
911
+ }
912
+ });
913
+ return new Response(body, sent);
914
+ }
915
+ return {
916
+ fetch,
917
+ linkBridgeModules
918
+ };
919
+ }
920
+ function flattenError(error) {
921
+ if (error instanceof Error) return error.message;
922
+ if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") return error.message;
923
+ return String(error);
924
+ }
925
+ function assertCurrentRouteAssets(options) {
926
+ if (options.routeStylesheets === void 0) return;
927
+ const routeMaps = [
928
+ ["navigation", options.routeModulePreloads ?? {}],
929
+ ["SSR", options.routeSsrModulePreloads ?? {}],
930
+ ["style", options.routeStylesheets]
931
+ ];
932
+ const expected = [...new Set(options.routeFileIds.map(normalizeRouteFileId))].filter((file) => /^pages\/.+\.(?:tsrx|mdx)$/.test(file)).toSorted();
933
+ for (const [label, routes] of routeMaps) {
934
+ const actual = Object.keys(routes).toSorted();
935
+ if (JSON.stringify(actual) !== JSON.stringify(expected)) throw new Error(`Markless Router ${label} client-asset routes are stale; expected ${JSON.stringify(expected)}, received ${JSON.stringify(actual)}. Rebuild the client environment before the server environment.`);
936
+ }
937
+ }
938
+ function dedupeHeadLinks(injections) {
939
+ const seen = /* @__PURE__ */ new Set();
940
+ return injections.filter((injection) => {
941
+ const href = injection.tag === "link" ? injection.attributes?.href : void 0;
942
+ if (typeof href !== "string") return true;
943
+ if (seen.has(href)) return false;
944
+ seen.add(href);
945
+ return true;
946
+ });
947
+ }
948
+ function pageStylesheets(options, file) {
949
+ const documentHrefs = options.documentStylesheets ?? [];
950
+ const routeHrefs = options.routeStylesheets?.[file];
951
+ if (documentHrefs.length === 0) return routeHrefs;
952
+ return [.../* @__PURE__ */ new Set([...documentHrefs, ...routeHrefs ?? []])];
953
+ }
954
+ function routedPageArtifact(renderSsr, baseArtifact, pageProps, file, navigationEntryPath, stylesheetHrefs, documentHeadInjections = [], intentPreloads, documentLoads = false, fragment, linkBridgePaths) {
955
+ const headInjections = dedupeHeadLinks([
956
+ ...documentHeadInjections,
957
+ ...baseArtifact?.headInjections ?? [],
958
+ ...(stylesheetHrefs ?? []).map((href) => ({
959
+ tag: "link",
960
+ location: "head",
961
+ attributes: {
962
+ rel: "stylesheet",
963
+ href
964
+ }
965
+ }))
966
+ ]);
967
+ return {
968
+ resumeModuleUrl: baseArtifact?.resumeModuleUrl,
969
+ inlineResumerSources: baseArtifact?.inlineResumerSources,
970
+ storageSeeds: baseArtifact?.storageSeeds,
971
+ ...headInjections.length > 0 ? { headInjections } : {},
972
+ async renderSsr(renderProps, renderContext) {
973
+ const output = await renderSsr(renderProps ?? pageProps, renderContext);
974
+ if (!output) return { html: "" };
975
+ const routeScript = output.state || output.view ? renderRouteScript(file) : "";
976
+ const linkBridge = navigationEntryPath ? renderLinkBridgeScript(navigationEntryPath, intentPreloads?.(output.html), documentLoads, fragment, output.html.includes("href=\"#/"), linkBridgePaths) : "";
977
+ const stateWithProps = withPagePropsCell(output.state, pageProps);
978
+ return routeScript || linkBridge || stateWithProps !== output.state ? {
979
+ ...output,
980
+ state: stateWithProps,
981
+ html: `${output.html}${routeScript}${linkBridge}`
982
+ } : output;
983
+ }
984
+ };
985
+ }
986
+ function withPagePropsCell(state, props) {
987
+ if (!state || typeof state !== "object") return state;
988
+ const { http: _serverOnly, ...browserProps } = props;
989
+ const cells = state.cells;
990
+ if (!Array.isArray(cells) || cells.some((cell) => cell?.graphNodeId === "prop:props")) return state;
991
+ return {
992
+ ...state,
993
+ cells: [...cells, {
994
+ graphNodeId: "prop:props",
995
+ name: "props",
996
+ valueKind: "object",
997
+ directValue: browserProps
998
+ }]
999
+ };
1000
+ }
1001
+ function landingModulePreloads(routePreloads, linkBridgePath) {
1002
+ const preloads = [];
1003
+ addModulePreloads(preloads, /* @__PURE__ */ new Set(), routePreloads);
1004
+ if (linkBridgePath) preloads.push(linkBridgePath);
1005
+ return preloads.length > 0 ? preloads : void 0;
1006
+ }
1007
+ function routerLinkHrefs(html) {
1008
+ return [...html.matchAll(/<a\b(?=[^>]*\bdata-markless-router-link(?:[\s=>]|$))(?=[^>]*\bhref="([^"]*)")[^>]*>/g)].map((match) => unescapeHtmlAttribute(match[1] ?? ""));
1009
+ }
1010
+ function anchorHrefs(html) {
1011
+ return [...html.matchAll(/<a\b(?=[^>]*\bhref="([^"]*)")[^>]*>/g)].map((match) => unescapeHtmlAttribute(match[1] ?? ""));
1012
+ }
1013
+ function linkIntentPreloads(html, baseHref, manifest, routes, plainAnchors = false) {
1014
+ const destinations = Object.create(null);
1015
+ for (const href of plainAnchors ? anchorHrefs(html) : routerLinkHrefs(html)) {
1016
+ const url = parseSameOriginUrl(href, baseHref);
1017
+ const match = url && matchRouteManifest(url.pathname, manifest);
1018
+ const preloads = match && routes?.[match.route.file];
1019
+ if (url && preloads && (preloads.length > 0 || plainAnchors)) destinations[url.pathname] = preloads;
1020
+ }
1021
+ return destinations;
1022
+ }
1023
+ function indexedPreloads(intentPreloads) {
1024
+ const preloads = [];
1025
+ const indexes = /* @__PURE__ */ new Map();
1026
+ const destinations = Object.create(null);
1027
+ for (const [pathname, list] of Object.entries(intentPreloads)) destinations[pathname] = list.map((preload) => {
1028
+ const key = JSON.stringify(preload);
1029
+ let index = indexes.get(key);
1030
+ if (index === void 0) {
1031
+ index = preloads.push(preload) - 1;
1032
+ indexes.set(key, index);
1033
+ }
1034
+ return index;
1035
+ });
1036
+ return {
1037
+ preloads,
1038
+ destinations
1039
+ };
1040
+ }
1041
+ function parseSameOriginUrl(href, base) {
1042
+ try {
1043
+ const current = new URL(base);
1044
+ const url = new URL(href, current);
1045
+ return url.origin === current.origin ? url : void 0;
1046
+ } catch {
1047
+ return;
1048
+ }
1049
+ }
1050
+ function unescapeHtmlAttribute(value) {
1051
+ return value.replaceAll("&quot;", "\"").replaceAll("&gt;", ">").replaceAll("&lt;", "<").replaceAll("&amp;", "&");
1052
+ }
1053
+ function addModulePreloads(preloads, seen, items) {
1054
+ for (const preload of items ?? []) {
1055
+ const preloadHref = typeof preload === "string" ? preload : preload.href;
1056
+ if (!preloadHref || seen.has(preloadHref)) continue;
1057
+ seen.add(preloadHref);
1058
+ preloads.push(preload);
1059
+ }
1060
+ }
1061
+ async function renderDocumentShell(documentModule, pageProps, headHtml, importMap = "") {
1062
+ const written = {};
1063
+ const documentHtml = await renderDocumentModule(documentModule, {
1064
+ ...pageProps,
1065
+ children: DOCUMENT_CHILDREN_PLACEHOLDER
1066
+ }, written);
1067
+ const attributes = htmlAttributes(documentModule, pageProps, written);
1068
+ if (documentHtml !== void 0) return [
1069
+ "<!doctype html>",
1070
+ `<html${renderAttributes(attributes)}>`,
1071
+ insertImportMap(insertHeadHtml(documentHtml, headHtml), importMap),
1072
+ "</html>"
1073
+ ].join("");
1074
+ return [
1075
+ "<!doctype html>",
1076
+ `<html${renderAttributes(attributes)}>`,
1077
+ "<head>",
1078
+ "<meta charset=\"utf-8\">",
1079
+ importMap,
1080
+ "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">",
1081
+ headHtml,
1082
+ "</head>",
1083
+ "<body>",
1084
+ DOCUMENT_CHILDREN_PLACEHOLDER,
1085
+ "</body>",
1086
+ "</html>"
1087
+ ].join("");
1088
+ }
1089
+ function fillDocumentChildren(documentShell, children) {
1090
+ const at = documentShell.indexOf(DOCUMENT_CHILDREN_PLACEHOLDER);
1091
+ if (at === -1) return documentShell;
1092
+ return documentShell.slice(0, at) + children + documentShell.slice(at + 37);
1093
+ }
1094
+ function fragmentDocument(shell, page, documentLoads) {
1095
+ if (!documentLoads) return fragmentPage(shell, DOCUMENT_CHILDREN_PLACEHOLDER, page);
1096
+ const placed = placePage(shell, DOCUMENT_CHILDREN_PLACEHOLDER, page);
1097
+ return placed.prefix + placed.suffix;
1098
+ }
1099
+ function wholeDocument(shell, page, fragment, documentLoads) {
1100
+ if (fragment) return fragmentDocument(shell, page, documentLoads) + FRAGMENT_REGION_END;
1101
+ const placed = placePage(shell, DOCUMENT_CHILDREN_PLACEHOLDER, page);
1102
+ return placed.prefix + placed.suffix;
1103
+ }
1104
+ async function renderDocumentModule(documentModule, props, htmlAttributes) {
1105
+ const output = await documentModule?.default?.renderSsr?.(props, { [HTML_ATTRIBUTES_SLOT]: htmlAttributes });
1106
+ assertDocumentStorageFree(output);
1107
+ return output?.html;
1108
+ }
1109
+ function assertDocumentStorageFree(output) {
1110
+ const storage = documentStorageRecords(output?.state);
1111
+ if (storage.length === 0) return;
1112
+ const keys = storage.map((record) => record.key).join(", ");
1113
+ throw new Error(`MARKLESS_ROUTER_DOCUMENT_STORAGE_UNSUPPORTED: the document declares storage cells (${keys}), but the router serves only the document's HTML, so their state payload never reaches the browser and they can never resume. Declare storage() at module scope in a component the page renders instead.`);
1114
+ }
1115
+ function documentStorageRecords(state) {
1116
+ if (!state || typeof state !== "object") return [];
1117
+ const storage = state.storage;
1118
+ return Array.isArray(storage) ? storage : [];
1119
+ }
1120
+ function insertHeadHtml(documentHtml, headHtml) {
1121
+ if (!headHtml) return documentHtml;
1122
+ const headEnd = documentHtml.indexOf("</head>");
1123
+ if (headEnd === -1) return `${headHtml}${documentHtml}`;
1124
+ return `${documentHtml.slice(0, headEnd)}${headHtml}${documentHtml.slice(headEnd)}`;
1125
+ }
1126
+ function renderRouteScript(file) {
1127
+ return `<script type="${ROUTE_SCRIPT_TYPE}">${escapeScriptJson({ file })}<\/script>`;
1128
+ }
1129
+ function linkBridgeBody(resumeEntryPath, linkIntent, documentLoads, fragment, page, debug = debugDefined()) {
1130
+ const inline = !("module" in page);
1131
+ const fragmentNavigation = fragment !== void 0;
1132
+ const fragmentSource = fragment ? `
1133
+ (${fragment.source})(d,()=>${JSON.stringify(LINK_ATTRIBUTE)},${inline ? escapeScriptJson(page.intentPreloads ?? {}) : "r"},(href)=>import(href),${"runPath" in page && page.runPath ? `()=>import(${JSON.stringify(page.runPath)}).then((m)=>m.default)` : `()=>Promise.resolve(${boundFragmentRun(fragment, documentLoads)})`},()=>(${fragment.prefetch ? escapeScriptJson({
1134
+ code: [fragment.entry, ...fragment.entryImports],
1135
+ hoverDwellMs: 20,
1136
+ requestPrefix: FRAGMENT_PATH_PREFIX,
1137
+ prefetchAttribute: PREFETCH_ATTRIBUTE
1138
+ }) : "null"}));` : "";
1139
+ const indexed = !fragmentNavigation && linkIntent ? inline ? page.intentPreloads && indexedPreloads(page.intentPreloads) : {
1140
+ preloads: "preloads",
1141
+ destinations: "destinations"
1142
+ } : void 0;
1143
+ const intentSource = indexed ? `
1144
+ const preloads = ${inline ? escapeScriptJson(indexed.preloads) : "p"};
1145
+ const destinations = ${inline ? escapeScriptJson(indexed.destinations) : "q"};
1146
+ const destination = (url) => destinations[url.pathname]?.map((index) => preloads[index]);
1147
+ const append = (${appendModulePreloads.toString()});
1148
+ (${listenForLinkIntent.toString()})(d, linkAttr, (url) => {
1149
+ append(d, destination(url));
1150
+ }, prefetchAttr);` : "";
1151
+ const hashClickSource = fragment && !page.hashLinks ? "" : `
1152
+ if (d.__marklessRouterHashClickStarted) return;
1153
+ d.__marklessRouterHashClickStarted = true;
1154
+ const anchorFrom = (event) => {
1155
+ const target = event.composedPath && event.composedPath()[0] || event.target;
1156
+ return target && target.closest ? target.closest('a[href]') : target && target.parentElement && target.parentElement.closest ? target.parentElement.closest('a[href]') : null;
1157
+ };
1158
+ const sameOrigin = (href) => {
1159
+ try {
1160
+ const url = new URL(href, location.href);
1161
+ return url.origin === location.origin ? url : null;
1162
+ } catch {
1163
+ return null;
1164
+ }
1165
+ };
1166
+ d.addEventListener('click', async (event) => {
1167
+ if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.altKey || event.ctrlKey || event.shiftKey) return;
1168
+ const anchor = anchorFrom(event);
1169
+ if (!anchor || !anchor.hasAttribute(linkAttr) || anchor.hasAttribute('download')) return;
1170
+ const target = anchor.getAttribute('target');
1171
+ if (target && target !== '_self') return;
1172
+ if (anchor.relList && anchor.relList.contains('external')) return;
1173
+ const url = sameOrigin(anchor.href);
1174
+ if (!url${documentLoads || fragmentNavigation ? " || !url.hash.startsWith('#/')" : ""}) return;
1175
+ event.preventDefault();
1176
+ try {
1177
+ const mod = await import(${JSON.stringify(resumeEntryPath)});
1178
+ const navigate = mod.navigateMarklessRouterLink;
1179
+ if (typeof navigate === 'function') {
1180
+ await navigate({
1181
+ href: url.href,
1182
+ replace: anchor.hasAttribute(replaceAttr),
1183
+ scroll: anchor.getAttribute(scrollAttr) === 'manual' ? 'manual' : undefined,
1184
+ });
1185
+ } else {
1186
+ location.assign(url.href);
1187
+ }
1188
+ } catch (error) {
1189
+ setTimeout(() => { throw error; });
1190
+ location.assign(url.href);
1191
+ }
1192
+ }, true);`;
1193
+ const debugRegistration = debug ? `
1194
+ try {
1195
+ const key = Symbol.for('markless.debug.channel.v1.bootstrap');
1196
+ const md = globalThis[key];
1197
+ if (md) { md.router('ssr-link-bridge'); md.activate(); }
1198
+ } catch {}` : "";
1199
+ return `${fragmentSource}${fragment && !hashClickSource ? "" : `
1200
+ if(d.__marklessRouterLinkResumerStarted${fragment ? "&&d.__marklessRouterHashClickStarted" : ""})return;d.__marklessRouterLinkResumerStarted=true;`}${intentSource || hashClickSource ? `
1201
+ const linkAttr = ${JSON.stringify(LINK_ATTRIBUTE)};` : ""}${intentSource ? `
1202
+ const prefetchAttr = ${JSON.stringify(PREFETCH_ATTRIBUTE)};` : ""}${hashClickSource ? `
1203
+ const replaceAttr = ${JSON.stringify(REPLACE_ATTRIBUTE)};
1204
+ const scrollAttr = ${JSON.stringify(SCROLL_ATTRIBUTE)};` : ""}${intentSource}
1205
+ if(location.hash&&location.hash.startsWith('#/'))import(${JSON.stringify(resumeEntryPath)}).catch(()=>{});${hashClickSource}${debugRegistration}`;
1206
+ }
1207
+ function debugDefined() {
1208
+ return typeof __MARKLESS_DEBUG_ENABLED__ !== "undefined" && __MARKLESS_DEBUG_ENABLED__;
1209
+ }
1210
+ function linkBridgeModules(base, resumeEntryPath, linkIntent, documentLoads, fragment, debug) {
1211
+ const runSource = fragment && `export default ${boundFragmentRun(fragment, documentLoads)};`;
1212
+ const run = runSource ? {
1213
+ path: linkBridgeModulePath(base, contentHash(runSource)),
1214
+ source: runSource
1215
+ } : void 0;
1216
+ const variant = (hashLinks) => {
1217
+ const source = `export default(r,p,q)=>{const d=document;${linkBridgeBody(resumeEntryPath, linkIntent, documentLoads, fragment, {
1218
+ module: true,
1219
+ hashLinks,
1220
+ runPath: run?.path
1221
+ }, debug)}\n};`;
1222
+ return {
1223
+ path: linkBridgeModulePath(base, contentHash(source)),
1224
+ source
1225
+ };
1226
+ };
1227
+ const withoutHashLinks = variant(false);
1228
+ const pages = [withoutHashLinks, fragment ? variant(true) : withoutHashLinks];
1229
+ return run ? [...pages, run] : pages;
1230
+ }
1231
+ function contentHash(text) {
1232
+ let hash = 2166136261;
1233
+ for (let at = 0; at < text.length; at++) hash = Math.imul(hash ^ text.charCodeAt(at), 16777619);
1234
+ return (hash >>> 0).toString(36);
1235
+ }
1236
+ function renderLinkBridgeScript(resumeEntryPath, intentPreloads, documentLoads = false, fragment, hashLinks = false, modulePaths) {
1237
+ const debugBootstrap = typeof __MARKLESS_DEBUG_ENABLED__ !== "undefined" && __MARKLESS_DEBUG_ENABLED__ ? `<script data-markless-router-debug-bootstrap>${escapeInlineScript(`(() => {
1238
+ const d = document;
1239
+ const s = d.currentScript;
1240
+ const r = s && s.closest('[data-async-container]');
1241
+ if (!r) return;
1242
+ try { globalThis[Symbol.for('markless.debug.channel.v1.bootstrap')] = ${__marklessDebugBootstrapSource()}(r, 'ssr-inline', false); } catch {}
1243
+ })();`)}<\/script>` : "";
1244
+ const modulePath = modulePaths?.[hashLinks ? 1 : 0];
1245
+ if (modulePath) {
1246
+ const indexed = !fragment && intentPreloads ? indexedPreloads(intentPreloads) : void 0;
1247
+ const args = fragment ? [escapeScriptJson(intentPreloads ?? {})] : indexed ? [
1248
+ "0",
1249
+ escapeScriptJson(indexed.preloads),
1250
+ escapeScriptJson(indexed.destinations)
1251
+ ] : [];
1252
+ return `${debugBootstrap}<script type="module" ${LINK_BRIDGE_SCRIPT_ATTRIBUTE}>${escapeInlineScript(`import b from${JSON.stringify(modulePath)};b(${args.join(",")})`)}<\/script>`;
1253
+ }
1254
+ const body = linkBridgeBody(resumeEntryPath, intentPreloads !== void 0, documentLoads, fragment, {
1255
+ intentPreloads,
1256
+ hashLinks
1257
+ });
1258
+ return `${debugBootstrap}<script ${LINK_BRIDGE_SCRIPT_ATTRIBUTE}>${escapeInlineScript(`(()=>{const d=document,s=d.currentScript,r=s&&s.closest('[data-async-container]');if(!r)return;${body}
1259
+ })();`)}<\/script>`;
1260
+ }
1261
+ function boundFragmentRun(fragment, documentLoads) {
1262
+ return `(d,...a)=>(${fragment.runSource})(d,${escapeScriptJson(fragmentConfig(fragment, documentLoads))},...a)`;
1263
+ }
1264
+ function fragmentConfig(fragment, documentLoads) {
1265
+ return {
1266
+ entry: fragment.entry,
1267
+ entryImports: fragment.entryImports,
1268
+ linkAttribute: LINK_ATTRIBUTE,
1269
+ prefetchAttribute: PREFETCH_ATTRIBUTE,
1270
+ replaceAttribute: REPLACE_ATTRIBUTE,
1271
+ scrollAttribute: SCROLL_ATTRIBUTE,
1272
+ routeScriptType: ROUTE_SCRIPT_TYPE,
1273
+ servedHtmlAttributesProperty: SERVED_HTML_ATTRIBUTES_PROPERTY,
1274
+ retireProperty: CONTAINER_RETIRE_PROPERTY,
1275
+ streamExecutorGlobal: STREAM_ARM_EXECUTOR_GLOBAL,
1276
+ streamedArmSelector: STREAMED_ARM_SELECTOR,
1277
+ requestPrefix: FRAGMENT_PATH_PREFIX,
1278
+ regionEndMarker: FRAGMENT_REGION_END,
1279
+ appendMarker: FRAGMENT_APPEND_END,
1280
+ pageEndComment: PROTOCOL_CONTAINER_END_COMMENT,
1281
+ pageStartComment: PROTOCOL_CONTAINER_START_COMMENT,
1282
+ containerAttributes: [CONTAINER_ATTRIBUTE, PROTOCOL_LOCATOR_PATHS_ATTRIBUTE],
1283
+ documentLoads,
1284
+ hoverDwellMs: 20,
1285
+ prefetchTtlMs: FRAGMENT_PREFETCH_TTL_MS,
1286
+ prefetchConnections: 2,
1287
+ endpointPrefixes: [API_PATHNAME_PREFIX],
1288
+ idleFragments: 6,
1289
+ idleWithoutConnectionInfo: true,
1290
+ prefetch: fragment.prefetch,
1291
+ pages: fragment.pages
1292
+ };
1293
+ }
1294
+ function htmlAttributes(documentModule, pageProps, written) {
1295
+ const attributes = documentModule?.__marklessRouterHtmlAttributes?.(pageProps) ?? {
1296
+ lang: "en",
1297
+ ...written
1298
+ };
1299
+ const normalized = {};
1300
+ for (const [name, value] of Object.entries(attributes)) {
1301
+ if (value === false || value === null || value === void 0) continue;
1302
+ normalized[name] = String(value);
1303
+ }
1304
+ return normalized;
1305
+ }
1306
+ function renderAttributes(attributes) {
1307
+ return Object.entries(attributes).map(([name, value]) => ` ${name}="${escapeHtml(value)}"`).join("");
1308
+ }
1309
+ function escapeHtml(value) {
1310
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
1311
+ }
1312
+ function escapeScriptJson(value) {
1313
+ return JSON.stringify(value).replaceAll("<", "\\u003c");
1314
+ }
1315
+ function escapeInlineScript(value) {
1316
+ return value.replace(/<\/script/gi, "<\\/script");
1317
+ }
1318
+ const API_PATHNAME_PREFIX = "/api";
1319
+ function isNitroApiPathname(pathname) {
1320
+ return pathname === API_PATHNAME_PREFIX || pathname.startsWith(`${API_PATHNAME_PREFIX}/`);
1321
+ }
1322
+ function insertImportMap(html, importMap) {
1323
+ if (!importMap) return html;
1324
+ const head = /<head(?:\s[^>]*)?>/i.exec(html);
1325
+ if (!head) return `${importMap}${html}`;
1326
+ const afterHead = head.index + head[0].length;
1327
+ const headEnd = html.indexOf("</head>", afterHead);
1328
+ const charset = /<meta\s[^>]*charset[^>]*>(?:<\/meta>)?/i.exec(html.slice(afterHead));
1329
+ const at = charset && (headEnd === -1 || afterHead + charset.index < headEnd) ? afterHead + charset.index + charset[0].length : afterHead;
1330
+ return `${html.slice(0, at)}${importMap}${html.slice(at)}`;
1331
+ }
1332
+ //#endregion
1333
+ export { runFragmentNavigation as a, FRAGMENT_PATH_PREFIX as i, routerLinkBridgeModules as n, startFragmentNavigation as o, LINK_BRIDGE_DIR as r, listenForLinkIntent as s, createServerEntry as t };