@vmz/vmz 0.1.10 → 0.1.12
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/content-addressed-assets.js +2 -1
- package/dist/dev-session.js +57 -10
- package/dist/dev-watch-roots.d.ts +13 -0
- package/dist/dev-watch-roots.js +25 -0
- package/dist/document-build.d.ts +2 -1
- package/dist/document-build.js +83 -57
- package/dist/document-enrich.d.ts +2 -1
- package/dist/document-enrich.js +15 -4
- package/dist/document-integrate.js +19 -4
- package/dist/document-layout-render.d.ts +20 -0
- package/dist/document-layout-render.js +87 -0
- package/dist/document-routing-config.d.ts +13 -0
- package/dist/document-routing-config.js +40 -0
- package/dist/index.js +1 -0
- package/dist/static-emit.js +2 -19
- package/package.json +12 -12
|
@@ -154,7 +154,8 @@ function ingestCandidate(absDist, rel, rewrites, objects, opts) {
|
|
|
154
154
|
else {
|
|
155
155
|
const existing = sha256Hex(fs.readFileSync(dest));
|
|
156
156
|
if (existing !== digest) {
|
|
157
|
-
|
|
157
|
+
// Stale assets/ from a prior partial build can reuse hash filenames with different bytes.
|
|
158
|
+
fs.writeFileSync(dest, buf);
|
|
158
159
|
}
|
|
159
160
|
}
|
|
160
161
|
objects.push({
|
package/dist/dev-session.js
CHANGED
|
@@ -17,7 +17,7 @@ import { buildIntegratedDocuments, projectHasDocuments } from './document-integr
|
|
|
17
17
|
import { createWorkspace, resolveNativePath } from './index.js';
|
|
18
18
|
import { emitLocaleRuntimeModules, localeHasErrors } from './locale-check.js';
|
|
19
19
|
import { log } from './log.js';
|
|
20
|
-
import { coalesceRootBurst, collectDevWatchRoots, isDependencyPath, mergeDirtySets } from './dev-watch-roots.js';
|
|
20
|
+
import { coalesceRootBurst, collectDevWatchRoots, classifyWatchRoot, isDependencyPath, mergeDirtySets } from './dev-watch-roots.js';
|
|
21
21
|
import { diffFingerprints, fileFingerprintMap } from './watch-diff.js';
|
|
22
22
|
/**
|
|
23
23
|
* @typedef {object} DevSessionOptions
|
|
@@ -190,6 +190,7 @@ export function createDevSession(options) {
|
|
|
190
190
|
}
|
|
191
191
|
const docsRoot = path.join(project, 'documents');
|
|
192
192
|
const localesRoot = path.join(project, 'locales');
|
|
193
|
+
const designsRoot = path.join(project, 'designs');
|
|
193
194
|
const watched = collectDevWatchRoots({ project, outDir });
|
|
194
195
|
/** @type {string[]} */
|
|
195
196
|
const watchRoots = [...watched.roots];
|
|
@@ -218,37 +219,46 @@ export function createDevSession(options) {
|
|
|
218
219
|
* Scan all watch roots into a batch. Does not update fingerprints.
|
|
219
220
|
*/
|
|
220
221
|
function scanBatch() {
|
|
221
|
-
/** @type {{ srcChanged: string[], srcDeleted: string[], depChanged: string[], depDeleted: string[], docsDirty: boolean, localesDirty: boolean }} */
|
|
222
|
+
/** @type {{ srcChanged: string[], srcDeleted: string[], depChanged: string[], depDeleted: string[], designsChanged: string[], designsDeleted: string[], docsDirty: boolean, localesDirty: boolean, designsDirty: boolean }} */
|
|
222
223
|
const batch = {
|
|
223
224
|
srcChanged: [],
|
|
224
225
|
srcDeleted: [],
|
|
225
226
|
depChanged: [],
|
|
226
227
|
depDeleted: [],
|
|
228
|
+
designsChanged: [],
|
|
229
|
+
designsDeleted: [],
|
|
227
230
|
docsDirty: false,
|
|
228
231
|
localesDirty: false,
|
|
232
|
+
designsDirty: false,
|
|
229
233
|
};
|
|
234
|
+
const watchCtx = { src, docsRoot, localesRoot, designsRoot, dependencyRoots };
|
|
230
235
|
for (const root of watchRoots) {
|
|
231
236
|
const prev = fingerprints.get(root) || new Map();
|
|
232
237
|
const next = fileFingerprintMap(root);
|
|
233
238
|
const diff = diffFingerprints(prev, next);
|
|
234
239
|
if (!diff.changed.length && !diff.deleted.length)
|
|
235
240
|
continue;
|
|
236
|
-
|
|
241
|
+
const kind = classifyWatchRoot(root, watchCtx);
|
|
242
|
+
if (kind === 'src') {
|
|
237
243
|
batch.srcChanged.push(...diff.changed);
|
|
238
244
|
batch.srcDeleted.push(...diff.deleted);
|
|
239
245
|
}
|
|
240
|
-
else if (
|
|
246
|
+
else if (kind === 'locales') {
|
|
241
247
|
batch.localesDirty = true;
|
|
242
248
|
}
|
|
243
|
-
else if (
|
|
249
|
+
else if (kind === 'docs') {
|
|
244
250
|
batch.docsDirty = true;
|
|
245
251
|
}
|
|
246
|
-
else if (
|
|
252
|
+
else if (kind === 'designs') {
|
|
253
|
+
batch.designsDirty = true;
|
|
254
|
+
batch.designsChanged.push(...diff.changed);
|
|
255
|
+
batch.designsDeleted.push(...diff.deleted);
|
|
256
|
+
}
|
|
257
|
+
else if (kind === 'dep') {
|
|
247
258
|
batch.depChanged.push(...diff.changed);
|
|
248
259
|
batch.depDeleted.push(...diff.deleted);
|
|
249
260
|
}
|
|
250
261
|
else {
|
|
251
|
-
// designs or other application roots → treat like docs (full reload)
|
|
252
262
|
batch.docsDirty = true;
|
|
253
263
|
}
|
|
254
264
|
}
|
|
@@ -280,7 +290,8 @@ export function createDevSession(options) {
|
|
|
280
290
|
}
|
|
281
291
|
const srcDirty = batch.srcChanged.length + batch.srcDeleted.length;
|
|
282
292
|
const depDirty = batch.depChanged.length + batch.depDeleted.length;
|
|
283
|
-
|
|
293
|
+
const designsDirty = batch.designsDirty || batch.designsChanged.length + batch.designsDeleted.length;
|
|
294
|
+
if (!srcDirty && !depDirty && !batch.docsDirty && !batch.localesDirty && !designsDirty)
|
|
284
295
|
continue;
|
|
285
296
|
// Coalesce multi-file bursts without dropping the initial dirty set.
|
|
286
297
|
if (srcDirty > 1) {
|
|
@@ -329,6 +340,10 @@ export function createDevSession(options) {
|
|
|
329
340
|
batch.depDeleted = depMerged.deleted;
|
|
330
341
|
batch.docsDirty = batch.docsDirty || residual.docsDirty;
|
|
331
342
|
batch.localesDirty = batch.localesDirty || residual.localesDirty;
|
|
343
|
+
batch.designsDirty = batch.designsDirty || residual.designsDirty;
|
|
344
|
+
const designsMerged = mergeDirtySets({ changed: batch.designsChanged, deleted: batch.designsDeleted }, { changed: residual.designsChanged, deleted: residual.designsDeleted });
|
|
345
|
+
batch.designsChanged = designsMerged.changed;
|
|
346
|
+
batch.designsDeleted = designsMerged.deleted;
|
|
332
347
|
}
|
|
333
348
|
commitFingerprints();
|
|
334
349
|
if (!batch.srcChanged.length &&
|
|
@@ -336,7 +351,8 @@ export function createDevSession(options) {
|
|
|
336
351
|
!batch.depChanged.length &&
|
|
337
352
|
!batch.depDeleted.length &&
|
|
338
353
|
!batch.docsDirty &&
|
|
339
|
-
!batch.localesDirty
|
|
354
|
+
!batch.localesDirty &&
|
|
355
|
+
!(batch.designsDirty || batch.designsChanged.length || batch.designsDeleted.length)) {
|
|
340
356
|
continue;
|
|
341
357
|
}
|
|
342
358
|
// Dependency changes: conservative full rebuild + full reload (v0.1.5).
|
|
@@ -376,7 +392,7 @@ export function createDevSession(options) {
|
|
|
376
392
|
log.info(kind === 'respawn' ? 'reload ok (respawned; deps)' : 'soft reload ok (full page; deps)');
|
|
377
393
|
continue;
|
|
378
394
|
}
|
|
379
|
-
let needFullReload = batch.docsDirty;
|
|
395
|
+
let needFullReload = batch.docsDirty || batch.designsDirty;
|
|
380
396
|
if (batch.srcChanged.length || batch.srcDeleted.length) {
|
|
381
397
|
log.info(`change detected (${batch.srcChanged.length} update, ${batch.srcDeleted.length} delete) — affected rebuild…`);
|
|
382
398
|
const changes = [
|
|
@@ -419,6 +435,37 @@ export function createDevSession(options) {
|
|
|
419
435
|
: 'soft reload ok');
|
|
420
436
|
continue;
|
|
421
437
|
}
|
|
438
|
+
if (batch.designsChanged.length || batch.designsDeleted.length || batch.designsDirty) {
|
|
439
|
+
log.info(`designs change detected (${batch.designsChanged.length} update, ${batch.designsDeleted.length} delete) — rebuilding styles…`);
|
|
440
|
+
const changes = [
|
|
441
|
+
...batch.designsChanged.map((p) => ({ path: p, kind: /** @type {'update'} */ ('update') })),
|
|
442
|
+
...batch.designsDeleted.map((p) => ({ path: p, kind: /** @type {'delete'} */ ('delete') })),
|
|
443
|
+
];
|
|
444
|
+
const report = rebuild(changes.length ? changes : undefined);
|
|
445
|
+
if (!printReport(report, 'rebuild')) {
|
|
446
|
+
log.warn('designs rebuild failed — keeping previous styles');
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
if (batch.docsDirty || projectHasDocuments(project)) {
|
|
450
|
+
const docs = await buildIntegratedDocuments({ projectRoot: project, outDir });
|
|
451
|
+
if (!docs.ok)
|
|
452
|
+
log.warn('document mount rebuild failed — keeping previous docs');
|
|
453
|
+
}
|
|
454
|
+
if (wechatPreview) {
|
|
455
|
+
if (!packWechatPreview())
|
|
456
|
+
log.warn('wechat pack failed — keeping previous dist/wechat');
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
const kind = await reloadAfterBuild({
|
|
460
|
+
affectedChunks: report.affectedChunks ?? [],
|
|
461
|
+
seedChunks: report.seedChunks ?? [],
|
|
462
|
+
emitted: report.emitted ?? [],
|
|
463
|
+
full: true,
|
|
464
|
+
islandHmr: false,
|
|
465
|
+
});
|
|
466
|
+
log.info(kind === 'respawn' ? 'reload ok (respawned; designs)' : 'soft reload ok (full page; designs)');
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
422
469
|
if (batch.localesDirty) {
|
|
423
470
|
log.info('locales change detected — re-emitting #locales runtime…');
|
|
424
471
|
if (!emitLocales()) {
|
|
@@ -59,6 +59,19 @@ export declare function collectDevWatchRoots(opts: any): {
|
|
|
59
59
|
dependencyRoots: unknown[];
|
|
60
60
|
applicationRoots: any[];
|
|
61
61
|
};
|
|
62
|
+
/**
|
|
63
|
+
* Classify which watch bucket a root belongs to.
|
|
64
|
+
* @param {string} root
|
|
65
|
+
* @param {{
|
|
66
|
+
* src: string,
|
|
67
|
+
* docsRoot: string,
|
|
68
|
+
* localesRoot: string,
|
|
69
|
+
* designsRoot: string,
|
|
70
|
+
* dependencyRoots: string[],
|
|
71
|
+
* }} ctx
|
|
72
|
+
* @returns {'src' | 'locales' | 'docs' | 'designs' | 'dep' | 'other'}
|
|
73
|
+
*/
|
|
74
|
+
export declare function classifyWatchRoot(root: any, ctx: any): "src" | "locales" | "docs" | "designs" | "dep" | "other";
|
|
62
75
|
/**
|
|
63
76
|
* Classify whether a changed file lives under a dependency watch root (not app src).
|
|
64
77
|
* @param {string} file
|
package/dist/dev-watch-roots.js
CHANGED
|
@@ -209,6 +209,31 @@ export function collectDevWatchRoots(opts) {
|
|
|
209
209
|
}
|
|
210
210
|
return { roots, dependencyRoots, applicationRoots };
|
|
211
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* Classify which watch bucket a root belongs to.
|
|
214
|
+
* @param {string} root
|
|
215
|
+
* @param {{
|
|
216
|
+
* src: string,
|
|
217
|
+
* docsRoot: string,
|
|
218
|
+
* localesRoot: string,
|
|
219
|
+
* designsRoot: string,
|
|
220
|
+
* dependencyRoots: string[],
|
|
221
|
+
* }} ctx
|
|
222
|
+
* @returns {'src' | 'locales' | 'docs' | 'designs' | 'dep' | 'other'}
|
|
223
|
+
*/
|
|
224
|
+
export function classifyWatchRoot(root, ctx) {
|
|
225
|
+
if (root === ctx.src)
|
|
226
|
+
return 'src';
|
|
227
|
+
if (root === ctx.localesRoot)
|
|
228
|
+
return 'locales';
|
|
229
|
+
if (root === ctx.docsRoot)
|
|
230
|
+
return 'docs';
|
|
231
|
+
if (root === ctx.designsRoot)
|
|
232
|
+
return 'designs';
|
|
233
|
+
if ((ctx.dependencyRoots || []).includes(root))
|
|
234
|
+
return 'dep';
|
|
235
|
+
return 'other';
|
|
236
|
+
}
|
|
212
237
|
/**
|
|
213
238
|
* Classify whether a changed file lives under a dependency watch root (not app src).
|
|
214
239
|
* @param {string} file
|
package/dist/document-build.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @param {{ projectRoot: string, outDir?: string, strict?: boolean, engines?: { markdown?: string } }} opts
|
|
2
|
+
* @param {{ projectRoot: string, outDir?: string, appDistDir?: string, strict?: boolean, engines?: { markdown?: string } }} opts
|
|
3
3
|
*/
|
|
4
4
|
export declare function buildDocuments(opts: any): Promise<{
|
|
5
5
|
ok: boolean;
|
|
@@ -57,6 +57,7 @@ export declare function buildDocuments(opts: any): Promise<{
|
|
|
57
57
|
outDir: string;
|
|
58
58
|
designs: string;
|
|
59
59
|
designsCss: any;
|
|
60
|
+
hostShell: string;
|
|
60
61
|
pages: any[];
|
|
61
62
|
evidence: string;
|
|
62
63
|
search: string;
|
package/dist/document-build.js
CHANGED
|
@@ -9,23 +9,27 @@ import { resolveDocumentDesignsCss } from './document-designs.js';
|
|
|
9
9
|
import { enrichDocumentContent, pageHtmlRel } from './document-enrich.js';
|
|
10
10
|
import { enrichDocumentEvidence } from './document-evidence.js';
|
|
11
11
|
import { artifactHrefFromHtml, buildDocumentIslands, buildDocumentSearch, collectFenceBodies, renderIslandShellsHtml, } from './document-interactive.js';
|
|
12
|
+
import { assertIntegratedDistReady, renderCompiledDocumentLayout } from './document-layout-render.js';
|
|
12
13
|
import { resolveMarkdownEngine } from './document-markdown.js';
|
|
14
|
+
import { loadLocalesRouting } from './document-routing-config.js';
|
|
13
15
|
import { DOCUMENT_VIEW_SCHEMA } from './document-schema.js';
|
|
14
16
|
import { createWorkspace } from './index.js';
|
|
15
17
|
import { requireNativeAddon } from './native-addon.js';
|
|
16
18
|
import { writePrettyJsonFile } from './pretty-json.js';
|
|
17
19
|
/**
|
|
18
|
-
* @param {{ projectRoot: string, outDir?: string, strict?: boolean, engines?: { markdown?: string } }} opts
|
|
20
|
+
* @param {{ projectRoot: string, outDir?: string, appDistDir?: string, strict?: boolean, engines?: { markdown?: string } }} opts
|
|
19
21
|
*/
|
|
20
22
|
export async function buildDocuments(opts) {
|
|
21
23
|
const projectRoot = path.resolve(opts.projectRoot);
|
|
22
24
|
const outDir = path.resolve(opts.outDir || path.join(projectRoot, 'dist', 'documents'));
|
|
23
25
|
const strict = Boolean(opts.strict);
|
|
24
26
|
const manifest = checkDocuments({ projectRoot, strict });
|
|
27
|
+
const routing = loadLocalesRouting(projectRoot) || { strategy: 'prefix' };
|
|
25
28
|
const engine = await resolveMarkdownEngine({ engines: opts.engines, projectRoot });
|
|
26
29
|
const enriched = enrichDocumentContent(manifest, {
|
|
27
30
|
analyzeMarkdown: engine.analyzeMarkdown,
|
|
28
31
|
projectRoot,
|
|
32
|
+
routing,
|
|
29
33
|
});
|
|
30
34
|
manifest.diagnostics = enriched.diagnostics;
|
|
31
35
|
const evidence = await enrichDocumentEvidence(manifest, {
|
|
@@ -60,8 +64,12 @@ export async function buildDocuments(opts) {
|
|
|
60
64
|
});
|
|
61
65
|
manifest.search = search;
|
|
62
66
|
manifest.islands = islands;
|
|
63
|
-
const
|
|
64
|
-
const
|
|
67
|
+
const integratedMount = (manifest.mounts || []).some((m) => m.mode === 'integrated');
|
|
68
|
+
const appDistDir = resolveAppDistDir(opts, outDir);
|
|
69
|
+
const useCompiledShell = Boolean(integratedMount && appDistDir);
|
|
70
|
+
if (integratedMount && appDistDir) {
|
|
71
|
+
assertIntegratedDistReady(appDistDir);
|
|
72
|
+
}
|
|
65
73
|
fs.mkdirSync(outDir, { recursive: true });
|
|
66
74
|
const designs = resolveDocumentDesignsCss(projectRoot);
|
|
67
75
|
/** @type {string | null} */
|
|
@@ -92,6 +100,20 @@ export async function buildDocuments(opts) {
|
|
|
92
100
|
pageKey: page.identity.pageKey,
|
|
93
101
|
locale: page.identity.locale,
|
|
94
102
|
});
|
|
103
|
+
const slotHtml = buildDocumentSlotHtml({
|
|
104
|
+
nav,
|
|
105
|
+
bodyHtml: info.html,
|
|
106
|
+
headings: info.headings,
|
|
107
|
+
htmlRel,
|
|
108
|
+
route: info.route,
|
|
109
|
+
routing,
|
|
110
|
+
searchShellHtml: shells.searchHtml,
|
|
111
|
+
playgroundShellHtml: shells.playgroundHtml,
|
|
112
|
+
});
|
|
113
|
+
let compiledLayoutHtml = null;
|
|
114
|
+
if (useCompiledShell) {
|
|
115
|
+
compiledLayoutHtml = await renderCompiledDocumentLayout(appDistDir, page.identity.locale, slotHtml);
|
|
116
|
+
}
|
|
95
117
|
const view = {
|
|
96
118
|
schema: DOCUMENT_VIEW_SCHEMA,
|
|
97
119
|
pageKey: page.identity.pageKey,
|
|
@@ -105,7 +127,7 @@ export async function buildDocuments(opts) {
|
|
|
105
127
|
designsCss: designsHref,
|
|
106
128
|
noJsReadable: true,
|
|
107
129
|
hydrate: 'island-only',
|
|
108
|
-
hostShell:
|
|
130
|
+
hostShell: useCompiledShell ? 'compiled-layout' : false,
|
|
109
131
|
islands: ['DocumentSearch'].concat((islands.islands || [])
|
|
110
132
|
.filter((isl) => isl.kind === 'playground' &&
|
|
111
133
|
isl.fence?.locale === page.identity.locale &&
|
|
@@ -127,7 +149,9 @@ export async function buildDocuments(opts) {
|
|
|
127
149
|
htmlRel,
|
|
128
150
|
searchShellHtml: shells.searchHtml,
|
|
129
151
|
playgroundShellHtml: shells.playgroundHtml,
|
|
130
|
-
|
|
152
|
+
routing,
|
|
153
|
+
compiledLayoutHtml,
|
|
154
|
+
useCompiledShell,
|
|
131
155
|
});
|
|
132
156
|
fs.writeFileSync(htmlAbs, html, 'utf8');
|
|
133
157
|
written.push({ route: info.route, htmlPath: htmlRel, viewPath: viewRel });
|
|
@@ -143,6 +167,7 @@ export async function buildDocuments(opts) {
|
|
|
143
167
|
outDir: path.relative(projectRoot, outDir).replace(/\\/g, '/') || '.',
|
|
144
168
|
designs: designs.source,
|
|
145
169
|
designsCss: designsHref,
|
|
170
|
+
hostShell: useCompiledShell ? 'compiled-layout' : 'standalone',
|
|
146
171
|
pages: written,
|
|
147
172
|
evidence: 'document.evidence.json',
|
|
148
173
|
search: 'document.search.json',
|
|
@@ -156,25 +181,24 @@ export async function buildDocuments(opts) {
|
|
|
156
181
|
return { ok: true, manifest: manifestOut, outDir, pages: written, search, islands };
|
|
157
182
|
}
|
|
158
183
|
/**
|
|
159
|
-
*
|
|
160
|
-
*
|
|
184
|
+
* @param {{ appDistDir?: string }} opts
|
|
185
|
+
* @param {string} outDir
|
|
161
186
|
*/
|
|
162
|
-
function
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
/** @type {string[]} */
|
|
167
|
-
const cssHrefs = [];
|
|
168
|
-
if (hostChrome) {
|
|
169
|
-
// Integrated documents are served with pretty directory URLs. Root
|
|
170
|
-
// absolute assets remain correct for both emitted files and rewrites.
|
|
171
|
-
cssHrefs.push('/vmz.css');
|
|
187
|
+
function resolveAppDistDir(opts, outDir) {
|
|
188
|
+
if (opts.appDistDir) {
|
|
189
|
+
const p = path.resolve(opts.appDistDir);
|
|
190
|
+
return fs.existsSync(path.join(p, 'vmz-dom.js')) ? p : null;
|
|
172
191
|
}
|
|
173
|
-
|
|
174
|
-
|
|
192
|
+
return fs.existsSync(path.join(outDir, 'vmz-dom.js')) ? outDir : null;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Document main column + sidebar (injected into DocumentLayout slot).
|
|
196
|
+
*/
|
|
197
|
+
function buildDocumentSlotHtml({ nav, bodyHtml, headings, htmlRel, route, routing, searchShellHtml = '', playgroundShellHtml = '' }) {
|
|
198
|
+
const esc = (s) => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
175
199
|
const navItems = nav
|
|
176
200
|
.map((n) => {
|
|
177
|
-
const href = relativeHref(htmlRel, n.href, route);
|
|
201
|
+
const href = routing.strategy === 'none' || routing.strategy === 'domain' ? n.href : relativeHref(htmlRel, n.href, route);
|
|
178
202
|
const current = n.href === route ? ' aria-current="page"' : '';
|
|
179
203
|
return ` <li><a href="${esc(href)}"${current}>${esc(n.title)}</a></li>`;
|
|
180
204
|
})
|
|
@@ -189,15 +213,7 @@ function renderStaticHtml({ title, locale, route, nav, bodyHtml, headings, desig
|
|
|
189
213
|
${navItems}
|
|
190
214
|
</ul>
|
|
191
215
|
</nav>`;
|
|
192
|
-
|
|
193
|
-
let bodyInner;
|
|
194
|
-
if (hostChrome) {
|
|
195
|
-
const header = hostChrome.header.replace(/(<a\s+href="\/d\/?")([^>]*>文档<\/a>)/, '$1 aria-current="page"$2');
|
|
196
|
-
bodyInner = ` <div class="site site--docs">
|
|
197
|
-
<a class="skip-link" href="#main">Skip to content</a>
|
|
198
|
-
${header}
|
|
199
|
-
<div class="doc-body">
|
|
200
|
-
<aside class="doc-sidebar">
|
|
216
|
+
return ` <aside class="doc-sidebar">
|
|
201
217
|
${docsNav}
|
|
202
218
|
${searchShellHtml}
|
|
203
219
|
</aside>
|
|
@@ -206,15 +222,47 @@ ${searchShellHtml}
|
|
|
206
222
|
${bodyHtml}
|
|
207
223
|
${playgroundShellHtml}
|
|
208
224
|
</main>
|
|
209
|
-
</div
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
225
|
+
</div>`;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* No-JS readable static HTML: nav + main landmarks, Island shells without scripts.
|
|
229
|
+
* Integrated mounts wrap content in compiled DocumentLayout (SiteHeader/SiteFooter SSR).
|
|
230
|
+
*/
|
|
231
|
+
function renderStaticHtml({ title, locale, route, nav, bodyHtml, headings, designsHref, htmlRel, searchShellHtml = '', playgroundShellHtml = '', routing = { strategy: 'prefix' }, compiledLayoutHtml, useCompiledShell = false, }) {
|
|
232
|
+
const esc = (s) => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
233
|
+
const depth = htmlRel.split('/').length - 1;
|
|
234
|
+
const prefix = depth > 0 ? '../'.repeat(depth) : './';
|
|
235
|
+
/** @type {string[]} */
|
|
236
|
+
const cssHrefs = [];
|
|
237
|
+
if (useCompiledShell) {
|
|
238
|
+
cssHrefs.push('/vmz.css');
|
|
239
|
+
}
|
|
240
|
+
if (designsHref)
|
|
241
|
+
cssHrefs.push(useCompiledShell ? `/${designsHref}` : prefix + designsHref);
|
|
242
|
+
/** @type {string} */
|
|
243
|
+
let bodyInner;
|
|
244
|
+
if (useCompiledShell) {
|
|
245
|
+
bodyInner = compiledLayoutHtml;
|
|
214
246
|
}
|
|
215
247
|
else {
|
|
248
|
+
const navItems = nav
|
|
249
|
+
.map((n) => {
|
|
250
|
+
const href = routing.strategy === 'none' || routing.strategy === 'domain' ? n.href : relativeHref(htmlRel, n.href, route);
|
|
251
|
+
const current = n.href === route ? ' aria-current="page"' : '';
|
|
252
|
+
return ` <li><a href="${esc(href)}"${current}>${esc(n.title)}</a></li>`;
|
|
253
|
+
})
|
|
254
|
+
.join('\n');
|
|
255
|
+
const toc = headings.length > 1
|
|
256
|
+
? `<nav aria-label="On this page" class="toc">\n <ol>\n${headings
|
|
257
|
+
.map((h) => ` <li class="h${h.level}"><a href="#${esc(h.id)}">${esc(h.text)}</a></li>`)
|
|
258
|
+
.join('\n')}\n </ol>\n </nav>\n`
|
|
259
|
+
: '';
|
|
216
260
|
bodyInner = ` <a class="skip-link" href="#main">Skip to content</a>
|
|
217
|
-
|
|
261
|
+
<nav aria-label="Documents" class="doc-subnav">
|
|
262
|
+
<ul>
|
|
263
|
+
${navItems}
|
|
264
|
+
</ul>
|
|
265
|
+
</nav>
|
|
218
266
|
${searchShellHtml}
|
|
219
267
|
${toc}<main id="main">
|
|
220
268
|
${bodyHtml}
|
|
@@ -234,28 +282,6 @@ ${playgroundShellHtml}
|
|
|
234
282
|
bodyAttrs: ['data-vmz-hydrate', 'island-only'],
|
|
235
283
|
});
|
|
236
284
|
}
|
|
237
|
-
/**
|
|
238
|
-
* Integrated DocumentMount: reuse host SiteHeader / SiteFooter .vmz templates.
|
|
239
|
-
* @param {string} projectRoot
|
|
240
|
-
* @returns {{ header: string, footer: string } | null}
|
|
241
|
-
*/
|
|
242
|
-
function resolveHostSiteChrome(projectRoot) {
|
|
243
|
-
const headerPath = path.join(projectRoot, 'src', 'components', 'SiteHeader.vmz');
|
|
244
|
-
const footerPath = path.join(projectRoot, 'src', 'components', 'SiteFooter.vmz');
|
|
245
|
-
if (!fs.existsSync(headerPath) || !fs.existsSync(footerPath))
|
|
246
|
-
return null;
|
|
247
|
-
const header = extractVmzTemplateHtml(headerPath);
|
|
248
|
-
const footer = extractVmzTemplateHtml(footerPath);
|
|
249
|
-
if (!header || !footer)
|
|
250
|
-
return null;
|
|
251
|
-
return { header, footer };
|
|
252
|
-
}
|
|
253
|
-
/** @param {string} filePath */
|
|
254
|
-
function extractVmzTemplateHtml(filePath) {
|
|
255
|
-
const src = fs.readFileSync(filePath, 'utf8');
|
|
256
|
-
const m = src.match(/<template>([\s\S]*?)<\/template>/);
|
|
257
|
-
return m ? m[1].trim() : '';
|
|
258
|
-
}
|
|
259
285
|
function relativeHref(fromHtmlRel, toRoute, _fromRoute) {
|
|
260
286
|
const toParts = String(toRoute).replace(/^\//, '').split('/').filter(Boolean);
|
|
261
287
|
let toRel;
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* @param {string} routeBase e.g. /docs
|
|
3
3
|
* @param {string} locale
|
|
4
4
|
* @param {string} pageKey
|
|
5
|
+
* @param {{ strategy?: string }} [routing]
|
|
5
6
|
*/
|
|
6
|
-
export declare function pageRoute(routeBase: any, locale: any, pageKey: any): string;
|
|
7
|
+
export declare function pageRoute(routeBase: any, locale: any, pageKey: any, routing?: {}): string;
|
|
7
8
|
/**
|
|
8
9
|
* Static file path relative to out dir (posix).
|
|
9
10
|
* @param {string} routeBase
|
package/dist/document-enrich.js
CHANGED
|
@@ -9,10 +9,16 @@ import { DIAG } from './document-schema.js';
|
|
|
9
9
|
* @param {string} routeBase e.g. /docs
|
|
10
10
|
* @param {string} locale
|
|
11
11
|
* @param {string} pageKey
|
|
12
|
+
* @param {{ strategy?: string }} [routing]
|
|
12
13
|
*/
|
|
13
|
-
export function pageRoute(routeBase, locale, pageKey) {
|
|
14
|
+
export function pageRoute(routeBase, locale, pageKey, routing = {}) {
|
|
14
15
|
const base = String(routeBase || '/').replace(/\/$/, '') || '';
|
|
15
16
|
const key = pageKey === 'index' ? '' : pageKey.replace(/\\/g, '/');
|
|
17
|
+
const strategy = routing.strategy || 'prefix';
|
|
18
|
+
if (strategy === 'none' || strategy === 'domain') {
|
|
19
|
+
const parts = [base.replace(/^\//, ''), key].filter((p) => p !== '');
|
|
20
|
+
return '/' + (parts.length ? parts.join('/') : '');
|
|
21
|
+
}
|
|
16
22
|
const parts = [base.replace(/^\//, ''), locale, key].filter((p) => p !== '');
|
|
17
23
|
return '/' + parts.join('/');
|
|
18
24
|
}
|
|
@@ -35,6 +41,7 @@ export function pageHtmlRel(routeBase, locale, pageKey) {
|
|
|
35
41
|
*/
|
|
36
42
|
export function enrichDocumentContent(manifest, ctx) {
|
|
37
43
|
const routeBase = manifest.mounts?.[0]?.routeBase || '/docs';
|
|
44
|
+
const routing = ctx.routing || { strategy: 'prefix' };
|
|
38
45
|
/** @type {Map<string, { html: string, headings: any[], links: any[], title: string, route: string, anchors: string[] }>} */
|
|
39
46
|
const byId = new Map();
|
|
40
47
|
/** @type {import('./document-schema.js').DocumentDiagnostic[]} */
|
|
@@ -45,7 +52,7 @@ export function enrichDocumentContent(manifest, ctx) {
|
|
|
45
52
|
const abs = path.isAbsolute(page.sourcePath) ? page.sourcePath : path.join(manifest.root, page.sourcePath);
|
|
46
53
|
const source = fs.existsSync(abs) ? fs.readFileSync(abs, 'utf8') : '';
|
|
47
54
|
const analyzed = ctx.analyzeMarkdown(source);
|
|
48
|
-
const route = pageRoute(routeBase, page.identity.locale, page.identity.pageKey);
|
|
55
|
+
const route = pageRoute(routeBase, page.identity.locale, page.identity.pageKey, routing);
|
|
49
56
|
const anchors = analyzed.headings.map((h) => h.id);
|
|
50
57
|
const title = analyzed.headings.find((h) => h.level === 1)?.text || analyzed.headings[0]?.text || page.identity.pageKey;
|
|
51
58
|
// Duplicate anchors on page
|
|
@@ -61,7 +68,11 @@ export function enrichDocumentContent(manifest, ctx) {
|
|
|
61
68
|
}
|
|
62
69
|
seen.add(id);
|
|
63
70
|
}
|
|
64
|
-
|
|
71
|
+
const owner = `${page.identity.locale}:${page.identity.pageKey}`;
|
|
72
|
+
if (routing.strategy === 'none' || routing.strategy === 'domain') {
|
|
73
|
+
routeOwners.set(route, owner);
|
|
74
|
+
}
|
|
75
|
+
else if (routeOwners.has(route)) {
|
|
65
76
|
diagnostics.push({
|
|
66
77
|
code: DIAG.ROUTE_DUPLICATE,
|
|
67
78
|
severity: 'error',
|
|
@@ -70,7 +81,7 @@ export function enrichDocumentContent(manifest, ctx) {
|
|
|
70
81
|
});
|
|
71
82
|
}
|
|
72
83
|
else {
|
|
73
|
-
routeOwners.set(route,
|
|
84
|
+
routeOwners.set(route, owner);
|
|
74
85
|
}
|
|
75
86
|
page.route = route;
|
|
76
87
|
page.anchors = anchors;
|
|
@@ -7,6 +7,8 @@ import fs from 'node:fs';
|
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import { buildDocuments } from './document-build.js';
|
|
9
9
|
import { resolveDocumentsRoot } from './document-check.js';
|
|
10
|
+
import { loadLocalesRouting } from './document-routing-config.js';
|
|
11
|
+
import { pageHtmlRel } from './document-enrich.js';
|
|
10
12
|
import { log } from './log.js';
|
|
11
13
|
import { requireNativeAddon } from './native-addon.js';
|
|
12
14
|
/**
|
|
@@ -31,6 +33,7 @@ export async function buildIntegratedDocuments(opts) {
|
|
|
31
33
|
const result = await buildDocuments({
|
|
32
34
|
projectRoot,
|
|
33
35
|
outDir,
|
|
36
|
+
appDistDir: outDir,
|
|
34
37
|
strict: Boolean(opts.strict),
|
|
35
38
|
});
|
|
36
39
|
if (!result.ok) {
|
|
@@ -40,7 +43,7 @@ export async function buildIntegratedDocuments(opts) {
|
|
|
40
43
|
}
|
|
41
44
|
return { ok: false, error: 'document diagnostics', pages: 0 };
|
|
42
45
|
}
|
|
43
|
-
writeMountRootRedirects(result.manifest, outDir);
|
|
46
|
+
writeMountRootRedirects(result.manifest, outDir, projectRoot);
|
|
44
47
|
log.info(`document mount: pages=${result.pages.length} → ${path.relative(process.cwd(), outDir) || '.'}`);
|
|
45
48
|
return { ok: true, pages: result.pages.length };
|
|
46
49
|
}
|
|
@@ -51,22 +54,34 @@ export async function buildIntegratedDocuments(opts) {
|
|
|
51
54
|
}
|
|
52
55
|
}
|
|
53
56
|
/**
|
|
54
|
-
* Emit `{routeBase}/index.html`
|
|
57
|
+
* Emit `{routeBase}/index.html` for integrated mounts.
|
|
58
|
+
* `routing.strategy: none` → copy default-locale docs index (LocaleId is Host state).
|
|
59
|
+
* prefix strategy → redirect HTML to `{routeBase}/{defaultLocale}/`.
|
|
55
60
|
* @param {import('./document-schema.js').DocumentManifest} manifest
|
|
56
61
|
* @param {string} outDir
|
|
62
|
+
* @param {string} projectRoot
|
|
57
63
|
*/
|
|
58
|
-
function writeMountRootRedirects(manifest, outDir) {
|
|
64
|
+
function writeMountRootRedirects(manifest, outDir, projectRoot) {
|
|
59
65
|
const defaultLocale = manifest.defaultLocale || manifest.locales?.[0];
|
|
60
66
|
if (!defaultLocale)
|
|
61
67
|
return;
|
|
68
|
+
const routing = loadLocalesRouting(projectRoot) || { strategy: 'prefix' };
|
|
62
69
|
for (const mount of manifest.mounts || []) {
|
|
63
70
|
if (!mount?.routeBase || mount.routeBase === '/')
|
|
64
71
|
continue;
|
|
65
72
|
const base = String(mount.routeBase).replace(/\/$/, '');
|
|
66
|
-
const target = `${base}/${defaultLocale}/`;
|
|
67
73
|
const relDir = base.replace(/^\//, '');
|
|
68
74
|
const abs = path.join(outDir, relDir, 'index.html');
|
|
69
75
|
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
76
|
+
if (routing.strategy === 'none' || routing.strategy === 'domain') {
|
|
77
|
+
const srcRel = pageHtmlRel(base, defaultLocale, 'index');
|
|
78
|
+
const srcAbs = path.join(outDir, srcRel);
|
|
79
|
+
if (fs.existsSync(srcAbs)) {
|
|
80
|
+
fs.copyFileSync(srcAbs, abs);
|
|
81
|
+
}
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const target = `${base}/${defaultLocale}/`;
|
|
70
85
|
const native = requireNativeAddon();
|
|
71
86
|
if (typeof native.generateRedirectHtml !== 'function') {
|
|
72
87
|
throw new Error('vmz native addon missing generateRedirectHtml — rebuild with `pnpm napi:build`');
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integrated DocumentMount — compile host chrome via DocumentLayout + createRenderHost.
|
|
3
|
+
* Replaces the removed regex template lowering in document-host-chrome.ts.
|
|
4
|
+
*/
|
|
5
|
+
/** @param {string} distDir */
|
|
6
|
+
export declare function resolveDocumentLayoutChunkId(distDir: any): string;
|
|
7
|
+
/**
|
|
8
|
+
* @param {string} distDir
|
|
9
|
+
*/
|
|
10
|
+
export declare function assertIntegratedDistReady(distDir: any): string;
|
|
11
|
+
/**
|
|
12
|
+
* @param {string} html
|
|
13
|
+
*/
|
|
14
|
+
export declare function assertCompiledShellHtml(html: any): void;
|
|
15
|
+
/**
|
|
16
|
+
* @param {string} distDir
|
|
17
|
+
* @param {string} localeId
|
|
18
|
+
* @param {string} slotHtml
|
|
19
|
+
*/
|
|
20
|
+
export declare function renderCompiledDocumentLayout(distDir: any, localeId: any, slotHtml: any): Promise<any>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Integrated DocumentMount — compile host chrome via DocumentLayout + createRenderHost.
|
|
4
|
+
* Replaces the removed regex template lowering in document-host-chrome.ts.
|
|
5
|
+
*/
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { pathToFileURL } from 'node:url';
|
|
9
|
+
import { createRenderHost } from '@vmz/core/render-host';
|
|
10
|
+
/** @param {string} distDir */
|
|
11
|
+
export function resolveDocumentLayoutChunkId(distDir) {
|
|
12
|
+
for (const chunkId of ['layouts/DocumentLayout', 'components/DocumentLayout']) {
|
|
13
|
+
if (fs.existsSync(path.join(distDir, `${chunkId}.client.js`)))
|
|
14
|
+
return chunkId;
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* @param {string} distDir
|
|
20
|
+
*/
|
|
21
|
+
export function assertIntegratedDistReady(distDir) {
|
|
22
|
+
const dom = path.join(distDir, 'vmz-dom.js');
|
|
23
|
+
if (!fs.existsSync(dom)) {
|
|
24
|
+
throw new Error('integrated document mount requires vmz build output (vmz-dom.js in app dist). Run `vmz build` before document emit.');
|
|
25
|
+
}
|
|
26
|
+
const chunkId = resolveDocumentLayoutChunkId(distDir);
|
|
27
|
+
if (!chunkId) {
|
|
28
|
+
throw new Error('integrated document mount requires compiled DocumentLayout (add src/layouts/DocumentLayout.vmz and rebuild)');
|
|
29
|
+
}
|
|
30
|
+
return chunkId;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* @param {string} html
|
|
34
|
+
*/
|
|
35
|
+
export function assertCompiledShellHtml(html) {
|
|
36
|
+
const header = html.match(/<header[^>]*data-vmz-fixture="site-header"[\s\S]*?<\/header>/i)?.[0] ?? '';
|
|
37
|
+
const footer = html.match(/<footer[^>]*data-vmz-fixture="site-footer"[\s\S]*?<\/footer>/i)?.[0] ?? '';
|
|
38
|
+
for (const part of [header, footer]) {
|
|
39
|
+
const leak = part.match(/\{[A-Za-z][A-Za-z0-9]*\}/);
|
|
40
|
+
if (leak) {
|
|
41
|
+
throw new Error(`document layout SSR leaked binding placeholder ${leak[0]}`);
|
|
42
|
+
}
|
|
43
|
+
if (/<(?:Link|Button|Icon)\b/.test(part)) {
|
|
44
|
+
throw new Error('document layout SSR leaked uncompiled VMZ component tag in chrome');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (!header || !footer) {
|
|
48
|
+
throw new Error('document layout SSR missing compiled SiteHeader/SiteFooter fixtures');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* @param {string} distDir
|
|
53
|
+
* @param {string} chunkId
|
|
54
|
+
*/
|
|
55
|
+
async function loadCtor(distDir, chunkId) {
|
|
56
|
+
const href = pathToFileURL(path.join(distDir, `${chunkId}.client.js`)).href;
|
|
57
|
+
const mod = await import(`${href}?t=${Date.now()}`);
|
|
58
|
+
return mod.default;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* @param {string} distDir
|
|
62
|
+
* @param {string} localeId
|
|
63
|
+
* @param {string} slotHtml
|
|
64
|
+
*/
|
|
65
|
+
export async function renderCompiledDocumentLayout(distDir, localeId, slotHtml) {
|
|
66
|
+
const chunkId = assertIntegratedDistReady(distDir);
|
|
67
|
+
const prevHint = globalThis.__vmzLocaleIdHint;
|
|
68
|
+
globalThis.__vmzLocaleIdHint = localeId;
|
|
69
|
+
try {
|
|
70
|
+
if (typeof globalThis.document !== 'undefined' && globalThis.document?.documentElement) {
|
|
71
|
+
globalThis.document.documentElement.setAttribute('data-locale', localeId);
|
|
72
|
+
globalThis.document.documentElement.setAttribute('lang', localeId);
|
|
73
|
+
}
|
|
74
|
+
const host = await createRenderHost(distDir, { strictDeployment: true, preload: 'none' });
|
|
75
|
+
await host.ensureComponents([chunkId]);
|
|
76
|
+
const Layout = await loadCtor(distDir, chunkId);
|
|
77
|
+
const html = await host.renderToString(Layout, {}, { slotHtml });
|
|
78
|
+
assertCompiledShellHtml(html);
|
|
79
|
+
return html;
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
if (prevHint === undefined)
|
|
83
|
+
delete globalThis.__vmzLocaleIdHint;
|
|
84
|
+
else
|
|
85
|
+
globalThis.__vmzLocaleIdHint = prevHint;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project locale routing config for document mount (locales/locales.json5).
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* @param {string} projectRoot
|
|
6
|
+
* @returns {{ strategy?: string, defaultLocale?: string } | null}
|
|
7
|
+
*/
|
|
8
|
+
export declare function loadLocalesRouting(projectRoot: any): any;
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} routeBase
|
|
11
|
+
* @param {string} pageKey
|
|
12
|
+
*/
|
|
13
|
+
export declare function docsRouteNone(routeBase: any, pageKey: any): string;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Project locale routing config for document mount (locales/locales.json5).
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
/**
|
|
8
|
+
* @param {string} projectRoot
|
|
9
|
+
* @returns {{ strategy?: string, defaultLocale?: string } | null}
|
|
10
|
+
*/
|
|
11
|
+
export function loadLocalesRouting(projectRoot) {
|
|
12
|
+
const p = path.join(projectRoot, 'locales', 'locales.json5');
|
|
13
|
+
if (!fs.existsSync(p))
|
|
14
|
+
return null;
|
|
15
|
+
try {
|
|
16
|
+
const raw = fs.readFileSync(p, 'utf8');
|
|
17
|
+
let s = raw.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '');
|
|
18
|
+
const m = s.match(/routing\s*:\s*\{([\s\S]*?)\}/);
|
|
19
|
+
if (!m)
|
|
20
|
+
return null;
|
|
21
|
+
let block = `{${m[1]}}`;
|
|
22
|
+
block = block.replace(/([,{]\s*)([A-Za-z_][A-Za-z0-9_]*)\s*:/g, '$1"$2":');
|
|
23
|
+
block = block.replace(/'([^'\\]*(?:\\.[^'\\]*)*)'/g, (_, inner) => JSON.stringify(inner));
|
|
24
|
+
block = block.replace(/,\s*([}\]])/g, '$1');
|
|
25
|
+
return JSON.parse(block);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* @param {string} routeBase
|
|
33
|
+
* @param {string} pageKey
|
|
34
|
+
*/
|
|
35
|
+
export function docsRouteNone(routeBase, pageKey) {
|
|
36
|
+
const base = String(routeBase || '/').replace(/\/$/, '') || '';
|
|
37
|
+
const key = pageKey === 'index' ? '' : pageKey.replace(/\\/g, '/');
|
|
38
|
+
const parts = [base.replace(/^\//, ''), key].filter((p) => p !== '');
|
|
39
|
+
return '/' + (parts.length ? parts.join('/') : '');
|
|
40
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -150,6 +150,7 @@ export const SERVE_HOST_RUNTIME_FILES = [
|
|
|
150
150
|
['list-client-components.js', 'list-client-components.js'],
|
|
151
151
|
['deployment-registry.js', 'deployment-registry.js'],
|
|
152
152
|
['render-host.js', 'render-host.js'],
|
|
153
|
+
['route-layout-chain.js', 'route-layout-chain.js'],
|
|
153
154
|
];
|
|
154
155
|
/**
|
|
155
156
|
* Copy serve-host + registry bootstrap modules from `@vmz/core` into app outDir.
|
package/dist/static-emit.js
CHANGED
|
@@ -8,6 +8,7 @@ import fs from 'node:fs';
|
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { pathToFileURL } from 'node:url';
|
|
10
10
|
import { createRenderHost } from '@vmz/core/render-host';
|
|
11
|
+
import { resolveRouteLayoutChain } from '@vmz/core/route-layout-chain';
|
|
11
12
|
import { listClientComponentsSync } from '@vmz/core/component-registry';
|
|
12
13
|
import { emitCdnPolicy } from './cdn-policy.js';
|
|
13
14
|
import { emitContentAddressedAssets } from './content-addressed-assets.js';
|
|
@@ -100,7 +101,7 @@ export async function emitWebStatic(distDir, opts = {}) {
|
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
103
|
const meta = await resolvePageMeta(Page, { params, props, pathname: pattern, origin });
|
|
103
|
-
const layoutChain =
|
|
104
|
+
const layoutChain = resolveRouteLayoutChain(distDir, page.chunkId);
|
|
104
105
|
await host.ensureComponents([page.chunkId, ...layoutChain]);
|
|
105
106
|
let bodyHtml = '';
|
|
106
107
|
for await (const chunk of renderToStream(Page, props, {})) {
|
|
@@ -353,24 +354,6 @@ async function loadCtor(distDir, chunkId) {
|
|
|
353
354
|
const mod = await import(`${href}?t=${Date.now()}`);
|
|
354
355
|
return mod.default;
|
|
355
356
|
}
|
|
356
|
-
/**
|
|
357
|
-
* @param {string} distDir
|
|
358
|
-
* @param {string} pageChunkId
|
|
359
|
-
*/
|
|
360
|
-
function resolveLayoutChain(distDir, pageChunkId) {
|
|
361
|
-
const rel = pageChunkId.replace(/^pages\//, '');
|
|
362
|
-
const parts = rel.split('/').filter(Boolean);
|
|
363
|
-
parts.pop();
|
|
364
|
-
/** @type {string[]} */
|
|
365
|
-
const chain = [];
|
|
366
|
-
for (let i = parts.length; i >= 0; i--) {
|
|
367
|
-
const dirParts = parts.slice(0, i);
|
|
368
|
-
const layoutChunk = ['pages', ...dirParts, 'Layout'].join('/');
|
|
369
|
-
if (fs.existsSync(path.join(distDir, `${layoutChunk}.client.js`)))
|
|
370
|
-
chain.unshift(layoutChunk);
|
|
371
|
-
}
|
|
372
|
-
return chain;
|
|
373
|
-
}
|
|
374
357
|
/**
|
|
375
358
|
* @param {string} distDir
|
|
376
359
|
* @param {string} chunkId
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vmz/vmz",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "VMZ Node toolchain — N-API workspace session + CLI (publish name @vmz/vmz)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -48,15 +48,15 @@
|
|
|
48
48
|
}
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@vmz/core": "0.1.
|
|
52
|
-
"@vmz/plugin": "0.1.
|
|
53
|
-
"@vmz/protocol": "0.1.
|
|
51
|
+
"@vmz/core": "0.1.12",
|
|
52
|
+
"@vmz/plugin": "0.1.12",
|
|
53
|
+
"@vmz/protocol": "0.1.12",
|
|
54
54
|
"jiti": "^2.6.1",
|
|
55
55
|
"json5": "^2.2.3"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
|
-
"@vmz/plugin-markdown-it": "0.1.
|
|
59
|
-
"@vmz/test": "0.1.
|
|
58
|
+
"@vmz/plugin-markdown-it": "0.1.12",
|
|
59
|
+
"@vmz/test": "0.1.12",
|
|
60
60
|
"typescript": "^5.8.3"
|
|
61
61
|
},
|
|
62
62
|
"peerDependenciesMeta": {
|
|
@@ -90,12 +90,12 @@
|
|
|
90
90
|
"cli"
|
|
91
91
|
],
|
|
92
92
|
"optionalDependencies": {
|
|
93
|
-
"@vmz/vmz-win32-x64": "0.1.
|
|
94
|
-
"@vmz/vmz-win32-arm64": "0.1.
|
|
95
|
-
"@vmz/vmz-darwin-x64": "0.1.
|
|
96
|
-
"@vmz/vmz-darwin-arm64": "0.1.
|
|
97
|
-
"@vmz/vmz-linux-x64": "0.1.
|
|
98
|
-
"@vmz/vmz-linux-arm64": "0.1.
|
|
93
|
+
"@vmz/vmz-win32-x64": "0.1.12",
|
|
94
|
+
"@vmz/vmz-win32-arm64": "0.1.12",
|
|
95
|
+
"@vmz/vmz-darwin-x64": "0.1.12",
|
|
96
|
+
"@vmz/vmz-darwin-arm64": "0.1.12",
|
|
97
|
+
"@vmz/vmz-linux-x64": "0.1.12",
|
|
98
|
+
"@vmz/vmz-linux-arm64": "0.1.12"
|
|
99
99
|
},
|
|
100
100
|
"publishConfig": {
|
|
101
101
|
"access": "public"
|