@vmz/core 0.2.5 → 0.2.7

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.
Files changed (77) hide show
  1. package/dist/browser/client-nav.d.ts +5 -55
  2. package/dist/browser/client-nav.js +5 -769
  3. package/dist/browser/diagnostics/precision-trace.d.ts +47 -0
  4. package/dist/browser/diagnostics/precision-trace.js +86 -0
  5. package/dist/browser/dom/attributes.d.ts +5 -0
  6. package/dist/browser/dom/attributes.js +102 -0
  7. package/dist/browser/dom/component.d.ts +11 -0
  8. package/dist/browser/dom/component.js +255 -0
  9. package/dist/browser/dom/direct-api.d.ts +66 -0
  10. package/dist/browser/dom/direct-api.js +446 -0
  11. package/dist/browser/dom/lifecycle.d.ts +9 -0
  12. package/dist/browser/dom/lifecycle.js +161 -0
  13. package/dist/browser/dom/registry.d.ts +5 -0
  14. package/dist/browser/dom/registry.js +16 -0
  15. package/dist/browser/dom-core.d.ts +51 -249
  16. package/dist/browser/dom-core.js +51 -2521
  17. package/dist/browser/navigation/client-navigation.d.ts +44 -0
  18. package/dist/browser/navigation/client-navigation.js +277 -0
  19. package/dist/browser/navigation/extract-html.d.ts +1 -0
  20. package/dist/browser/navigation/extract-html.js +5 -0
  21. package/dist/browser/navigation/layout-retain.d.ts +3 -0
  22. package/dist/browser/navigation/layout-retain.js +34 -0
  23. package/dist/browser/navigation/locale-transition.d.ts +24 -0
  24. package/dist/browser/navigation/locale-transition.js +323 -0
  25. package/dist/browser/navigation/scroll-focus.d.ts +14 -0
  26. package/dist/browser/navigation/scroll-focus.js +87 -0
  27. package/dist/browser/reactivity/patch-scheduler.d.ts +12 -0
  28. package/dist/browser/reactivity/patch-scheduler.js +720 -0
  29. package/dist/browser/reactivity/write-barrier.d.ts +16 -0
  30. package/dist/browser/reactivity/write-barrier.js +546 -0
  31. package/dist/browser/resume/adopt.d.ts +1 -0
  32. package/dist/browser/resume/adopt.js +168 -0
  33. package/dist/browser/resume/hydrate.d.ts +4 -0
  34. package/dist/browser/resume/hydrate.js +107 -0
  35. package/dist/browser/resume/resume.d.ts +3 -0
  36. package/dist/browser/resume/resume.js +102 -0
  37. package/dist/faces/dom.browser.d.ts +2 -1
  38. package/dist/faces/dom.browser.js +2 -1
  39. package/dist/host/{deployment-registry.d.ts → deployment/deployment-registry.d.ts} +1 -1
  40. package/dist/host/{deployment-registry.js → deployment/deployment-registry.js} +1 -1
  41. package/dist/host/list-client-components.d.ts +2 -2
  42. package/dist/host/list-client-components.js +4 -4
  43. package/dist/host/{render-host.d.ts → render/render-host.d.ts} +2 -2
  44. package/dist/host/{render-host.js → render/render-host.js} +1 -1
  45. package/dist/host/{route-layout-chain.js → render/route-layout-chain.js} +1 -1
  46. package/dist/host/serve/bootstrap.d.ts +1 -0
  47. package/dist/host/serve/bootstrap.js +25 -0
  48. package/dist/host/serve/constants.d.ts +6 -0
  49. package/dist/host/serve/constants.js +6 -0
  50. package/dist/host/serve/hmr-dev.d.ts +34 -0
  51. package/dist/host/serve/hmr-dev.js +294 -0
  52. package/dist/host/serve/http.d.ts +1 -0
  53. package/dist/host/serve/http.js +124 -0
  54. package/dist/host/serve/locale-theme.d.ts +19 -0
  55. package/dist/host/serve/locale-theme.js +169 -0
  56. package/dist/host/serve/page-catalog.d.ts +14 -0
  57. package/dist/host/serve/page-catalog.js +105 -0
  58. package/dist/host/serve/page-render.d.ts +17 -0
  59. package/dist/host/serve/page-render.js +419 -0
  60. package/dist/host/serve/paths.d.ts +3 -0
  61. package/dist/host/serve/paths.js +74 -0
  62. package/dist/host/serve/route-catalog.d.ts +6 -0
  63. package/dist/host/serve/route-catalog.js +108 -0
  64. package/dist/host/serve/state.d.ts +30 -0
  65. package/dist/host/serve/state.js +36 -0
  66. package/dist/host/serve-host.d.ts +0 -17
  67. package/dist/host/serve-host.mjs +4 -1420
  68. package/dist/ssr/document.d.ts +2 -0
  69. package/dist/ssr/document.js +71 -0
  70. package/dist/ssr/dom-ssr.d.ts +10 -47
  71. package/dist/ssr/dom-ssr.js +10 -1284
  72. package/dist/ssr/render.d.ts +2 -0
  73. package/dist/ssr/render.js +78 -0
  74. package/dist/ssr/serialize.d.ts +3 -0
  75. package/dist/ssr/serialize.js +678 -0
  76. package/package.json +11 -11
  77. /package/dist/host/{route-layout-chain.d.ts → render/route-layout-chain.d.ts} +0 -0
