@vmz/core 0.1.31 → 0.2.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/dist/{client-nav.d.ts → browser/client-nav.d.ts} +4 -13
- package/dist/{client-nav.js → browser/client-nav.js} +16 -74
- package/dist/browser/direct-api.types.d.ts +58 -0
- package/dist/browser/direct-api.types.js +4 -0
- package/dist/browser/direct-host-box.d.ts +11 -0
- package/dist/{direct-host-box.js → browser/direct-host-box.js} +2 -18
- package/dist/browser/dom-core.d.ts +246 -0
- package/dist/browser/dom-core.js +2453 -0
- package/dist/browser/dom-core.types.d.ts +138 -0
- package/dist/browser/dom-core.types.js +4 -0
- package/dist/{position-context.js → browser/position-context.js} +1 -1
- package/dist/browser/unknown-component.d.ts +14 -0
- package/dist/{unknown-component.js → browser/unknown-component.js} +4 -28
- package/dist/{dom.browser.d.ts → faces/dom.browser.d.ts} +2 -2
- package/dist/{dom.browser.js → faces/dom.browser.js} +2 -3
- package/dist/faces/dom.client.d.ts +4 -0
- package/dist/faces/dom.client.js +4 -0
- package/dist/faces/dom.d.ts +6 -0
- package/dist/faces/dom.js +6 -0
- package/dist/{http.d.ts → faces/http.d.ts} +1 -1
- package/dist/{http.js → faces/http.js} +1 -2
- package/dist/{server.d.ts → faces/server.d.ts} +8 -25
- package/dist/{server.js → faces/server.js} +25 -155
- package/dist/{vmz-dom.d.ts → faces/vmz-dom.d.ts} +1 -1
- package/dist/{vmz-dom.js → faces/vmz-dom.js} +1 -2
- package/dist/{vmz-runtime.d.ts → faces/vmz-runtime.d.ts} +1 -1
- package/dist/{vmz-runtime.js → faces/vmz-runtime.js} +1 -2
- package/dist/host/deployment-registry.d.ts +16 -0
- package/dist/{deployment-registry.js → host/deployment-registry.js} +1 -68
- package/dist/host/list-client-components.d.ts +14 -0
- package/dist/{list-client-components.js → host/list-client-components.js} +0 -37
- package/dist/{localize-body-links.js → host/localize-body-links.js} +0 -1
- package/dist/host/native-addon.d.ts +6 -0
- package/dist/{native-addon.js → host/native-addon.js} +0 -20
- package/dist/host/render-host.d.ts +20 -0
- package/dist/{render-host.js → host/render-host.js} +0 -16
- package/dist/{serve-host.d.ts → host/serve-host.d.ts} +3 -3
- package/dist/{serve-host.mjs → host/serve-host.mjs} +47 -185
- package/dist/shared/host.types.d.ts +67 -0
- package/dist/shared/host.types.js +4 -0
- package/dist/shared/server.types.d.ts +39 -0
- package/dist/shared/server.types.js +4 -0
- package/dist/{dom-ssr.d.ts → ssr/dom-ssr.d.ts} +8 -37
- package/dist/{dom-ssr.js → ssr/dom-ssr.js} +162 -219
- package/package.json +34 -34
- package/dist/deployment-registry.d.ts +0 -70
- package/dist/direct-host-box.d.ts +0 -26
- package/dist/dom-core.d.ts +0 -402
- package/dist/dom-core.js +0 -4813
- package/dist/dom.client.d.ts +0 -4
- package/dist/dom.client.js +0 -5
- package/dist/dom.d.ts +0 -6
- package/dist/dom.js +0 -7
- package/dist/list-client-components.d.ts +0 -62
- package/dist/native-addon.d.ts +0 -12
- package/dist/render-host.d.ts +0 -32
- package/dist/unknown-component.d.ts +0 -40
- /package/dist/{position-context.d.ts → browser/position-context.d.ts} +0 -0
- /package/dist/{index.d.ts → faces/index.d.ts} +0 -0
- /package/dist/{index.js → faces/index.js} +0 -0
- /package/dist/{localize-body-links.d.ts → host/localize-body-links.d.ts} +0 -0
- /package/dist/{route-layout-chain.d.ts → host/route-layout-chain.d.ts} +0 -0
- /package/dist/{route-layout-chain.js → host/route-layout-chain.js} +0 -0
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
/**
|
|
3
|
-
* Generic VMZ Node host
|
|
2
|
+
* Generic VMZ Node host �?SSR Route Graph pages + dist static + RPC/REST.
|
|
4
3
|
*
|
|
5
4
|
* Invoked by `vmz serve` / `vmz dev` (or: node dist/vmz-serve-host.mjs).
|
|
6
5
|
*
|
|
@@ -10,8 +9,8 @@
|
|
|
10
9
|
*
|
|
11
10
|
* `VMZ_DEV=1`: POST `/__vmz/reload` soft-reloads modules (cache-bust import);
|
|
12
11
|
* GET `/__vmz/events` SSE notifies the browser:
|
|
13
|
-
* - island HMR
|
|
14
|
-
* - otherwise
|
|
12
|
+
* - island HMR �?re-import `entry-client.js` (no full document reload)
|
|
13
|
+
* - otherwise �?`location.reload`
|
|
15
14
|
*
|
|
16
15
|
* Dev resolve hook propagates `?t=` onto nested relative `file:` imports under
|
|
17
16
|
* dist so soft reload does not keep a stale `lib/*.js` ESM cache entry.
|
|
@@ -27,12 +26,11 @@ import { LOCALE_LINK_PLAN_SCHEMA, linkRouteAliasesFromUnits, localeHrefTableFrom
|
|
|
27
26
|
import { loadNativeAddon } from './native-addon.js';
|
|
28
27
|
import { createRenderHost } from './render-host.js';
|
|
29
28
|
import { resolveRouteLayoutChain } from './route-layout-chain.js';
|
|
30
|
-
import { handleNodeRequest, setRoutes, setServerModuleResolver } from '
|
|
29
|
+
import { handleNodeRequest, setRoutes, setServerModuleResolver } from '../faces/vmz-runtime.js';
|
|
31
30
|
const ROUTE_CATALOG_SCHEMA = 'vmz.route.catalog.v0';
|
|
32
31
|
const ROUTE_CATALOG_REL = '_vmz/route-catalog.json';
|
|
33
32
|
const LOCALE_LINK_PLAN_REL = '_vmz/locale-link-plan.json';
|
|
34
33
|
const require = createRequire(import.meta.url);
|
|
35
|
-
/** App delivery root. Host companions may live under `_vmz/host/` (0.1.31). */
|
|
36
34
|
function resolveServeDistDir() {
|
|
37
35
|
if (process.env.VMZ_DIST)
|
|
38
36
|
return path.resolve(process.env.VMZ_DIST);
|
|
@@ -45,14 +43,12 @@ function resolveServeDistDir() {
|
|
|
45
43
|
return here;
|
|
46
44
|
}
|
|
47
45
|
const distDir = resolveServeDistDir();
|
|
48
|
-
/** App project root for bare import / JSON resolve (dev SSR ≡ build node_modules). */
|
|
49
46
|
function projectRootForResolve() {
|
|
50
47
|
const fromEnv = typeof process.env.VMZ_PROJECT_ROOT === 'string' ? process.env.VMZ_PROJECT_ROOT.trim() : '';
|
|
51
48
|
if (fromEnv)
|
|
52
49
|
return path.resolve(fromEnv);
|
|
53
50
|
return process.cwd();
|
|
54
51
|
}
|
|
55
|
-
/** @type {ReturnType<typeof createRequire> | null} */
|
|
56
52
|
let appPackageRequire = null;
|
|
57
53
|
function appPackageRequireResolve() {
|
|
58
54
|
if (!appPackageRequire) {
|
|
@@ -112,7 +108,7 @@ const isDev = process.env.VMZ_DEV === '1' || process.env.VMZ_DEV === 'true';
|
|
|
112
108
|
globalThis.__VMZ_RPC_ORIGIN = `http://${host}:${port}`;
|
|
113
109
|
/**
|
|
114
110
|
* Soft reload only busts the top-level `import(page?t=token)`. Nested relative
|
|
115
|
-
* imports (`../../lib/units.js`) keep the first-loaded ESM cache entry
|
|
111
|
+
* imports (`../../lib/units.js`) keep the first-loaded ESM cache entry �?so a
|
|
116
112
|
* page can demand exports that the stale dep never had (or vice versa).
|
|
117
113
|
* Propagate `t` from parentURL onto file: children under this dist.
|
|
118
114
|
*/
|
|
@@ -151,34 +147,20 @@ if (isDev) {
|
|
|
151
147
|
},
|
|
152
148
|
});
|
|
153
149
|
}
|
|
154
|
-
/** @type {number} */
|
|
155
150
|
let reloadToken = Date.now();
|
|
156
|
-
/** @type {Awaited<ReturnType<typeof createRenderHost>> | null} */
|
|
157
151
|
let ssrRenderHost = null;
|
|
158
|
-
/** @type {string | null} Correlatable build id from vmz dev (Living §12.8). */
|
|
159
152
|
let lastDevBuildId = null;
|
|
160
|
-
/** @type {Array<{ chunkId: string, pageRel: string, segs: ReturnType<typeof parsePathPattern> }>} */
|
|
161
153
|
let pageCatalog = [];
|
|
162
|
-
/** @type {Map<string, any>} */
|
|
163
154
|
const pageCtors = new Map();
|
|
164
|
-
/** Stylesheet from deployment `cssEntry` (e.g. vmz.css). */
|
|
165
155
|
let cssEntry = null;
|
|
166
|
-
/** Fingerprint of style inputs — busts `@import` siblings when tokens change (VMZ-8). */
|
|
167
156
|
let styleBundleHash = null;
|
|
168
|
-
/** @type {{ defaultThemeId: string, themeIds: string[], activationAttr: string, contentHash: string|null } | null} */
|
|
169
157
|
let styleTheme = null;
|
|
170
|
-
/** Locale route realization artifact from `_vmz/locale-route-realization.json` (optional). */
|
|
171
158
|
let localeArtifact = null;
|
|
172
|
-
/** Frozen locale link plan from `_vmz/locale-link-plan.json` (0.1.30 authority). */
|
|
173
159
|
let localeLinkPlan = null;
|
|
174
|
-
/** @type {Set<import('node:http').ServerResponse>} */
|
|
175
160
|
const sseClients = new Set();
|
|
176
|
-
/** In-flight HTTP requests (graceful shutdown drain). */
|
|
177
161
|
let inFlight = 0;
|
|
178
|
-
/** When true, refuse new work except health. */
|
|
179
162
|
let shuttingDown = false;
|
|
180
163
|
let ready = false;
|
|
181
|
-
/** @type {{ message: string, stack?: string, at: number } | null} */
|
|
182
164
|
let lastDevError = null;
|
|
183
165
|
setServerModuleResolver((moduleId) => {
|
|
184
166
|
const rel = moduleId.replace(/^#server\//, '') + '.js';
|
|
@@ -190,13 +172,9 @@ try {
|
|
|
190
172
|
}
|
|
191
173
|
catch (err) {
|
|
192
174
|
lastDevError = normalizeDevError(err);
|
|
193
|
-
ready = true; // still accept HTTP
|
|
175
|
+
ready = true; // still accept HTTP �?serve error page / recover on next reload
|
|
194
176
|
console.error('vmz serve: initial load failed (dev host stays up)', lastDevError.message);
|
|
195
177
|
}
|
|
196
|
-
/**
|
|
197
|
-
* @param {import('node:http').IncomingMessage} req
|
|
198
|
-
* @returns {Promise<string>}
|
|
199
|
-
*/
|
|
200
178
|
function readRequestBody(req) {
|
|
201
179
|
return new Promise((resolve, reject) => {
|
|
202
180
|
const chunks = [];
|
|
@@ -205,17 +183,16 @@ function readRequestBody(req) {
|
|
|
205
183
|
req.on('error', reject);
|
|
206
184
|
});
|
|
207
185
|
}
|
|
208
|
-
/**
|
|
209
|
-
* @param {string} pathname
|
|
210
|
-
* @returns {Promise<string | null>}
|
|
211
|
-
*/
|
|
212
186
|
async function renderPage(pathname, opts = {}) {
|
|
213
187
|
const rendered = await renderPageStream(pathname, opts);
|
|
214
188
|
if (!rendered)
|
|
215
189
|
return null;
|
|
216
|
-
const
|
|
190
|
+
const raw = rendered.stream ?? rendered;
|
|
191
|
+
if (!raw || typeof raw !== 'object' || typeof raw[Symbol.asyncIterator] !== 'function') {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
217
194
|
let html = '';
|
|
218
|
-
for await (const chunk of
|
|
195
|
+
for await (const chunk of raw) {
|
|
219
196
|
html += chunk;
|
|
220
197
|
}
|
|
221
198
|
return html;
|
|
@@ -224,9 +201,6 @@ async function renderPage(pathname, opts = {}) {
|
|
|
224
201
|
* Stream shell + Direct serialize body for the matched file-route page.
|
|
225
202
|
* Runs Page.access (closed allow/redirect/not-found/deny) before load;
|
|
226
203
|
* POST may run Page.action before re-render.
|
|
227
|
-
* @param {string} pathname
|
|
228
|
-
* @param {{ signal?: AbortSignal, searchParams?: URLSearchParams, cookieHeader?: string, method?: string, body?: unknown }} [opts]
|
|
229
|
-
* @returns {Promise<{ status: number, stream?: AsyncGenerator<string, void, void>, redirect?: string, headers?: Record<string, string> } | null>}
|
|
230
204
|
*/
|
|
231
205
|
async function renderPageStream(pathname, opts = {}) {
|
|
232
206
|
try {
|
|
@@ -239,11 +213,6 @@ async function renderPageStream(pathname, opts = {}) {
|
|
|
239
213
|
return { status: 500, stream: emitDevErrorHtml(normalized) };
|
|
240
214
|
}
|
|
241
215
|
}
|
|
242
|
-
/**
|
|
243
|
-
* @param {string} pathname
|
|
244
|
-
* @param {{ signal?: AbortSignal, searchParams?: URLSearchParams, cookieHeader?: string, method?: string, body?: unknown }} [opts]
|
|
245
|
-
* @returns {Promise<{ status: number, stream?: AsyncGenerator<string, void, void>, redirect?: string, headers?: Record<string, string> } | null>}
|
|
246
|
-
*/
|
|
247
216
|
async function renderPageStreamInner(pathname, opts = {}) {
|
|
248
217
|
if (isDev && lastDevError && pageCtors.size === 0) {
|
|
249
218
|
return { status: 500, stream: emitDevErrorHtml(lastDevError) };
|
|
@@ -376,10 +345,6 @@ async function renderPageStreamInner(pathname, opts = {}) {
|
|
|
376
345
|
stream: emitPageHtml(Page, match.chunkId, eventOnlyShell, props, opts, layoutChain, localeCtx),
|
|
377
346
|
};
|
|
378
347
|
}
|
|
379
|
-
/**
|
|
380
|
-
* @param {unknown} access
|
|
381
|
-
* @returns {{ kind: 'allow' } | { kind: 'redirect', location: string } | { kind: 'deny' } | { kind: 'not-found' }}
|
|
382
|
-
*/
|
|
383
348
|
function normalizeAccessResult(access) {
|
|
384
349
|
if (access == null || access === true)
|
|
385
350
|
return { kind: 'allow' };
|
|
@@ -392,10 +357,11 @@ function normalizeAccessResult(access) {
|
|
|
392
357
|
if (k === 'not-found' || k === 'notfound')
|
|
393
358
|
return { kind: 'not-found' };
|
|
394
359
|
}
|
|
395
|
-
if (typeof access === 'object') {
|
|
396
|
-
const
|
|
360
|
+
if (typeof access === 'object' && access) {
|
|
361
|
+
const row = access;
|
|
362
|
+
const kind = String(row.kind || row.type || 'allow').toLowerCase();
|
|
397
363
|
if (kind === 'redirect') {
|
|
398
|
-
const location = String(
|
|
364
|
+
const location = String(row.location || row.to || row.href || '');
|
|
399
365
|
if (!location)
|
|
400
366
|
return { kind: 'deny' };
|
|
401
367
|
return { kind: 'redirect', location };
|
|
@@ -408,30 +374,28 @@ function normalizeAccessResult(access) {
|
|
|
408
374
|
}
|
|
409
375
|
return { kind: 'allow' };
|
|
410
376
|
}
|
|
411
|
-
/**
|
|
412
|
-
* @param {unknown} acted
|
|
413
|
-
* @returns {{ kind: 'allow', props?: Record<string, unknown> } | { kind: 'redirect', location: string } | { kind: 'deny' } | { kind: 'not-found' }}
|
|
414
|
-
*/
|
|
415
377
|
function normalizeActionResult(acted) {
|
|
416
378
|
const base = normalizeAccessResult(acted);
|
|
417
379
|
if (base.kind !== 'allow')
|
|
418
380
|
return base;
|
|
419
|
-
if (acted && typeof acted === 'object' &&
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
381
|
+
if (acted && typeof acted === 'object' && !Array.isArray(acted)) {
|
|
382
|
+
const row = acted;
|
|
383
|
+
if (row.props && typeof row.props === 'object') {
|
|
384
|
+
return { kind: 'allow', props: row.props };
|
|
385
|
+
}
|
|
386
|
+
if (!('kind' in row) && !('type' in row)) {
|
|
387
|
+
return { kind: 'allow', props: row };
|
|
388
|
+
}
|
|
424
389
|
}
|
|
425
390
|
return { kind: 'allow' };
|
|
426
391
|
}
|
|
427
392
|
/**
|
|
428
393
|
* Minimal HTML for closed access/action results when no NotFound page exists.
|
|
429
|
-
* @param {string} marker
|
|
430
394
|
*/
|
|
431
395
|
async function* emitAccessShell(marker) {
|
|
432
396
|
const native = loadNativeAddon();
|
|
433
397
|
if (typeof native.generateHtmlShell !== 'function') {
|
|
434
|
-
throw new Error('vmz native addon missing generateHtmlShell
|
|
398
|
+
throw new Error('vmz native addon missing generateHtmlShell �?rebuild with `pnpm napi:build`');
|
|
435
399
|
}
|
|
436
400
|
yield native.generateHtmlShell({
|
|
437
401
|
title: 'App',
|
|
@@ -442,8 +406,7 @@ async function* emitAccessShell(marker) {
|
|
|
442
406
|
});
|
|
443
407
|
}
|
|
444
408
|
/**
|
|
445
|
-
* Prefer page `static meta()` for document title/description
|
|
446
|
-
* @param {any} Page
|
|
409
|
+
* Prefer page `static meta()` for document title/description �?never brand the framework in business HTML.
|
|
447
410
|
*/
|
|
448
411
|
function resolvePageDocumentMeta(Page) {
|
|
449
412
|
try {
|
|
@@ -460,14 +423,6 @@ function resolvePageDocumentMeta(Page) {
|
|
|
460
423
|
return { title: 'App', description: '' };
|
|
461
424
|
}
|
|
462
425
|
}
|
|
463
|
-
/**
|
|
464
|
-
* @param {any} Page
|
|
465
|
-
* @param {string} chunkId
|
|
466
|
-
* @param {boolean} eventOnlyShell
|
|
467
|
-
* @param {Record<string, unknown>} props
|
|
468
|
-
* @param {{ signal?: AbortSignal, searchParams?: URLSearchParams, cookieHeader?: string }} [opts]
|
|
469
|
-
* @param {string[]} [layoutChain] layout chunk ids outer→inner
|
|
470
|
-
*/
|
|
471
426
|
async function* emitPageHtml(Page, chunkId, eventOnlyShell, props = {}, opts = {}, layoutChain = [], localeCtx = {}) {
|
|
472
427
|
const signal = opts.signal;
|
|
473
428
|
const live = isDev
|
|
@@ -477,7 +432,7 @@ async function* emitPageHtml(Page, chunkId, eventOnlyShell, props = {}, opts = {
|
|
|
477
432
|
let sawDisconnect = false;
|
|
478
433
|
es.onerror = () => { sawDisconnect = true; };
|
|
479
434
|
es.onopen = () => {
|
|
480
|
-
// Host respawn drops SSE
|
|
435
|
+
// Host respawn drops SSE �?reload once the new process is up (no manual restart).
|
|
481
436
|
if (sawDisconnect) location.reload();
|
|
482
437
|
};
|
|
483
438
|
function showOverlay(err) {
|
|
@@ -501,7 +456,7 @@ async function* emitPageHtml(Page, chunkId, eventOnlyShell, props = {}, opts = {
|
|
|
501
456
|
+ "<p style=\\"margin:0 0 .5rem;color:#f87171;font-weight:700\\">Dev Error</p>"
|
|
502
457
|
+ "<pre style=\\"white-space:pre-wrap;margin:0 0 1rem;font-size:13px;line-height:1.45\\">" + esc(msg) + "</pre>"
|
|
503
458
|
+ (stack ? "<pre style=\\"white-space:pre-wrap;opacity:.7;font-size:12px\\">" + esc(stack) + "</pre>" : "")
|
|
504
|
-
+ "<p style=\\"opacity:.65;font-size:12px\\">Fix the file and save
|
|
459
|
+
+ "<p style=\\"opacity:.65;font-size:12px\\">Fix the file and save �?soft reload will clear this overlay.</p>"
|
|
505
460
|
+ "</div>";
|
|
506
461
|
}
|
|
507
462
|
function hideOverlay() {
|
|
@@ -522,26 +477,11 @@ async function* emitPageHtml(Page, chunkId, eventOnlyShell, props = {}, opts = {
|
|
|
522
477
|
hideOverlay();
|
|
523
478
|
if (msg.mode === "island") {
|
|
524
479
|
try {
|
|
525
|
-
const {
|
|
526
|
-
const names = (msg.affectedChunks || [])
|
|
527
|
-
.map((c) => String(c))
|
|
528
|
-
.filter((c) => c.startsWith("components/") || !c.includes("/"))
|
|
529
|
-
.map((c) => c.split("/").pop())
|
|
530
|
-
.filter(Boolean);
|
|
531
|
-
const map = {};
|
|
532
|
-
for (const name of names) {
|
|
533
|
-
let mod;
|
|
534
|
-
try {
|
|
535
|
-
mod = await import("/components/" + name + ".client.js?t=" + msg.token);
|
|
536
|
-
} catch {
|
|
537
|
-
mod = await import("/" + name + ".client.js?t=" + msg.token);
|
|
538
|
-
}
|
|
539
|
-
map[name] = mod.default;
|
|
540
|
-
}
|
|
541
|
-
if (Object.keys(map).length) registerComponents(map);
|
|
480
|
+
const { hydrate } = await import("/dom.browser.js?t=" + msg.token);
|
|
542
481
|
const root = document.getElementById("app");
|
|
543
482
|
const pageChunk = root && root.getAttribute("data-vmz-page");
|
|
544
483
|
if (root && pageChunk) {
|
|
484
|
+
// Child Ctors come from page static imports after reload with cache token.
|
|
545
485
|
const pageMod = await import("/" + pageChunk + ".client.js?t=" + msg.token);
|
|
546
486
|
let hmrProps = {};
|
|
547
487
|
try {
|
|
@@ -583,7 +523,6 @@ async function* emitPageHtml(Page, chunkId, eventOnlyShell, props = {}, opts = {
|
|
|
583
523
|
const propsJson = JSON.stringify(props ?? {});
|
|
584
524
|
const localeId = localeCtx.localeId || localeArtifact?.defaultLocale || 'en';
|
|
585
525
|
const dir = localeCtx.dir || 'ltr';
|
|
586
|
-
/** @type {string[]} */
|
|
587
526
|
const htmlExtraAttrs = [...htmlThemeAttrPair(themeId)];
|
|
588
527
|
if (localeArtifact?.routing) {
|
|
589
528
|
htmlExtraAttrs.push('data-vmz-locale-routing', JSON.stringify({
|
|
@@ -617,7 +556,7 @@ async function* emitPageHtml(Page, chunkId, eventOnlyShell, props = {}, opts = {
|
|
|
617
556
|
}
|
|
618
557
|
if (signal?.aborted)
|
|
619
558
|
return;
|
|
620
|
-
// Wrap page HTML in layout chain (outer
|
|
559
|
+
// Wrap page HTML in layout chain (outer �?inner) via default slot injection.
|
|
621
560
|
for (let i = layoutChain.length - 1; i >= 0; i--) {
|
|
622
561
|
const Layout = await loadPageCtor(layoutChain[i]);
|
|
623
562
|
if (!Layout)
|
|
@@ -626,7 +565,7 @@ async function* emitPageHtml(Page, chunkId, eventOnlyShell, props = {}, opts = {
|
|
|
626
565
|
if (signal?.aborted)
|
|
627
566
|
return;
|
|
628
567
|
}
|
|
629
|
-
// Locale discipline: apply frozen link plan rows (0.1.30)
|
|
568
|
+
// Locale discipline: apply frozen link plan rows (0.1.30) �?no path algebra.
|
|
630
569
|
if (localeArtifact && localeId) {
|
|
631
570
|
bodyHtml = localizeBodyLinks(bodyHtml, localeId, localeArtifact, undefined, localeLinkPlan);
|
|
632
571
|
}
|
|
@@ -641,7 +580,7 @@ async function* emitPageHtml(Page, chunkId, eventOnlyShell, props = {}, opts = {
|
|
|
641
580
|
return;
|
|
642
581
|
const native = loadNativeAddon();
|
|
643
582
|
if (typeof native.generatePageShell !== 'function') {
|
|
644
|
-
throw new Error('vmz native addon missing generatePageShell
|
|
583
|
+
throw new Error('vmz native addon missing generatePageShell �?rebuild with `pnpm napi:build`');
|
|
645
584
|
}
|
|
646
585
|
const entrySrc = `/${eventOnlyShell ? 'entry-event.js' : 'entry-client.js'}?t=${reloadToken}`;
|
|
647
586
|
const cssHref = cssEntryWithBust(cssEntry);
|
|
@@ -705,7 +644,7 @@ const server = http.createServer((req, res) => {
|
|
|
705
644
|
.then((raw) => {
|
|
706
645
|
let payload = {};
|
|
707
646
|
try {
|
|
708
|
-
payload = raw ? JSON.parse(raw) : {};
|
|
647
|
+
payload = raw ? JSON.parse(String(raw)) : {};
|
|
709
648
|
}
|
|
710
649
|
catch {
|
|
711
650
|
payload = {};
|
|
@@ -754,7 +693,7 @@ async function gracefulShutdown(signal) {
|
|
|
754
693
|
return;
|
|
755
694
|
shuttingDown = true;
|
|
756
695
|
ready = false;
|
|
757
|
-
console.log(`vmz serve: ${signal}
|
|
696
|
+
console.log(`vmz serve: ${signal} �?draining in-flight=${inFlight} timeout=${SHUTDOWN_TIMEOUT_MS}ms`);
|
|
758
697
|
server.close();
|
|
759
698
|
const start = Date.now();
|
|
760
699
|
while (inFlight > 0 && Date.now() - start < SHUTDOWN_TIMEOUT_MS) {
|
|
@@ -780,8 +719,6 @@ process.on('SIGINT', () => {
|
|
|
780
719
|
/**
|
|
781
720
|
* Attach author Link RouteId aliases so localizeBodyLinks can match `data-vmz-route="IndexPage"`
|
|
782
721
|
* against realization rows keyed by chunk path (`pages/index`).
|
|
783
|
-
* @param {any} artifact
|
|
784
|
-
* @param {string} dir
|
|
785
722
|
*/
|
|
786
723
|
function attachLinkRouteAliases(artifact, dir) {
|
|
787
724
|
if (!artifact || typeof artifact !== 'object')
|
|
@@ -801,7 +738,6 @@ function attachLinkRouteAliases(artifact, dir) {
|
|
|
801
738
|
* Re-import routes / pages / components with a new cache-bust token.
|
|
802
739
|
* Keeps the HTTP server process alive (no Node restart).
|
|
803
740
|
* Failed reloads keep the previous in-memory modules (Vite-like resilience).
|
|
804
|
-
* @param {{ quiet?: boolean, payload?: { affectedChunks?: string[], seedChunks?: string[], emitted?: string[], full?: boolean, islandHmr?: boolean, buildId?: string, sourceRevision?: string, bundleRevision?: string, changed?: string[] } }} [opts]
|
|
805
741
|
*/
|
|
806
742
|
async function softReload(opts = {}) {
|
|
807
743
|
const prevToken = reloadToken;
|
|
@@ -820,7 +756,7 @@ async function softReload(opts = {}) {
|
|
|
820
756
|
const bundleRevision = opts.payload?.bundleRevision != null ? String(opts.payload.bundleRevision) : null;
|
|
821
757
|
if (buildId)
|
|
822
758
|
lastDevBuildId = buildId;
|
|
823
|
-
// 0.1.31: payload-only scope. `reloadToken` is opaque cache-bust
|
|
759
|
+
// 0.1.31: payload-only scope. `reloadToken` is opaque cache-bust �?never invent full/affected here.
|
|
824
760
|
const reloadAllPages = Boolean(full) || (!islandHmr && rerunLoaders.length === 0 && affected.length === 0);
|
|
825
761
|
try {
|
|
826
762
|
try {
|
|
@@ -852,7 +788,6 @@ async function softReload(opts = {}) {
|
|
|
852
788
|
});
|
|
853
789
|
const nextCatalog = await listPageClientFiles(distDir);
|
|
854
790
|
// empty catalog throws inside listPageClientFiles
|
|
855
|
-
/** @type {Map<string, any>} */
|
|
856
791
|
const nextCtors = new Map();
|
|
857
792
|
if (!islandHmr) {
|
|
858
793
|
const reloadSet = rerunLoaders.length > 0
|
|
@@ -952,7 +887,6 @@ async function softReload(opts = {}) {
|
|
|
952
887
|
throw err;
|
|
953
888
|
}
|
|
954
889
|
}
|
|
955
|
-
/** @param {string} chunkId @param {string[]} affected */
|
|
956
890
|
function pageNeedsReload(chunkId, affected) {
|
|
957
891
|
if (chunkId === 'pages/Layout' || chunkId.endsWith('/Layout'))
|
|
958
892
|
return true;
|
|
@@ -961,7 +895,6 @@ function pageNeedsReload(chunkId, affected) {
|
|
|
961
895
|
return id === chunkId || chunkId.startsWith(`${id}/`) || id.startsWith(`${chunkId}/`);
|
|
962
896
|
});
|
|
963
897
|
}
|
|
964
|
-
/** @param {string} event */
|
|
965
898
|
function notifySse(event) {
|
|
966
899
|
for (const client of [...sseClients]) {
|
|
967
900
|
try {
|
|
@@ -972,10 +905,9 @@ function notifySse(event) {
|
|
|
972
905
|
}
|
|
973
906
|
}
|
|
974
907
|
}
|
|
975
|
-
/** @param {unknown} err */
|
|
976
908
|
function normalizeDevError(err) {
|
|
977
909
|
if (err && typeof err === 'object') {
|
|
978
|
-
const e =
|
|
910
|
+
const e = err;
|
|
979
911
|
return {
|
|
980
912
|
message: e.message ? String(e.message) : String(err),
|
|
981
913
|
stack: e.stack ? String(e.stack) : undefined,
|
|
@@ -984,7 +916,6 @@ function normalizeDevError(err) {
|
|
|
984
916
|
}
|
|
985
917
|
return { message: String(err), at: Date.now() };
|
|
986
918
|
}
|
|
987
|
-
/** @param {{ message: string, stack?: string }} err */
|
|
988
919
|
async function* emitDevErrorHtml(err) {
|
|
989
920
|
const msg = escapeHtml(err.message || 'Unknown error');
|
|
990
921
|
const stack = err.stack ? escapeHtml(err.stack) : '';
|
|
@@ -1000,7 +931,7 @@ async function* emitDevErrorHtml(err) {
|
|
|
1000
931
|
<h1>Dev Error</h1>
|
|
1001
932
|
<pre>${msg}</pre>
|
|
1002
933
|
${stack ? `<pre style="opacity:.7;font-size:12px">${stack}</pre>` : ''}
|
|
1003
|
-
<p class="hint">Dev host stayed up. Fix the source and save
|
|
934
|
+
<p class="hint">Dev host stayed up. Fix the source and save �?soft reload will recover.</p>
|
|
1004
935
|
</main>
|
|
1005
936
|
<script>
|
|
1006
937
|
(() => {
|
|
@@ -1026,11 +957,10 @@ async function* emitDevErrorHtml(err) {
|
|
|
1026
957
|
}
|
|
1027
958
|
}
|
|
1028
959
|
catch {
|
|
1029
|
-
/* fall through to plain HTML
|
|
960
|
+
/* fall through to plain HTML �?never throw from the error page itself */
|
|
1030
961
|
}
|
|
1031
962
|
yield `<!DOCTYPE html><html lang="en"><head><meta charset="utf-8" /><title>Dev Error</title></head><body>${body}</body></html>`;
|
|
1032
963
|
}
|
|
1033
|
-
/** @param {string} s */
|
|
1034
964
|
function escapeHtml(s) {
|
|
1035
965
|
return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
1036
966
|
}
|
|
@@ -1039,8 +969,6 @@ function escapeHtml(s) {
|
|
|
1039
969
|
* - `prefix`: LocaleId from URL path (existing).
|
|
1040
970
|
* - `none`: Host preference from cookie `vmz.locale` (validated), else defaultLocale.
|
|
1041
971
|
* URL never carries LocaleId.
|
|
1042
|
-
* @param {string} pathname
|
|
1043
|
-
* @param {string | undefined} cookieHeader
|
|
1044
972
|
*/
|
|
1045
973
|
function resolveLocalePath(pathname, cookieHeader) {
|
|
1046
974
|
const raw = String(pathname || '/');
|
|
@@ -1088,10 +1016,6 @@ function resolveLocalePath(pathname, cookieHeader) {
|
|
|
1088
1016
|
redirectTo: null,
|
|
1089
1017
|
};
|
|
1090
1018
|
}
|
|
1091
|
-
/**
|
|
1092
|
-
* @param {string} chunkId
|
|
1093
|
-
* @param {string} localeId
|
|
1094
|
-
*/
|
|
1095
1019
|
function pageMetaAlternates(chunkId, localeId) {
|
|
1096
1020
|
if (!localeArtifact?.pageMetas)
|
|
1097
1021
|
return [];
|
|
@@ -1099,13 +1023,11 @@ function pageMetaAlternates(chunkId, localeId) {
|
|
|
1099
1023
|
localeArtifact.pageMetas.find((m) => m.routeId === chunkId && m.locale === localeArtifact.defaultLocale);
|
|
1100
1024
|
return Array.isArray(meta?.alternates) ? meta.alternates : [];
|
|
1101
1025
|
}
|
|
1102
|
-
/** @param {string} href */
|
|
1103
1026
|
function bustUrl(href) {
|
|
1104
1027
|
const u = new URL(href);
|
|
1105
1028
|
u.searchParams.set('t', String(reloadToken));
|
|
1106
1029
|
return u.href;
|
|
1107
1030
|
}
|
|
1108
|
-
/** @param {string} chunkId */
|
|
1109
1031
|
async function loadPageCtor(chunkId) {
|
|
1110
1032
|
const pageRel = `${chunkId}.client.js`;
|
|
1111
1033
|
const href = bustUrl(pathToFileURL(path.join(distDir, pageRel)).href);
|
|
@@ -1116,7 +1038,6 @@ async function loadPageCtor(chunkId) {
|
|
|
1116
1038
|
/**
|
|
1117
1039
|
* Load compiled page catalog from `_vmz/route-catalog.json` (0.1.30 authority).
|
|
1118
1040
|
* Hosts must not re-parse deployment pathPattern into a live catalog.
|
|
1119
|
-
* @param {string} dir
|
|
1120
1041
|
*/
|
|
1121
1042
|
async function listPageClientFiles(dir) {
|
|
1122
1043
|
const fromCatalog = await listPagesFromRouteCatalog(dir);
|
|
@@ -1129,11 +1050,7 @@ async function listPageClientFiles(dir) {
|
|
|
1129
1050
|
}
|
|
1130
1051
|
return fromCatalog;
|
|
1131
1052
|
}
|
|
1132
|
-
/**
|
|
1133
|
-
* @param {string} dir
|
|
1134
|
-
*/
|
|
1135
1053
|
async function listPagesFromRouteCatalog(dir) {
|
|
1136
|
-
/** @type {Array<{ chunkId: string, pageRel: string, routeId?: string, pathPattern?: string, segs: ReturnType<typeof parsePathPattern> }>} */
|
|
1137
1054
|
const out = [];
|
|
1138
1055
|
try {
|
|
1139
1056
|
const raw = await readFile(path.join(dir, ...ROUTE_CATALOG_REL.split('/')), 'utf8');
|
|
@@ -1165,14 +1082,12 @@ async function listPagesFromRouteCatalog(dir) {
|
|
|
1165
1082
|
}
|
|
1166
1083
|
/**
|
|
1167
1084
|
* Browser HTTP pattern (`/` / `/home` / `/users/:id` / `/blog/[...slug]`).
|
|
1168
|
-
* @param {string} pattern
|
|
1169
1085
|
*/
|
|
1170
1086
|
function parsePathPattern(pattern) {
|
|
1171
1087
|
const raw = String(pattern || '').trim();
|
|
1172
1088
|
if (!raw || raw === '/')
|
|
1173
1089
|
return [];
|
|
1174
1090
|
const parts = raw.replace(/^\/+/, '').split('/').filter(Boolean);
|
|
1175
|
-
/** @type {Array<{ kind: 'static' | 'param' | 'catch', value?: string, name?: string }>} */
|
|
1176
1091
|
const segs = [];
|
|
1177
1092
|
for (const p of parts) {
|
|
1178
1093
|
if (isRouteGroupDir(p))
|
|
@@ -1181,9 +1096,6 @@ function parsePathPattern(pattern) {
|
|
|
1181
1096
|
}
|
|
1182
1097
|
return segs;
|
|
1183
1098
|
}
|
|
1184
|
-
/**
|
|
1185
|
-
* @param {string} p
|
|
1186
|
-
*/
|
|
1187
1099
|
function parsePathSegment(p) {
|
|
1188
1100
|
const catchAll = /^\[\.\.\.([^\]]+)\]$/.exec(p);
|
|
1189
1101
|
const param = /^\[([^\]]+)\]$/.exec(p);
|
|
@@ -1202,10 +1114,6 @@ function isRouteGroupDir(seg) {
|
|
|
1202
1114
|
function isRouteBoundaryStem(stem) {
|
|
1203
1115
|
return stem === 'Layout' || stem === 'Loading' || stem === 'Error' || stem === 'NotFound';
|
|
1204
1116
|
}
|
|
1205
|
-
/**
|
|
1206
|
-
* @param {string} pathname
|
|
1207
|
-
* @param {typeof pageCatalog} catalog
|
|
1208
|
-
*/
|
|
1209
1117
|
function matchFileRoute(pathname, catalog) {
|
|
1210
1118
|
const pathParts = decodeURIComponent(pathname.split('?')[0] || '/')
|
|
1211
1119
|
.replace(/\/+$/, '')
|
|
@@ -1225,17 +1133,11 @@ function matchFileRoute(pathname, catalog) {
|
|
|
1225
1133
|
}
|
|
1226
1134
|
return best;
|
|
1227
1135
|
}
|
|
1228
|
-
/**
|
|
1229
|
-
* @param {ReturnType<typeof parsePathPattern>} segs
|
|
1230
|
-
* @param {string} pathname
|
|
1231
|
-
* @returns {Record<string, string>}
|
|
1232
|
-
*/
|
|
1233
1136
|
function extractRouteParams(segs, pathname) {
|
|
1234
1137
|
const pathParts = decodeURIComponent(pathname.split('?')[0] || '/')
|
|
1235
1138
|
.replace(/\/+$/, '')
|
|
1236
1139
|
.split('/')
|
|
1237
1140
|
.filter(Boolean);
|
|
1238
|
-
/** @type {Record<string, string>} */
|
|
1239
1141
|
const params = {};
|
|
1240
1142
|
let j = 0;
|
|
1241
1143
|
for (let i = 0; i < segs.length; i++) {
|
|
@@ -1254,11 +1156,6 @@ function extractRouteParams(segs, pathname) {
|
|
|
1254
1156
|
}
|
|
1255
1157
|
return params;
|
|
1256
1158
|
}
|
|
1257
|
-
/**
|
|
1258
|
-
* @param {ReturnType<typeof parsePathPattern>} segs
|
|
1259
|
-
* @param {string[]} pathParts
|
|
1260
|
-
* @returns {number | null}
|
|
1261
|
-
*/
|
|
1262
1159
|
function scoreRoute(segs, pathParts) {
|
|
1263
1160
|
let i = 0;
|
|
1264
1161
|
let j = 0;
|
|
@@ -1266,7 +1163,7 @@ function scoreRoute(segs, pathParts) {
|
|
|
1266
1163
|
while (i < segs.length) {
|
|
1267
1164
|
const s = segs[i];
|
|
1268
1165
|
if (s.kind === 'catch') {
|
|
1269
|
-
// Required catch-all `[...slug]` needs
|
|
1166
|
+
// Required catch-all `[...slug]` needs �? remaining segment (not `/`).
|
|
1270
1167
|
if (j >= pathParts.length)
|
|
1271
1168
|
return null;
|
|
1272
1169
|
score += 1;
|
|
@@ -1296,9 +1193,7 @@ function findRootCatchAll(catalog) {
|
|
|
1296
1193
|
return catalog.find((p) => isRootCatchAll(p)) || null;
|
|
1297
1194
|
}
|
|
1298
1195
|
/**
|
|
1299
|
-
* Optional app gate: `dist/vmz-route-gate.mjs`
|
|
1300
|
-
* @param {string} pathname
|
|
1301
|
-
* @param {string | undefined} chunkId
|
|
1196
|
+
* Optional app gate: `dist/vmz-route-gate.mjs` �?`{ check(pathname, chunkId) => 'not_found' | null }`.
|
|
1302
1197
|
*/
|
|
1303
1198
|
async function runRouteGate(pathname, chunkId) {
|
|
1304
1199
|
try {
|
|
@@ -1312,29 +1207,23 @@ async function runRouteGate(pathname, chunkId) {
|
|
|
1312
1207
|
return null;
|
|
1313
1208
|
}
|
|
1314
1209
|
}
|
|
1315
|
-
/**
|
|
1316
|
-
* @param {Array<{ name: string, entry: string }>} eager
|
|
1317
|
-
* @param {Array<{ name: string, entry: string }>} lazy
|
|
1318
|
-
* @param {number} token
|
|
1319
|
-
*/
|
|
1320
1210
|
function emitEntryClient(eager, lazy, token) {
|
|
1321
1211
|
const q = `?t=${token}`;
|
|
1322
1212
|
const native = loadNativeAddon();
|
|
1323
1213
|
if (typeof native.generateServeEntryClient !== 'function') {
|
|
1324
|
-
throw new Error('vmz native addon missing generateServeEntryClient
|
|
1214
|
+
throw new Error('vmz native addon missing generateServeEntryClient �?rebuild with `pnpm napi:build`');
|
|
1325
1215
|
}
|
|
1326
1216
|
return native.generateServeEntryClient(eager, lazy, q);
|
|
1327
1217
|
}
|
|
1328
1218
|
/**
|
|
1329
1219
|
* EventEntry zero-framework bootstrap: no static import of vmz-dom / page / islands.
|
|
1330
1220
|
* Framework bytes load only inside the first matching DOM event handler.
|
|
1331
|
-
* @param {number} token
|
|
1332
1221
|
*/
|
|
1333
1222
|
function emitEntryEvent(token) {
|
|
1334
1223
|
const q = `?t=${token}`;
|
|
1335
1224
|
const native = loadNativeAddon();
|
|
1336
1225
|
if (typeof native.generateServeEntryEvent !== 'function') {
|
|
1337
|
-
throw new Error('vmz native addon missing generateServeEntryEvent
|
|
1226
|
+
throw new Error('vmz native addon missing generateServeEntryEvent �?rebuild with `pnpm napi:build`');
|
|
1338
1227
|
}
|
|
1339
1228
|
return native.generateServeEntryEvent(q);
|
|
1340
1229
|
}
|
|
@@ -1342,11 +1231,9 @@ function emitEntryEvent(token) {
|
|
|
1342
1231
|
* Style Theme cookie / localStorage key (host contract, not a second theme API).
|
|
1343
1232
|
*/
|
|
1344
1233
|
const THEME_STORE_KEY = 'vmz-theme';
|
|
1345
|
-
/** Host preference key for `routing.strategy: 'none'` (cookie + localStorage). */
|
|
1346
1234
|
const LOCALE_STORE_KEY = 'vmz.locale';
|
|
1347
1235
|
/**
|
|
1348
1236
|
* Cache-bust stylesheet entry for dev reload (token + serve revision).
|
|
1349
|
-
* @param {string | null | undefined} entry
|
|
1350
1237
|
*/
|
|
1351
1238
|
function cssEntryWithBust(entry) {
|
|
1352
1239
|
if (!entry)
|
|
@@ -1358,10 +1245,6 @@ function cssEntryWithBust(entry) {
|
|
|
1358
1245
|
params.set('h', styleBundleHash);
|
|
1359
1246
|
return `${base}?${params.toString()}`;
|
|
1360
1247
|
}
|
|
1361
|
-
/**
|
|
1362
|
-
* @param {string} dir
|
|
1363
|
-
* @returns {Promise<{ cssEntry: string|null, styleTheme: typeof styleTheme, styleBundleHash: string|null }>}
|
|
1364
|
-
*/
|
|
1365
1248
|
async function loadDeploymentStyle(dir) {
|
|
1366
1249
|
try {
|
|
1367
1250
|
const raw = await readFile(path.join(dir, 'vmz-deployment.json'), 'utf8');
|
|
@@ -1389,11 +1272,8 @@ async function loadDeploymentStyle(dir) {
|
|
|
1389
1272
|
}
|
|
1390
1273
|
}
|
|
1391
1274
|
/**
|
|
1392
|
-
* Priority: `?theme=`
|
|
1275
|
+
* Priority: `?theme=` �?cookie �?none (CSS `:root` + prefers-color-scheme media).
|
|
1393
1276
|
* Explicit ids (including default) always win over OS preference via activation attr.
|
|
1394
|
-
* @param {URLSearchParams | undefined} searchParams
|
|
1395
|
-
* @param {string | undefined} cookieHeader
|
|
1396
|
-
* @returns {string|null}
|
|
1397
1277
|
*/
|
|
1398
1278
|
function resolveThemeId(searchParams, cookieHeader) {
|
|
1399
1279
|
if (!styleTheme)
|
|
@@ -1409,8 +1289,6 @@ function resolveThemeId(searchParams, cookieHeader) {
|
|
|
1409
1289
|
}
|
|
1410
1290
|
/**
|
|
1411
1291
|
* Always emit activation attr for an explicit theme id (incl. default) so it overrides OS media.
|
|
1412
|
-
* @param {string|null} themeId
|
|
1413
|
-
* @returns {[string, string] | []} flattened attr pair for generatePageShell
|
|
1414
1292
|
*/
|
|
1415
1293
|
function htmlThemeAttrPair(themeId) {
|
|
1416
1294
|
if (!styleTheme || !themeId)
|
|
@@ -1422,7 +1300,7 @@ function htmlThemeAttrPair(themeId) {
|
|
|
1422
1300
|
}
|
|
1423
1301
|
/**
|
|
1424
1302
|
* Inline boot when SSR had no query/cookie: apply explicit `localStorage` only.
|
|
1425
|
-
* No stored choice
|
|
1303
|
+
* No stored choice �?leave bare `<html>` so CSS `@media (prefers-color-scheme)` follows OS live.
|
|
1426
1304
|
* Explicit ids (incl. default) always set the activation attr so they override OS media.
|
|
1427
1305
|
*/
|
|
1428
1306
|
function themeBootstrapScript() {
|
|
@@ -1436,7 +1314,7 @@ function themeBootstrapScript() {
|
|
|
1436
1314
|
/**
|
|
1437
1315
|
* LocaleId as client state (routing.strategy = none): apply localStorage before any
|
|
1438
1316
|
* page/client module runs so `#locales/*` pick the right variant. Prefix strategy
|
|
1439
|
-
* keeps LocaleId in the URL
|
|
1317
|
+
* keeps LocaleId in the URL �?no boot rewrite.
|
|
1440
1318
|
* Also mirrors into cookie so the next SSR negotiate sees Host preference.
|
|
1441
1319
|
*/
|
|
1442
1320
|
function localeBootstrapScript() {
|
|
@@ -1453,8 +1331,8 @@ function localeBootstrapScript() {
|
|
|
1453
1331
|
return ` <script>(function(){try{var k=${key},ids=${idList};var id=localStorage.getItem(k);if(!id||ids.indexOf(id)<0)return;document.documentElement.setAttribute("data-locale",id);document.documentElement.setAttribute("lang",id);window.__vmzLocaleIdHint=id;document.cookie=k+"="+encodeURIComponent(id)+"; path=/; max-age=31536000; SameSite=Lax";}catch(e){}})();</script>\n`;
|
|
1454
1332
|
}
|
|
1455
1333
|
/**
|
|
1456
|
-
* Site favicon links from build artifact `_vmz/site-favicon.json` (author SVG
|
|
1457
|
-
* Empty when skipped / missing
|
|
1334
|
+
* Site favicon links from build artifact `_vmz/site-favicon.json` (author SVG �?PNG/ICO).
|
|
1335
|
+
* Empty when skipped / missing �?do not invent broken <link>s.
|
|
1458
1336
|
*/
|
|
1459
1337
|
function siteFaviconHeadHtml() {
|
|
1460
1338
|
try {
|
|
@@ -1472,10 +1350,6 @@ function siteFaviconHeadHtml() {
|
|
|
1472
1350
|
return '';
|
|
1473
1351
|
}
|
|
1474
1352
|
}
|
|
1475
|
-
/**
|
|
1476
|
-
* @param {string|undefined} header
|
|
1477
|
-
* @param {string} name
|
|
1478
|
-
*/
|
|
1479
1353
|
function readCookie(header, name) {
|
|
1480
1354
|
if (!header)
|
|
1481
1355
|
return null;
|
|
@@ -1496,19 +1370,10 @@ function readCookie(header, name) {
|
|
|
1496
1370
|
}
|
|
1497
1371
|
return null;
|
|
1498
1372
|
}
|
|
1499
|
-
/**
|
|
1500
|
-
* @param {string} dir
|
|
1501
|
-
* @returns {Promise<string|null>}
|
|
1502
|
-
*/
|
|
1503
1373
|
async function loadCssEntry(dir) {
|
|
1504
1374
|
const meta = await loadDeploymentStyle(dir);
|
|
1505
1375
|
return meta.cssEntry;
|
|
1506
1376
|
}
|
|
1507
|
-
/**
|
|
1508
|
-
* @param {string} dir
|
|
1509
|
-
* @param {string} chunkId
|
|
1510
|
-
* @returns {Promise<Array<{ component: string, strategy: string }>>}
|
|
1511
|
-
*/
|
|
1512
1377
|
async function loadPageResumeEntries(dir, chunkId) {
|
|
1513
1378
|
try {
|
|
1514
1379
|
const raw = await readFile(path.join(dir, 'vmz-deployment.json'), 'utf8');
|
|
@@ -1525,17 +1390,14 @@ async function loadPageResumeEntries(dir, chunkId) {
|
|
|
1525
1390
|
return [];
|
|
1526
1391
|
}
|
|
1527
1392
|
}
|
|
1528
|
-
/** @param {string} strategy */
|
|
1529
1393
|
function isEventStrategy(strategy) {
|
|
1530
1394
|
return strategy === 'event' || strategy === 'click' || strategy.startsWith('event:');
|
|
1531
1395
|
}
|
|
1532
|
-
/** @param {string[]} strategies */
|
|
1533
1396
|
function isEventOnlyShell(strategies) {
|
|
1534
1397
|
if (!strategies.length)
|
|
1535
1398
|
return false;
|
|
1536
1399
|
return strategies.every((s) => isEventStrategy(s));
|
|
1537
1400
|
}
|
|
1538
|
-
/** @param {string} s */
|
|
1539
1401
|
function escapeAttr(s) {
|
|
1540
1402
|
return String(s).replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<');
|
|
1541
1403
|
}
|