@vmz/vmz 0.0.4 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-assemble.d.ts +52 -0
- package/dist/build-assemble.js +191 -0
- package/dist/cdn-policy.d.ts +22 -4
- package/dist/cdn-policy.js +109 -10
- package/dist/cli.js +106 -24
- package/dist/delivery-profile.d.ts +74 -0
- package/dist/delivery-profile.js +279 -0
- package/dist/dev-session.js +2 -1
- package/dist/document-build.js +9 -4
- package/dist/document-enrich.js +8 -0
- package/dist/embedded-packaging.d.ts +22 -0
- package/dist/embedded-packaging.js +113 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.js +8 -1
- package/dist/locale-check.js +11 -8
- package/dist/locale-cmd.js +2 -2
- package/dist/locale-route-emit.d.ts +34 -0
- package/dist/locale-route-emit.js +134 -0
- package/dist/locale-router.d.ts +20 -0
- package/dist/locale-router.js +68 -0
- package/dist/log.d.ts +2 -2
- package/dist/log.js +11 -3
- package/dist/pack.d.ts +40 -0
- package/dist/pack.js +108 -0
- package/dist/plugin-host.d.ts +1 -1
- package/dist/plugin-host.js +2 -2
- package/dist/production-test-pack.d.ts +0 -14
- package/dist/production-test-pack.js +23 -28
- package/dist/release-pack.js +12 -3
- package/dist/server-artifact.d.ts +140 -0
- package/dist/server-artifact.js +205 -0
- package/dist/server-language-backend.d.ts +89 -0
- package/dist/server-language-backend.js +121 -0
- package/dist/static-emit.d.ts +10 -1
- package/dist/static-emit.js +140 -26
- package/package.json +12 -12
package/dist/static-emit.js
CHANGED
|
@@ -9,6 +9,7 @@ import path from 'node:path';
|
|
|
9
9
|
import { pathToFileURL } from 'node:url';
|
|
10
10
|
import { emitCdnPolicy } from './cdn-policy.js';
|
|
11
11
|
import { emitContentAddressedAssets } from './content-addressed-assets.js';
|
|
12
|
+
import { absoluteUrl, buildLocalePageMeta, localizeBodyLinks } from './locale-router.js';
|
|
12
13
|
export const STATIC_DELIVERY_MANIFEST_SCHEMA = 'vmz.static.delivery_manifest.v0';
|
|
13
14
|
/**
|
|
14
15
|
* @param {string} distDir
|
|
@@ -104,29 +105,46 @@ export async function emitWebStatic(distDir, opts = {}) {
|
|
|
104
105
|
continue;
|
|
105
106
|
bodyHtml = await renderToString(Layout, {}, { slotHtml: bodyHtml });
|
|
106
107
|
}
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
const localeArt = loadLocaleArtifact(distDir);
|
|
109
|
+
// Locale artifact routeId is chunkId (`pages/about`), not the page class name.
|
|
110
|
+
const localeWrites = expandLocaleStaticGenerations({
|
|
111
|
+
localeArt,
|
|
112
|
+
routeId: page.chunkId,
|
|
112
113
|
chunkId: page.chunkId,
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
meta,
|
|
116
|
-
cssEntry: readCssEntry(distDir),
|
|
117
|
-
});
|
|
118
|
-
fs.writeFileSync(absHtml, html, 'utf8');
|
|
119
|
-
generations.push({
|
|
120
|
-
routeId,
|
|
121
|
-
path: pattern,
|
|
122
|
-
chunkId: page.chunkId,
|
|
123
|
-
htmlPath: htmlPath.replaceAll('\\', '/'),
|
|
124
|
-
classification: 'Static',
|
|
125
|
-
title: meta.title,
|
|
126
|
-
description: meta.description,
|
|
127
|
-
canonical: meta.canonical,
|
|
128
|
-
robots: meta.robots,
|
|
114
|
+
pattern,
|
|
115
|
+
origin,
|
|
116
|
+
baseMeta: meta,
|
|
129
117
|
});
|
|
118
|
+
for (const gen of localeWrites) {
|
|
119
|
+
const absHtml = path.join(distDir, gen.htmlPath);
|
|
120
|
+
fs.mkdirSync(path.dirname(absHtml), { recursive: true });
|
|
121
|
+
// Each LocaleId HTML must retain locale on same-app Links (realization authority).
|
|
122
|
+
const localizedBody = gen.localeId && localeArt
|
|
123
|
+
? localizeBodyLinks(bodyHtml, gen.localeId, localeArt)
|
|
124
|
+
: bodyHtml;
|
|
125
|
+
const html = wrapDocument({
|
|
126
|
+
bodyHtml: localizedBody,
|
|
127
|
+
chunkId: page.chunkId,
|
|
128
|
+
layoutChain,
|
|
129
|
+
props,
|
|
130
|
+
meta: gen.meta,
|
|
131
|
+
cssEntry: readCssEntry(distDir),
|
|
132
|
+
});
|
|
133
|
+
fs.writeFileSync(absHtml, html, 'utf8');
|
|
134
|
+
generations.push({
|
|
135
|
+
routeId,
|
|
136
|
+
path: gen.path,
|
|
137
|
+
chunkId: page.chunkId,
|
|
138
|
+
htmlPath: gen.htmlPath.replaceAll('\\', '/'),
|
|
139
|
+
classification: 'Static',
|
|
140
|
+
title: gen.meta.title,
|
|
141
|
+
description: meta.description,
|
|
142
|
+
canonical: gen.meta.canonical,
|
|
143
|
+
robots: gen.meta.robots,
|
|
144
|
+
localeId: gen.localeId || null,
|
|
145
|
+
alternates: Array.isArray(gen.meta.alternates) ? gen.meta.alternates : [],
|
|
146
|
+
});
|
|
147
|
+
}
|
|
130
148
|
}
|
|
131
149
|
const notFoundHtml = wrapDocument({
|
|
132
150
|
bodyHtml: '<main><h1>Not Found</h1><p>route-static-404</p></main>',
|
|
@@ -174,11 +192,13 @@ export async function emitWebStatic(distDir, opts = {}) {
|
|
|
174
192
|
chunkId: g.chunkId,
|
|
175
193
|
htmlPath: g.htmlPath,
|
|
176
194
|
classification: g.classification,
|
|
195
|
+
localeId: g.localeId || null,
|
|
177
196
|
seo: {
|
|
178
197
|
title: g.title,
|
|
179
198
|
description: g.description,
|
|
180
199
|
canonical: g.canonical,
|
|
181
200
|
robots: g.robots,
|
|
201
|
+
alternates: g.alternates || [],
|
|
182
202
|
},
|
|
183
203
|
})),
|
|
184
204
|
skipped,
|
|
@@ -376,7 +396,94 @@ async function resolvePageMeta(Page, ctx) {
|
|
|
376
396
|
const canonical = String(raw.canonical || `${ctx.origin}${ctx.pathname === '/' ? '/' : ctx.pathname}`);
|
|
377
397
|
const robots = String(raw.robots || 'index,follow');
|
|
378
398
|
const lang = String(raw.lang || 'en');
|
|
379
|
-
return { title, description, canonical, robots, lang };
|
|
399
|
+
return { title, description, canonical, robots, lang, alternates: [] };
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* @param {string} distDir
|
|
403
|
+
*/
|
|
404
|
+
function loadLocaleArtifact(distDir) {
|
|
405
|
+
const p = path.join(distDir, '_vmz', 'locale-route-realization.json');
|
|
406
|
+
if (!fs.existsSync(p))
|
|
407
|
+
return null;
|
|
408
|
+
try {
|
|
409
|
+
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
410
|
+
}
|
|
411
|
+
catch {
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Expand one Static route across LocaleId realizations (hreflang seed + prefixed HTML).
|
|
417
|
+
* @param {{
|
|
418
|
+
* localeArt: any,
|
|
419
|
+
* routeId: string,
|
|
420
|
+
* chunkId: string,
|
|
421
|
+
* pattern: string,
|
|
422
|
+
* origin: string,
|
|
423
|
+
* baseMeta: { title: string, description: string, canonical: string, robots: string, lang: string, alternates?: any[] },
|
|
424
|
+
* }} input
|
|
425
|
+
*/
|
|
426
|
+
function expandLocaleStaticGenerations(input) {
|
|
427
|
+
const { localeArt, routeId, pattern, origin, baseMeta } = input;
|
|
428
|
+
if (!localeArt?.realizations?.length) {
|
|
429
|
+
return [
|
|
430
|
+
{
|
|
431
|
+
path: pattern,
|
|
432
|
+
htmlPath: htmlPathForRoute(pattern),
|
|
433
|
+
localeId: null,
|
|
434
|
+
meta: baseMeta,
|
|
435
|
+
},
|
|
436
|
+
];
|
|
437
|
+
}
|
|
438
|
+
const locales = (localeArt.locales || []).map((l) => l.id);
|
|
439
|
+
const directions = Object.fromEntries((localeArt.locales || []).map((l) => [l.id, l.direction || 'ltr']));
|
|
440
|
+
const defaultLocale = localeArt.defaultLocale || locales[0];
|
|
441
|
+
const forRoute = (localeArt.realizations || []).filter((r) => r.routeId === routeId ||
|
|
442
|
+
r.routeId === input.chunkId ||
|
|
443
|
+
r.pathPattern === pattern ||
|
|
444
|
+
(r.path === pattern && !r.prefixed));
|
|
445
|
+
/** @type {any[]} */
|
|
446
|
+
const out = [];
|
|
447
|
+
for (const loc of locales) {
|
|
448
|
+
const hit = forRoute.find((r) => r.localeId === loc);
|
|
449
|
+
if (!hit)
|
|
450
|
+
continue;
|
|
451
|
+
const built = buildLocalePageMeta({
|
|
452
|
+
routeId,
|
|
453
|
+
localeId: loc,
|
|
454
|
+
direction: directions[loc],
|
|
455
|
+
title: baseMeta.title,
|
|
456
|
+
description: baseMeta.description,
|
|
457
|
+
origin,
|
|
458
|
+
realizations: localeArt.realizations,
|
|
459
|
+
locales,
|
|
460
|
+
defaultLocale,
|
|
461
|
+
});
|
|
462
|
+
out.push({
|
|
463
|
+
path: hit.path,
|
|
464
|
+
htmlPath: htmlPathForRoute(hit.path),
|
|
465
|
+
localeId: loc,
|
|
466
|
+
meta: {
|
|
467
|
+
title: baseMeta.title,
|
|
468
|
+
description: baseMeta.description,
|
|
469
|
+
canonical: built.canonical || absoluteUrl(origin, hit.path),
|
|
470
|
+
robots: baseMeta.robots,
|
|
471
|
+
lang: loc,
|
|
472
|
+
dir: directions[loc] || 'ltr',
|
|
473
|
+
alternates: built.alternates || [],
|
|
474
|
+
},
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
return out.length
|
|
478
|
+
? out
|
|
479
|
+
: [
|
|
480
|
+
{
|
|
481
|
+
path: pattern,
|
|
482
|
+
htmlPath: htmlPathForRoute(pattern),
|
|
483
|
+
localeId: null,
|
|
484
|
+
meta: baseMeta,
|
|
485
|
+
},
|
|
486
|
+
];
|
|
380
487
|
}
|
|
381
488
|
function guessTitle(pathname) {
|
|
382
489
|
if (pathname === '/')
|
|
@@ -405,7 +512,7 @@ function readCssEntry(distDir) {
|
|
|
405
512
|
* chunkId: string,
|
|
406
513
|
* layoutChain: string[],
|
|
407
514
|
* props: Record<string, unknown>,
|
|
408
|
-
* meta: { title: string, description: string, canonical: string, robots: string, lang: string },
|
|
515
|
+
* meta: { title: string, description: string, canonical: string, robots: string, lang: string, dir?: string, alternates?: Array<{ hreflang: string, href: string }> },
|
|
409
516
|
* cssEntry: string | null,
|
|
410
517
|
* isErrorDocument?: boolean,
|
|
411
518
|
* }} input
|
|
@@ -414,10 +521,17 @@ function wrapDocument(input) {
|
|
|
414
521
|
const propsJson = JSON.stringify(input.props ?? {});
|
|
415
522
|
const layoutAttr = input.layoutChain.length ? ` data-vmz-layout="${escapeAttr(input.layoutChain.join(','))}"` : '';
|
|
416
523
|
const pageAttr = input.chunkId ? ` data-vmz-page="${escapeAttr(input.chunkId)}"` : '';
|
|
524
|
+
const localeId = input.meta.lang || 'en';
|
|
525
|
+
const dir = input.meta.dir || 'ltr';
|
|
526
|
+
const localeAttr = ` data-vmz-locale="${escapeAttr(localeId)}" data-vmz-dir="${escapeAttr(dir)}"`;
|
|
417
527
|
const cssLink = input.cssEntry ? ` <link rel="stylesheet" href="/${String(input.cssEntry).replace(/^\/+/, '')}" />\n` : '';
|
|
418
528
|
const entry = input.isErrorDocument ? '' : ` <script type="module" src="/entry-client.js"></script>\n`;
|
|
529
|
+
const hreflang = (input.meta.alternates || [])
|
|
530
|
+
.map((a) => ` <link rel="alternate" hreflang="${escapeAttr(a.hreflang)}" href="${escapeAttr(a.href)}" />`)
|
|
531
|
+
.join('\n');
|
|
532
|
+
const hreflangBlock = hreflang ? `${hreflang}\n` : '';
|
|
419
533
|
return `<!DOCTYPE html>
|
|
420
|
-
<html lang="${escapeAttr(
|
|
534
|
+
<html lang="${escapeAttr(localeId)}" data-locale="${escapeAttr(localeId)}" dir="${escapeAttr(dir)}">
|
|
421
535
|
<head>
|
|
422
536
|
<meta charset="utf-8" />
|
|
423
537
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
@@ -425,12 +539,12 @@ function wrapDocument(input) {
|
|
|
425
539
|
<meta name="description" content="${escapeAttr(input.meta.description)}" />
|
|
426
540
|
<meta name="robots" content="${escapeAttr(input.meta.robots)}" />
|
|
427
541
|
<link rel="canonical" href="${escapeAttr(input.meta.canonical)}" />
|
|
428
|
-
<meta property="og:title" content="${escapeAttr(input.meta.title)}" />
|
|
542
|
+
${hreflangBlock} <meta property="og:title" content="${escapeAttr(input.meta.title)}" />
|
|
429
543
|
<meta property="og:description" content="${escapeAttr(input.meta.description)}" />
|
|
430
544
|
<meta property="og:url" content="${escapeAttr(input.meta.canonical)}" />
|
|
431
545
|
${cssLink}</head>
|
|
432
546
|
<body>
|
|
433
|
-
<div id="app"${pageAttr}${layoutAttr} data-vmz-props="${escapeAttr(propsJson)}">${input.bodyHtml}</div>
|
|
547
|
+
<div id="app"${pageAttr}${layoutAttr}${localeAttr} data-vmz-props="${escapeAttr(propsJson)}">${input.bodyHtml}</div>
|
|
434
548
|
${entry}</body>
|
|
435
549
|
</html>
|
|
436
550
|
`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vmz/vmz",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
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.0
|
|
52
|
-
"@vmz/plugin": "0.0
|
|
53
|
-
"@vmz/protocol": "0.0
|
|
51
|
+
"@vmz/core": "0.1.0",
|
|
52
|
+
"@vmz/plugin": "0.1.0",
|
|
53
|
+
"@vmz/protocol": "0.1.0",
|
|
54
54
|
"jiti": "^2.6.1",
|
|
55
55
|
"json5": "^2.2.3"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
|
-
"@vmz/plugin-markdown-it": "0.0
|
|
59
|
-
"@vmz/test": "0.0
|
|
58
|
+
"@vmz/plugin-markdown-it": "0.1.0",
|
|
59
|
+
"@vmz/test": "0.1.0",
|
|
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.0
|
|
94
|
-
"@vmz/vmz-win32-arm64": "0.0
|
|
95
|
-
"@vmz/vmz-darwin-x64": "0.0
|
|
96
|
-
"@vmz/vmz-darwin-arm64": "0.0
|
|
97
|
-
"@vmz/vmz-linux-x64": "0.0
|
|
98
|
-
"@vmz/vmz-linux-arm64": "0.0
|
|
93
|
+
"@vmz/vmz-win32-x64": "0.1.0",
|
|
94
|
+
"@vmz/vmz-win32-arm64": "0.1.0",
|
|
95
|
+
"@vmz/vmz-darwin-x64": "0.1.0",
|
|
96
|
+
"@vmz/vmz-darwin-arm64": "0.1.0",
|
|
97
|
+
"@vmz/vmz-linux-x64": "0.1.0",
|
|
98
|
+
"@vmz/vmz-linux-arm64": "0.1.0"
|
|
99
99
|
},
|
|
100
100
|
"publishConfig": {
|
|
101
101
|
"access": "public"
|