@@ -0,0 +1,105 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
4
+ import { ROUTE_CATALOG_REL, ROUTE_CATALOG_SCHEMA } from './constants.js';
5
+ import { isRouteBoundaryStem, parsePathPattern } from './route-catalog.js';
6
+ import { serveState } from './state.js';
7
+ export function bustUrl(href) {
8
+ const u = new URL(href);
9
+ u.searchParams.set('t', String(serveState.reloadToken));
10
+ return u.href;
11
+ }
12
+ export async function loadPageCtor(chunkId) {
13
+ const pageRel = `${chunkId}.client.js`;
14
+ const href = bustUrl(pathToFileURL(path.join(serveState.distDir, pageRel)).href);
15
+ const mod = await import(href);
16
+ serveState.pageCtors.set(chunkId, mod.default);
17
+ return mod.default;
18
+ }
19
+ export async function listPageClientFiles(dir) {
20
+ const fromCatalog = await listPagesFromRouteCatalog(dir);
21
+ if (!fromCatalog.length) {
22
+ if (serveState.isDev) {
23
+ console.warn(`vmz dev: missing compiled ${ROUTE_CATALOG_SCHEMA} at ${path.join(dir, ...ROUTE_CATALOG_REL.split('/'))}`);
24
+ return [];
25
+ }
26
+ throw new Error(`vmz serve: missing compiled ${ROUTE_CATALOG_SCHEMA} at ${path.join(dir, ...ROUTE_CATALOG_REL.split('/'))}`);
27
+ }
28
+ return fromCatalog;
29
+ }
30
+ async function listPagesFromRouteCatalog(dir) {
31
+ const out = [];
32
+ try {
33
+ const raw = await readFile(path.join(dir, ...ROUTE_CATALOG_REL.split('/')), 'utf8');
34
+ const catalog = JSON.parse(raw);
35
+ if (catalog?.schema !== ROUTE_CATALOG_SCHEMA || !Array.isArray(catalog.pages))
36
+ return [];
37
+ for (const page of catalog.pages) {
38
+ const chunkId = String(page?.chunkId || '').replace(/\\/g, '/');
39
+ if (!chunkId.startsWith('pages/'))
40
+ continue;
41
+ const stem = chunkId.split('/').pop() || '';
42
+ if (isRouteBoundaryStem(stem))
43
+ continue;
44
+ const segs = Array.isArray(page?.segs) && page.segs.length ? page.segs : parsePathPattern(String(page?.pathPattern || ''));
45
+ const pageRel = String(page?.pageRel || `${chunkId}.client.js`).replace(/\\/g, '/');
46
+ out.push({
47
+ chunkId,
48
+ pageRel,
49
+ routeId: typeof page?.routeId === 'string' ? page.routeId : chunkId,
50
+ pathPattern: typeof page?.pathPattern === 'string' ? page.pathPattern : undefined,
51
+ segs,
52
+ });
53
+ }
54
+ }
55
+ catch {
56
+ return [];
57
+ }
58
+ return out;
59
+ }
60
+ export async function runRouteGate(pathname, chunkId) {
61
+ try {
62
+ const href = bustUrl(pathToFileURL(path.join(serveState.distDir, 'vmz-route-gate.mjs')).href);
63
+ const mod = await import(href);
64
+ if (typeof mod.check !== 'function')
65
+ return null;
66
+ return await mod.check(pathname, chunkId ?? null);
67
+ }
68
+ catch {
69
+ return null;
70
+ }
71
+ }
72
+ export function pageNeedsReload(chunkId, affected) {
73
+ if (chunkId === 'pages/Layout' || chunkId.endsWith('/Layout'))
74
+ return true;
75
+ return affected.some((a) => {
76
+ const id = String(a);
77
+ return id === chunkId || chunkId.startsWith(`${id}/`) || id.startsWith(`${chunkId}/`);
78
+ });
79
+ }
80
+ export async function loadPageResumeEntries(dir, chunkId) {
81
+ try {
82
+ const raw = await readFile(path.join(dir, 'vmz-deployment.json'), 'utf8');
83
+ const dep = JSON.parse(raw);
84
+ const units = Array.isArray(dep.units) ? dep.units : [];
85
+ const page = units.find((u) => u.chunkId === chunkId) ||
86
+ units.find((u) => u.chunkId === 'pages/index') ||
87
+ units.find((u) => u.kind === 'page');
88
+ const entries = Array.isArray(page?.resumeEntries) ? page.resumeEntries : [];
89
+ return entries.map((e) => ({
90
+ component: String(e.component || ''),
91
+ strategy: String(e.strategy || ''),
92
+ }));
93
+ }
94
+ catch {
95
+ return [];
96
+ }
97
+ }
98
+ export function isEventStrategy(strategy) {
99
+ return strategy === 'event' || strategy === 'click' || strategy.startsWith('event:');
100
+ }
101
+ export function isEventOnlyShell(strategies) {
102
+ if (!strategies.length)
103
+ return false;
104
+ return strategies.every((s) => isEventStrategy(s));
105
+ }
@@ -0,0 +1,17 @@
1
+ import type { HostRequestOpts } from '../../shared/host.types.js';
2
+ export declare function renderPage(pathname: string, opts?: HostRequestOpts): Promise<string>;
3
+ export declare function renderPageStream(pathname: string, opts?: HostRequestOpts & {
4
+ body?: unknown;
5
+ }): Promise<{
6
+ status: number;
7
+ stream: AsyncGenerator<any, void, unknown>;
8
+ redirect?: undefined;
9
+ headers?: undefined;
10
+ } | {
11
+ status: number;
12
+ redirect: string;
13
+ headers: {
14
+ Location: string;
15
+ };
16
+ stream?: undefined;
17
+ }>;
@@ -0,0 +1,419 @@
1
+ import { localeHrefTableFromPlan, localizeBodyLinks } from '../localize-body-links.js';
2
+ import { loadNativeAddon } from '../native-addon.js';
3
+ import { createRenderHost } from '../render/render-host.js';
4
+ import { resolveRouteLayoutChain } from '../render/route-layout-chain.js';
5
+ import { extractRouteParams, findRootCatchAll, isRootCatchAll, matchFileRoute } from './route-catalog.js';
6
+ import { emitDevErrorHtml, normalizeDevError } from './hmr-dev.js';
7
+ import { cssEntryWithBust, htmlThemeAttrPair, localeBootstrapScript, pageMetaAlternates, resolveLocalePath, resolveThemeId, siteFaviconHeadHtml, themeBootstrapScript, } from './locale-theme.js';
8
+ import { isEventOnlyShell, loadPageCtor, loadPageResumeEntries, runRouteGate } from './page-catalog.js';
9
+ import { serveState } from './state.js';
10
+ export async function renderPage(pathname, opts = {}) {
11
+ const rendered = await renderPageStream(pathname, opts);
12
+ if (!rendered)
13
+ return null;
14
+ const raw = rendered.stream ?? rendered;
15
+ if (!raw || typeof raw !== 'object' || typeof raw[Symbol.asyncIterator] !== 'function') {
16
+ return null;
17
+ }
18
+ let html = '';
19
+ for await (const chunk of raw) {
20
+ html += chunk;
21
+ }
22
+ return html;
23
+ }
24
+ export async function renderPageStream(pathname, opts = {}) {
25
+ try {
26
+ return await renderPageStreamInner(pathname, opts);
27
+ }
28
+ catch (err) {
29
+ const normalized = normalizeDevError(err);
30
+ serveState.lastDevError = normalized;
31
+ console.error('vmz serve: renderPageStream failed', normalized.message);
32
+ return { status: 500, stream: emitDevErrorHtml(normalized) };
33
+ }
34
+ }
35
+ async function renderPageStreamInner(pathname, opts = {}) {
36
+ if (serveState.isDev && serveState.lastDevError && serveState.pageCtors.size === 0) {
37
+ return { status: 500, stream: emitDevErrorHtml(serveState.lastDevError) };
38
+ }
39
+ const localePlan = resolveLocalePath(pathname, opts.cookieHeader);
40
+ if (localePlan.redirectTo) {
41
+ return { status: 302, redirect: localePlan.redirectTo, headers: { Location: localePlan.redirectTo } };
42
+ }
43
+ const routePath = localePlan.restPath || pathname;
44
+ let match = matchFileRoute(routePath, serveState.pageCatalog);
45
+ let status = 200;
46
+ const gated = await runRouteGate(routePath, match?.chunkId);
47
+ if (gated === 'not_found') {
48
+ match = findRootCatchAll(serveState.pageCatalog);
49
+ status = 404;
50
+ }
51
+ else if (!match) {
52
+ match = findRootCatchAll(serveState.pageCatalog);
53
+ status = 404;
54
+ }
55
+ else if (isRootCatchAll(match)) {
56
+ status = 404;
57
+ }
58
+ if (!match) {
59
+ if (serveState.isDev && serveState.lastDevError) {
60
+ return { status: 500, stream: emitDevErrorHtml(serveState.lastDevError) };
61
+ }
62
+ return null;
63
+ }
64
+ const Page = await loadPageCtor(match.chunkId);
65
+ if (!Page) {
66
+ if (serveState.isDev && serveState.lastDevError) {
67
+ return { status: 500, stream: emitDevErrorHtml(serveState.lastDevError) };
68
+ }
69
+ return null;
70
+ }
71
+ const params = extractRouteParams(match.segs, routePath);
72
+ const method = String(opts.method || 'GET').toUpperCase();
73
+ const localeCtx = {
74
+ localeId: localePlan.localeId,
75
+ dir: localePlan.dir,
76
+ pathname,
77
+ routePath,
78
+ alternates: pageMetaAlternates(match.chunkId, localePlan.localeId),
79
+ };
80
+ const pageCtor = Page;
81
+ if (typeof pageCtor.access === 'function') {
82
+ const access = await pageCtor.access({
83
+ params,
84
+ pathname: routePath,
85
+ chunkId: match.chunkId,
86
+ signal: opts.signal,
87
+ searchParams: opts.searchParams,
88
+ method,
89
+ localeId: localeCtx.localeId,
90
+ });
91
+ const closed = normalizeAccessResult(access);
92
+ if (closed.kind === 'redirect') {
93
+ return { status: 302, redirect: closed.location, headers: { Location: closed.location } };
94
+ }
95
+ if (closed.kind === 'deny') {
96
+ return { status: 403, stream: emitAccessShell('route-access-deny') };
97
+ }
98
+ if (closed.kind === 'not-found') {
99
+ const catchAll = findRootCatchAll(serveState.pageCatalog);
100
+ if (catchAll) {
101
+ const NotFound = await loadPageCtor(catchAll.chunkId);
102
+ if (NotFound) {
103
+ const resumeEntries = await loadPageResumeEntries(serveState.distDir, catchAll.chunkId);
104
+ const eventOnlyShell = isEventOnlyShell(resumeEntries.map((e) => e.strategy));
105
+ return {
106
+ status: 404,
107
+ stream: emitPageHtml(NotFound, catchAll.chunkId, eventOnlyShell, { ...params }, opts, [], localeCtx),
108
+ };
109
+ }
110
+ }
111
+ return { status: 404, stream: emitAccessShell('route-access-not-found') };
112
+ }
113
+ }
114
+ let props = { ...params };
115
+ if (method === 'POST' && typeof pageCtor.action === 'function') {
116
+ const acted = await pageCtor.action({
117
+ params,
118
+ pathname: routePath,
119
+ chunkId: match.chunkId,
120
+ signal: opts.signal,
121
+ searchParams: opts.searchParams,
122
+ body: opts.body,
123
+ method,
124
+ localeId: localeCtx.localeId,
125
+ });
126
+ const actionClosed = normalizeActionResult(acted);
127
+ if (actionClosed.kind === 'redirect') {
128
+ return { status: 302, redirect: actionClosed.location, headers: { Location: actionClosed.location } };
129
+ }
130
+ if (actionClosed.kind === 'deny') {
131
+ return { status: 403, stream: emitAccessShell('route-action-deny') };
132
+ }
133
+ if (actionClosed.kind === 'not-found') {
134
+ return { status: 404, stream: emitAccessShell('route-action-not-found') };
135
+ }
136
+ if (actionClosed.props) {
137
+ props = { ...props, ...actionClosed.props };
138
+ }
139
+ }
140
+ if (typeof pageCtor.load === 'function') {
141
+ const loaded = await pageCtor.load({
142
+ params,
143
+ pathname: routePath,
144
+ chunkId: match.chunkId,
145
+ signal: opts.signal,
146
+ searchParams: opts.searchParams,
147
+ localeId: localeCtx.localeId,
148
+ });
149
+ if (opts.signal?.aborted) {
150
+ return { status: 499, stream: emitAccessShell('route-nav-cancelled') };
151
+ }
152
+ if (loaded && typeof loaded === 'object' && !Array.isArray(loaded)) {
153
+ props = { ...props, ...loaded };
154
+ }
155
+ }
156
+ if (opts.signal?.aborted) {
157
+ return { status: 499, stream: emitAccessShell('route-nav-cancelled') };
158
+ }
159
+ const resumeEntries = await loadPageResumeEntries(serveState.distDir, match.chunkId);
160
+ const strategies = resumeEntries.map((e) => e.strategy);
161
+ const eventOnlyShell = isEventOnlyShell(strategies);
162
+ const layoutChain = resolveRouteLayoutChain(serveState.distDir, match.chunkId);
163
+ return {
164
+ status,
165
+ stream: emitPageHtml(Page, match.chunkId, eventOnlyShell, props, opts, layoutChain, localeCtx),
166
+ };
167
+ }
168
+ function normalizeAccessResult(access) {
169
+ if (access == null || access === true)
170
+ return { kind: 'allow' };
171
+ if (typeof access === 'string') {
172
+ const k = access.toLowerCase();
173
+ if (k === 'allow')
174
+ return { kind: 'allow' };
175
+ if (k === 'deny')
176
+ return { kind: 'deny' };
177
+ if (k === 'not-found' || k === 'notfound')
178
+ return { kind: 'not-found' };
179
+ }
180
+ if (typeof access === 'object' && access) {
181
+ const row = access;
182
+ const kind = String(row.kind || row.type || 'allow').toLowerCase();
183
+ if (kind === 'redirect') {
184
+ const location = String(row.location || row.to || row.href || '');
185
+ if (!location)
186
+ return { kind: 'deny' };
187
+ return { kind: 'redirect', location };
188
+ }
189
+ if (kind === 'deny')
190
+ return { kind: 'deny' };
191
+ if (kind === 'not-found' || kind === 'notfound')
192
+ return { kind: 'not-found' };
193
+ return { kind: 'allow' };
194
+ }
195
+ return { kind: 'allow' };
196
+ }
197
+ function normalizeActionResult(acted) {
198
+ const base = normalizeAccessResult(acted);
199
+ if (base.kind !== 'allow')
200
+ return base;
201
+ if (acted && typeof acted === 'object' && !Array.isArray(acted)) {
202
+ const row = acted;
203
+ if (row.props && typeof row.props === 'object') {
204
+ return { kind: 'allow', props: row.props };
205
+ }
206
+ if (!('kind' in row) && !('type' in row)) {
207
+ return { kind: 'allow', props: row };
208
+ }
209
+ }
210
+ return { kind: 'allow' };
211
+ }
212
+ async function* emitAccessShell(marker) {
213
+ const native = loadNativeAddon();
214
+ if (typeof native.generateHtmlShell !== 'function') {
215
+ throw new Error('vmz native addon missing generateHtmlShell — rebuild with `pnpm napi:build`');
216
+ }
217
+ yield native.generateHtmlShell({
218
+ title: 'App',
219
+ lang: 'en',
220
+ cssHrefs: [],
221
+ bodyHtml: `<p>${marker}</p>`,
222
+ bodyAttrs: [],
223
+ });
224
+ }
225
+ function resolvePageDocumentMeta(Page) {
226
+ try {
227
+ let raw = {};
228
+ if (typeof Page?.meta === 'function')
229
+ raw = Page.meta() || {};
230
+ else if (Page?.meta && typeof Page.meta === 'object')
231
+ raw = Page.meta;
232
+ const title = String(raw.title || '').trim();
233
+ const description = String(raw.description || '').trim();
234
+ return { title: title || 'App', description };
235
+ }
236
+ catch {
237
+ return { title: 'App', description: '' };
238
+ }
239
+ }
240
+ async function* emitPageHtml(Page, chunkId, eventOnlyShell, props = {}, opts = {}, layoutChain = [], localeCtx = {}) {
241
+ const signal = opts.signal;
242
+ const live = serveState.isDev
243
+ ? `\n <script>
244
+ (() => {
245
+ const es = new EventSource("/__vmz/events");
246
+ let sawDisconnect = false;
247
+ es.onerror = () => { sawDisconnect = true; };
248
+ es.onopen = () => {
249
+ if (sawDisconnect) location.reload();
250
+ };
251
+ function showOverlay(err) {
252
+ let el = document.getElementById("vmz-dev-overlay");
253
+ if (!el) {
254
+ el = document.createElement("div");
255
+ el.id = "vmz-dev-overlay";
256
+ el.setAttribute("role", "alert");
257
+ Object.assign(el.style, {
258
+ position: "fixed", inset: "0", zIndex: "2147483646",
259
+ background: "rgba(15,17,21,0.92)", color: "#f4f4f5",
260
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
261
+ padding: "2rem", overflow: "auto",
262
+ });
263
+ document.documentElement.appendChild(el);
264
+ }
265
+ const msg = (err && err.message) || String(err || "Unknown error");
266
+ const stack = (err && err.stack) || "";
267
+ const esc = (s) => String(s).replace(/[&<>]/g, (c) => ({"&":"&amp;","<":"&lt;",">":"&gt;"}[c]));
268
+ el.innerHTML = "<div style=\\"max-width:56rem;margin:0 auto\\">"
269
+ + "<p style=\\"margin:0 0 .5rem;color:#f87171;font-weight:700\\">Dev Error</p>"
270
+ + "<pre style=\\"white-space:pre-wrap;margin:0 0 1rem;font-size:13px;line-height:1.45\\">" + esc(msg) + "</pre>"
271
+ + (stack ? "<pre style=\\"white-space:pre-wrap;opacity:.7;font-size:12px\\">" + esc(stack) + "</pre>" : "")
272
+ + "<p style=\\"opacity:.65;font-size:12px\\">Fix the file and save — soft reload will clear this overlay.</p>"
273
+ + "</div>";
274
+ }
275
+ function hideOverlay() {
276
+ const el = document.getElementById("vmz-dev-overlay");
277
+ if (el) el.remove();
278
+ }
279
+ es.onmessage = async (ev) => {
280
+ let msg = null;
281
+ try { msg = JSON.parse(ev.data); } catch { /* plain string */ }
282
+ if (msg && msg.type === "error") {
283
+ showOverlay(msg);
284
+ return;
285
+ }
286
+ if (!msg || msg.type !== "hmr") {
287
+ if (ev.data === "reload") location.reload();
288
+ return;
289
+ }
290
+ hideOverlay();
291
+ if (msg.mode === "island") {
292
+ try {
293
+ const { hydrate } = await import("/dom.browser.js?t=" + msg.token);
294
+ const root = document.getElementById("app");
295
+ const pageChunk = root && root.getAttribute("data-vmz-page");
296
+ if (root && pageChunk) {
297
+ const pageMod = await import("/" + pageChunk + ".client.js?t=" + msg.token);
298
+ let hmrProps = {};
299
+ try {
300
+ const raw = root.getAttribute("data-vmz-props");
301
+ if (raw) hmrProps = JSON.parse(raw);
302
+ } catch { /* ignore */ }
303
+ await hydrate(pageMod.default, root, hmrProps, { preserveState: true, skipOnMount: true });
304
+ } else {
305
+ location.reload();
306
+ }
307
+ } catch (err) {
308
+ console.error("vmz island HMR failed", err);
309
+ showOverlay({ message: String(err && err.message || err), stack: err && err.stack });
310
+ }
311
+ return;
312
+ }
313
+ location.reload();
314
+ };
315
+ })();
316
+ </script>`
317
+ : '';
318
+ const bootOverlay = serveState.isDev && serveState.lastDevError
319
+ ? `\n <script>window.__VMZ_DEV_ERROR__=${JSON.stringify(serveState.lastDevError)};` +
320
+ `(function(){var e=window.__VMZ_DEV_ERROR__;if(!e)return;` +
321
+ `var ev=new Event("message");ev.data=JSON.stringify({type:"error",message:e.message,stack:e.stack});` +
322
+ `var d=document.createElement("div");d.id="vmz-dev-overlay";d.setAttribute("role","alert");` +
323
+ `Object.assign(d.style,{position:"fixed",inset:"0",zIndex:"2147483646",background:"rgba(15,17,21,0.92)",color:"#f4f4f5",fontFamily:"ui-monospace,monospace",padding:"2rem",overflow:"auto"});` +
324
+ `d.innerHTML="<div style='max-width:56rem;margin:0 auto'><p style='color:#f87171;font-weight:700'>Dev Error</p><pre style='white-space:pre-wrap'>"+String(e.message||e).replace(/[<>&]/g,function(c){return {"<":"&lt;",">":"&gt;","&":"&amp;"}[c]})+"</pre></div>";` +
325
+ `document.documentElement.appendChild(d);})();</script>`
326
+ : '';
327
+ const buildIdBoot = serveState.isDev && serveState.lastDevBuildId
328
+ ? `\n <script>window.__VMZ_DEV_BUILD_ID__=${JSON.stringify(serveState.lastDevBuildId)};</script>`
329
+ : '';
330
+ if (signal?.aborted)
331
+ return;
332
+ const themeId = resolveThemeId(opts.searchParams, opts.cookieHeader);
333
+ const themeBoot = themeBootstrapScript();
334
+ const localeBoot = localeBootstrapScript();
335
+ const faviconHead = siteFaviconHeadHtml();
336
+ const propsJson = JSON.stringify(props ?? {});
337
+ const localeId = localeCtx.localeId || serveState.localeArtifact?.defaultLocale || 'en';
338
+ const dir = localeCtx.dir || 'ltr';
339
+ const htmlExtraAttrs = [...htmlThemeAttrPair(themeId)];
340
+ if (serveState.localeArtifact?.routing) {
341
+ htmlExtraAttrs.push('data-vmz-locale-routing', JSON.stringify({
342
+ strategy: serveState.localeArtifact.routing.strategy || 'prefix',
343
+ defaultPrefix: serveState.localeArtifact.routing.defaultPrefix || 'include',
344
+ defaultLocale: serveState.localeArtifact.defaultLocale,
345
+ locales: (serveState.localeArtifact.locales || []).map((l) => l.id),
346
+ }));
347
+ const hrefTable = localeHrefTableFromPlan(serveState.localeLinkPlan);
348
+ if (hrefTable && Object.keys(hrefTable).length) {
349
+ htmlExtraAttrs.push('data-vmz-locale-hrefs', JSON.stringify(hrefTable));
350
+ }
351
+ }
352
+ const pageDocMeta = resolvePageDocumentMeta(Page);
353
+ const prevLocaleHint = globalThis.__vmzLocaleIdHint;
354
+ globalThis.__vmzLocaleIdHint = localeId;
355
+ if (!serveState.ssrRenderHost) {
356
+ serveState.ssrRenderHost = await createRenderHost(serveState.distDir, {
357
+ strictDeployment: !serveState.isDev,
358
+ preload: 'none',
359
+ cacheBust: serveState.reloadToken,
360
+ });
361
+ }
362
+ await serveState.ssrRenderHost.ensureComponents([chunkId, ...layoutChain]);
363
+ let bodyHtml = '';
364
+ try {
365
+ for await (const chunk of serveState.ssrRenderHost.renderToStream(Page, props, { signal })) {
366
+ if (signal?.aborted)
367
+ return;
368
+ bodyHtml += chunk;
369
+ }
370
+ if (signal?.aborted)
371
+ return;
372
+ for (let i = layoutChain.length - 1; i >= 0; i--) {
373
+ const Layout = await loadPageCtor(layoutChain[i]);
374
+ if (!Layout)
375
+ continue;
376
+ bodyHtml = await serveState.ssrRenderHost.renderToString(Layout, {}, { signal, slotHtml: bodyHtml });
377
+ if (signal?.aborted)
378
+ return;
379
+ }
380
+ if (serveState.localeArtifact && localeId) {
381
+ bodyHtml = localizeBodyLinks(bodyHtml, localeId, serveState.localeArtifact, undefined, serveState.localeLinkPlan);
382
+ }
383
+ }
384
+ finally {
385
+ if (prevLocaleHint === undefined)
386
+ delete globalThis.__vmzLocaleIdHint;
387
+ else
388
+ globalThis.__vmzLocaleIdHint = prevLocaleHint;
389
+ }
390
+ if (signal?.aborted)
391
+ return;
392
+ const native = loadNativeAddon();
393
+ if (typeof native.generatePageShell !== 'function') {
394
+ throw new Error('vmz native addon missing generatePageShell — rebuild with `pnpm napi:build`');
395
+ }
396
+ const entrySrc = `/${eventOnlyShell ? 'entry-event.js' : 'entry-client.js'}?t=${serveState.reloadToken}`;
397
+ const cssHref = cssEntryWithBust(serveState.cssEntry);
398
+ yield native.generatePageShell({
399
+ bodyHtml,
400
+ chunkId,
401
+ layoutChain,
402
+ propsJson,
403
+ meta: {
404
+ title: pageDocMeta.title,
405
+ description: pageDocMeta.description,
406
+ canonical: '',
407
+ robots: '',
408
+ lang: localeId,
409
+ dir,
410
+ alternates: localeCtx.alternates || [],
411
+ },
412
+ ...(cssHref ? { cssEntry: cssHref } : {}),
413
+ isErrorDocument: false,
414
+ htmlExtraAttrs,
415
+ headExtraHtml: `${themeBoot}${localeBoot}${faviconHead}`,
416
+ moduleScriptSrc: entrySrc,
417
+ bodyTailHtml: `${live}${bootOverlay}${buildIdBoot}`,
418
+ });
419
+ }
@@ -0,0 +1,3 @@
1
+ export declare function projectRootForResolve(): string;
2
+ export declare function appPackageRequireResolve(): NodeJS.Require;
3
+ export declare function installAppModuleResolveHooks(): void;
@@ -0,0 +1,74 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { createRequire, registerHooks } from 'node:module';
3
+ import path from 'node:path';
4
+ import { fileURLToPath, pathToFileURL } from 'node:url';
5
+ import { serveState } from './state.js';
6
+ const require = createRequire(import.meta.url);
7
+ export function projectRootForResolve() {
8
+ const fromEnv = typeof process.env.VMZ_PROJECT_ROOT === 'string' ? process.env.VMZ_PROJECT_ROOT.trim() : '';
9
+ if (fromEnv)
10
+ return path.resolve(fromEnv);
11
+ return process.cwd();
12
+ }
13
+ export function appPackageRequireResolve() {
14
+ if (!serveState.appPackageRequire) {
15
+ const root = projectRootForResolve();
16
+ const pkg = path.join(root, 'package.json');
17
+ serveState.appPackageRequire = existsSync(pkg) ? createRequire(pkg) : require;
18
+ }
19
+ return serveState.appPackageRequire;
20
+ }
21
+ export function installAppModuleResolveHooks() {
22
+ registerHooks({
23
+ resolve(specifier, context, nextResolve) {
24
+ if (!specifier ||
25
+ specifier.startsWith('.') ||
26
+ specifier.startsWith('node:') ||
27
+ specifier.startsWith('file:') ||
28
+ specifier.startsWith('#')) {
29
+ return nextResolve(specifier, context);
30
+ }
31
+ if (context.parentURL?.startsWith('file:')) {
32
+ try {
33
+ const parentPath = fileURLToPath(context.parentURL);
34
+ if (!parentPath.startsWith(serveState.distDir + path.sep))
35
+ return nextResolve(specifier, context);
36
+ }
37
+ catch {
38
+ return nextResolve(specifier, context);
39
+ }
40
+ }
41
+ try {
42
+ const appParent = pathToFileURL(path.join(projectRootForResolve(), 'package.json')).href;
43
+ return nextResolve(specifier, { ...context, parentURL: appParent });
44
+ }
45
+ catch {
46
+ // Some legacy packages expose only a CommonJS entry.
47
+ }
48
+ try {
49
+ const resolved = appPackageRequireResolve().resolve(specifier);
50
+ return { url: pathToFileURL(resolved).href, shortCircuit: true };
51
+ }
52
+ catch {
53
+ return nextResolve(specifier, context);
54
+ }
55
+ },
56
+ load(url, context, nextLoad) {
57
+ const pathOnly = url.split('?')[0].split('#')[0];
58
+ if (!pathOnly.endsWith('.json'))
59
+ return nextLoad(url, context);
60
+ try {
61
+ const filePath = fileURLToPath(pathOnly);
62
+ const raw = readFileSync(filePath, 'utf8');
63
+ return {
64
+ format: 'module',
65
+ shortCircuit: true,
66
+ source: `export default ${raw}`,
67
+ };
68
+ }
69
+ catch {
70
+ return nextLoad(url, context);
71
+ }
72
+ },
73
+ });
74
+ }
@@ -0,0 +1,6 @@
1
+ export declare function parsePathPattern(pattern: any): any[];
2
+ export declare function isRouteBoundaryStem(stem: any): boolean;
3
+ export declare function matchFileRoute(pathname: any, catalog: any): any;
4
+ export declare function extractRouteParams(segs: any, pathname: any): {};
5
+ export declare function isRootCatchAll(page: any): boolean;
6
+ export declare function findRootCatchAll(catalog: any): any;