@vmz/core 0.3.6 → 0.3.7
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 +50 -50
- package/dist/browser/dom/component.d.ts +12 -0
- package/dist/browser/dom/component.js +89 -0
- package/dist/browser/dom/direct-api.js +14 -10
- package/dist/browser/reactivity/patch-scheduler.js +27 -2
- package/dist/faces/server.js +26 -26
- package/dist/host/serve/hmr-dev.js +22 -22
- package/dist/host/serve/page-render.js +73 -73
- package/dist/ssr/serialize.js +3 -2
- package/package.json +7 -7
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
|
|
@@ -5,6 +5,18 @@ export declare function findOwnedDefaultSlot(root: any): any;
|
|
|
5
5
|
export declare function findOwnedDefaultSlotTarget(root: any): any;
|
|
6
6
|
export declare function createInstance(Component: any, props?: any): any;
|
|
7
7
|
export declare function destroy(inst: any): void;
|
|
8
|
+
export declare function insertProjectedSlotNode(target: any, node: any): any;
|
|
9
|
+
/**
|
|
10
|
+
* Hold projected children until the component's default slot exists.
|
|
11
|
+
*
|
|
12
|
+
* A slot inside an unmounted `v-if` branch (closed drawer/dialog, loading
|
|
13
|
+
* skeleton) is absent at projection time. Appending to the component root would
|
|
14
|
+
* paint the child into the live page, so keep it detached and splice it in when
|
|
15
|
+
* the slot is mounted.
|
|
16
|
+
*/
|
|
17
|
+
export declare function parkSlotNode(hostEl: any, node: any): void;
|
|
18
|
+
/** Move parked children into the now-mounted default slot. Returns true when drained. */
|
|
19
|
+
export declare function flushParkedSlotNodes(hostEl: any): boolean;
|
|
8
20
|
export declare function disposeDomTree(root: any): void;
|
|
9
21
|
export declare function snapshotInstanceState(inst: any): {};
|
|
10
22
|
export declare function applyPreservedState(inst: any, state: any): void;
|
|
@@ -183,6 +183,87 @@ export function destroy(inst) {
|
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
|
+
export function insertProjectedSlotNode(target, node) {
|
|
187
|
+
if (!target || !node)
|
|
188
|
+
return null;
|
|
189
|
+
if (String(target.tagName || '').toLowerCase() === 'slot') {
|
|
190
|
+
target.replaceWith(node);
|
|
191
|
+
return node;
|
|
192
|
+
}
|
|
193
|
+
target.appendChild(node);
|
|
194
|
+
return node;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Hold projected children until the component's default slot exists.
|
|
198
|
+
*
|
|
199
|
+
* A slot inside an unmounted `v-if` branch (closed drawer/dialog, loading
|
|
200
|
+
* skeleton) is absent at projection time. Appending to the component root would
|
|
201
|
+
* paint the child into the live page, so keep it detached and splice it in when
|
|
202
|
+
* the slot is mounted.
|
|
203
|
+
*/
|
|
204
|
+
export function parkSlotNode(hostEl, node) {
|
|
205
|
+
if (!hostEl || !node)
|
|
206
|
+
return;
|
|
207
|
+
if (typeof MutationObserver === 'undefined') {
|
|
208
|
+
// Non-browser DOM (unit hosts): preserve the legacy root fallback.
|
|
209
|
+
const child = hostEl.__vmzInst;
|
|
210
|
+
const root = (child && child.__vmzDomRoot) || hostEl;
|
|
211
|
+
if (root && root.nodeType === 1 && typeof root.appendChild === 'function')
|
|
212
|
+
root.appendChild(node);
|
|
213
|
+
else if (typeof hostEl.appendChild === 'function')
|
|
214
|
+
hostEl.appendChild(node);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const parked = hostEl.__vmzSlotParked || (hostEl.__vmzSlotParked = []);
|
|
218
|
+
parked.push(node);
|
|
219
|
+
if (hostEl.__vmzSlotObserver)
|
|
220
|
+
return;
|
|
221
|
+
hostEl.__vmzSlotObserver = new MutationObserver(() => {
|
|
222
|
+
flushParkedSlotNodes(hostEl);
|
|
223
|
+
});
|
|
224
|
+
const scope = (hostEl.__vmzInst && hostEl.__vmzInst.__vmzDomRoot) || hostEl;
|
|
225
|
+
if (scope && scope.nodeType === 1) {
|
|
226
|
+
hostEl.__vmzSlotObserver.observe(scope, { childList: true, subtree: true });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/** Move parked children into the now-mounted default slot. Returns true when drained. */
|
|
230
|
+
export function flushParkedSlotNodes(hostEl) {
|
|
231
|
+
if (!hostEl)
|
|
232
|
+
return false;
|
|
233
|
+
const parked = hostEl.__vmzSlotParked;
|
|
234
|
+
if (!parked || !parked.length)
|
|
235
|
+
return false;
|
|
236
|
+
const child = hostEl.__vmzInst;
|
|
237
|
+
const root = (child && child.__vmzDomRoot) || hostEl;
|
|
238
|
+
if (!root || root.nodeType !== 1)
|
|
239
|
+
return false;
|
|
240
|
+
const target = findOwnedDefaultSlotTarget(root);
|
|
241
|
+
if (!target || !target.parentNode)
|
|
242
|
+
return false;
|
|
243
|
+
if (hostEl.__vmzSlotObserver) {
|
|
244
|
+
hostEl.__vmzSlotObserver.disconnect();
|
|
245
|
+
hostEl.__vmzSlotObserver = null;
|
|
246
|
+
}
|
|
247
|
+
hostEl.__vmzSlotParked = [];
|
|
248
|
+
const tag = String(target.tagName || '').toLowerCase();
|
|
249
|
+
if (tag === 'slot') {
|
|
250
|
+
const frag = typeof document !== 'undefined' ? document.createDocumentFragment() : null;
|
|
251
|
+
if (!frag) {
|
|
252
|
+
target.replaceWith(parked[0]);
|
|
253
|
+
hostEl.__vmzSlotAnchor = parked[parked.length - 1];
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
for (const node of parked)
|
|
257
|
+
frag.appendChild(node);
|
|
258
|
+
target.replaceWith(frag);
|
|
259
|
+
hostEl.__vmzSlotAnchor = parked[parked.length - 1];
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
for (const node of parked)
|
|
263
|
+
target.appendChild(node);
|
|
264
|
+
hostEl.__vmzSlotAnchor = parked[parked.length - 1];
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
186
267
|
export function disposeDomTree(root) {
|
|
187
268
|
if (!root)
|
|
188
269
|
return;
|
|
@@ -205,6 +286,14 @@ export function disposeDomTree(root) {
|
|
|
205
286
|
node.__vmzInst = null;
|
|
206
287
|
destroy(child);
|
|
207
288
|
}
|
|
289
|
+
if (node.__vmzSlotObserver) {
|
|
290
|
+
node.__vmzSlotObserver.disconnect();
|
|
291
|
+
node.__vmzSlotObserver = null;
|
|
292
|
+
}
|
|
293
|
+
if (node.__vmzSlotParked)
|
|
294
|
+
node.__vmzSlotParked = [];
|
|
295
|
+
if (node.__vmzSlotAnchor)
|
|
296
|
+
node.__vmzSlotAnchor = null;
|
|
208
297
|
let child = node.firstChild;
|
|
209
298
|
while (child) {
|
|
210
299
|
const next = child.nextSibling;
|
|
@@ -2,7 +2,7 @@ import { mergeClassParts, mergeStyleParts } from '../../shared/dom-attr-normaliz
|
|
|
2
2
|
import { applyDirectHostBox } from '../direct-host-box.js';
|
|
3
3
|
import { createUnknownComponentElement } from '../unknown-component.js';
|
|
4
4
|
import { applyDomAttr, eventPropHandlerName } from './attributes.js';
|
|
5
|
-
import { createInstance, disposeDomTree, findOwnedDefaultSlotTarget, runDirectCreate, stripFns } from './component.js';
|
|
5
|
+
import { createInstance, disposeDomTree, findOwnedDefaultSlotTarget, insertProjectedSlotNode, parkSlotNode, runDirectCreate, stripFns, } from './component.js';
|
|
6
6
|
import { isEventEntryStrategy, noteDomCreate, noteDomRemove, scheduleClientOn } from './lifecycle.js';
|
|
7
7
|
import { components, resolveComponent } from './registry.js';
|
|
8
8
|
import { beginEventFlush, endEventFlush, inferHandlerMethod, methodAllowsSyncEventFlush, registerBind, runDomEventHandler, runPatch, scheduleRefresh, unregisterBind, } from '../reactivity/patch-scheduler.js';
|
|
@@ -431,16 +431,20 @@ export const directApi = {
|
|
|
431
431
|
root = hostEl;
|
|
432
432
|
const target = root && root.nodeType === 1 ? findOwnedDefaultSlotTarget(root) : null;
|
|
433
433
|
if (target && target.parentNode) {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
434
|
+
hostEl.__vmzSlotAnchor = insertProjectedSlotNode(target, node);
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
// The `<slot>` was already replaced by an earlier projected child, so this
|
|
438
|
+
// call appends after the last one instead of re-searching for a slot.
|
|
439
|
+
const anchor = hostEl.__vmzSlotAnchor;
|
|
440
|
+
if (anchor && anchor.parentNode) {
|
|
441
|
+
anchor.parentNode.insertBefore(node, anchor.nextSibling);
|
|
442
|
+
hostEl.__vmzSlotAnchor = node;
|
|
439
443
|
return;
|
|
440
444
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
+
// Component slot may live inside an unmounted `v-if` branch (closed overlay,
|
|
446
|
+
// loading skeleton). Parking the child until the slot appears keeps it out of
|
|
447
|
+
// the component root, where it would otherwise leak into the rendered page.
|
|
448
|
+
parkSlotNode(hostEl, node);
|
|
445
449
|
},
|
|
446
450
|
};
|
|
@@ -53,6 +53,32 @@ export function scheduleRefresh(inst, notice) {
|
|
|
53
53
|
}
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
|
+
const ARROW_SIGNATURE = /^(?:async\s*)?(?:\(([^)]*)\)|([A-Za-z_$][\w$]*))\s*=>\s*([\s\S]*)$/;
|
|
57
|
+
const EVENT_PASSTHROUGH_CALL = /^this\.([A-Za-z_$][\w$]*)\s*\(([^)]*)\)\s*;?$/;
|
|
58
|
+
/**
|
|
59
|
+
* Direct-bind hint for `(ev) => this.method(ev)` handlers only. The closure is
|
|
60
|
+
* replaced by `method.call(inst, ev)`, so the shape must forward exactly the
|
|
61
|
+
* event: a handler that passes its own literal arguments (`() => this.pick("dark")`)
|
|
62
|
+
* must keep running as a closure, otherwise the event would overwrite the argument.
|
|
63
|
+
*/
|
|
64
|
+
function inferEventPassthroughMethod(src) {
|
|
65
|
+
const arrow = src.match(ARROW_SIGNATURE);
|
|
66
|
+
if (!arrow)
|
|
67
|
+
return null;
|
|
68
|
+
const params = (arrow[1] ?? arrow[2] ?? '')
|
|
69
|
+
.split(',')
|
|
70
|
+
.map((p) => p.trim())
|
|
71
|
+
.filter(Boolean);
|
|
72
|
+
if (params.length !== 1)
|
|
73
|
+
return null;
|
|
74
|
+
let body = arrow[3].trim();
|
|
75
|
+
if (body.startsWith('{') && body.endsWith('}'))
|
|
76
|
+
body = body.slice(1, -1).trim();
|
|
77
|
+
const call = body.match(EVENT_PASSTHROUGH_CALL);
|
|
78
|
+
if (!call || call[2].trim() !== params[0])
|
|
79
|
+
return null;
|
|
80
|
+
return call[1];
|
|
81
|
+
}
|
|
56
82
|
export function inferHandlerMethod(handler) {
|
|
57
83
|
if (typeof handler !== 'function')
|
|
58
84
|
return null;
|
|
@@ -62,8 +88,7 @@ export function inferHandlerMethod(handler) {
|
|
|
62
88
|
let name = null;
|
|
63
89
|
try {
|
|
64
90
|
const src = Function.prototype.toString.call(handler);
|
|
65
|
-
|
|
66
|
-
name = m ? m[1] : null;
|
|
91
|
+
name = inferEventPassthroughMethod(src);
|
|
67
92
|
}
|
|
68
93
|
catch {
|
|
69
94
|
name = null;
|
package/dist/faces/server.js
CHANGED
|
@@ -605,32 +605,32 @@ function wantsBrowserHtml(req) {
|
|
|
605
605
|
function browserErrorHtml(status, title, message, stack) {
|
|
606
606
|
const esc = (s) => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
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) => ({"&":"&","<":"<",">":">"}[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) => ({"&":"&","<":"<",">":">"}[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
|
package/dist/ssr/serialize.js
CHANGED
|
@@ -548,8 +548,9 @@ const serializeApi = {
|
|
|
548
548
|
target.children.push(node);
|
|
549
549
|
return;
|
|
550
550
|
}
|
|
551
|
-
|
|
552
|
-
|
|
551
|
+
// No default slot in this render: children belong to a branch that is not
|
|
552
|
+
// mounted (closed overlay, loading skeleton). Mirror the browser runtime and
|
|
553
|
+
// drop them instead of painting them into the component root.
|
|
553
554
|
},
|
|
554
555
|
setHtml(el, value) {
|
|
555
556
|
if (!el || el.__kind !== 'el')
|
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.
|
|
72
|
-
"@vmz/vmz-darwin-x64": "0.3.
|
|
73
|
-
"@vmz/vmz-linux-arm64": "0.3.
|
|
74
|
-
"@vmz/vmz-linux-x64": "0.3.
|
|
75
|
-
"@vmz/vmz-win32-arm64": "0.3.
|
|
76
|
-
"@vmz/vmz-win32-x64": "0.3.
|
|
71
|
+
"@vmz/vmz-darwin-arm64": "0.3.7",
|
|
72
|
+
"@vmz/vmz-darwin-x64": "0.3.7",
|
|
73
|
+
"@vmz/vmz-linux-arm64": "0.3.7",
|
|
74
|
+
"@vmz/vmz-linux-x64": "0.3.7",
|
|
75
|
+
"@vmz/vmz-win32-arm64": "0.3.7",
|
|
76
|
+
"@vmz/vmz-win32-x64": "0.3.7"
|
|
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.
|
|
91
|
+
"version": "0.3.7"
|
|
92
92
|
}
|