@vmz/core 0.1.25 → 0.1.27
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/dom-core.d.ts +18 -2
- package/dist/dom-core.js +45 -9
- package/dist/dom-ssr.js +3 -0
- package/dist/localize-body-links.d.ts +17 -0
- package/dist/localize-body-links.js +33 -1
- package/dist/serve-host.mjs +23 -2
- package/package.json +7 -7
package/dist/dom-core.d.ts
CHANGED
|
@@ -161,6 +161,14 @@ export declare const directApi: {
|
|
|
161
161
|
* @param {string | null} client
|
|
162
162
|
*/
|
|
163
163
|
component(hostInst: any, name: any, props: any, client: any): any;
|
|
164
|
+
/**
|
|
165
|
+
* Subscribe to a child component event (`@submit` → event name `submit`).
|
|
166
|
+
* Orthogonal to function props (`:on-submit` → prop `onSubmit`).
|
|
167
|
+
* @param {HTMLElement} hostEl
|
|
168
|
+
* @param {string} eventName
|
|
169
|
+
* @param {(ev?: any, ...rest: any[]) => any} handler
|
|
170
|
+
*/
|
|
171
|
+
onComponentEvent(hostEl: any, eventName: any, handler: any): void;
|
|
164
172
|
/**
|
|
165
173
|
* Keep nested Direct child props live with parent field writes.
|
|
166
174
|
* @param {object} hostInst
|
|
@@ -198,12 +206,20 @@ export declare const directApi: {
|
|
|
198
206
|
eachBlock(inst: any, bindingId: any, deps: any, spec: any, regionId?: any): DocumentFragment;
|
|
199
207
|
};
|
|
200
208
|
/**
|
|
201
|
-
*
|
|
202
|
-
*
|
|
209
|
+
* Function prop wire: only `onXxx` camelCase is a handler prop.
|
|
210
|
+
* Component `@event` must never normalize here — that channel is `onComponentEvent`.
|
|
203
211
|
* @param {unknown} name
|
|
204
212
|
* @returns {string | null}
|
|
205
213
|
*/
|
|
206
214
|
export declare function eventPropHandlerName(name: any): string;
|
|
215
|
+
/**
|
|
216
|
+
* Dispatch a component event to parent subscribers registered via `onComponentEvent`.
|
|
217
|
+
* Installed on every Direct instance as `inst.emit`.
|
|
218
|
+
* @param {object} inst
|
|
219
|
+
* @param {string} eventName
|
|
220
|
+
* @param {...any} payload
|
|
221
|
+
*/
|
|
222
|
+
export declare function emitComponentEvent(inst: any, eventName: any, ...payload: any[]): void;
|
|
207
223
|
export declare function isEventPropName(name: any): boolean;
|
|
208
224
|
/** HTML boolean attributes: presence means true; `false`/`null` must remove the attr. */
|
|
209
225
|
export declare const BOOLEAN_HTML_ATTRS: Set<string>;
|
package/dist/dom-core.js
CHANGED
|
@@ -505,6 +505,8 @@ export const directApi = {
|
|
|
505
505
|
/** @type {Record<string, any>} */
|
|
506
506
|
const resolved = {};
|
|
507
507
|
for (const [k, v] of Object.entries(props || {})) {
|
|
508
|
+
// Function props that already look like `onXxx` stay as handlers.
|
|
509
|
+
// Component `@event` never arrives here (emit uses onComponentEvent).
|
|
508
510
|
const onKey = typeof v === 'function' ? eventPropHandlerName(k) : null;
|
|
509
511
|
if (onKey)
|
|
510
512
|
resolved[onKey] = v;
|
|
@@ -574,6 +576,23 @@ export const directApi = {
|
|
|
574
576
|
}
|
|
575
577
|
return host;
|
|
576
578
|
},
|
|
579
|
+
/**
|
|
580
|
+
* Subscribe to a child component event (`@submit` → event name `submit`).
|
|
581
|
+
* Orthogonal to function props (`:on-submit` → prop `onSubmit`).
|
|
582
|
+
* @param {HTMLElement} hostEl
|
|
583
|
+
* @param {string} eventName
|
|
584
|
+
* @param {(ev?: any, ...rest: any[]) => any} handler
|
|
585
|
+
*/
|
|
586
|
+
onComponentEvent(hostEl, eventName, handler) {
|
|
587
|
+
const child = hostEl && hostEl.__vmzInst;
|
|
588
|
+
if (!child || typeof eventName !== 'string' || !eventName)
|
|
589
|
+
return;
|
|
590
|
+
if (typeof handler !== 'function')
|
|
591
|
+
return;
|
|
592
|
+
const bag = child.__vmzComponentListeners || (child.__vmzComponentListeners = Object.create(null));
|
|
593
|
+
const list = bag[eventName] || (bag[eventName] = []);
|
|
594
|
+
list.push(handler);
|
|
595
|
+
},
|
|
577
596
|
/**
|
|
578
597
|
* Keep nested Direct child props live with parent field writes.
|
|
579
598
|
* @param {object} hostInst
|
|
@@ -2736,8 +2755,8 @@ function wireDirectBind(inst, bindingId, deps, get, write, cf) {
|
|
|
2736
2755
|
trackDirectBind(inst, liveDeps, apply, bindingId);
|
|
2737
2756
|
}
|
|
2738
2757
|
/**
|
|
2739
|
-
*
|
|
2740
|
-
*
|
|
2758
|
+
* Function prop wire: only `onXxx` camelCase is a handler prop.
|
|
2759
|
+
* Component `@event` must never normalize here — that channel is `onComponentEvent`.
|
|
2741
2760
|
* @param {unknown} name
|
|
2742
2761
|
* @returns {string | null}
|
|
2743
2762
|
*/
|
|
@@ -2746,15 +2765,27 @@ export function eventPropHandlerName(name) {
|
|
|
2746
2765
|
return null;
|
|
2747
2766
|
if (/^on[A-Z]/.test(name))
|
|
2748
2767
|
return name;
|
|
2749
|
-
// `@input` → `onInput`; reject `@update:modelValue` until that surface exists.
|
|
2750
|
-
if (name.charAt(0) === '@' && name.length > 1 && !name.includes(':')) {
|
|
2751
|
-
const ev = name.slice(1);
|
|
2752
|
-
if (!ev || !/^[A-Za-z]/.test(ev))
|
|
2753
|
-
return null;
|
|
2754
|
-
return `on${ev.charAt(0).toUpperCase()}${ev.slice(1)}`;
|
|
2755
|
-
}
|
|
2756
2768
|
return null;
|
|
2757
2769
|
}
|
|
2770
|
+
/**
|
|
2771
|
+
* Dispatch a component event to parent subscribers registered via `onComponentEvent`.
|
|
2772
|
+
* Installed on every Direct instance as `inst.emit`.
|
|
2773
|
+
* @param {object} inst
|
|
2774
|
+
* @param {string} eventName
|
|
2775
|
+
* @param {...any} payload
|
|
2776
|
+
*/
|
|
2777
|
+
export function emitComponentEvent(inst, eventName, ...payload) {
|
|
2778
|
+
if (!inst || typeof eventName !== 'string' || !eventName)
|
|
2779
|
+
return;
|
|
2780
|
+
const bag = inst.__vmzComponentListeners;
|
|
2781
|
+
const list = bag && bag[eventName];
|
|
2782
|
+
if (!Array.isArray(list) || list.length === 0)
|
|
2783
|
+
return;
|
|
2784
|
+
for (const fn of list) {
|
|
2785
|
+
if (typeof fn === 'function')
|
|
2786
|
+
fn(...payload);
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2758
2789
|
export function isEventPropName(name) {
|
|
2759
2790
|
return eventPropHandlerName(name) != null;
|
|
2760
2791
|
}
|
|
@@ -3137,6 +3168,11 @@ export function createInstance(Component, props = {}) {
|
|
|
3137
3168
|
inst.__vmzBinders = Object.create(null);
|
|
3138
3169
|
inst.__vmzBindings = Object.create(null);
|
|
3139
3170
|
inst.__vmzDepToBindings = Object.create(null);
|
|
3171
|
+
inst.__vmzComponentListeners = Object.create(null);
|
|
3172
|
+
// Compiler intrinsic surface: `this.emit('submit', payload)` — not a string bus.
|
|
3173
|
+
inst.emit = function emit(eventName, ...payload) {
|
|
3174
|
+
return emitComponentEvent(inst, eventName, ...payload);
|
|
3175
|
+
};
|
|
3140
3176
|
makeReactive(inst, Component.__vmzState || []);
|
|
3141
3177
|
makeReactive(inst, Component.__vmzProps || []);
|
|
3142
3178
|
// WriteBarrier: install Component helpers once (no import needed in emitted code).
|
package/dist/dom-ssr.js
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
* Href authority is RouteId × LocaleId realization rows — not path parsing on existing href.
|
|
4
4
|
*/
|
|
5
5
|
export declare const LOCALE_LINK_PLAN_SCHEMA = "vmz.static.locale_link_plan.v0";
|
|
6
|
+
export type LinkRouteAlias = {
|
|
7
|
+
/** Author Link `to` / `data-vmz-route` (class RouteId, e.g. IndexPage). */
|
|
8
|
+
linkRouteId: string;
|
|
9
|
+
/** Locale realization `routeId` (often chunk path, e.g. pages/index). */
|
|
10
|
+
realizationRouteId: string;
|
|
11
|
+
};
|
|
6
12
|
export type LocaleHrefArtifact = {
|
|
7
13
|
locales?: Array<{
|
|
8
14
|
id: string;
|
|
@@ -18,6 +24,11 @@ export type LocaleHrefArtifact = {
|
|
|
18
24
|
localeId: string;
|
|
19
25
|
path: string;
|
|
20
26
|
}>;
|
|
27
|
+
/**
|
|
28
|
+
* Bridge author Link RouteId ↔ realization routeId.
|
|
29
|
+
* Realization artifacts historically key by chunk path; Links emit class names.
|
|
30
|
+
*/
|
|
31
|
+
linkRouteAliases?: LinkRouteAlias[];
|
|
21
32
|
};
|
|
22
33
|
export type LocaleLinkPlanRow = {
|
|
23
34
|
routeId: string;
|
|
@@ -28,6 +39,12 @@ export type LocaleLinkPlan = {
|
|
|
28
39
|
schema: typeof LOCALE_LINK_PLAN_SCHEMA;
|
|
29
40
|
rows: LocaleLinkPlanRow[];
|
|
30
41
|
};
|
|
42
|
+
/** Build Link RouteId aliases from deployment page units (`routeId` + `chunkId`). */
|
|
43
|
+
export declare function linkRouteAliasesFromUnits(units: Array<{
|
|
44
|
+
kind?: string;
|
|
45
|
+
routeId?: string;
|
|
46
|
+
chunkId?: string;
|
|
47
|
+
}> | null | undefined): LinkRouteAlias[];
|
|
31
48
|
/** Build RouteId × LocaleId → href rows from locale route realization artifact. */
|
|
32
49
|
export declare function buildLocaleLinkPlan(artifact: LocaleHrefArtifact | null | undefined): LocaleLinkPlan;
|
|
33
50
|
/** Apply plan rows to `<a data-vmz-route="RouteId">` markers for one LocaleId. */
|
|
@@ -3,13 +3,41 @@
|
|
|
3
3
|
* Href authority is RouteId × LocaleId realization rows — not path parsing on existing href.
|
|
4
4
|
*/
|
|
5
5
|
export const LOCALE_LINK_PLAN_SCHEMA = 'vmz.static.locale_link_plan.v0';
|
|
6
|
+
/** Build Link RouteId aliases from deployment page units (`routeId` + `chunkId`). */
|
|
7
|
+
export function linkRouteAliasesFromUnits(units) {
|
|
8
|
+
const out = [];
|
|
9
|
+
const seen = new Set();
|
|
10
|
+
for (const unit of units || []) {
|
|
11
|
+
if (unit?.kind && unit.kind !== 'page')
|
|
12
|
+
continue;
|
|
13
|
+
const linkRouteId = typeof unit?.routeId === 'string' ? unit.routeId.trim() : '';
|
|
14
|
+
const realizationRouteId = typeof unit?.chunkId === 'string' ? unit.chunkId.replace(/\\/g, '/').trim() : '';
|
|
15
|
+
if (!linkRouteId || !realizationRouteId || linkRouteId === realizationRouteId)
|
|
16
|
+
continue;
|
|
17
|
+
const key = `${linkRouteId}\0${realizationRouteId}`;
|
|
18
|
+
if (seen.has(key))
|
|
19
|
+
continue;
|
|
20
|
+
seen.add(key);
|
|
21
|
+
out.push({ linkRouteId, realizationRouteId });
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
6
25
|
/** Build RouteId × LocaleId → href rows from locale route realization artifact. */
|
|
7
26
|
export function buildLocaleLinkPlan(artifact) {
|
|
8
27
|
const rows = [];
|
|
28
|
+
const aliases = artifact?.linkRouteAliases || [];
|
|
9
29
|
for (const r of artifact?.realizations || []) {
|
|
10
30
|
if (!r?.routeId || !r?.localeId || !r?.path)
|
|
11
31
|
continue;
|
|
12
|
-
|
|
32
|
+
const realizationRouteId = String(r.routeId);
|
|
33
|
+
const localeId = String(r.localeId);
|
|
34
|
+
const href = String(r.path);
|
|
35
|
+
rows.push({ routeId: realizationRouteId, localeId, href });
|
|
36
|
+
for (const alias of aliases) {
|
|
37
|
+
if (alias.realizationRouteId === realizationRouteId && alias.linkRouteId) {
|
|
38
|
+
rows.push({ routeId: String(alias.linkRouteId), localeId, href });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
13
41
|
}
|
|
14
42
|
rows.sort((a, b) => (a.routeId === b.routeId ? a.localeId.localeCompare(b.localeId) : a.routeId.localeCompare(b.routeId)));
|
|
15
43
|
return { schema: LOCALE_LINK_PLAN_SCHEMA, rows };
|
|
@@ -32,6 +60,10 @@ export function applyLocaleLinkPlan(html, localeId, plan, escapeAttr = (s) => St
|
|
|
32
60
|
const href = byRoute.get(rm[1]);
|
|
33
61
|
if (!href)
|
|
34
62
|
return full;
|
|
63
|
+
// Realization rows for dynamic routes keep unresolved patterns (`[id]` / `:id`).
|
|
64
|
+
// Do not clobber compile-time realized hrefs like `/products/sku-1`.
|
|
65
|
+
if (/\[[^\]]+\]/.test(href) || /\/:[^/]+/.test(href))
|
|
66
|
+
return full;
|
|
35
67
|
const hm = attrs.match(/\bhref\s*=\s*"([^"]*)"/i);
|
|
36
68
|
if (hm && hm[1] === href)
|
|
37
69
|
return full;
|
package/dist/serve-host.mjs
CHANGED
|
@@ -22,10 +22,10 @@ import http from 'node:http';
|
|
|
22
22
|
import { createRequire, registerHooks } from 'node:module';
|
|
23
23
|
import path from 'node:path';
|
|
24
24
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
25
|
-
import { localizeBodyLinks } from './localize-body-links.js';
|
|
26
|
-
import { createRenderHost } from './render-host.js';
|
|
27
25
|
import { listClientComponents } from './list-client-components.js';
|
|
26
|
+
import { linkRouteAliasesFromUnits, localizeBodyLinks } from './localize-body-links.js';
|
|
28
27
|
import { loadNativeAddon } from './native-addon.js';
|
|
28
|
+
import { createRenderHost } from './render-host.js';
|
|
29
29
|
import { resolveRouteLayoutChain } from './route-layout-chain.js';
|
|
30
30
|
import { handleNodeRequest, setRoutes, setServerModuleResolver } from './vmz-runtime.js';
|
|
31
31
|
const require = createRequire(import.meta.url);
|
|
@@ -756,6 +756,26 @@ process.on('SIGTERM', () => {
|
|
|
756
756
|
process.on('SIGINT', () => {
|
|
757
757
|
void gracefulShutdown('SIGINT');
|
|
758
758
|
});
|
|
759
|
+
/**
|
|
760
|
+
* Attach author Link RouteId aliases so localizeBodyLinks can match `data-vmz-route="IndexPage"`
|
|
761
|
+
* against realization rows keyed by chunk path (`pages/index`).
|
|
762
|
+
* @param {any} artifact
|
|
763
|
+
* @param {string} dir
|
|
764
|
+
*/
|
|
765
|
+
function attachLinkRouteAliases(artifact, dir) {
|
|
766
|
+
if (!artifact || typeof artifact !== 'object')
|
|
767
|
+
return artifact;
|
|
768
|
+
try {
|
|
769
|
+
const dep = JSON.parse(readFileSync(path.join(dir, 'vmz-deployment.json'), 'utf8'));
|
|
770
|
+
const aliases = linkRouteAliasesFromUnits(Array.isArray(dep.units) ? dep.units : []);
|
|
771
|
+
if (!aliases.length)
|
|
772
|
+
return artifact;
|
|
773
|
+
return { ...artifact, linkRouteAliases: aliases };
|
|
774
|
+
}
|
|
775
|
+
catch {
|
|
776
|
+
return artifact;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
759
779
|
/**
|
|
760
780
|
* Re-import routes / pages / components with a new cache-bust token.
|
|
761
781
|
* Keeps the HTTP server process alive (no Node restart).
|
|
@@ -791,6 +811,7 @@ async function softReload(opts = {}) {
|
|
|
791
811
|
}
|
|
792
812
|
try {
|
|
793
813
|
localeArtifact = JSON.parse(await readFile(path.join(distDir, '_vmz', 'locale-route-realization.json'), 'utf8'));
|
|
814
|
+
localeArtifact = attachLinkRouteAliases(localeArtifact, distDir);
|
|
794
815
|
}
|
|
795
816
|
catch {
|
|
796
817
|
localeArtifact = null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vmz/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.27",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "VMZ production runtime core — DOM / SSR / HTTP / WriteBarrier (no compiler)",
|
|
6
6
|
"exports": {
|
|
@@ -66,12 +66,12 @@
|
|
|
66
66
|
"linkedom": "^0.18.13"
|
|
67
67
|
},
|
|
68
68
|
"optionalDependencies": {
|
|
69
|
-
"@vmz/vmz-win32-x64": "0.1.
|
|
70
|
-
"@vmz/vmz-win32-arm64": "0.1.
|
|
71
|
-
"@vmz/vmz-darwin-x64": "0.1.
|
|
72
|
-
"@vmz/vmz-darwin-arm64": "0.1.
|
|
73
|
-
"@vmz/vmz-linux-x64": "0.1.
|
|
74
|
-
"@vmz/vmz-linux-arm64": "0.1.
|
|
69
|
+
"@vmz/vmz-win32-x64": "0.1.27",
|
|
70
|
+
"@vmz/vmz-win32-arm64": "0.1.27",
|
|
71
|
+
"@vmz/vmz-darwin-x64": "0.1.27",
|
|
72
|
+
"@vmz/vmz-darwin-arm64": "0.1.27",
|
|
73
|
+
"@vmz/vmz-linux-x64": "0.1.27",
|
|
74
|
+
"@vmz/vmz-linux-arm64": "0.1.27"
|
|
75
75
|
},
|
|
76
76
|
"publishConfig": {
|
|
77
77
|
"access": "public"
|