@kudzujs/core 0.6.26 → 0.6.28
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/GOAL_A.md +1 -1
- package/README.md +53 -37
- package/framework/README.md +7 -3
- package/framework/binding-runtime.js +4 -2
- package/framework/build.mjs +397 -155
- package/framework/collection-selector.js +63 -0
- package/framework/core.d.ts +2 -1
- package/framework/core.mjs +80 -41
- package/framework/list-runtime.js +342 -96
- package/framework/native-runtime.js +13 -2
- package/framework/shared-runtime.js +18 -5
- package/package.json +1 -1
package/GOAL_A.md
CHANGED
|
@@ -31,7 +31,7 @@ In a matched one-effect navigation build with the same conditional capability, m
|
|
|
31
31
|
|
|
32
32
|
In matched state-only and item-property keyed-row builds, targeted notification adds 821 B raw / 255 B gzip across the route effect entry, shared runtime, and list runtime. Builds without item-property dependencies retain their previous generated path.
|
|
33
33
|
|
|
34
|
-
In the matched 1,000-row keyed-effect runtime microbenchmark, Kudzu measured 3.
|
|
34
|
+
In the latest matched 1,000-row keyed-effect runtime microbenchmark, Kudzu measured 3.6 ms selected-row cleanup/update/setup, 2.4 ms unrelated-field update, and 8.8 ms reorder after all rows and effects were ready. React CSR measured 9.8, 5.9, and 16.8 ms; Vue measured 5.8, 2.4, and 10.5 ms; and Svelte measured 5.7, 4.1, and 58.1 ms. Targeted changed-root notification avoids an extra O(n) effect-record scan; list reconciliation remains O(n). Kudzu emits initial rows while these framework fixtures are CSR, so their JavaScript, output, and build observations are not architecture-equivalent claims.
|
|
35
35
|
|
|
36
36
|
A 0.6.4 release-tree desktop rerun of the matched six-route commerce fixture measured Kudzu at 486.8 ms build, 35,355 deploy bytes, 7,334 B gzip product JavaScript, 332/156 ms cold/warm LCP, 122.6 ms startup task, 4.8 ms interaction, and 5.6 ms product-cart navigation. React measured 545.4 ms build, 61,464 B gzip product JavaScript, 332/264 ms LCP, 179.9 ms startup task, 10.2 ms interaction, and 9.9 ms navigation. Kudzu built 10.7% faster and its top-level-only fixture retained byte-identical deploy, product-graph, and navigation-asset sizes after navigation-owned effects were added. Raw arrays and the consolidated report are retained under the local demo benchmark workspace.
|
|
37
37
|
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
HTML-first TSX framework with synchronous state semantics and no virtual DOM.
|
|
8
8
|
|
|
9
|
-
Kudzu keeps
|
|
9
|
+
Kudzu is designed so ordinary common React-shaped TSX can migrate with minimal source restructuring. It keeps familiar function components, props, children, collection rendering, conditions, event handlers, `useState`, reduced `useReducer`, refs, and effects, preferring compiler specialization over imperative DOM rewrites. This is a general migration model, not compatibility for one application. Static components compile to HTML; interactions compile to direct DOM capabilities and external ESM only where used.
|
|
10
10
|
|
|
11
11
|
> Experimental `0.6.x`: the compiler API and supported TSX surface may change.
|
|
12
12
|
|
|
@@ -198,7 +198,7 @@ function Controls({ dispatch }: { dispatch: Dispatch<TodoAction> }) {
|
|
|
198
198
|
return <Controls dispatch={dispatch} />
|
|
199
199
|
```
|
|
200
200
|
|
|
201
|
-
Kudzu specializes that call at build time; no function prop or child component survives in the browser. Reducers follow React's pure reducer contract. The direct child may also be a keyed row such as `todos.map(todo => <Item key={todo.id} todo={todo} dispatch={dispatch} />)`. Its inline or simple `const` event handler receives the latest keyed item
|
|
201
|
+
Kudzu specializes that call at build time; no function prop or child component survives in the browser. Reducers follow React's pure reducer contract. The direct child may also be a keyed row such as `todos.map(todo => <Item key={todo.id} todo={todo} dispatch={dispatch} />)`. Its inline or simple `const` event handler receives the latest keyed item, and it has the same multiple serializable state, effect, condition, and object-ref support as other keyed rows. The list key path owns those hooks across item updates and reorder; removal cleans up and releases them. Relative TypeScript constants and helpers used inside the handler are renamed for call-site safety and bundled into the parent handler graph. Lazy state or reducer initializers, non-keyed specialized local state, package, namespace, local, async, and generator reducers, package imports or child imports used outside event handlers, further dispatch forwarding, and reducer dispatch through context remain unsupported.
|
|
202
202
|
|
|
203
203
|
That specialized component may pass one inline or simple `const` callback containing dispatch to one relative-imported synchronous child with an intrinsic root:
|
|
204
204
|
|
|
@@ -264,7 +264,7 @@ return <>
|
|
|
264
264
|
</>
|
|
265
265
|
```
|
|
266
266
|
|
|
267
|
-
Kudzu resolves `current` when the handler reads it, so removed conditional elements return `null` without a component runtime. Refs must initialize with `null`; callback refs
|
|
267
|
+
Kudzu resolves `current` when the handler reads it, so removed conditional elements return `null` without a component runtime. Keyed row components may also declare object refs; the row key path scopes the ref and removal releases it. Refs must initialize directly with `null`; callback refs and mutable value refs are not supported.
|
|
268
268
|
|
|
269
269
|
## Context
|
|
270
270
|
|
|
@@ -394,9 +394,11 @@ function ItemList({ items }: { items: Item[] }) {
|
|
|
394
394
|
return <ItemList items={items} />
|
|
395
395
|
```
|
|
396
396
|
|
|
397
|
-
The original row component remains reusable across multiple lists and ordinary JSX. State-backed list wrappers and row components are specialized to intrinsic JSX at build time; no component function or component runtime is shipped to the browser. Kudzu emits initial items as static HTML, then adds, removes, updates, styles, conditional branches, and moves keyed elements directly. The map may appear directly in JSX, in one top-level immutable `const` rendered once as a JSX child, or in one synchronous wrapper receiving the state identifier as a direct prop. Existing keys move without remounting, preserving uncontrolled descendant state. Direct `item.<field>` reads use compact markers; derived item expressions compile to external ESM evaluators.
|
|
397
|
+
The original row component remains reusable across multiple lists and ordinary JSX. State-backed list wrappers and row components are specialized to intrinsic JSX at build time; no component function or component runtime is shipped to the browser. Kudzu emits initial items as static HTML, then adds, removes, updates, styles, conditional branches, and moves keyed elements directly. The map may appear directly in JSX, in one top-level immutable `const` rendered once as a JSX child, or in one synchronous wrapper receiving the state identifier as a direct prop. Existing keys move without remounting, preserving uncontrolled descendant state. Direct `item.<field>` reads use compact markers; derived item expressions compile to external ESM evaluators. Nested item-local `&&` and ternary conditions patch bounded branches and mount or unmount their handlers. Item-local handlers and effects receive the latest JSON-safe item for their key. Effects mount after a row is connected and clean up when it is removed. A direct primitive item dependency such as `[item.name]`, optionally mixed with row or page state, reruns only rows whose selected values changed; unrelated fields and reorder do not rerun it, while removal cleans it up. The item remains stored once in shared list state; runtime descriptors carry a placeholder that the list runtime fills when mounting or updating the keyed root.
|
|
398
398
|
|
|
399
|
-
|
|
399
|
+
Rendered collections may use one-use top-level aliases and analyzable pipelines over local array state. Inline arrow callbacks accept `(item)` or `(item, index)`; `filter()` supports pure synchronous expressions, `flatMap()` projects one direct array property, `Array.from()` accepts an optional pure mapper, and the final `map()` may use `key={item.field}` or positional `key={index}`. Field keys preserve the matching DOM node through filtering and reorder. Positional keys deliberately preserve the DOM node at each position while its item changes, matching React key semantics.
|
|
400
|
+
|
|
401
|
+
A keyed row may contain multiple keyed maps over direct array properties of its item at any nesting depth. This supports recursively nested data populated after mount while preserving keyed DOM identity across updates and reorder:
|
|
400
402
|
|
|
401
403
|
```tsx
|
|
402
404
|
function ItemCard({ item, onSelect }: {
|
|
@@ -407,6 +409,12 @@ function ItemCard({ item, onSelect }: {
|
|
|
407
409
|
<span>{item.title}</span>
|
|
408
410
|
{item.available ? <strong>Available</strong> : <small>Unavailable</small>}
|
|
409
411
|
<button onClick={() => onSelect()}>Select</button>
|
|
412
|
+
<ul>{item.groups.map(group => <li key={group.id}>
|
|
413
|
+
<strong>{group.title}</strong>
|
|
414
|
+
{group.options.map(option => <button key={option.id} onClick={() => console.log(option.title)}>
|
|
415
|
+
{option.title}
|
|
416
|
+
</button>)}
|
|
417
|
+
</li>)}</ul>
|
|
410
418
|
</li>
|
|
411
419
|
}
|
|
412
420
|
|
|
@@ -420,25 +428,27 @@ function ItemCard({ item, onSelect }: {
|
|
|
420
428
|
</section>)}
|
|
421
429
|
```
|
|
422
430
|
|
|
423
|
-
|
|
431
|
+
Each nested collection must be `parent.<field>`, and a row may own multiple sibling child maps. There is no numeric nesting-depth limit. Nested rows may be intrinsic or recursively specialized through same-file and relative-imported components to one intrinsic root. They support nested item conditions, latest-item handlers, effects, object refs initialized with `null`, and multiple `useState` declarations whose initial values are directly serializable primitives, arrays, or plain objects. The structural list site plus ancestor key path owns each hook slot across updates and reorder; removal cleans up effects and releases state/ref ownership, so re-adding the key starts from its initial values. Computed nested collections, parent-item capture from a child row, component cycles, package or namespace row imports, and arbitrary collection callbacks remain unsupported.
|
|
424
432
|
|
|
425
433
|
#### Nested list output
|
|
426
434
|
|
|
427
435
|
Initial child rows remain complete HTML. Kudzu stores one child row prototype, including inert condition branches and descriptors, and reuses it across parent rows.
|
|
428
436
|
|
|
429
|
-
- HTML:
|
|
430
|
-
- Total deploy output:
|
|
431
|
-
- Compressed-file sum:
|
|
432
|
-
- Initial JavaScript:
|
|
437
|
+
- HTML: 339,601 B.
|
|
438
|
+
- Total deploy output: 359,271 B.
|
|
439
|
+
- Compressed-file sum: 24,173 B.
|
|
440
|
+
- Initial JavaScript: 7,204 B gzip. Routes without nested lists compile out prototype and marker lookup.
|
|
433
441
|
|
|
434
|
-
In the matched 100-parent/1,000-child fixture, Kudzu measured 1.
|
|
442
|
+
In the matched 100-parent/1,000-child fixture, Kudzu measured 1.3/0.4/5.0/0.7 ms for child update and condition change, child reverse, parent reverse, and parent removal. Hand-written Astro/native measured 0.5/0.4/3.9/0.2 ms, Svelte 2.7/1.2/6.7/1.3 ms, Vue 4.9/2.5/6.1/2.2 ms, and React 11.8/5.0/8.2/4.4 ms. Kudzu and Astro emit initial rows while the CSR targets do not, so artifact sizes are not architecture-equivalent.
|
|
435
443
|
|
|
436
|
-
Each item must be an ordinary plain object with a unique string or finite-number key; nested data may contain only JSON-safe arrays, ordinary plain objects, and primitive values. Null-prototype objects are rejected to preserve JSON round-trip parity.
|
|
444
|
+
Each item must be an ordinary plain object with a unique string or finite-number key; nested data may contain only JSON-safe arrays, ordinary plain objects, and primitive values. Null-prototype objects are rejected to preserve JSON round-trip parity. Collections must remain anchored to local array state; inline callbacks accept one or two identifier parameters, and row roots must be intrinsic JSX or supported same-file/relative components with `key={item.<field>}` or `key={index}`. State-backed list wrappers use one destructured props parameter, an intrinsic return root, no effects, and a direct local-state prop. Whole-item, computed, nested, derived, `__proto__`, `prototype`, and `constructor` effect dependencies are rejected. A collection alias may only be rendered once and cannot be read by other JavaScript. Collection callbacks and derived expressions must be pure and synchronous: supported reads, operators, templates, approved read-only methods, deterministic `Math`, and primitive conversion compile; imported callbacks, browser globals, promises, mutation, arbitrary calls, and prototype-sensitive properties fail. Lazy or dynamic keyed-row state initializers, non-`null` refs, callback refs, package/namespace/star row imports, same-file exported rows, reusable aliases, prop spreads/defaults/rest, children, fragments, and `dangerouslySetInnerHTML` remain unsupported. Keyed rows must be placed inside an explicit `<tbody>`, `<thead>`, or `<tfoot>`.
|
|
437
445
|
|
|
438
446
|
The focused wrapper fixture emits 1,393 B raw / 500 B gzip HTML and 10,719 B raw / 4,665 B gzip JavaScript across its route capabilities. After one warm-up, seven clean builds measured 314.1, 325.3, 322.3, 327.2, 336.1, 322.4, and 315.0 ms, with a 322.4 ms median.
|
|
439
447
|
|
|
440
448
|
The three-wrapper relative-import fixture emits 2,279 B raw / 629 B gzip HTML and 11,370 B raw / 4,828 B gzip JavaScript, including one imported-wrapper item expression; its unused component handler module is not emitted. After one warm-up, seven clean builds measured 340.2, 349.4, 352.8, 335.0, 354.1, 364.3, and 349.5 ms, with a 349.5 ms median.
|
|
441
449
|
|
|
450
|
+
The compact neutral integration fixture combines the ordinary migration shapes above: a `flatMap`/`filter` alias, `(item, index)`, stable and positional keys, sibling/deep same-file and relative component lists, three nested conditions, and keyed-row state/effect/ref lifecycles. It emits 13,898 B HTML and 39,387 B raw/14,655 B gzip JavaScript across 11 files (16,514 B total file-by-file gzip at level 9). Seven artifact-clean builds after one warm-up measured 515.185, 516.919, 486.594, 517.121, 436.251, 454.066, and 444.403 ms, with a 486.594 ms median. Seven fresh Chrome profiles measured 2.2 ms filter update, 1.2 ms flatMap reorder, 0.6 ms keyed-row state/effect rerun, 3.5 ms ref focus/read, 1.0 ms nested-condition re-entry, 0.8 ms removal cleanup, 2.1 ms re-add/reset, and 0.8/0.6/0.5 ms sibling-list update/add/reorder.
|
|
451
|
+
|
|
442
452
|
## Effects
|
|
443
453
|
|
|
444
454
|
Browser-only initial work uses the familiar empty-dependency effect shape:
|
|
@@ -631,6 +641,10 @@ Supported:
|
|
|
631
641
|
- Conditional child `&&` and ternary DOM patches
|
|
632
642
|
- Top-level and block-scoped JSX locals, terminal early returns, and exhaustive JSX assignment
|
|
633
643
|
- Direct keyed local-state lists
|
|
644
|
+
- Analyzable `filter`/direct-property `flatMap`/`Array.from` collection pipelines with item or positional keys
|
|
645
|
+
- Multiple sibling and recursively deep direct-property child lists
|
|
646
|
+
- Recursive same-file/relative keyed-row specialization with nested conditions and latest-item handlers
|
|
647
|
+
- Keyed-row multiple serializable state slots, effects, and `null`-initialized object refs
|
|
634
648
|
- Page-exported shared layouts with layout/route state lifetimes
|
|
635
649
|
- Opt-in exact/runtime-route navigation with complete-document prefetch and native fallback
|
|
636
650
|
- Layout- and route-lifetime effect mounts in navigation groups
|
|
@@ -639,8 +653,10 @@ Supported:
|
|
|
639
653
|
Not implemented yet:
|
|
640
654
|
|
|
641
655
|
- Reusable keyed-list aliases
|
|
656
|
+
- Arbitrary, imported, mutating, or asynchronous collection callbacks
|
|
657
|
+
- Lazy/dynamic keyed-row state initializers and callback refs
|
|
642
658
|
- Server actions and request-time SSR
|
|
643
|
-
- React package islands
|
|
659
|
+
- React package/runtime/ecosystem compatibility or React islands
|
|
644
660
|
- HMR and framework DevTools
|
|
645
661
|
|
|
646
662
|
## Benchmarks
|
|
@@ -752,21 +768,21 @@ Same content and CSS across every fixture:
|
|
|
752
768
|
|
|
753
769
|
### 1,000-item Keyed List
|
|
754
770
|
|
|
755
|
-
The list starts with 1,000 keyed items, then updates every label, reverses the order, removes odd IDs, and adds 500 items.
|
|
771
|
+
The list starts with 1,000 keyed items, then updates every label, reverses the order, removes odd IDs, and adds 500 items. Kudzu, Next, React, Vue, and Svelte browser timings are medians from 31 rotating fresh headless Chrome profiles, measured when a DOM observer sees each expected result rather than at the next animation frame. The Astro and Qwik rows retain their earlier seven-profile measurements.
|
|
756
772
|
|
|
757
773
|
| Framework | Initial content | Initial JS gzip | Total output | Build | Update | Reverse | Remove | Add | Operations total |
|
|
758
774
|
|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|
|
|
759
|
-
| Astro | Yes | **324 B** | **43.6 KB** |
|
|
760
|
-
| Kudzu | Yes |
|
|
761
|
-
| Next.js | Yes | 182.2 KB | 695.2 KB |
|
|
762
|
-
| React CSR | No | 59.3 KB | 189.4 KB |
|
|
763
|
-
| Vue CSR | No | 24.3 KB | 61.3 KB |
|
|
764
|
-
| Svelte CSR | No | 12.9 KB | 33.1 KB |
|
|
765
|
-
| Qwik CSR | No | 22.2 KB | 64.1 KB |
|
|
775
|
+
| Astro | Yes | **324 B** | **43.6 KB** | 920 ms | **4.4 ms** | **4.1 ms** | **1.5 ms** | **3.6 ms** | **13.6 ms** |
|
|
776
|
+
| Kudzu | Yes | 6.7 KB | 65.9 KB | **380 ms** | **5.7 ms** | **7.3 ms** | **2.8 ms** | **5.6 ms** | **21.4 ms** |
|
|
777
|
+
| Next.js | Yes | 182.2 KB | 695.2 KB | 3142 ms | 8.0 ms | 13.0 ms | 4.4 ms | 7.4 ms | 32.8 ms |
|
|
778
|
+
| React CSR | No | 59.3 KB | 189.4 KB | 1074 ms | 9.7 ms | 12.6 ms | 4.3 ms | 5.9 ms | 32.5 ms |
|
|
779
|
+
| Vue CSR | No | 24.3 KB | 61.3 KB | 791 ms | 11.0 ms | 9.7 ms | 4.4 ms | 6.7 ms | 31.8 ms |
|
|
780
|
+
| Svelte CSR | No | 12.9 KB | 33.1 KB | 910 ms | 6.0 ms | 42.3 ms | 4.5 ms | 6.2 ms | 59.0 ms |
|
|
781
|
+
| Qwik CSR | No | 22.2 KB | 64.1 KB | 634 ms | 10.6 ms | 25.6 ms | 36.5 ms | 22.1 ms | 94.8 ms |
|
|
766
782
|
|
|
767
|
-
An intrinsic-root versus projected-prop row-component A/B build produced byte-for-byte identical `dist` output:
|
|
783
|
+
An intrinsic-root versus projected-prop row-component A/B build produced byte-for-byte identical `dist` output: 6,817 B JS gzip and 67,495 B total. Seven rotating clean builds measured 380 ms and 384 ms. In 31 paired fresh-profile rounds, no browser operation differed significantly; component specialization adds no deployed runtime overhead.
|
|
768
784
|
|
|
769
|
-
Astro is the hand-authored native DOM baseline in the interactive fixtures. React, Vue, Svelte, and Qwik used client-rendered fixtures, while Kudzu and Astro emitted initial HTML; Qwik therefore did not exercise its SSR resumability advantage. Kudzu
|
|
785
|
+
Astro is the hand-authored native DOM baseline in the interactive fixtures. React, Vue, Svelte, and Qwik used client-rendered fixtures, while Kudzu and Astro emitted initial HTML; Qwik therefore did not exercise its SSR resumability advantage. Kudzu has the lowest median for every operation among the 31-profile framework targets, and every exact paired sign test is significant.
|
|
770
786
|
|
|
771
787
|
### 1,000-item Keyed Effect
|
|
772
788
|
|
|
@@ -774,29 +790,29 @@ Each keyed row owns one effect depending on `item.name`. The measured actions re
|
|
|
774
790
|
|
|
775
791
|
| Framework | Initial rows | Initial JS gzip | Total output | Build | Selected update | Unrelated update | Reverse |
|
|
776
792
|
|---|---:|---:|---:|---:|---:|---:|---:|
|
|
777
|
-
| Astro native | Yes | **381 B** | **90,734 B** |
|
|
778
|
-
| Kudzu | Yes |
|
|
779
|
-
| Vue CSR | No | 25,091 B | 63,368 B |
|
|
780
|
-
| Svelte CSR | No | 12,848 B | 33,222 B | 1,
|
|
781
|
-
| React CSR | No | 60,921 B | 194,301 B | 1,
|
|
793
|
+
| Astro native | Yes | **381 B** | **90,734 B** | 998 ms | **0.4 ms** | **0.2 ms** | **5.7 ms** |
|
|
794
|
+
| Kudzu | Yes | 8,264 B | 225,248 B | **426 ms** | 3.6 ms | 2.4 ms | 8.8 ms |
|
|
795
|
+
| Vue CSR | No | 25,091 B | 63,368 B | 935 ms | 5.8 ms | 2.4 ms | 10.5 ms |
|
|
796
|
+
| Svelte CSR | No | 12,848 B | 33,222 B | 1,040 ms | 5.7 ms | 4.1 ms | 58.1 ms |
|
|
797
|
+
| React CSR | No | 60,921 B | 194,301 B | 1,198 ms | 9.8 ms | 5.9 ms | 16.8 ms |
|
|
782
798
|
|
|
783
|
-
This is a post-initialization runtime microbenchmark, not an architecture-equivalent loading comparison. Kudzu and Astro emit all 1,000 rows in HTML while React, Vue, and Svelte use empty CSR shells, so their JavaScript, output, and build columns are observations rather than framework-size or startup claims. Once every target has 1,000 rows and effects ready, targeted changed-root
|
|
799
|
+
This is a post-initialization runtime microbenchmark, not an architecture-equivalent loading comparison. Kudzu and Astro emit all 1,000 rows in HTML while React, Vue, and Svelte use empty CSR shells, so their JavaScript, output, and build columns are observations rather than framework-size or startup claims. Once every target has 1,000 rows and effects ready, Kudzu's targeted changed-root path measures 3.6 ms versus Vue at 5.8 ms, Svelte at 5.7 ms, and React at 9.8 ms. List reconciliation remains O(n); Kudzu and Vue both measure 2.4 ms for the unrelated detail update. Astro is the hand-written direct-DOM lower bound.
|
|
784
800
|
|
|
785
801
|
### 1,000-item Keyed Row State
|
|
786
802
|
|
|
787
|
-
Row 500 enters local edit state, the list reverses while preserving that row and its input DOM identity, then the row is removed and the same key is re-added with fresh non-editing state.
|
|
803
|
+
Row 500 enters local edit state, the list reverses while preserving that row and its input DOM identity, then the row is removed and the same key is re-added with fresh non-editing state. Framework browser timings use 31 rotating fresh profiles; the native baseline retains its latest seven-profile run. Timings start at click and stop only after row order, unique IDs, labels, local state, and DOM identity match.
|
|
788
804
|
|
|
789
805
|
| Framework | Initial rows | Initial JS gzip | Total output | Build | Edit | Reverse | Remove | Re-add |
|
|
790
806
|
|---|---:|---:|---:|---:|---:|---:|---:|---:|
|
|
791
|
-
| Astro native | Yes | **373 B** | **83.7 KB** |
|
|
792
|
-
| Kudzu | Yes |
|
|
793
|
-
| Vue CSR | No | 24.4 KB | 61.6 KB |
|
|
794
|
-
| Svelte CSR | No | 13.1 KB | 33.8 KB |
|
|
795
|
-
| React CSR | No | 59.4 KB | 189.5 KB | 1,
|
|
807
|
+
| Astro native | Yes | **373 B** | **83.7 KB** | 903 ms | **1.6 ms** | **5.7 ms** | **1.2 ms** | **1.3 ms** |
|
|
808
|
+
| Kudzu | Yes | 9.2 KB | 572.9 KB | **434 ms** | **2.7 ms** | 10.8 ms | 3.0 ms | 3.7 ms |
|
|
809
|
+
| Vue CSR | No | 24.4 KB | 61.6 KB | 793 ms | 3.0 ms | 12.1 ms | 4.2 ms | 4.1 ms |
|
|
810
|
+
| Svelte CSR | No | 13.1 KB | 33.8 KB | 911 ms | 2.8 ms | 50.4 ms | 4.6 ms | 5.9 ms |
|
|
811
|
+
| React CSR | No | 59.4 KB | 189.5 KB | 1,054 ms | 6.2 ms | 25.8 ms | 9.1 ms | 6.7 ms |
|
|
796
812
|
|
|
797
|
-
Kudzu builds fastest and
|
|
813
|
+
Kudzu builds fastest and has the lowest framework median for every operation. The 0.1 ms displayed edit lead over Svelte is not statistically significant (p = 0.572); every other framework comparison is significant. Stable identity fast paths preserve direct keyed DOM identity across reorder, removal, and append. Astro remains the hand-written native lower bound. Kudzu's 572.9 KB output includes complete initial HTML plus per-row direct-patch descriptors; React, Vue, and Svelte ship CSR shells, so deploy size and loading architecture are not equivalent comparisons.
|
|
798
814
|
|
|
799
|
-
The general benchmark snapshot was collected on July 22, 2026, the keyed-effect comparison on July 27, and keyed-row-state on July
|
|
815
|
+
The general benchmark snapshot was collected on July 22, 2026, the keyed-effect comparison on July 27, and the 31-profile keyed-list and keyed-row-state comparisons on July 30 with Node 24.14.0 on an Intel i5-9500. These results compare the selected one-page fixtures, not ecosystem maturity, browser interaction speed beyond the listed operations, or each framework's full rendering options. Build times vary with machine load and filesystem cache.
|
|
800
816
|
|
|
801
817
|
## Development
|
|
802
818
|
|
package/framework/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Framework Internals
|
|
2
2
|
|
|
3
|
+
Kudzu specializes ordinary common React-shaped TSX so migrations need minimal source restructuring. Declarative components, collection pipelines, conditions, hooks, and handlers should be lowered at build time rather than replaced with application-owned imperative DOM code. This principle applies across migrations and is not Stay-specific; it does not imply a React package, VDOM, hydration, or ecosystem runtime.
|
|
4
|
+
|
|
3
5
|
- `build.mjs`: TSX compilation, static, `getStaticPaths`, and runtime-fallback routes, base paths, CSS collection, post-build hooks, behavior extraction, static HTML output, and the development server.
|
|
4
6
|
- `core.mjs`: server-side JSX rendering, state slots, context providers, behavior metadata, and serializable capture validation.
|
|
5
7
|
- `jsx-runtime.mjs`: automatic JSX runtime used by TypeScript.
|
|
@@ -23,9 +25,11 @@ Page `metadata` can emit description, canonical, favicon, manifest, Open Graph,
|
|
|
23
25
|
|
|
24
26
|
Inline SVG rendering normalizes an explicit set of common React presentation aliases before static serialization and binding descriptor creation. Reactive aliases therefore use the existing generic `setAttribute` path; static SVG adds no JavaScript and reactive SVG adds no SVG-specific runtime.
|
|
25
27
|
|
|
26
|
-
Same-file and relative-imported
|
|
28
|
+
Same-file and relative-imported component chains receiving a direct local-state array or keyed item are recursively specialized to intrinsic JSX before keyed-list analysis, so their component functions are not retained in the browser. Rows may own multiple direct-property child maps recursively, nested conditions, latest-item handlers, multiple directly serializable state slots, effects, and `null`-initialized object refs. Structural list sites and ancestor key paths scope hooks across updates and reorder and release them on removal. Handler modules are emitted only when a rendered descriptor references them. Direct JSON-safe primitive keyed-item dependencies subscribe each row record to its owning list commit and compare selected fields after `list-runtime.js` synchronously refreshes the row marker. Only changed rows rerun with the complete latest item; reorder compares equal and unrelated fields do nothing. Builds without item dependencies emit no item reader or list-state subscription code.
|
|
29
|
+
|
|
30
|
+
Rendered collection selectors compile one-use aliases and inline `(item)` or `(item, index)` pipelines over local array state. Supported selectors are pure `filter`, direct-property `flatMap`, and `Array.from` before a final keyed `map`; field keys retain item identity while `key={index}` retains positional identity. Arbitrary callbacks, mutation, asynchronous selectors, imported callback functions, prototype-sensitive reads, lazy/dynamic row state initializers, non-`null` or callback refs, and recursive component cycles fail during compilation.
|
|
27
31
|
|
|
28
|
-
The reduced `useReducer` form reuses ordinary state slots and React's pure reducer contract. A direct dispatch in a compiled handler becomes a functional `set` whose reducer is bundled from a relative TypeScript module into that handler graph. Pure reducer-owned keyed lists reuse unchanged item identities for reorder, one removal, and append fast paths; ordinary `useState` lists retain full validation. One direct dispatch prop into a same-file or relative-imported synchronous component, including a direct keyed row, is specialized to intrinsic JSX at the call site, so its handler retains the parent reducer scope and no dispatch capture or child handler asset is emitted. A
|
|
32
|
+
The reduced `useReducer` form reuses ordinary state slots and React's pure reducer contract. A direct dispatch in a compiled handler becomes a functional `set` whose reducer is bundled from a relative TypeScript module into that handler graph. Pure reducer-owned keyed lists reuse unchanged item identities for reorder, one removal, and append fast paths; ordinary `useState` lists retain full validation. One direct dispatch prop into a same-file or relative-imported synchronous component, including a direct keyed row, is specialized to intrinsic JSX at the call site, so its handler retains the parent reducer scope and no dispatch capture or child handler asset is emitted. A reducer row reads the latest item through the existing list scope and uses the same multiple serializable state, effect, condition, and object-ref specialization as other keyed rows. Relative TypeScript imports referenced inside that child handler receive collision-free call-site aliases and join the parent handler graph. One nested relative-imported intrinsic child may receive an inline or simple `const` callback containing dispatch; the compiler recursively substitutes that callback once and omits the nested child handler asset. Missing primitive literal defaults in these reducer specializations are substituted at the same call site. Reducer-free routes and shared runtimes are unchanged; no reducer runtime or browser component instance exists.
|
|
29
33
|
|
|
30
34
|
`kudzu.config` may opt one emitted shared-layout group into same-document navigation with legacy `navigation: { routes: ["/product", "/items/[id]"] }`, or multiple groups with `navigation: { groups: [{ routes: [...] }, { routes: [...] }] }`. The forms are mutually exclusive. Identities are globally unique emitted exact paths or `runtimeParams` patterns; each group uses one page-exported layout function identity. Runtime records securely match concrete pathnames under `base`, and their cache-safe parameter initializer runs before route DOM/effects mount on every transition. Each group receives a deterministic route-hashed asset specialized to only its records, pattern decoder, and effect/parameter lifecycle needs. Cross-group and ungrouped anchors remain native and are not prefetched; overlapping path domains across groups fail the build. Route effect entries export cache-safe layout and route mount functions: layout effects, including conditional/keyed DOM-owned effects, persist for the group session; route effects receive a fresh owner registry after each route insertion; and non-persisted page disposal cleans route before layout. Direct primitive state, runtime parameter, and keyed-item property dependencies and cleanup are supported. Fragment payloads and coordinated View Transitions are not implemented.
|
|
31
35
|
|
|
@@ -33,4 +37,4 @@ The current matched commerce profile emits 35,355 deploy bytes and loads 7,334 B
|
|
|
33
37
|
|
|
34
38
|
In matched state-only and item-property keyed-row builds, the minified route effect entry changes from 3,829 B raw/1,667 B gzip to 4,392 B raw/1,823 B gzip, the shared runtime from 1,291 B raw/671 B gzip to 1,503 B raw/751 B gzip, and the list runtime from 6,606 B raw/2,474 B gzip to 6,652 B raw/2,493 B gzip. The complete targeted-notification capability costs +821 B raw/+255 B gzip and remains absent from builds without item dependencies. Seven clean builds of the expanded three-route fixture measured 420-440 ms with a 430 ms median; this records current build cost rather than claiming a cross-version speed change.
|
|
35
39
|
|
|
36
|
-
The matched 1,000-row cross-framework effect fixture measured Kudzu at
|
|
40
|
+
The matched 1,000-row cross-framework effect fixture measured Kudzu at 8,264 B initial JavaScript gzip, 426 ms build, 3.6 ms selected-row cleanup/update/setup, 2.4 ms unrelated-field update, and 8.8 ms reorder. React CSR measured 60,921 B, 1,198 ms, 9.8 ms, 5.9 ms, and 16.8 ms respectively; Vue measured 5.8, 2.4, and 10.5 ms for the browser operations, and Svelte measured 5.7, 4.1, and 58.1 ms. Browser operations begin only after all targets have 1,000 rows and effects ready. Kudzu emits those rows in HTML while the framework CSR fixtures begin from empty shells, so JavaScript, output, and build values are not architecture-equivalent comparisons. Targeted changed-root notification avoids an extra O(n) effect-record scan; list validation, serialization, and reconciliation remain O(n).
|
|
@@ -157,8 +157,10 @@ function updateCondition(condition) {
|
|
|
157
157
|
condition.current = next
|
|
158
158
|
if (condition.mount) for (const node of nodes) mountDom(node)
|
|
159
159
|
const select = condition.start.closest("select[data-k-bind-value]")
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
if (select) {
|
|
161
|
+
for (const binding of new Set((bindingRegistrations.get(select) ?? []).map(([, entry]) => entry))) {
|
|
162
|
+
patchBinding(binding.node, binding.target, binding.read())
|
|
163
|
+
}
|
|
162
164
|
}
|
|
163
165
|
}
|
|
164
166
|
|