what-core 0.6.2 → 0.7.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/index.js +312 -182
- package/dist/index.js.map +3 -3
- package/dist/index.min.js +6 -6
- package/dist/index.min.js.map +3 -3
- package/dist/jsx-dev-runtime.js +37 -16
- package/dist/jsx-dev-runtime.js.map +2 -2
- package/dist/jsx-dev-runtime.min.js +1 -1
- package/dist/jsx-dev-runtime.min.js.map +3 -3
- package/dist/jsx-runtime.js +37 -16
- package/dist/jsx-runtime.js.map +2 -2
- package/dist/jsx-runtime.min.js +1 -1
- package/dist/jsx-runtime.min.js.map +3 -3
- package/dist/render.js +256 -194
- package/dist/render.js.map +3 -3
- package/dist/render.min.js +1 -1
- package/dist/render.min.js.map +3 -3
- package/dist/testing.js +174 -149
- package/dist/testing.js.map +2 -2
- package/dist/testing.min.js +1 -1
- package/dist/testing.min.js.map +3 -3
- package/package.json +1 -1
- package/src/dom.js +78 -47
- package/src/h.js +43 -18
- package/src/reactive.js +176 -101
- package/src/render.js +118 -34
package/package.json
CHANGED
package/src/dom.js
CHANGED
|
@@ -74,16 +74,20 @@ function disposeComponent(ctx) {
|
|
|
74
74
|
mountedComponents.delete(ctx);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
// Dispose all components and reactive effects attached to a DOM subtree
|
|
77
|
+
// Dispose all components and reactive effects attached to a DOM subtree.
|
|
78
|
+
// Performance: checks _componentCtx / _dispose / _propEffects before walking
|
|
79
|
+
// children, and only checks _commentCtxMap for comment nodes (nodeType 8).
|
|
78
80
|
export function disposeTree(node) {
|
|
79
81
|
if (!node) return;
|
|
80
82
|
if (node._componentCtx) {
|
|
81
83
|
disposeComponent(node._componentCtx);
|
|
82
84
|
}
|
|
83
|
-
// Check comment node WeakMap for component context
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
// Check comment node WeakMap for component context — only for comment nodes
|
|
86
|
+
if (node.nodeType === 8) {
|
|
87
|
+
const commentCtx = _commentCtxMap.get(node);
|
|
88
|
+
if (commentCtx) {
|
|
89
|
+
disposeComponent(commentCtx);
|
|
90
|
+
}
|
|
87
91
|
}
|
|
88
92
|
// Dispose reactive function child effects ({() => ...} wrappers)
|
|
89
93
|
if (node._dispose) {
|
|
@@ -95,9 +99,11 @@ export function disposeTree(node) {
|
|
|
95
99
|
try { node._propEffects[key](); } catch (e) { /* already disposed */ }
|
|
96
100
|
}
|
|
97
101
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
102
|
+
// Recursively dispose children
|
|
103
|
+
const children = node.childNodes;
|
|
104
|
+
if (children && children.length > 0) {
|
|
105
|
+
for (let i = 0; i < children.length; i++) {
|
|
106
|
+
disposeTree(children[i]);
|
|
101
107
|
}
|
|
102
108
|
}
|
|
103
109
|
}
|
|
@@ -216,6 +222,31 @@ export function createDOM(vnode, parent, isSvg) {
|
|
|
216
222
|
// --- Component Rendering ---
|
|
217
223
|
// Components run ONCE. Props are passed as a signal for reactive access.
|
|
218
224
|
|
|
225
|
+
// Shared Proxy handler for reactive props — defined once, reused by all components.
|
|
226
|
+
// The Proxy target must be a plain object (not a function) so that ownKeys
|
|
227
|
+
// invariants are satisfied. The propsSignal is stored as target._sig.
|
|
228
|
+
const _propsProxyHandler = {
|
|
229
|
+
get(target, key) {
|
|
230
|
+
if (key === '_sig') return undefined; // hide internal property
|
|
231
|
+
return target._sig()[key];
|
|
232
|
+
},
|
|
233
|
+
has(target, key) {
|
|
234
|
+
if (key === '_sig') return false;
|
|
235
|
+
return key in target._sig();
|
|
236
|
+
},
|
|
237
|
+
ownKeys(target) {
|
|
238
|
+
return Reflect.ownKeys(target._sig());
|
|
239
|
+
},
|
|
240
|
+
getOwnPropertyDescriptor(target, key) {
|
|
241
|
+
if (key === '_sig') return undefined;
|
|
242
|
+
const current = target._sig();
|
|
243
|
+
if (key in current) {
|
|
244
|
+
return { value: current[key], writable: false, enumerable: true, configurable: true };
|
|
245
|
+
}
|
|
246
|
+
return undefined;
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
|
|
219
250
|
const componentStack = [];
|
|
220
251
|
|
|
221
252
|
export function getCurrentComponent() {
|
|
@@ -256,6 +287,20 @@ function createComponent(vnode, parent, isSvg) {
|
|
|
256
287
|
}
|
|
257
288
|
|
|
258
289
|
// Component context for hooks
|
|
290
|
+
// Error boundary lookup: walk the parent chain once, cache the result.
|
|
291
|
+
const parentCtx = componentStack[componentStack.length - 1] || null;
|
|
292
|
+
let errorBoundary = null;
|
|
293
|
+
if (parentCtx) {
|
|
294
|
+
// Fast path: if parent has an error boundary, use it directly
|
|
295
|
+
errorBoundary = parentCtx._errorBoundary || null;
|
|
296
|
+
if (!errorBoundary) {
|
|
297
|
+
let p = parentCtx._parentCtx;
|
|
298
|
+
while (p) {
|
|
299
|
+
if (p._errorBoundary) { errorBoundary = p._errorBoundary; break; }
|
|
300
|
+
p = p._parentCtx;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
259
304
|
const ctx = {
|
|
260
305
|
hooks: [],
|
|
261
306
|
hookIndex: 0,
|
|
@@ -264,15 +309,8 @@ function createComponent(vnode, parent, isSvg) {
|
|
|
264
309
|
mounted: false,
|
|
265
310
|
disposed: false,
|
|
266
311
|
Component,
|
|
267
|
-
_parentCtx:
|
|
268
|
-
_errorBoundary:
|
|
269
|
-
let p = componentStack[componentStack.length - 1];
|
|
270
|
-
while (p) {
|
|
271
|
-
if (p._errorBoundary) return p._errorBoundary;
|
|
272
|
-
p = p._parentCtx;
|
|
273
|
-
}
|
|
274
|
-
return null;
|
|
275
|
-
})()
|
|
312
|
+
_parentCtx: parentCtx,
|
|
313
|
+
_errorBoundary: errorBoundary,
|
|
276
314
|
};
|
|
277
315
|
|
|
278
316
|
// Component boundaries: use comment nodes instead of <span style="display:contents">
|
|
@@ -294,35 +332,24 @@ function createComponent(vnode, parent, isSvg) {
|
|
|
294
332
|
|
|
295
333
|
// Props signal for reactive updates from parent
|
|
296
334
|
const propsChildren = children.length === 0 ? undefined : children.length === 1 ? children[0] : children;
|
|
297
|
-
|
|
335
|
+
// Merge children into props without spreading when possible
|
|
336
|
+
let mergedProps;
|
|
337
|
+
if (propsChildren !== undefined) {
|
|
338
|
+
mergedProps = props ? Object.assign({}, props, { children: propsChildren }) : { children: propsChildren };
|
|
339
|
+
} else {
|
|
340
|
+
mergedProps = props ? Object.assign({}, props) : {};
|
|
341
|
+
}
|
|
342
|
+
const propsSignal = signal(mergedProps);
|
|
298
343
|
ctx._propsSignal = propsSignal;
|
|
299
344
|
|
|
300
345
|
// Create a reactive props proxy: reading any prop inside an effect
|
|
301
346
|
// will auto-track the dependency on the propsSignal. This makes prop
|
|
302
347
|
// access reactive across re-renders without requiring the component
|
|
303
348
|
// to be re-executed.
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
return current[key];
|
|
309
|
-
},
|
|
310
|
-
has(_, key) {
|
|
311
|
-
const current = propsSignal();
|
|
312
|
-
return key in current;
|
|
313
|
-
},
|
|
314
|
-
ownKeys() {
|
|
315
|
-
const current = propsSignal();
|
|
316
|
-
return Reflect.ownKeys(current);
|
|
317
|
-
},
|
|
318
|
-
getOwnPropertyDescriptor(_, key) {
|
|
319
|
-
const current = propsSignal();
|
|
320
|
-
if (key in current) {
|
|
321
|
-
return { value: current[key], writable: false, enumerable: true, configurable: true };
|
|
322
|
-
}
|
|
323
|
-
return undefined;
|
|
324
|
-
},
|
|
325
|
-
});
|
|
349
|
+
// Reuse shared trap handlers to minimize per-component allocation.
|
|
350
|
+
// Store propsSignal on a plain object target (Proxy invariant: ownKeys must
|
|
351
|
+
// match non-configurable own properties of target; functions have 'prototype').
|
|
352
|
+
const reactiveProps = new Proxy({ _sig: propsSignal }, _propsProxyHandler);
|
|
326
353
|
|
|
327
354
|
// Component runs ONCE — not inside an effect
|
|
328
355
|
componentStack.push(ctx);
|
|
@@ -550,8 +577,9 @@ function createElementFromVNode(vnode, parent, isSvg) {
|
|
|
550
577
|
}
|
|
551
578
|
|
|
552
579
|
// Append children
|
|
553
|
-
|
|
554
|
-
|
|
580
|
+
const isSvgChildren = svgContext && tag !== 'foreignObject';
|
|
581
|
+
for (let i = 0; i < children.length; i++) {
|
|
582
|
+
const node = createDOM(children[i], el, isSvgChildren);
|
|
555
583
|
if (node) el.appendChild(node);
|
|
556
584
|
}
|
|
557
585
|
|
|
@@ -563,16 +591,16 @@ function createElementFromVNode(vnode, parent, isSvg) {
|
|
|
563
591
|
// Only applied once for fine-grained (no diffing). Reactive props use effects.
|
|
564
592
|
|
|
565
593
|
function applyProps(el, newProps, oldProps, isSvg) {
|
|
566
|
-
|
|
567
|
-
oldProps = oldProps || {};
|
|
594
|
+
if (!newProps) return;
|
|
568
595
|
|
|
569
596
|
for (const key in newProps) {
|
|
570
597
|
if (key === 'key' || key === 'children') continue;
|
|
571
598
|
|
|
572
599
|
// Handle ref
|
|
573
600
|
if (key === 'ref') {
|
|
574
|
-
|
|
575
|
-
|
|
601
|
+
const ref = newProps.ref;
|
|
602
|
+
if (typeof ref === 'function') ref(el);
|
|
603
|
+
else if (ref) ref.current = el;
|
|
576
604
|
continue;
|
|
577
605
|
}
|
|
578
606
|
|
|
@@ -609,10 +637,13 @@ function setProp(el, key, value, isSvg) {
|
|
|
609
637
|
if (old) el.removeEventListener(event, old, useCapture);
|
|
610
638
|
if (value == null) return;
|
|
611
639
|
if (!el._events) el._events = {};
|
|
640
|
+
// Single closure per event listener. Uses untrack to prevent accidental
|
|
641
|
+
// signal subscriptions inside event handlers.
|
|
612
642
|
const wrappedHandler = (e) => {
|
|
613
643
|
if (!e.nativeEvent) e.nativeEvent = e;
|
|
614
|
-
return untrack(() =>
|
|
644
|
+
return untrack(() => wrappedHandler._handler(e));
|
|
615
645
|
};
|
|
646
|
+
wrappedHandler._handler = value;
|
|
616
647
|
wrappedHandler._original = value;
|
|
617
648
|
el._events[storageKey] = wrappedHandler;
|
|
618
649
|
const eventOpts = value._eventOpts;
|
package/src/h.js
CHANGED
|
@@ -8,9 +8,25 @@
|
|
|
8
8
|
const EMPTY_OBJ = Object.create(null);
|
|
9
9
|
const EMPTY_ARR = [];
|
|
10
10
|
|
|
11
|
-
export function h(tag, props
|
|
11
|
+
export function h(tag, props) {
|
|
12
12
|
props = props || EMPTY_OBJ;
|
|
13
|
-
|
|
13
|
+
// Collect children from arguments[2..n] without rest args — avoids array allocation
|
|
14
|
+
// when there are 0-1 children (common case: leaf elements, text nodes).
|
|
15
|
+
const argLen = arguments.length;
|
|
16
|
+
let flat;
|
|
17
|
+
if (argLen <= 2) {
|
|
18
|
+
flat = EMPTY_ARR;
|
|
19
|
+
} else if (argLen === 3) {
|
|
20
|
+
flat = _flattenSingle(arguments[2]);
|
|
21
|
+
} else {
|
|
22
|
+
// Multiple children: flatten all arguments from position 2 onward
|
|
23
|
+
const out = [];
|
|
24
|
+
for (let i = 2; i < argLen; i++) {
|
|
25
|
+
_flattenInto(arguments[i], out);
|
|
26
|
+
}
|
|
27
|
+
flat = out;
|
|
28
|
+
}
|
|
29
|
+
|
|
14
30
|
const key = props.key ?? null;
|
|
15
31
|
|
|
16
32
|
// Strip key from props passed to component/element
|
|
@@ -27,24 +43,33 @@ export function Fragment({ children }) {
|
|
|
27
43
|
return children;
|
|
28
44
|
}
|
|
29
45
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
// Fast path for single child (most common case)
|
|
47
|
+
function _flattenSingle(child) {
|
|
48
|
+
if (child == null || child === false || child === true) return EMPTY_ARR;
|
|
49
|
+
if (Array.isArray(child)) {
|
|
50
|
+
const out = [];
|
|
51
|
+
_flattenInto(child, out);
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
if (typeof child === 'object' && child._vnode) return [child];
|
|
55
|
+
if (typeof child === 'function') return [child];
|
|
56
|
+
return [String(child)];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Flatten a child (or array of children) into the output array
|
|
60
|
+
function _flattenInto(child, out) {
|
|
61
|
+
if (child == null || child === false || child === true) return;
|
|
62
|
+
if (Array.isArray(child)) {
|
|
63
|
+
for (let i = 0; i < child.length; i++) {
|
|
64
|
+
_flattenInto(child[i], out);
|
|
45
65
|
}
|
|
66
|
+
} else if (typeof child === 'object' && child._vnode) {
|
|
67
|
+
out.push(child);
|
|
68
|
+
} else if (typeof child === 'function') {
|
|
69
|
+
out.push(child);
|
|
70
|
+
} else {
|
|
71
|
+
out.push(String(child));
|
|
46
72
|
}
|
|
47
|
-
return out;
|
|
48
73
|
}
|
|
49
74
|
|
|
50
75
|
// JSX-like tagged template alternative (no build step needed)
|