@vmz/core 0.3.4 → 0.3.6

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/README.md CHANGED
@@ -1,50 +1,50 @@
1
- # @vmz/core
2
-
3
- The browser should not have to rediscover an application's structure after every click. Yet that is the default cost of
4
- many UI systems: execute components again, compare a virtual tree, and infer which DOM work might be necessary.
5
-
6
- `@vmz/core` is the production runtime behind VMZ's alternative. It executes the creation, patch, SSR, resumption, HTTP,
7
- and lifetime schedules produced by the compiler. When VMZ can prove that a write affects one text binding, one control
8
- region, or one keyed item, the runtime can update that target directly rather than begin from a whole component tree.
9
-
10
- The same idea continues beyond a single browser update. SSR serializes planned regions; resumption attaches only
11
- reachable state and event entries instead of repeating completed server work; ownership regions give async tasks and
12
- resources a deterministic end; server and HTTP output stay tied to compiler-visible capabilities.
13
-
14
- | Instead of... | VMZ runtime work begins from... |
15
- |------------------------------------|-------------------------------------------------------|
16
- | Re-running a component tree | A known affected binding or region |
17
- | Reconciling a generic virtual tree | Direct create, patch, switch, or keyed-list work |
18
- | Hydrating every rendered component | The event and state entries that are actually reached |
19
- | Hoping cleanup follows callbacks | A compiler-known ownership and disposal boundary |
20
-
21
- For application authors, this mostly stays out of sight: write VMZ source and let the compiler generate the plan.
22
- `@vmz/core` matters when you care about the kind of runtime your product ships: small in responsibility, direct in
23
- execution, and able to account for why its work exists. 🌱
24
-
25
- ## The hot path VMZ is aiming for
26
-
27
- ```text
28
- state write
29
- -> known dependency edge
30
- -> affected computation or region
31
- -> direct patch / switch / reconcile
32
- ```
33
-
34
- There is no default detour through “execute every component that might matter, build virtual nodes, then compare them.”
35
- If analysis must widen, it widens to a safe region and should preserve the reason.
36
-
37
- ## More than DOM updates
38
-
39
- - **Transactions:** related writes can settle before dependent work runs.
40
- - **Ownership:** branches, list items, resources, and tasks have a known lifetime.
41
- - **Cancellation:** an obsolete navigation or async generation cannot write into new state.
42
- - **Resumption:** the browser attaches to server-produced work at the smallest reachable boundary.
43
- - **Zero-JS delivery:** pages with no interactive requirement do not need an eager framework shell.
44
-
45
- The runtime should not grow a second compiler made of reflection, string dependencies, or generic proxies. Its quality
46
- comes from faithfully executing the generated plan while keeping the production surface focused.
47
-
48
- ## License
49
-
50
- MIT
1
+ # @vmz/core
2
+
3
+ The browser should not have to rediscover an application's structure after every click. Yet that is the default cost of
4
+ many UI systems: execute components again, compare a virtual tree, and infer which DOM work might be necessary.
5
+
6
+ `@vmz/core` is the production runtime behind VMZ's alternative. It executes the creation, patch, SSR, resumption, HTTP,
7
+ and lifetime schedules produced by the compiler. When VMZ can prove that a write affects one text binding, one control
8
+ region, or one keyed item, the runtime can update that target directly rather than begin from a whole component tree.
9
+
10
+ The same idea continues beyond a single browser update. SSR serializes planned regions; resumption attaches only
11
+ reachable state and event entries instead of repeating completed server work; ownership regions give async tasks and
12
+ resources a deterministic end; server and HTTP output stay tied to compiler-visible capabilities.
13
+
14
+ | Instead of... | VMZ runtime work begins from... |
15
+ |------------------------------------|-------------------------------------------------------|
16
+ | Re-running a component tree | A known affected binding or region |
17
+ | Reconciling a generic virtual tree | Direct create, patch, switch, or keyed-list work |
18
+ | Hydrating every rendered component | The event and state entries that are actually reached |
19
+ | Hoping cleanup follows callbacks | A compiler-known ownership and disposal boundary |
20
+
21
+ For application authors, this mostly stays out of sight: write VMZ source and let the compiler generate the plan.
22
+ `@vmz/core` matters when you care about the kind of runtime your product ships: small in responsibility, direct in
23
+ execution, and able to account for why its work exists. 🌱
24
+
25
+ ## The hot path VMZ is aiming for
26
+
27
+ ```text
28
+ state write
29
+ -> known dependency edge
30
+ -> affected computation or region
31
+ -> direct patch / switch / reconcile
32
+ ```
33
+
34
+ There is no default detour through “execute every component that might matter, build virtual nodes, then compare them.”
35
+ If analysis must widen, it widens to a safe region and should preserve the reason.
36
+
37
+ ## More than DOM updates
38
+
39
+ - **Transactions:** related writes can settle before dependent work runs.
40
+ - **Ownership:** branches, list items, resources, and tasks have a known lifetime.
41
+ - **Cancellation:** an obsolete navigation or async generation cannot write into new state.
42
+ - **Resumption:** the browser attaches to server-produced work at the smallest reachable boundary.
43
+ - **Zero-JS delivery:** pages with no interactive requirement do not need an eager framework shell.
44
+
45
+ The runtime should not grow a second compiler made of reflection, string dependencies, or generic proxies. Its quality
46
+ comes from faithfully executing the generated plan while keeping the production surface focused.
47
+
48
+ ## License
49
+
50
+ MIT
@@ -605,32 +605,32 @@ function wantsBrowserHtml(req) {
605
605
  function browserErrorHtml(status, title, message, stack) {
606
606
  const esc = (s) => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
607
607
  const stackBlock = stack ? `<pre class="stack">${esc(stack)}</pre>` : '';
608
- return `<!DOCTYPE html>
609
- <html lang="en">
610
- <head>
611
- <meta charset="utf-8" />
612
- <meta name="viewport" content="width=device-width, initial-scale=1" />
613
- <title>${esc(title)}</title>
614
- <style>
615
- body{margin:0;background:#0f1115;color:#f4f4f5;font:14px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
616
- main{max-width:56rem;margin:0 auto;padding:2rem 1.25rem}
617
- h1{margin:0 0 .75rem;color:#f87171;font-size:1.1rem}
618
- .code{opacity:.65;font-size:12px;margin-bottom:.75rem}
619
- pre{white-space:pre-wrap;margin:0 0 1rem}
620
- .stack{opacity:.7;font-size:12px}
621
- .hint{opacity:.65;font-size:12px}
622
- </style>
623
- </head>
624
- <body data-vmz-error="${status}">
625
- <main>
626
- <p class="code">HTTP ${status}</p>
627
- <h1>${esc(title)}</h1>
628
- <pre>${esc(message)}</pre>
629
- ${stackBlock}
630
- <p class="hint">Document navigation returns HTML errors — not API JSON.</p>
631
- </main>
632
- </body>
633
- </html>
608
+ return `<!DOCTYPE html>
609
+ <html lang="en">
610
+ <head>
611
+ <meta charset="utf-8" />
612
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
613
+ <title>${esc(title)}</title>
614
+ <style>
615
+ body{margin:0;background:#0f1115;color:#f4f4f5;font:14px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
616
+ main{max-width:56rem;margin:0 auto;padding:2rem 1.25rem}
617
+ h1{margin:0 0 .75rem;color:#f87171;font-size:1.1rem}
618
+ .code{opacity:.65;font-size:12px;margin-bottom:.75rem}
619
+ pre{white-space:pre-wrap;margin:0 0 1rem}
620
+ .stack{opacity:.7;font-size:12px}
621
+ .hint{opacity:.65;font-size:12px}
622
+ </style>
623
+ </head>
624
+ <body data-vmz-error="${status}">
625
+ <main>
626
+ <p class="code">HTTP ${status}</p>
627
+ <h1>${esc(title)}</h1>
628
+ <pre>${esc(message)}</pre>
629
+ ${stackBlock}
630
+ <p class="hint">Document navigation returns HTML errors — not API JSON.</p>
631
+ </main>
632
+ </body>
633
+ </html>
634
634
  `;
635
635
  }
636
636
  function sendJson(res, status, body) {
@@ -87,29 +87,29 @@ export function normalizeDevError(err) {
87
87
  export async function* emitDevErrorHtml(err) {
88
88
  const msg = escapeHtml(err.message || 'Unknown error');
89
89
  const stack = err.stack ? escapeHtml(err.stack) : '';
90
- const style = `<style>
91
- body{margin:0;background:#0f1115;color:#f4f4f5;font:14px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
92
- main{max-width:56rem;margin:0 auto;padding:2rem 1.25rem}
93
- h1{margin:0 0 .75rem;color:#f87171;font-size:1.1rem}
94
- pre{white-space:pre-wrap;margin:0 0 1rem}
95
- .hint{opacity:.65;font-size:12px}
90
+ const style = `<style>
91
+ body{margin:0;background:#0f1115;color:#f4f4f5;font:14px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
92
+ main{max-width:56rem;margin:0 auto;padding:2rem 1.25rem}
93
+ h1{margin:0 0 .75rem;color:#f87171;font-size:1.1rem}
94
+ pre{white-space:pre-wrap;margin:0 0 1rem}
95
+ .hint{opacity:.65;font-size:12px}
96
96
  </style>`;
97
- const body = `${style}
98
- <main data-vmz-error="500">
99
- <h1>Dev Error</h1>
100
- <pre>${msg}</pre>
101
- ${stack ? `<pre style="opacity:.7;font-size:12px">${stack}</pre>` : ''}
102
- <p class="hint">Dev host stayed up. Fix the source and save — soft reload will recover.</p>
103
- </main>
104
- <script>
105
- (() => {
106
- const es = new EventSource("/__vmz/events");
107
- es.onmessage = (ev) => {
108
- let msg = null;
109
- try { msg = JSON.parse(ev.data); } catch {}
110
- if (msg && msg.type === "hmr") location.reload();
111
- };
112
- })();
97
+ const body = `${style}
98
+ <main data-vmz-error="500">
99
+ <h1>Dev Error</h1>
100
+ <pre>${msg}</pre>
101
+ ${stack ? `<pre style="opacity:.7;font-size:12px">${stack}</pre>` : ''}
102
+ <p class="hint">Dev host stayed up. Fix the source and save — soft reload will recover.</p>
103
+ </main>
104
+ <script>
105
+ (() => {
106
+ const es = new EventSource("/__vmz/events");
107
+ es.onmessage = (ev) => {
108
+ let msg = null;
109
+ try { msg = JSON.parse(ev.data); } catch {}
110
+ if (msg && msg.type === "hmr") location.reload();
111
+ };
112
+ })();
113
113
  </script>`;
114
114
  try {
115
115
  const native = loadNativeAddon();
@@ -240,79 +240,79 @@ function resolvePageDocumentMeta(Page) {
240
240
  async function* emitPageHtml(Page, chunkId, eventOnlyShell, props = {}, opts = {}, layoutChain = [], localeCtx = {}) {
241
241
  const signal = opts.signal;
242
242
  const live = serveState.isDev
243
- ? `\n <script>
244
- (() => {
245
- const es = new EventSource("/__vmz/events");
246
- let sawDisconnect = false;
247
- es.onerror = () => { sawDisconnect = true; };
248
- es.onopen = () => {
249
- if (sawDisconnect) location.reload();
250
- };
251
- function showOverlay(err) {
252
- let el = document.getElementById("vmz-dev-overlay");
253
- if (!el) {
254
- el = document.createElement("div");
255
- el.id = "vmz-dev-overlay";
256
- el.setAttribute("role", "alert");
257
- Object.assign(el.style, {
258
- position: "fixed", inset: "0", zIndex: "2147483646",
259
- background: "rgba(15,17,21,0.92)", color: "#f4f4f5",
260
- fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
261
- padding: "2rem", overflow: "auto",
262
- });
263
- document.documentElement.appendChild(el);
264
- }
265
- const msg = (err && err.message) || String(err || "Unknown error");
266
- const stack = (err && err.stack) || "";
267
- const esc = (s) => String(s).replace(/[&<>]/g, (c) => ({"&":"&amp;","<":"&lt;",">":"&gt;"}[c]));
268
- el.innerHTML = "<div style=\\"max-width:56rem;margin:0 auto\\">"
269
- + "<p style=\\"margin:0 0 .5rem;color:#f87171;font-weight:700\\">Dev Error</p>"
270
- + "<pre style=\\"white-space:pre-wrap;margin:0 0 1rem;font-size:13px;line-height:1.45\\">" + esc(msg) + "</pre>"
271
- + (stack ? "<pre style=\\"white-space:pre-wrap;opacity:.7;font-size:12px\\">" + esc(stack) + "</pre>" : "")
272
- + "<p style=\\"opacity:.65;font-size:12px\\">Fix the file and save — soft reload will clear this overlay.</p>"
273
- + "</div>";
274
- }
275
- function hideOverlay() {
276
- const el = document.getElementById("vmz-dev-overlay");
277
- if (el) el.remove();
278
- }
279
- es.onmessage = async (ev) => {
280
- let msg = null;
281
- try { msg = JSON.parse(ev.data); } catch { /* plain string */ }
282
- if (msg && msg.type === "error") {
283
- showOverlay(msg);
284
- return;
285
- }
286
- if (!msg || msg.type !== "hmr") {
287
- if (ev.data === "reload") location.reload();
288
- return;
289
- }
290
- hideOverlay();
291
- if (msg.mode === "island") {
292
- try {
293
- const { hydrate } = await import("/dom.browser.js?t=" + msg.token);
294
- const root = document.getElementById("app");
295
- const pageChunk = root && root.getAttribute("data-vmz-page");
296
- if (root && pageChunk) {
297
- const pageMod = await import("/" + pageChunk + ".client.js?t=" + msg.token);
298
- let hmrProps = {};
299
- try {
300
- const raw = root.getAttribute("data-vmz-props");
301
- if (raw) hmrProps = JSON.parse(raw);
302
- } catch { /* ignore */ }
303
- await hydrate(pageMod.default, root, hmrProps, { preserveState: true, skipOnMount: true });
304
- } else {
305
- location.reload();
306
- }
307
- } catch (err) {
308
- console.error("vmz island HMR failed", err);
309
- showOverlay({ message: String(err && err.message || err), stack: err && err.stack });
310
- }
311
- return;
312
- }
313
- location.reload();
314
- };
315
- })();
243
+ ? `\n <script>
244
+ (() => {
245
+ const es = new EventSource("/__vmz/events");
246
+ let sawDisconnect = false;
247
+ es.onerror = () => { sawDisconnect = true; };
248
+ es.onopen = () => {
249
+ if (sawDisconnect) location.reload();
250
+ };
251
+ function showOverlay(err) {
252
+ let el = document.getElementById("vmz-dev-overlay");
253
+ if (!el) {
254
+ el = document.createElement("div");
255
+ el.id = "vmz-dev-overlay";
256
+ el.setAttribute("role", "alert");
257
+ Object.assign(el.style, {
258
+ position: "fixed", inset: "0", zIndex: "2147483646",
259
+ background: "rgba(15,17,21,0.92)", color: "#f4f4f5",
260
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
261
+ padding: "2rem", overflow: "auto",
262
+ });
263
+ document.documentElement.appendChild(el);
264
+ }
265
+ const msg = (err && err.message) || String(err || "Unknown error");
266
+ const stack = (err && err.stack) || "";
267
+ const esc = (s) => String(s).replace(/[&<>]/g, (c) => ({"&":"&amp;","<":"&lt;",">":"&gt;"}[c]));
268
+ el.innerHTML = "<div style=\\"max-width:56rem;margin:0 auto\\">"
269
+ + "<p style=\\"margin:0 0 .5rem;color:#f87171;font-weight:700\\">Dev Error</p>"
270
+ + "<pre style=\\"white-space:pre-wrap;margin:0 0 1rem;font-size:13px;line-height:1.45\\">" + esc(msg) + "</pre>"
271
+ + (stack ? "<pre style=\\"white-space:pre-wrap;opacity:.7;font-size:12px\\">" + esc(stack) + "</pre>" : "")
272
+ + "<p style=\\"opacity:.65;font-size:12px\\">Fix the file and save — soft reload will clear this overlay.</p>"
273
+ + "</div>";
274
+ }
275
+ function hideOverlay() {
276
+ const el = document.getElementById("vmz-dev-overlay");
277
+ if (el) el.remove();
278
+ }
279
+ es.onmessage = async (ev) => {
280
+ let msg = null;
281
+ try { msg = JSON.parse(ev.data); } catch { /* plain string */ }
282
+ if (msg && msg.type === "error") {
283
+ showOverlay(msg);
284
+ return;
285
+ }
286
+ if (!msg || msg.type !== "hmr") {
287
+ if (ev.data === "reload") location.reload();
288
+ return;
289
+ }
290
+ hideOverlay();
291
+ if (msg.mode === "island") {
292
+ try {
293
+ const { hydrate } = await import("/dom.browser.js?t=" + msg.token);
294
+ const root = document.getElementById("app");
295
+ const pageChunk = root && root.getAttribute("data-vmz-page");
296
+ if (root && pageChunk) {
297
+ const pageMod = await import("/" + pageChunk + ".client.js?t=" + msg.token);
298
+ let hmrProps = {};
299
+ try {
300
+ const raw = root.getAttribute("data-vmz-props");
301
+ if (raw) hmrProps = JSON.parse(raw);
302
+ } catch { /* ignore */ }
303
+ await hydrate(pageMod.default, root, hmrProps, { preserveState: true, skipOnMount: true });
304
+ } else {
305
+ location.reload();
306
+ }
307
+ } catch (err) {
308
+ console.error("vmz island HMR failed", err);
309
+ showOverlay({ message: String(err && err.message || err), stack: err && err.stack });
310
+ }
311
+ return;
312
+ }
313
+ location.reload();
314
+ };
315
+ })();
316
316
  </script>`
317
317
  : '';
318
318
  const bootOverlay = serveState.isDev && serveState.lastDevError
@@ -36,6 +36,26 @@ export async function runDirectSerializeTreeWithMounts(Component, inst) {
36
36
  }
37
37
  throw new Error('vmz:dom SSR child onMount expansion exceeded 32 rounds');
38
38
  }
39
+ /** HTML void elements never take a closing tag; `</br>` is parsed as another `<br>`. */
40
+ const VOID_TAGS = new Set([
41
+ 'area',
42
+ 'base',
43
+ 'br',
44
+ 'col',
45
+ 'embed',
46
+ 'hr',
47
+ 'img',
48
+ 'input',
49
+ 'link',
50
+ 'meta',
51
+ 'param',
52
+ 'source',
53
+ 'track',
54
+ 'wbr',
55
+ ]);
56
+ function isVoidTag(tag) {
57
+ return VOID_TAGS.has(String(tag).toLowerCase());
58
+ }
39
59
  function serializeOpenTag(node) {
40
60
  const tag = node.tag || 'div';
41
61
  let attrs = '';
@@ -75,10 +95,10 @@ export function flattenSerializeNode(node) {
75
95
  return String(node.__rawHtml);
76
96
  const { open } = serializeOpenTag(node);
77
97
  if (node.__rawHtml != null) {
78
- return `${open}${String(node.__rawHtml)}</${tag}>`;
98
+ return isVoidTag(tag) ? `${open}${String(node.__rawHtml)}` : `${open}${String(node.__rawHtml)}</${tag}>`;
79
99
  }
80
100
  const inner = (node.children || []).map(flattenSerializeNode).join('');
81
- return `${open}${inner}</${tag}>`;
101
+ return isVoidTag(tag) ? `${open}${inner}` : `${open}${inner}</${tag}>`;
82
102
  }
83
103
  return '';
84
104
  }
@@ -128,7 +148,8 @@ export function* streamSerializeChunks(node) {
128
148
  for (const c of node.children || [])
129
149
  yield* streamSerializeChunks(c);
130
150
  }
131
- yield `</${tag}>`;
151
+ if (!isVoidTag(tag))
152
+ yield `</${tag}>`;
132
153
  }
133
154
  }
134
155
  function fillRowKernelHtml(rk, item, key) {
package/package.json CHANGED
@@ -68,12 +68,12 @@
68
68
  "main": "./dist/faces/server.js",
69
69
  "name": "@vmz/core",
70
70
  "optionalDependencies": {
71
- "@vmz/vmz-darwin-arm64": "0.3.4",
72
- "@vmz/vmz-darwin-x64": "0.3.4",
73
- "@vmz/vmz-linux-arm64": "0.3.4",
74
- "@vmz/vmz-linux-x64": "0.3.4",
75
- "@vmz/vmz-win32-arm64": "0.3.4",
76
- "@vmz/vmz-win32-x64": "0.3.4"
71
+ "@vmz/vmz-darwin-arm64": "0.3.6",
72
+ "@vmz/vmz-darwin-x64": "0.3.6",
73
+ "@vmz/vmz-linux-arm64": "0.3.6",
74
+ "@vmz/vmz-linux-x64": "0.3.6",
75
+ "@vmz/vmz-win32-arm64": "0.3.6",
76
+ "@vmz/vmz-win32-x64": "0.3.6"
77
77
  },
78
78
  "publishConfig": {
79
79
  "access": "public"
@@ -88,5 +88,5 @@
88
88
  },
89
89
  "type": "module",
90
90
  "types": "./dist/faces/index.d.ts",
91
- "version": "0.3.4"
91
+ "version": "0.3.6"
92
92
  }