@vmz/core 0.1.8 → 0.1.9
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/list-client-components.d.ts +25 -0
- package/dist/list-client-components.js +120 -0
- package/dist/serve-host.mjs +2 -43
- package/dist/server.js +2 -2
- package/package.json +5 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discover compiled client component modules from dist (deployment graph or components/).
|
|
3
|
+
* Shared by serve-host SSR and static emit assemble.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* @param {string} dir
|
|
7
|
+
* @returns {Promise<Array<{ name: string, entry: string }>>}
|
|
8
|
+
*/
|
|
9
|
+
export declare function listClientComponents(dir: any): Promise<any[]>;
|
|
10
|
+
/**
|
|
11
|
+
* Sync variant for callers that already use fs sync (legacy static-emit helpers).
|
|
12
|
+
* @param {string} dir
|
|
13
|
+
* @returns {Array<{ name: string, entry: string }>}
|
|
14
|
+
*/
|
|
15
|
+
export declare function listClientComponentsSync(dir: any): any[];
|
|
16
|
+
/**
|
|
17
|
+
* Import all (or filtered) client components and register for SSR / static emit.
|
|
18
|
+
* @param {string} distDir
|
|
19
|
+
* @param {(map: Record<string, unknown>) => void} registerComponents
|
|
20
|
+
* @param {{
|
|
21
|
+
* cacheBust?: string | number,
|
|
22
|
+
* include?: (entry: { name: string, entry: string }) => boolean,
|
|
23
|
+
* }} [opts]
|
|
24
|
+
*/
|
|
25
|
+
export declare function preloadComponentRegistry(distDir: any, registerComponents: any, opts?: {}): Promise<{}>;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Discover compiled client component modules from dist (deployment graph or components/).
|
|
4
|
+
* Shared by serve-host SSR and static emit assemble.
|
|
5
|
+
*/
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { pathToFileURL } from 'node:url';
|
|
10
|
+
/**
|
|
11
|
+
* @param {string} dir
|
|
12
|
+
* @returns {Promise<Array<{ name: string, entry: string }>>}
|
|
13
|
+
*/
|
|
14
|
+
export async function listClientComponents(dir) {
|
|
15
|
+
/** @type {Map<string, { name: string, entry: string }>} */
|
|
16
|
+
const byName = new Map();
|
|
17
|
+
try {
|
|
18
|
+
const raw = await readFile(path.join(dir, 'vmz-deployment.json'), 'utf8');
|
|
19
|
+
const dep = JSON.parse(raw);
|
|
20
|
+
for (const unit of dep.units || []) {
|
|
21
|
+
if (unit?.kind !== 'component')
|
|
22
|
+
continue;
|
|
23
|
+
const chunkId = String(unit.chunkId || '');
|
|
24
|
+
const name = chunkId.split('/').pop();
|
|
25
|
+
if (!name)
|
|
26
|
+
continue;
|
|
27
|
+
const entry = String(unit.clientEntry || `${chunkId}.client.js`).replace(/\\/g, '/');
|
|
28
|
+
byName.set(name, { name, entry });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
/* fall through to directory scan */
|
|
33
|
+
}
|
|
34
|
+
if (byName.size === 0) {
|
|
35
|
+
const folder = path.join(dir, 'components');
|
|
36
|
+
let files = [];
|
|
37
|
+
try {
|
|
38
|
+
files = await readdir(folder);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
for (const f of files.filter((name) => name.endsWith('.client.js'))) {
|
|
44
|
+
const name = f.replace(/\.client\.js$/, '');
|
|
45
|
+
byName.set(name, { name, entry: `components/${name}.client.js` });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Sync variant for callers that already use fs sync (legacy static-emit helpers).
|
|
52
|
+
* @param {string} dir
|
|
53
|
+
* @returns {Array<{ name: string, entry: string }>}
|
|
54
|
+
*/
|
|
55
|
+
export function listClientComponentsSync(dir) {
|
|
56
|
+
/** @type {Map<string, { name: string, entry: string }>} */
|
|
57
|
+
const byName = new Map();
|
|
58
|
+
const deploymentPath = path.join(dir, 'vmz-deployment.json');
|
|
59
|
+
if (fs.existsSync(deploymentPath)) {
|
|
60
|
+
try {
|
|
61
|
+
const dep = JSON.parse(fs.readFileSync(deploymentPath, 'utf8'));
|
|
62
|
+
for (const unit of dep.units || []) {
|
|
63
|
+
if (unit?.kind !== 'component')
|
|
64
|
+
continue;
|
|
65
|
+
const chunkId = String(unit.chunkId || '');
|
|
66
|
+
const name = chunkId.split('/').pop();
|
|
67
|
+
if (!name)
|
|
68
|
+
continue;
|
|
69
|
+
const entry = String(unit.clientEntry || `${chunkId}.client.js`).replace(/\\/g, '/');
|
|
70
|
+
byName.set(name, { name, entry });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
/* fall through */
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (byName.size === 0) {
|
|
78
|
+
const folder = path.join(dir, 'components');
|
|
79
|
+
let files = [];
|
|
80
|
+
try {
|
|
81
|
+
files = fs.readdirSync(folder);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
for (const f of files.filter((name) => name.endsWith('.client.js'))) {
|
|
87
|
+
const name = f.replace(/\.client\.js$/, '');
|
|
88
|
+
byName.set(name, { name, entry: `components/${name}.client.js` });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Import all (or filtered) client components and register for SSR / static emit.
|
|
95
|
+
* @param {string} distDir
|
|
96
|
+
* @param {(map: Record<string, unknown>) => void} registerComponents
|
|
97
|
+
* @param {{
|
|
98
|
+
* cacheBust?: string | number,
|
|
99
|
+
* include?: (entry: { name: string, entry: string }) => boolean,
|
|
100
|
+
* }} [opts]
|
|
101
|
+
*/
|
|
102
|
+
export async function preloadComponentRegistry(distDir, registerComponents, opts = {}) {
|
|
103
|
+
const entries = await listClientComponents(distDir);
|
|
104
|
+
/** @type {Record<string, unknown>} */
|
|
105
|
+
const map = {};
|
|
106
|
+
for (const entry of entries) {
|
|
107
|
+
if (opts.include && !opts.include(entry))
|
|
108
|
+
continue;
|
|
109
|
+
const abs = path.join(distDir, entry.entry);
|
|
110
|
+
let href = pathToFileURL(abs).href;
|
|
111
|
+
if (opts.cacheBust != null && opts.cacheBust !== '') {
|
|
112
|
+
href = `${href}?t=${encodeURIComponent(String(opts.cacheBust))}`;
|
|
113
|
+
}
|
|
114
|
+
const mod = await import(href);
|
|
115
|
+
map[entry.name] = mod.default;
|
|
116
|
+
}
|
|
117
|
+
if (Object.keys(map).length)
|
|
118
|
+
registerComponents(map);
|
|
119
|
+
return map;
|
|
120
|
+
}
|
package/dist/serve-host.mjs
CHANGED
|
@@ -23,6 +23,7 @@ import { createRequire, registerHooks } from 'node:module';
|
|
|
23
23
|
import path from 'node:path';
|
|
24
24
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
25
25
|
import { registerComponents, renderToStream, renderToString } from './vmz-dom.js';
|
|
26
|
+
import { listClientComponents } from './list-client-components.js';
|
|
26
27
|
import { handleNodeRequest, setRoutes, setServerModuleResolver } from './vmz-runtime.js';
|
|
27
28
|
const require = createRequire(import.meta.url);
|
|
28
29
|
const distDir = process.env.VMZ_DIST ? path.resolve(process.env.VMZ_DIST) : path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -479,9 +480,7 @@ async function* emitPageHtml(Page, chunkId, eventOnlyShell, props = {}, opts = {
|
|
|
479
480
|
`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 {"<":"<",">":">","&":"&"}[c]})+"</pre></div>";` +
|
|
480
481
|
`document.documentElement.appendChild(d);})();</script>`
|
|
481
482
|
: '';
|
|
482
|
-
const buildIdBoot = isDev && lastDevBuildId
|
|
483
|
-
? `\n <script>window.__VMZ_DEV_BUILD_ID__=${JSON.stringify(lastDevBuildId)};</script>`
|
|
484
|
-
: '';
|
|
483
|
+
const buildIdBoot = isDev && lastDevBuildId ? `\n <script>window.__VMZ_DEV_BUILD_ID__=${JSON.stringify(lastDevBuildId)};</script>` : '';
|
|
485
484
|
if (signal?.aborted)
|
|
486
485
|
return;
|
|
487
486
|
const themeId = resolveThemeId(opts.searchParams, opts.cookieHeader);
|
|
@@ -1075,46 +1074,6 @@ async function loadPageCtor(chunkId) {
|
|
|
1075
1074
|
pageCtors.set(chunkId, mod.default);
|
|
1076
1075
|
return mod.default;
|
|
1077
1076
|
}
|
|
1078
|
-
/**
|
|
1079
|
-
* @param {string} dir
|
|
1080
|
-
* @returns {Promise<Array<{ name: string, entry: string }>>}
|
|
1081
|
-
*/
|
|
1082
|
-
async function listClientComponents(dir) {
|
|
1083
|
-
/** @type {Map<string, { name: string, entry: string }>} */
|
|
1084
|
-
const byName = new Map();
|
|
1085
|
-
try {
|
|
1086
|
-
const raw = await readFile(path.join(dir, 'vmz-deployment.json'), 'utf8');
|
|
1087
|
-
const dep = JSON.parse(raw);
|
|
1088
|
-
for (const unit of dep.units || []) {
|
|
1089
|
-
if (unit?.kind !== 'component')
|
|
1090
|
-
continue;
|
|
1091
|
-
const chunkId = String(unit.chunkId || '');
|
|
1092
|
-
const name = chunkId.split('/').pop();
|
|
1093
|
-
if (!name)
|
|
1094
|
-
continue;
|
|
1095
|
-
const entry = String(unit.clientEntry || `${chunkId}.client.js`).replace(/\\/g, '/');
|
|
1096
|
-
byName.set(name, { name, entry });
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
catch {
|
|
1100
|
-
/* fall through to directory scan */
|
|
1101
|
-
}
|
|
1102
|
-
if (byName.size === 0) {
|
|
1103
|
-
const folder = path.join(dir, 'components');
|
|
1104
|
-
let files = [];
|
|
1105
|
-
try {
|
|
1106
|
-
files = await readdir(folder);
|
|
1107
|
-
}
|
|
1108
|
-
catch {
|
|
1109
|
-
return [];
|
|
1110
|
-
}
|
|
1111
|
-
for (const f of files.filter((name) => name.endsWith('.client.js'))) {
|
|
1112
|
-
const name = f.replace(/\.client\.js$/, '');
|
|
1113
|
-
byName.set(name, { name, entry: `components/${name}.client.js` });
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
1117
|
-
}
|
|
1118
1077
|
/**
|
|
1119
1078
|
* Discover compiled page modules. Prefer Route Graph `pathPattern` from
|
|
1120
1079
|
* `vmz-deployment.json`; fall back to walking `pages/**` (file-route only).
|
package/dist/server.js
CHANGED
|
@@ -226,7 +226,7 @@ export async function handleNodeRequest(req, res, opts = {}) {
|
|
|
226
226
|
return await writeFetchResponse(res, response);
|
|
227
227
|
}
|
|
228
228
|
// Static first for assets + DocumentMount (`/d/…`) so docs aren't swallowed by SSR 404 shells.
|
|
229
|
-
//
|
|
229
|
+
// static route HTML (`index.html`, `about/index.html`, …) is a CDN/deploy projection only —
|
|
230
230
|
// when Server Host SSR is active, those files must not shadow live render (local/dev ≡ SSR truth).
|
|
231
231
|
if (verb === 'GET' && opts.distDir) {
|
|
232
232
|
const nodePath = await import('node:path');
|
|
@@ -404,7 +404,7 @@ function safeDistFile(distDir, pathname, nodePath) {
|
|
|
404
404
|
return full;
|
|
405
405
|
}
|
|
406
406
|
/**
|
|
407
|
-
*
|
|
407
|
+
* static profile emits per-route HTML beside client assets. That HTML is for CDN / local-static
|
|
408
408
|
* delivery hosts — not for Server Host when SSR is available. DocumentMount stays static.
|
|
409
409
|
* @param {string} file
|
|
410
410
|
* @param {string} pathname
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vmz/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "VMZ production runtime core — DOM / SSR / HTTP / WriteBarrier (no compiler)",
|
|
6
6
|
"exports": {
|
|
@@ -27,6 +27,10 @@
|
|
|
27
27
|
"./client-nav": {
|
|
28
28
|
"types": "./dist/client-nav.d.ts",
|
|
29
29
|
"default": "./dist/client-nav.js"
|
|
30
|
+
},
|
|
31
|
+
"./component-registry": {
|
|
32
|
+
"types": "./dist/list-client-components.d.ts",
|
|
33
|
+
"default": "./dist/list-client-components.js"
|
|
30
34
|
}
|
|
31
35
|
},
|
|
32
36
|
"files": [
|