@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.
@@ -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
+ }
@@ -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 {"<":"&lt;",">":"&gt;","&":"&amp;"}[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
- // web-static route HTML (`index.html`, `about/index.html`, …) is a CDN/deploy projection only —
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
- * web-static emits per-route HTML beside client assets. That HTML is for CDN / local-static
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.8",
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": [