@vmz/vmz 0.3.12 → 0.3.13
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/delivery-runtime-files.json +1 -0
- package/dist/cli/cli.js +46 -23
- package/dist/cli/format-cmd.d.ts +9 -17
- package/dist/cli/format-cmd.js +37 -49
- package/dist/cli/format-config.d.ts +6 -9
- package/dist/cli/format-config.js +5 -29
- package/dist/delivery/delivery-profile.js +1 -1
- package/dist/dev/dev-session.js +16 -5
- package/dist/document/document-base-css.d.ts +15 -0
- package/dist/document/document-base-css.js +578 -0
- package/dist/document/document-build.d.ts +1 -0
- package/dist/document/document-build.js +94 -78
- package/dist/document/document-client.d.ts +13 -0
- package/dist/document/document-client.js +217 -0
- package/dist/document/document-cmd.js +3 -1
- package/dist/document/document-designs.d.ts +9 -3
- package/dist/document/document-designs.js +20 -34
- package/dist/document/document-enrich.d.ts +9 -0
- package/dist/document/document-enrich.js +55 -0
- package/dist/document/document-highlight.d.ts +26 -0
- package/dist/document/document-highlight.js +54 -0
- package/dist/document/document-integrate.d.ts +5 -0
- package/dist/document/document-integrate.js +1 -0
- package/dist/document/document-interactive.js +10 -2
- package/dist/document/document-layout-render.js +6 -2
- package/dist/document/document-schema.d.ts +5 -0
- package/dist/workspace/build-assemble.js +18 -6
- package/dist/workspace/content-addressed-assets.d.ts +6 -17
- package/dist/workspace/content-addressed-assets.js +10 -95
- package/dist/workspace/delivery-runtime-imports.js +3 -0
- package/dist/workspace/native-addon.d.ts +4 -0
- package/dist/workspace/pack-native.d.ts +139 -0
- package/dist/workspace/pack-native.js +273 -0
- package/dist/workspace/pack.d.ts +112 -32
- package/dist/workspace/pack.js +270 -65
- package/dist/workspace/plugin-host.d.ts +4 -4
- package/dist/workspace/plugin-host.js +6 -6
- package/dist/workspace/production-observability.d.ts +2 -0
- package/dist/workspace/production-observability.js +6 -2
- package/dist/workspace/project-build.js +26 -4
- package/dist/workspace/public-api.d.ts +7 -3
- package/dist/workspace/public-api.js +5 -3
- package/dist/workspace/static-emit.d.ts +30 -0
- package/dist/workspace/static-emit.js +211 -18
- package/dist/workspace/user-config.d.ts +112 -0
- package/dist/workspace/user-config.js +12 -0
- package/host-runtime-files.json +1 -0
- package/locales/en-US/cli.json +5 -4
- package/package.json +20 -20
- package/dist/workspace/pack-client-packages.d.ts +0 -34
- package/dist/workspace/pack-client-packages.js +0 -550
|
@@ -9,6 +9,12 @@ export type EmitWebStaticOpts = {
|
|
|
9
9
|
applicationId?: string;
|
|
10
10
|
staticParams?: Record<string, Array<Record<string, string>>>;
|
|
11
11
|
projectRoot?: string;
|
|
12
|
+
/** Assembly recorded in the manifest (`local-static` overrides the static default). */
|
|
13
|
+
deliveryProfile?: string;
|
|
14
|
+
/** Build cancellation signal, forwarded into the SSR `onMount` budget race. */
|
|
15
|
+
signal?: AbortSignal;
|
|
16
|
+
/** Per-instance SSR `onMount` budget in ms (runtime default applies when unset). */
|
|
17
|
+
onMountBudgetMs?: number;
|
|
12
18
|
};
|
|
13
19
|
type StaticDeliveryManifest = {
|
|
14
20
|
schema: string;
|
|
@@ -134,4 +140,28 @@ export declare function emitWebStatic(distDir: string, opts?: EmitWebStaticOpts)
|
|
|
134
140
|
};
|
|
135
141
|
};
|
|
136
142
|
}>;
|
|
143
|
+
/**
|
|
144
|
+
* Write the browser bootstrap modules ahead of the pack stage: `pack` needs the
|
|
145
|
+
* literal dynamic-import tables in `entry-client.js` / `entry-event.js` as graph
|
|
146
|
+
* entries and must run before HTML emit (HTML resolves `_vmz/pack/` URLs). The
|
|
147
|
+
* static emit stage re-runs the same function, so both passes produce identical
|
|
148
|
+
* bytes.
|
|
149
|
+
* @param {string} distDir
|
|
150
|
+
* @returns {string[]} written bootstrap file names
|
|
151
|
+
*/
|
|
152
|
+
export declare function emitStaticBootstrapEntries(distDir: any): string[];
|
|
153
|
+
/**
|
|
154
|
+
* Prune the web-static delivery to its final browser set after HTML emit:
|
|
155
|
+
* remove the Node-only serve-host material (`_vmz/host/`, `vmz-serve-host.mjs`)
|
|
156
|
+
* and every unpacked module JS/map that `_vmz/pack/` chunks supersede
|
|
157
|
+
* (bootstrap entries, page/component/layout units, server faces, `#server/`,
|
|
158
|
+
* `ssr/`). The `/dom.browser.js` face closure stays — the packed navigation
|
|
159
|
+
* keeps that root-URL load by contract. JSON metadata and assets are untouched.
|
|
160
|
+
* Only `vmz serve` re-materializes the host (`materializeServeHostRuntime`);
|
|
161
|
+
* static hosting never needs it.
|
|
162
|
+
*/
|
|
163
|
+
export declare function pruneWebStaticDelivery(outDir: string): {
|
|
164
|
+
removed: number;
|
|
165
|
+
keptFaces: string[];
|
|
166
|
+
};
|
|
137
167
|
export {};
|
|
@@ -6,7 +6,7 @@ import crypto from 'node:crypto';
|
|
|
6
6
|
import fs from 'node:fs';
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import { pathToFileURL } from 'node:url';
|
|
9
|
-
import { listClientComponentsSync } from '@vmz/core/component-registry';
|
|
9
|
+
import { collectDependsOnClosure, listClientComponentsSync, readDeploymentDocument } from '@vmz/core/component-registry';
|
|
10
10
|
import { createRenderHost } from '@vmz/core/render-host';
|
|
11
11
|
import { resolveRouteLayoutChain } from '@vmz/core/route-layout-chain';
|
|
12
12
|
import { emitCdnPolicy } from './cdn-policy.js';
|
|
@@ -14,6 +14,7 @@ import { emitContentAddressedAssets, hashedAssetHref } from './content-addressed
|
|
|
14
14
|
import { LOCALE_LINK_PLAN_REL } from '../locale/locale-route-emit.js';
|
|
15
15
|
import { absoluteUrl, applyLocaleLinkPlan, buildLocaleLinkPlan, buildLocalePageMeta, linkRouteAliasesFromUnits, } from '../locale/locale-router.js';
|
|
16
16
|
import { requireNativeAddon } from './native-addon.js';
|
|
17
|
+
import { PACK_NODE_MANIFEST_REL, PACK_NODE_OUTPUT_DIR_REL, packEntryHref, packNodeEntryFile } from './pack.js';
|
|
17
18
|
import { writePrettyJsonFile } from './pretty-json.js';
|
|
18
19
|
import { emitPublicStaticAssets } from './public-static-assets.js';
|
|
19
20
|
import { emitRouteCatalog, loadRouteCatalog, ROUTE_CATALOG_SCHEMA } from './route-catalog-emit.js';
|
|
@@ -23,6 +24,7 @@ export const STATIC_EMIT_PLAN_SCHEMA = 'vmz.static.emit_plan.v0';
|
|
|
23
24
|
export async function emitWebStatic(distDir, opts = {}) {
|
|
24
25
|
const origin = String(opts.origin || process.env.VMZ_SITE_ORIGIN || 'https://example.test').replace(/\/$/, '');
|
|
25
26
|
const applicationId = opts.applicationId || path.basename(path.dirname(distDir));
|
|
27
|
+
const deliveryProfile = String(opts.deliveryProfile || 'static');
|
|
26
28
|
const domPath = path.join(distDir, 'vmz-dom.js');
|
|
27
29
|
if (!fs.existsSync(domPath)) {
|
|
28
30
|
throw new Error(`emitWebStatic: missing ${domPath} — run vmz build first`);
|
|
@@ -32,6 +34,9 @@ export async function emitWebStatic(distDir, opts = {}) {
|
|
|
32
34
|
const publicAssets = emitPublicStaticAssets(distDir, { projectRoot: opts.projectRoot });
|
|
33
35
|
const host = await createRenderHost(distDir, { strictDeployment: true, preload: 'none' });
|
|
34
36
|
const { renderToString, renderToStream } = host;
|
|
37
|
+
// One budget/signal pair for every SSR render of this build: a hung or
|
|
38
|
+
// aborted `onMount` fails the page build instead of stalling it.
|
|
39
|
+
const renderOpts = { signal: opts.signal ?? undefined, onMountBudgetMs: opts.onMountBudgetMs };
|
|
35
40
|
const pageCatalog = listPageClientFiles(distDir);
|
|
36
41
|
const generations = [];
|
|
37
42
|
const skipped = [];
|
|
@@ -41,7 +46,9 @@ export async function emitWebStatic(distDir, opts = {}) {
|
|
|
41
46
|
const assets = emitContentAddressedAssets(distDir);
|
|
42
47
|
const cssLogical = readCssEntry(distDir);
|
|
43
48
|
const cssEntry = hashedAssetHref(assets.rewrites, cssLogical) || hashedAssetHref(assets.rewrites, 'vmz.css');
|
|
44
|
-
|
|
49
|
+
// Packed browser JS is final-named under `_vmz/pack/` — resolve via the pack
|
|
50
|
+
// manifest entryFiles mapping; unpacked root bytes are never a fallback.
|
|
51
|
+
const moduleScriptSrc = packEntryHref(distDir, 'entry-client.js');
|
|
45
52
|
const localeArt = loadLocaleArtifact(distDir);
|
|
46
53
|
const localeLinkPlan = loadLocaleLinkPlan(distDir) || buildLocaleLinkPlan(withLinkRouteAliases(localeArt, distDir, pageCatalog));
|
|
47
54
|
for (const page of pageCatalog) {
|
|
@@ -99,14 +106,23 @@ export async function emitWebStatic(distDir, opts = {}) {
|
|
|
99
106
|
const layoutChain = resolveRouteLayoutChain(distDir, page.chunkId);
|
|
100
107
|
await host.ensureComponents([page.chunkId, ...layoutChain]);
|
|
101
108
|
let bodyHtml = '';
|
|
102
|
-
|
|
103
|
-
|
|
109
|
+
try {
|
|
110
|
+
for await (const chunk of renderToStream(Page, props, renderOpts)) {
|
|
111
|
+
bodyHtml += chunk;
|
|
112
|
+
}
|
|
113
|
+
for (let i = layoutChain.length - 1; i >= 0; i--) {
|
|
114
|
+
const Layout = await loadCtor(distDir, layoutChain[i]);
|
|
115
|
+
if (!Layout)
|
|
116
|
+
continue;
|
|
117
|
+
bodyHtml = await renderToString(Layout, {}, { ...renderOpts, slotHtml: bodyHtml });
|
|
118
|
+
}
|
|
104
119
|
}
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
120
|
+
catch (err) {
|
|
121
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
122
|
+
throw new Error(`emitWebStatic: SSR render failed for route ${pattern} (${page.chunkId}): ${detail}`, { cause: err });
|
|
123
|
+
}
|
|
124
|
+
if (opts.signal?.aborted) {
|
|
125
|
+
throw new Error(`emitWebStatic: build aborted during SSR render for route ${pattern} (${page.chunkId})`);
|
|
110
126
|
}
|
|
111
127
|
const localeWrites = expandLocaleStaticGenerations({
|
|
112
128
|
localeArt,
|
|
@@ -188,7 +204,7 @@ export async function emitWebStatic(distDir, opts = {}) {
|
|
|
188
204
|
const manifest = {
|
|
189
205
|
schema: STATIC_DELIVERY_MANIFEST_SCHEMA,
|
|
190
206
|
applicationId,
|
|
191
|
-
deliveryProfile
|
|
207
|
+
deliveryProfile,
|
|
192
208
|
origin,
|
|
193
209
|
generatedAt: new Date().toISOString(),
|
|
194
210
|
spaFallback: false,
|
|
@@ -232,7 +248,7 @@ export async function emitWebStatic(distDir, opts = {}) {
|
|
|
232
248
|
writePrettyJsonFile(path.join(vmzDir, 'static-emit-plan.json'), {
|
|
233
249
|
schema: STATIC_EMIT_PLAN_SCHEMA,
|
|
234
250
|
applicationId,
|
|
235
|
-
deliveryProfile
|
|
251
|
+
deliveryProfile,
|
|
236
252
|
origin,
|
|
237
253
|
localeLinks: localeLinkPlan,
|
|
238
254
|
assetPlanPath: '_vmz/asset-plan.json',
|
|
@@ -331,8 +347,11 @@ function htmlPathForRoute(pathname) {
|
|
|
331
347
|
* @param {string} chunkId
|
|
332
348
|
*/
|
|
333
349
|
async function loadCtor(distDir, chunkId) {
|
|
334
|
-
|
|
335
|
-
|
|
350
|
+
// Node chunk identity comes from `_vmz/pack-node-manifest.json`; the
|
|
351
|
+
// unpacked root `${chunkId}.client.js` is not a fallback. No `?t=` cache
|
|
352
|
+
// bust: packed chunks are shared instance URLs.
|
|
353
|
+
const href = pathToFileURL(packNodeEntryFile(distDir, chunkId)).href;
|
|
354
|
+
const mod = await import(href);
|
|
336
355
|
return mod.default;
|
|
337
356
|
}
|
|
338
357
|
/**
|
|
@@ -559,9 +578,14 @@ function buildSitemap(_origin, generations) {
|
|
|
559
578
|
return native.generateSitemapXml(urls);
|
|
560
579
|
}
|
|
561
580
|
/**
|
|
562
|
-
* Static CDN must ship entry-client/event like serve-host
|
|
581
|
+
* Static CDN must ship entry-client/event like serve-host. The bootstrap tables
|
|
582
|
+
* are literal dynamic-import maps: page/layout units are keyed by chunkId
|
|
583
|
+
* (`data-vmz-page` / `data-vmz-layout` lookups) and islands are enumerated
|
|
584
|
+
* explicitly — unknown names throw at runtime by design, never via fallback
|
|
585
|
+
* specifier concatenation.
|
|
563
586
|
* @param {string} distDir
|
|
564
587
|
* @param {Array<{ chunkId: string }>} pageCatalog
|
|
588
|
+
* @returns {string[]} written bootstrap file names
|
|
565
589
|
*/
|
|
566
590
|
function emitStaticClientEntries(distDir, pageCatalog) {
|
|
567
591
|
const componentEntries = listClientComponentsSync(distDir, { strict: true });
|
|
@@ -571,16 +595,74 @@ function emitStaticClientEntries(distDir, pageCatalog) {
|
|
|
571
595
|
.filter((e) => isEventResumeStrategy(e.strategy))
|
|
572
596
|
.map((e) => e.component)
|
|
573
597
|
.filter(Boolean));
|
|
574
|
-
|
|
575
|
-
|
|
598
|
+
// Pages/layouts resolve `<chunkId>.client.js` at the dist root (SSR loadCtor
|
|
599
|
+
// uses the same path); they must be in the table or hydration throws.
|
|
600
|
+
const pageEntries = pageCatalog.map((p) => ({ name: p.chunkId, entry: `${p.chunkId}.client.js` }));
|
|
601
|
+
const layoutNames = new Set();
|
|
602
|
+
for (const page of pageCatalog) {
|
|
603
|
+
for (const id of resolveRouteLayoutChain(distDir, page.chunkId))
|
|
604
|
+
layoutNames.add(String(id));
|
|
605
|
+
}
|
|
606
|
+
const layoutEntries = [...layoutNames].sort().map((id) => ({ name: id, entry: `${id}.client.js` }));
|
|
607
|
+
// 引导表只登记从页面/布局可达的组件:部署 IR 的 `dependsOn` 闭包之外的
|
|
608
|
+
// 组件没有任何模板引用,进入表只会变成 pack 死字节;不登记后 pack 图
|
|
609
|
+
// 不可达,prune 同时清掉未打包的散落模块。
|
|
610
|
+
const deployment = readDeploymentDocument(distDir, { strict: true });
|
|
611
|
+
const closure = collectDependsOnClosure(deployment, [...pageCatalog.map((p) => p.chunkId), ...layoutNames]);
|
|
612
|
+
const reachableComponents = componentEntries.filter((e) => closure.has(e.chunkId));
|
|
613
|
+
const eagerComponents = reachableComponents.filter((e) => !lazySet.has(e.name));
|
|
614
|
+
const lazyComponents = reachableComponents.filter((e) => lazySet.has(e.name));
|
|
615
|
+
const eager = [...eagerComponents, ...pageEntries, ...layoutEntries];
|
|
576
616
|
const native = requireNativeAddon();
|
|
577
617
|
if (typeof native.generateServeEntryClient !== 'function') {
|
|
578
618
|
throw new Error('vmz native addon missing generateServeEntryClient — rebuild with `pnpm napi:build`');
|
|
579
619
|
}
|
|
580
|
-
|
|
620
|
+
const written = ['entry-client.js'];
|
|
621
|
+
fs.writeFileSync(path.join(distDir, 'entry-client.js'), native.generateServeEntryClient(eager, lazyComponents, ''), 'utf8');
|
|
581
622
|
if (typeof native.generateServeEntryEvent === 'function') {
|
|
582
|
-
|
|
623
|
+
// Event shell islands: event-strategy components + document islands whose
|
|
624
|
+
// compiled module exists in the component set (explicit enumeration).
|
|
625
|
+
const islandNames = readDocumentIslandNames(distDir);
|
|
626
|
+
const islands = [...lazyComponents];
|
|
627
|
+
for (const entry of eagerComponents) {
|
|
628
|
+
if (islandNames.has(entry.name))
|
|
629
|
+
islands.push(entry);
|
|
630
|
+
}
|
|
631
|
+
fs.writeFileSync(path.join(distDir, 'entry-event.js'), native.generateServeEntryEvent([], islands, ''), 'utf8');
|
|
632
|
+
written.push('entry-event.js');
|
|
583
633
|
}
|
|
634
|
+
return written;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Write the browser bootstrap modules ahead of the pack stage: `pack` needs the
|
|
638
|
+
* literal dynamic-import tables in `entry-client.js` / `entry-event.js` as graph
|
|
639
|
+
* entries and must run before HTML emit (HTML resolves `_vmz/pack/` URLs). The
|
|
640
|
+
* static emit stage re-runs the same function, so both passes produce identical
|
|
641
|
+
* bytes.
|
|
642
|
+
* @param {string} distDir
|
|
643
|
+
* @returns {string[]} written bootstrap file names
|
|
644
|
+
*/
|
|
645
|
+
export function emitStaticBootstrapEntries(distDir) {
|
|
646
|
+
return emitStaticClientEntries(distDir, listPageClientFiles(distDir));
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Island names from an integrated document surface (`document.islands.json`,
|
|
650
|
+
* written by the document build into the same dist). Absent file → no islands.
|
|
651
|
+
* @param {string} distDir
|
|
652
|
+
*/
|
|
653
|
+
function readDocumentIslandNames(distDir) {
|
|
654
|
+
const names = new Set();
|
|
655
|
+
try {
|
|
656
|
+
const plan = JSON.parse(fs.readFileSync(path.join(distDir, 'document.islands.json'), 'utf8'));
|
|
657
|
+
for (const island of plan?.islands || []) {
|
|
658
|
+
if (typeof island?.name === 'string' && island.name)
|
|
659
|
+
names.add(island.name);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
catch {
|
|
663
|
+
/* no document surface */
|
|
664
|
+
}
|
|
665
|
+
return names;
|
|
584
666
|
}
|
|
585
667
|
/**
|
|
586
668
|
* @param {string} distDir
|
|
@@ -605,3 +687,114 @@ function loadPageResumeEntriesSync(distDir, chunkId) {
|
|
|
605
687
|
function isEventResumeStrategy(strategy) {
|
|
606
688
|
return strategy === 'event' || strategy === 'click' || String(strategy).startsWith('event:');
|
|
607
689
|
}
|
|
690
|
+
/** Runtime face barrel loaded by URL (`import('/dom.browser.js')`) in the packed client navigation. */
|
|
691
|
+
const FACE_ENTRY_REL = 'dom.browser.js';
|
|
692
|
+
/**
|
|
693
|
+
* Transitive closure of the URL-loaded browser face: start at `dom.browser.js`
|
|
694
|
+
* and follow relative import specifiers that resolve to files in the dist tree.
|
|
695
|
+
* Everything else that is executable JS is either packed into `_vmz/pack/` or
|
|
696
|
+
* Node-side, so it must not ship in a web-static delivery.
|
|
697
|
+
*/
|
|
698
|
+
function collectFaceClosure(abs) {
|
|
699
|
+
const closure = new Set();
|
|
700
|
+
if (!fs.existsSync(path.join(abs, FACE_ENTRY_REL)))
|
|
701
|
+
return closure;
|
|
702
|
+
const queue = [FACE_ENTRY_REL];
|
|
703
|
+
while (queue.length) {
|
|
704
|
+
const rel = queue.shift();
|
|
705
|
+
if (closure.has(rel))
|
|
706
|
+
continue;
|
|
707
|
+
closure.add(rel);
|
|
708
|
+
let text;
|
|
709
|
+
try {
|
|
710
|
+
text = fs.readFileSync(path.join(abs, ...rel.split('/')), 'utf8');
|
|
711
|
+
}
|
|
712
|
+
catch {
|
|
713
|
+
continue;
|
|
714
|
+
}
|
|
715
|
+
const specs = [
|
|
716
|
+
...text.matchAll(/from\s+['"](\.[^'"]+)['"]/g),
|
|
717
|
+
...text.matchAll(/import\(\s*['"](\.[^'"]+)['"]/g),
|
|
718
|
+
];
|
|
719
|
+
for (const m of specs) {
|
|
720
|
+
const next = path.posix.normalize(`${path.posix.dirname(rel)}/${m[1]}`);
|
|
721
|
+
if (next.startsWith('..'))
|
|
722
|
+
continue;
|
|
723
|
+
if (!fs.existsSync(path.join(abs, ...next.split('/'))))
|
|
724
|
+
continue;
|
|
725
|
+
if (!closure.has(next))
|
|
726
|
+
queue.push(next);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
return closure;
|
|
730
|
+
}
|
|
731
|
+
function listFilesRecursive(abs, rel = '') {
|
|
732
|
+
const out = [];
|
|
733
|
+
for (const entry of fs.readdirSync(path.join(abs, ...rel.split('/')), { withFileTypes: true })) {
|
|
734
|
+
const nextRel = rel ? `${rel}/${entry.name}` : entry.name;
|
|
735
|
+
if (entry.isDirectory())
|
|
736
|
+
out.push(...listFilesRecursive(abs, nextRel));
|
|
737
|
+
else if (entry.isFile())
|
|
738
|
+
out.push(nextRel);
|
|
739
|
+
}
|
|
740
|
+
return out;
|
|
741
|
+
}
|
|
742
|
+
function removeEmptyDirsBottomUp(abs, rel = '') {
|
|
743
|
+
for (const entry of fs.readdirSync(path.join(abs, ...rel.split('/')), { withFileTypes: true })) {
|
|
744
|
+
if (!entry.isDirectory())
|
|
745
|
+
continue;
|
|
746
|
+
const nextRel = rel ? `${rel}/${entry.name}` : entry.name;
|
|
747
|
+
if (nextRel === '_vmz/pack' || nextRel.startsWith('_vmz/pack/'))
|
|
748
|
+
continue;
|
|
749
|
+
removeEmptyDirsBottomUp(abs, nextRel);
|
|
750
|
+
if (nextRel === '_vmz')
|
|
751
|
+
continue;
|
|
752
|
+
if (fs.readdirSync(path.join(abs, ...nextRel.split('/'))).length === 0) {
|
|
753
|
+
fs.rmdirSync(path.join(abs, ...nextRel.split('/')));
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Prune the web-static delivery to its final browser set after HTML emit:
|
|
759
|
+
* remove the Node-only serve-host material (`_vmz/host/`, `vmz-serve-host.mjs`)
|
|
760
|
+
* and every unpacked module JS/map that `_vmz/pack/` chunks supersede
|
|
761
|
+
* (bootstrap entries, page/component/layout units, server faces, `#server/`,
|
|
762
|
+
* `ssr/`). The `/dom.browser.js` face closure stays — the packed navigation
|
|
763
|
+
* keeps that root-URL load by contract. JSON metadata and assets are untouched.
|
|
764
|
+
* Only `vmz serve` re-materializes the host (`materializeServeHostRuntime`);
|
|
765
|
+
* static hosting never needs it.
|
|
766
|
+
*/
|
|
767
|
+
export function pruneWebStaticDelivery(outDir) {
|
|
768
|
+
const abs = path.resolve(outDir);
|
|
769
|
+
if (!fs.existsSync(abs))
|
|
770
|
+
throw new Error(`pruneWebStaticDelivery: missing dist ${abs}`);
|
|
771
|
+
const faceClosure = collectFaceClosure(abs);
|
|
772
|
+
let removed = 0;
|
|
773
|
+
for (const rel of listFilesRecursive(abs)) {
|
|
774
|
+
if (rel.startsWith('_vmz/pack/') || rel.startsWith('assets/'))
|
|
775
|
+
continue;
|
|
776
|
+
const isHost = rel.startsWith('_vmz/host/');
|
|
777
|
+
const isJs = rel.endsWith('.js') || rel.endsWith('.mjs') || rel.endsWith('.map');
|
|
778
|
+
if (!isHost && (!isJs || faceClosure.has(rel)))
|
|
779
|
+
continue;
|
|
780
|
+
fs.rmSync(path.join(abs, ...rel.split('/')), { force: true });
|
|
781
|
+
removed++;
|
|
782
|
+
}
|
|
783
|
+
// Node pack 是构建期产物:静态托管不会执行它,浏览器集已由 `_vmz/pack/`
|
|
784
|
+
// 定型。这里显式清掉 Node chunk 目录与 manifest,避免把服务端字节一起交付。
|
|
785
|
+
const nodePackDir = path.join(abs, ...PACK_NODE_OUTPUT_DIR_REL.split('/'));
|
|
786
|
+
if (fs.existsSync(nodePackDir)) {
|
|
787
|
+
for (const entry of fs.readdirSync(nodePackDir, { recursive: true, withFileTypes: true })) {
|
|
788
|
+
if (entry.isFile())
|
|
789
|
+
removed++;
|
|
790
|
+
}
|
|
791
|
+
fs.rmSync(nodePackDir, { recursive: true, force: true });
|
|
792
|
+
}
|
|
793
|
+
const nodeManifestPath = path.join(abs, ...PACK_NODE_MANIFEST_REL.split('/'));
|
|
794
|
+
if (fs.existsSync(nodeManifestPath)) {
|
|
795
|
+
fs.rmSync(nodeManifestPath, { force: true });
|
|
796
|
+
removed++;
|
|
797
|
+
}
|
|
798
|
+
removeEmptyDirsBottomUp(abs);
|
|
799
|
+
return { removed, keptFaces: [...faceClosure].sort() };
|
|
800
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application authoring config surface — `defineConfig` + `VmzUserConfig`.
|
|
3
|
+
*
|
|
4
|
+
* Ownership: the application config entry belongs to the `vmz` package (CLI /
|
|
5
|
+
* workspace host). `@vmz/plugin` keeps the plugin author surface (`definePlugin`,
|
|
6
|
+
* protocol types). Plugin-facing types (`VmzPlugin`, `VmzEngines`) stay in
|
|
7
|
+
* `@vmz/plugin` and are referenced here by type import only.
|
|
8
|
+
*/
|
|
9
|
+
import type { VmzEngines, VmzPlugin } from '@vmz/plugin';
|
|
10
|
+
/**
|
|
11
|
+
* Format options (`vmz format` reads `vmz.config` `format`).
|
|
12
|
+
*
|
|
13
|
+
* Scope is always the explicit CLI path; config can only narrow it. There is
|
|
14
|
+
* no `includes` / language toggle here — `vmz format <path>` formats the
|
|
15
|
+
* given file or directory and nothing else.
|
|
16
|
+
*/
|
|
17
|
+
export interface VmzFormatConfig {
|
|
18
|
+
/** Skip paths under these globs inside an explicit directory target. */
|
|
19
|
+
excludes?: string[];
|
|
20
|
+
/** Biome-style formatter config (default: `biome.json` at project root). */
|
|
21
|
+
style?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface VmzUserConfig {
|
|
24
|
+
plugins?: Array<string | VmzPlugin | Promise<VmzPlugin>>;
|
|
25
|
+
engines?: VmzEngines;
|
|
26
|
+
/** Format excludes / style (`vmz format` scope is the explicit CLI path). */
|
|
27
|
+
format?: VmzFormatConfig;
|
|
28
|
+
/** Application identity (optional authoring). */
|
|
29
|
+
application?: {
|
|
30
|
+
id?: string;
|
|
31
|
+
[key: string]: unknown;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Delivery: named profiles (`default` + `profiles`) or legacy site sugar.
|
|
35
|
+
* Pure data only; never a parallel `vmz.site.ts` entry.
|
|
36
|
+
*/
|
|
37
|
+
delivery?: DeliveryAuthoring;
|
|
38
|
+
}
|
|
39
|
+
/** Site resource sources (embedded | filesystem | remote) — orthogonal to assembly. */
|
|
40
|
+
export interface SiteDeliveryAuthoring {
|
|
41
|
+
artifact: string;
|
|
42
|
+
siteId?: string;
|
|
43
|
+
sources: Array<{
|
|
44
|
+
id: string;
|
|
45
|
+
kind: 'embedded' | 'filesystem' | 'remote';
|
|
46
|
+
directory?: string;
|
|
47
|
+
baseUrl?: string;
|
|
48
|
+
artifact?: string;
|
|
49
|
+
trust?: string;
|
|
50
|
+
timeoutMs?: number;
|
|
51
|
+
priority?: number;
|
|
52
|
+
integrity?: unknown;
|
|
53
|
+
signaturePolicy?: string;
|
|
54
|
+
}>;
|
|
55
|
+
resolution?: {
|
|
56
|
+
mode?: 'release';
|
|
57
|
+
fallback?: string[];
|
|
58
|
+
};
|
|
59
|
+
activation?: 'atomic';
|
|
60
|
+
expectedCompatibility?: unknown;
|
|
61
|
+
failure?: unknown;
|
|
62
|
+
failurePolicy?: unknown;
|
|
63
|
+
update?: unknown;
|
|
64
|
+
updatePolicy?: unknown;
|
|
65
|
+
rollback?: unknown;
|
|
66
|
+
rollbackPolicy?: unknown;
|
|
67
|
+
security?: unknown;
|
|
68
|
+
securityPolicy?: unknown;
|
|
69
|
+
}
|
|
70
|
+
export type DeliveryAssembly = 'local-static' | 'web-static' | 'server-host' | 'cdn+server' | 'rust-embedded';
|
|
71
|
+
export type DeliveryServerRuntime = 'node' | 'worker' | 'deno' | 'bun' | 'rust-host';
|
|
72
|
+
export interface DeliveryProfileAuthoring {
|
|
73
|
+
host?: 'browser';
|
|
74
|
+
assembly: DeliveryAssembly;
|
|
75
|
+
/**
|
|
76
|
+
* Artifact directory under CLI `--out-dir` (default = profile id).
|
|
77
|
+
* Example: profile `static` + `name: 'cdn'` → `dist/cdn`.
|
|
78
|
+
*/
|
|
79
|
+
name?: string;
|
|
80
|
+
serverRuntime?: DeliveryServerRuntime;
|
|
81
|
+
/** Inline site sources or `defineSite({...})`. */
|
|
82
|
+
sources?: SiteDeliveryAuthoring | SiteDeliveryAuthoring['sources'];
|
|
83
|
+
artifact?: string;
|
|
84
|
+
resolution?: SiteDeliveryAuthoring['resolution'];
|
|
85
|
+
activation?: SiteDeliveryAuthoring['activation'];
|
|
86
|
+
}
|
|
87
|
+
/** WeChat DevTools project identity. Pure data; not WeChat JSON / wx.* APIs. */
|
|
88
|
+
export interface WechatPackagingAuthoring {
|
|
89
|
+
/** Registered AppID. Omit for WeChat tourist mode (`touristappid`). */
|
|
90
|
+
appId?: string;
|
|
91
|
+
projectName?: string;
|
|
92
|
+
/** `app.json` `window.navigationBarTitleText`. */
|
|
93
|
+
title?: string;
|
|
94
|
+
}
|
|
95
|
+
/** Vendor packaging identity under `defineConfig({ delivery })`. */
|
|
96
|
+
export interface DeliveryPackagingAuthoring {
|
|
97
|
+
wechat?: WechatPackagingAuthoring;
|
|
98
|
+
}
|
|
99
|
+
/** Named profiles (preferred) or legacy site-only sugar. */
|
|
100
|
+
export type DeliveryAuthoring = {
|
|
101
|
+
default?: string;
|
|
102
|
+
profiles: Record<string, DeliveryProfileAuthoring>;
|
|
103
|
+
packaging?: DeliveryPackagingAuthoring;
|
|
104
|
+
} | (SiteDeliveryAuthoring & {
|
|
105
|
+
default?: string;
|
|
106
|
+
assembly?: DeliveryAssembly;
|
|
107
|
+
host?: 'browser';
|
|
108
|
+
serverRuntime?: DeliveryServerRuntime;
|
|
109
|
+
packaging?: DeliveryPackagingAuthoring;
|
|
110
|
+
});
|
|
111
|
+
/** Identity helper for `vmz.config.ts` inference. */
|
|
112
|
+
export declare function defineConfig(config: VmzUserConfig): VmzUserConfig;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application authoring config surface — `defineConfig` + `VmzUserConfig`.
|
|
3
|
+
*
|
|
4
|
+
* Ownership: the application config entry belongs to the `vmz` package (CLI /
|
|
5
|
+
* workspace host). `@vmz/plugin` keeps the plugin author surface (`definePlugin`,
|
|
6
|
+
* protocol types). Plugin-facing types (`VmzPlugin`, `VmzEngines`) stay in
|
|
7
|
+
* `@vmz/plugin` and are referenced here by type import only.
|
|
8
|
+
*/
|
|
9
|
+
/** Identity helper for `vmz.config.ts` inference. */
|
|
10
|
+
export function defineConfig(config) {
|
|
11
|
+
return config ?? {};
|
|
12
|
+
}
|
package/host-runtime-files.json
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
{ "src": "host/serve/hmr-dev.js", "out": "_vmz/host/serve/hmr-dev.js", "rewriteVmzRuntimeImport": true },
|
|
15
15
|
{ "src": "host/serve/page-render.js", "out": "_vmz/host/serve/page-render.js", "rewriteVmzRuntimeImport": true },
|
|
16
16
|
{ "src": "host/serve/page-catalog.js", "out": "_vmz/host/serve/page-catalog.js" },
|
|
17
|
+
{ "src": "host/serve/pack-entry.js", "out": "_vmz/host/serve/pack-entry.js" },
|
|
17
18
|
{ "src": "host/serve/locale-theme.js", "out": "_vmz/host/serve/locale-theme.js" },
|
|
18
19
|
{ "src": "host/serve/constants.js", "out": "_vmz/host/serve/constants.js" },
|
|
19
20
|
{ "src": "host/serve/route-catalog.js", "out": "_vmz/host/serve/route-catalog.js" },
|
package/locales/en-US/cli.json
CHANGED
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
"cli.cmd.build": "Build project via Workspace",
|
|
6
6
|
"cli.cmd.serve": "Serve dist (optional --build)",
|
|
7
7
|
"cli.cmd.dev": "Rebuild session",
|
|
8
|
-
"cli.cmd.format": "Format
|
|
9
|
-
"cli.opt.format-
|
|
10
|
-
"cli.opt.format-no-
|
|
11
|
-
"cli.opt.format-no-
|
|
8
|
+
"cli.cmd.format": "Format an explicit file/directory path (JS/TS, .vmz, .rs with --rust)",
|
|
9
|
+
"cli.opt.format-rust": "Also format .rs files under a directory target via rustfmt",
|
|
10
|
+
"cli.opt.format-no-javascript": "Reject JS/TS targets",
|
|
11
|
+
"cli.opt.format-no-rust": "Reject .rs targets",
|
|
12
|
+
"cli.opt.format-no-vmz": "Skip .vmz targets",
|
|
12
13
|
"cli.cmd.lint": "Lint (= check) via N-API",
|
|
13
14
|
"cli.cmd.test": "Native test discover / report",
|
|
14
15
|
"cli.cmd.document": "Project /documents domain",
|
package/package.json
CHANGED
|
@@ -3,23 +3,23 @@
|
|
|
3
3
|
"vmz": "./bin/vmz.js"
|
|
4
4
|
},
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@vmz/commander": "0.3.
|
|
7
|
-
"@vmz/core": "0.3.
|
|
8
|
-
"@vmz/diagnostic": "0.3.
|
|
9
|
-
"@vmz/plugin": "0.3.
|
|
10
|
-
"@vmz/protocol": "0.3.
|
|
6
|
+
"@vmz/commander": "0.3.13",
|
|
7
|
+
"@vmz/core": "0.3.13",
|
|
8
|
+
"@vmz/diagnostic": "0.3.13",
|
|
9
|
+
"@vmz/plugin": "0.3.13",
|
|
10
|
+
"@vmz/protocol": "0.3.13"
|
|
11
11
|
},
|
|
12
12
|
"description": "VMZ Node toolchain — N-API workspace session + CLI (publish name @vmz/vmz)",
|
|
13
13
|
"devDependencies": {
|
|
14
14
|
"@types/node": "^22.15.0",
|
|
15
|
-
"@vmz/plugin": "0.3.
|
|
16
|
-
"@vmz/plugin-katex": "0.3.
|
|
17
|
-
"@vmz/plugin-markdown-it": "0.3.
|
|
18
|
-
"@vmz/plugin-shiki": "0.3.
|
|
19
|
-
"@vmz/test": "0.3.
|
|
15
|
+
"@vmz/plugin": "0.3.13",
|
|
16
|
+
"@vmz/plugin-katex": "0.3.13",
|
|
17
|
+
"@vmz/plugin-markdown-it": "0.3.13",
|
|
18
|
+
"@vmz/plugin-shiki": "0.3.13",
|
|
19
|
+
"@vmz/test": "0.3.13",
|
|
20
20
|
"linkedom": "^0.18.13",
|
|
21
21
|
"typescript": "^5.8.3",
|
|
22
|
-
"vmz-plugin-conformance": "0.3.
|
|
22
|
+
"vmz-plugin-conformance": "0.3.13"
|
|
23
23
|
},
|
|
24
24
|
"engines": {
|
|
25
25
|
"node": ">=24.12.0"
|
|
@@ -80,16 +80,16 @@
|
|
|
80
80
|
"main": "./dist/index.js",
|
|
81
81
|
"name": "@vmz/vmz",
|
|
82
82
|
"optionalDependencies": {
|
|
83
|
-
"@vmz/vmz-darwin-arm64": "0.3.
|
|
84
|
-
"@vmz/vmz-darwin-x64": "0.3.
|
|
85
|
-
"@vmz/vmz-linux-arm64": "0.3.
|
|
86
|
-
"@vmz/vmz-linux-x64": "0.3.
|
|
87
|
-
"@vmz/vmz-win32-arm64": "0.3.
|
|
88
|
-
"@vmz/vmz-win32-x64": "0.3.
|
|
83
|
+
"@vmz/vmz-darwin-arm64": "0.3.13",
|
|
84
|
+
"@vmz/vmz-darwin-x64": "0.3.13",
|
|
85
|
+
"@vmz/vmz-linux-arm64": "0.3.13",
|
|
86
|
+
"@vmz/vmz-linux-x64": "0.3.13",
|
|
87
|
+
"@vmz/vmz-win32-arm64": "0.3.13",
|
|
88
|
+
"@vmz/vmz-win32-x64": "0.3.13"
|
|
89
89
|
},
|
|
90
90
|
"peerDependencies": {
|
|
91
|
-
"@vmz/plugin-markdown-it": "0.3.
|
|
92
|
-
"@vmz/test": "0.3.
|
|
91
|
+
"@vmz/plugin-markdown-it": "0.3.13",
|
|
92
|
+
"@vmz/test": "0.3.13",
|
|
93
93
|
"typescript": "^5.8.3"
|
|
94
94
|
},
|
|
95
95
|
"peerDependenciesMeta": {
|
|
@@ -122,5 +122,5 @@
|
|
|
122
122
|
},
|
|
123
123
|
"type": "module",
|
|
124
124
|
"types": "./dist/index.d.ts",
|
|
125
|
-
"version": "0.3.
|
|
125
|
+
"version": "0.3.13"
|
|
126
126
|
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browser-safe client package lowering (Pack stage thin slice).
|
|
3
|
-
*
|
|
4
|
-
* Bare npm/workspace imports are legal on the author surface (01/04).
|
|
5
|
-
* Browser ESM cannot resolve them. Pack materializes reachable package
|
|
6
|
-
* modules under `dist/vendor/<pkg>/…` and rewrites importers to relative paths
|
|
7
|
-
* via a bounded rewrite of generated module syntax. Pack must not re-enter the
|
|
8
|
-
* parser pipeline for every emitted JavaScript file.
|
|
9
|
-
*
|
|
10
|
-
* This stage is only that thin slice. Linking, dead-code elimination, chunk
|
|
11
|
-
* splitting, and minification are NOT implemented here — the pack manifest
|
|
12
|
-
* reports their absence rather than a placeholder backend.
|
|
13
|
-
*/
|
|
14
|
-
/**
|
|
15
|
-
* @param {string} outDir
|
|
16
|
-
* @param {{ projectRoot?: string | null }} [opts]
|
|
17
|
-
* @returns {{ rewrittenFiles: number, vendoredModules: string[], bareSpecs: string[] }}
|
|
18
|
-
*/
|
|
19
|
-
interface PackClientBareImportsOpts {
|
|
20
|
-
projectRoot?: string | null;
|
|
21
|
-
}
|
|
22
|
-
export declare function packClientBareImports(outDir: string, opts?: PackClientBareImportsOpts): {
|
|
23
|
-
rewrittenFiles: number;
|
|
24
|
-
vendoredModules: string[];
|
|
25
|
-
bareSpecs: any[];
|
|
26
|
-
unresolvedBareSpecs: any[];
|
|
27
|
-
skippedVmzExports: any[];
|
|
28
|
-
remainingBareSpecs: any[];
|
|
29
|
-
};
|
|
30
|
-
/** @param {string} js */
|
|
31
|
-
export declare function collectBareSpecs(js: any): unknown[];
|
|
32
|
-
/** Native (N-API) module specifier rewrite (`.ts`→`.js` + optional exact map). */
|
|
33
|
-
export declare function rewriteRelativeTsSpecs(js: any, exactMap?: any): any;
|
|
34
|
-
export {};
|