@kudzujs/core 0.8.23 → 0.8.25
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_B.md +5 -3
- package/MIGRATION_ROADMAP.md +13 -0
- package/PERFORMANCE.md +133 -7
- package/README.md +2 -2
- package/RELEASES.md +65 -0
- package/docs/next-architecture/README.md +2 -2
- package/docs/next-architecture/compiler-current-architecture.md +2 -2
- package/docs/next-architecture/goal-b-optimization-benchmarks.md +20 -6
- package/docs/next-architecture/versioning.md +3 -2
- package/framework/README.md +2 -2
- package/framework/build.mjs +16 -6
- package/framework/compiler/descriptor-session.mjs +2 -1
- package/framework/compiler/ir/module-ir.mjs +27 -0
- package/framework/compiler/normalization-pipeline.mjs +6 -3
- package/framework/list-runtime.js +2 -1
- package/package.json +4 -1
package/GOAL_B.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Goal B: Relative TypeScript Worker Capability Proof
|
|
2
2
|
|
|
3
|
+
This historical capability milestone is distinct from the active [Goal B optimization program](./docs/next-architecture/goal-b-optimization-benchmarks.md).
|
|
4
|
+
|
|
3
5
|
Goal B is a completed cross-application compiler-capability proof for one browser-only pattern used by realtime React-shaped applications: a route-owned relative TypeScript module Worker created and cleaned up from an inline `useEffect`.
|
|
4
6
|
|
|
5
7
|
The realtime dashboard is a stress and conformance fixture. It is not a Kudzu product vertical, dashboard API, stream runtime, widget framework, server platform, or claim of ThingsBoard compatibility. [`MIGRATION_ROADMAP.md`](./MIGRATION_ROADMAP.md) remains the source of truth for future fixture-driven migration work.
|
|
@@ -102,11 +104,11 @@ The focused fixture emitted a 907 B raw / 477 B gzip Worker graph and an 11,388
|
|
|
102
104
|
|
|
103
105
|
These numbers are dated conformance evidence. They do not define a dashboard product target.
|
|
104
106
|
|
|
105
|
-
##
|
|
107
|
+
## Historical 0.7.12 Rerun
|
|
106
108
|
|
|
107
|
-
At commit `05e5cc2` on Apple M3 / macOS 26.5.2 / Node 25.6.1, `npm run benchmark` measured clean build times of 404.2, 401.3, 408.2, 404.4, 399.9, 408.8, and 402.0 ms after one warm-up, for a 404.2 ms median.
|
|
109
|
+
At commit `05e5cc2` on Apple M3 / macOS 26.5.2 / Node 25.6.1, `npm run benchmark` measured clean build times of 404.2, 401.3, 408.2, 404.4, 399.9, 408.8, and 402.0 ms after one warm-up, for a 404.2 ms median. That rerun's Worker graph was 907 B raw / 475 B gzip; its complete dashboard window graph was 11,960 B raw / 5,365 B aggregate gzip.
|
|
108
110
|
|
|
109
|
-
Chrome 150.0.7871.187 passed the tracked throughput, cadence, stale-write, bounded-history, and 30-cycle start/termination checks through the focused Worker browser test.
|
|
111
|
+
Chrome 150.0.7871.187 passed the tracked throughput, cadence, stale-write, bounded-history, and 30-cycle start/termination checks through the focused Worker browser test. Those rerun values differ from the historical completion snapshot because shared window capabilities and the local toolchain changed; both sets remain release provenance, not current artifact claims.
|
|
110
112
|
|
|
111
113
|
## Non-Goals
|
|
112
114
|
|
package/MIGRATION_ROADMAP.md
CHANGED
|
@@ -243,6 +243,19 @@ This queue orders the next investigations by general migration value. Start only
|
|
|
243
243
|
- Source graph resolution and shared path conversion have focused owners, while Worker compilation directly owns Worker graph emission.
|
|
244
244
|
- Complete-site and representative deploy artifacts remain byte-identical to `v0.8.22`; diagnostics, RouteIR, CapabilityIR, browser ownership, and accepted syntax are unchanged.
|
|
245
245
|
|
|
246
|
+
### Completed In 0.8.24
|
|
247
|
+
|
|
248
|
+
- Goal B adds a maintained 2,000-row keyed browser benchmark with fresh-profile timing and ownership checks.
|
|
249
|
+
- Safe large flat-list restoration batches mount-hook discovery, reducing the measured median by 19.77% while retained-heavy append, filter, reverse, identity, state reset, and handler behavior remain unchanged.
|
|
250
|
+
- No-op normalization passes skip redundant recursive parent repair, reducing the measured 1,000-product build median by 6.26% with equivalent output.
|
|
251
|
+
- Identical generated route-entry transformation remains the next evidence-backed Goal B investigation; it is not implemented without an isolated benchmark.
|
|
252
|
+
|
|
253
|
+
### Completed In 0.8.25
|
|
254
|
+
|
|
255
|
+
- Seven alternating builds of the 1,000-product, 1,011-page fixture authorized build-local exact-source reuse for identical generated native, parameter, and effect route-entry transforms, improving the median by 9.17% with identical deploy paths and bytes.
|
|
256
|
+
- Normalization pass results and finalized ModuleIR cross-slot references now fail closed at their compiler boundaries.
|
|
257
|
+
- Node 22 compatibility, required Chrome coverage, packed-package installation, version alignment, and post-publish registry checks are explicit release gates.
|
|
258
|
+
|
|
246
259
|
## Cross-Cutting Performance Gates
|
|
247
260
|
|
|
248
261
|
Every migration feature must preserve:
|
package/PERFORMANCE.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Performance Records
|
|
2
2
|
|
|
3
|
+
Reproducibility classes: `npm run benchmark` and `npm run benchmark:keyed` are maintained in this repository; `npm run benchmark:commerce` is a maintained paired runner over the public external storefront; older excluded-workspace sections are historical provenance only and are not current framework rankings.
|
|
4
|
+
|
|
5
|
+
## 0.8.25 Route Entry Transform Reuse
|
|
6
|
+
|
|
7
|
+
Measured UTC 2026-08-10 on an Intel Core i5-9500 with 6 cores, Linux 6.17.0-19-generic, Node 24.14.0, and npm 11.9.0. The public 1,000-product storefront fixture at `f2d5be1` generated 1,011 pages against the `0.8.24` tree plus the same compiler-boundary safety changes in both targets.
|
|
8
|
+
|
|
9
|
+
One warm-up followed by seven alternating clean builds compared repeated esbuild transformation with a build-local exact-source result map used only by generated native, parameter, and effect route entries. The median decreased from 13,851.0 ms to 12,581.4 ms, a 9.17% improvement. Every emitted path and SHA-256 hash matched. The catalog was generated once before timing; `dist` and `.kudzu` cleanup and manifest hashing remained outside timing.
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
repeated transform: [13437.4,13851.0,13844.9,13440.8,13906.1,14310.0,14901.0]
|
|
13
|
+
exact-source reuse: [12480.2,12826.9,12777.9,12615.9,11869.7,12121.3,12581.4]
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The map lasts for one build and keys the complete generated source after route-relative URLs are resolved. It is not a persistent or generalized JavaScript transform cache. The result measures the large repeated-route fixture on this Linux machine and is not directly comparable to the Apple M3 `0.8.24` build medians below.
|
|
17
|
+
|
|
18
|
+
## 0.8.24 Measured Goal B Optimizations
|
|
19
|
+
|
|
20
|
+
Measured UTC 2026-08-09 on Apple M3, 8 logical CPUs, 8 GiB RAM, macOS 26.5.2 / Darwin 25.5.0, Node 25.6.1, npm 11.18.0, and Chrome 151.0.7922.76. Baseline tag `v0.8.23` at `39a065b` and the `0.8.24` candidate used the same local volume and dependencies.
|
|
21
|
+
|
|
22
|
+
The implementation and maintained benchmark patch over `v0.8.23` had SHA-256 `c6c39b0c64f7a5d3271cb9f0286a4c6787aaf7e293744457629e6ea249e9ef76`, produced by:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
git diff --binary v0.8.23 -- framework/compiler/normalization-pipeline.mjs framework/list-runtime.js test/compiler-passes.test.mjs test/keyed-performance.mjs test/fixtures/keyed-performance/src/pages/index.tsx test/commerce-build-performance.mjs package.json | shasum -a 256
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Twenty-one fresh profiles measured 2,000-row restoration at 21.1 ms versus 26.3 ms, a 19.77% improvement. Twenty-one alternating clean builds of the external 1,000-product fixture measured 6,266.5 ms versus 6,684.7 ms, a 6.26% improvement. The keyed route adds 127 B raw / 35 B aggregate gzip JavaScript; the normalization optimization adds no output bytes. Complete methodology, arrays, artifacts, correctness checks, and limitations are in [Goal B Measurement Details](#goal-b-measurement-details).
|
|
29
|
+
|
|
30
|
+
Before release-content updates, the complete site plus `lists`, `keyed-row-hooks`, and `navigation` retained identical deploy file lists. Only `assets/kudzu-list.js` changed; every other deploy file was byte-identical. Their normalized `.kudzu` trees were identical to `v0.8.23`.
|
|
31
|
+
|
|
3
32
|
## 0.8.23 Source Compiler Boundary
|
|
4
33
|
|
|
5
34
|
Measured UTC 2026-08-09 on Apple M3, 8 logical CPUs, 8 GiB RAM, macOS 26.5.2 / Darwin 25.5.0, Node 22.23.2, and npm 11.18.0. Baseline tag `v0.8.22` at `60b9bff` and the current `0.8.23` compiler candidate used the same local volume and identical installed dependencies.
|
|
@@ -24,16 +53,111 @@ Both targets received one warm-up followed by seven clean `worker-effects` produ
|
|
|
24
53
|
|
|
25
54
|
Before release-content updates, the complete 135-page site and the `bindings`, `keyed-row-hooks`, `effect-dependencies`, `worker-effects`, `runtime-params`, `navigation`, `config-authoring`, and `event-package` deploy trees had identical file lists and bytes. Their `.kudzu` trees also matched after replacing only each checkout root in existing source-location strings. The invalid-reducer fixture retained the same source file, line, column, and diagnostic text. `build.mjs` decreased from 3,732 to 744 lines; this source-organization metric is not a runtime performance claim.
|
|
26
55
|
|
|
56
|
+
## Goal B Measurement Details
|
|
57
|
+
|
|
58
|
+
Goal B started from clean commit `39a065b4284c74e7bf8ee5e39647ef771f2ba6f6`. On the same Apple M3 environment with Node 22.23.2, the maintained `worker-effects` benchmark measured a 289.9 ms clean-build median, 907 B raw / 477 B gzip Worker graph, and 12,148 B raw / 5,411 B gzip window graph:
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
build: [284.3,287.1,293.6,289.9,291.3,286.4,290.7]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
After the two candidate optimizations, the same maintained benchmark measured a 286.2 ms median and identical graph sizes. This is a regression check rather than evidence for either optimization:
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
build: [286.2,282.1,287.1,286.4,285.4,284.4,287.2]
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Chrome 151.0.7922.76 passed the tracked throughput, cadence, bounded-history, stale-write, and 30-cycle ownership checks. The first Node 22 browser attempt returned no Worker data; immediate Node 25 and Node 22 retries passed, so this is recorded as a startup flake rather than a performance result.
|
|
71
|
+
|
|
72
|
+
The preserved external cross-framework workspace then rebuilt its 1,000-row Kudzu fixture against clean `0.8.23` to locate a possible keyed-list loss. This exploratory harness is not maintained in the repository and its historical framework results were not reused. Seven fresh unthrottled Chrome profiles all passed row visibility, retained identity, removal, and fresh re-add checks:
|
|
73
|
+
|
|
74
|
+
| Operation | Median | Raw runs |
|
|
75
|
+
|---|---:|---|
|
|
76
|
+
| Edit row 500 | 0.5 ms | 1.1, 0.6, 0.5, 0.5, 0.6, 0.5, 0.5 |
|
|
77
|
+
| Reverse 1,000 rows | 4.0 ms | 4.8, 3.8, 3.8, 4.2, 4.0, 4.1, 3.8 |
|
|
78
|
+
| Remove row 500 | 1.2 ms | 1.5, 1.1, 1.1, 1.3, 1.2, 1.2, 1.2 |
|
|
79
|
+
| Re-add row 500 | 1.3 ms | 2.3, 1.0, 1.3, 1.5, 1.1, 1.2, 1.4 |
|
|
80
|
+
|
|
81
|
+
That fixture's seven clean builds were `[390.891,389.698,393.034,390.798,389.477,390.258,388.714]` ms for a 390.258 ms median. It emitted 28,243 B raw / 9,245 B aggregate gzip initial JavaScript across eight files and 943,075 B total output across nine files. The absolute build result has no current matched control and does not establish a regression. No browser operation in this exploratory fixture established a material loss.
|
|
82
|
+
|
|
83
|
+
#### Maintained Keyed Restoration Benchmark
|
|
84
|
+
|
|
85
|
+
The repository-owned `npm run benchmark:keyed` fixture renders 2,000 keyed rows with row-local state, a reactive slice, and a reactive string filter. Each revision received one clean build warm-up, 21 measured clean builds, and 21 unthrottled fresh Chrome 151.0.7922.76 profiles. In-page `MutationObserver` completion includes the synchronous state commit and terminal DOM mutation. Every run retained row 1000 and its selected state, appended 33 rows beside 1,967 retained rows, filtered to one row, released row 1, restored row 1 with fresh identity/state and a working handler, and retained row 1000 through reversal.
|
|
86
|
+
|
|
87
|
+
The baseline uses the identical `0.8.24` benchmark harness and fixture copied into a clean `v0.8.23` worktree; only compiler/runtime source differs:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
git worktree add --detach /tmp/kudzu-0.8.23 v0.8.23
|
|
91
|
+
mkdir -p /tmp/kudzu-0.8.23/test/fixtures/keyed-performance/src/pages
|
|
92
|
+
cp test/keyed-performance.mjs /tmp/kudzu-0.8.23/test/keyed-performance.mjs
|
|
93
|
+
cp test/fixtures/keyed-performance/src/pages/index.tsx /tmp/kudzu-0.8.23/test/fixtures/keyed-performance/src/pages/index.tsx
|
|
94
|
+
ln -s "$PWD/node_modules" /tmp/kudzu-0.8.23/node_modules
|
|
95
|
+
RUNS=21 CHROME_BIN="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" node /tmp/kudzu-0.8.23/test/keyed-performance.mjs
|
|
96
|
+
RUNS=21 CHROME_BIN="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" npm run benchmark:keyed
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The measured loss was restoration from one retained row to 2,000 rows. The baseline called `mountDom()` separately for 1,999 connected additions. For top-level flat lists, the candidate calls it once on the connected list parent when more than 32 additions are a majority of both the next rows and their parent children. Nested/parent lists, shared containers dominated by unrelated siblings, and small or retained-heavy updates keep per-root mounting. This reduces repeated mount-hook selector traversal without adding a scheduler, cache, tree, or public API.
|
|
100
|
+
|
|
101
|
+
| Target | Build median | Append 33 | Filter median | Restore median | Reverse median | JavaScript raw / gzip |
|
|
102
|
+
|---|---:|---:|---:|---:|---:|---:|
|
|
103
|
+
| `0.8.23` baseline | 263.0 ms | 2.6 ms | 4.6 ms | 26.3 ms | 6.2 ms | 28,308 B / 10,937 B |
|
|
104
|
+
| Goal B candidate | 265.6 ms | 2.7 ms | 4.6 ms | 21.1 ms | 6.1 ms | 28,435 B / 10,972 B |
|
|
105
|
+
|
|
106
|
+
Restore improved 19.77%, and its 20.4-21.9 ms candidate range did not overlap the 25.7-27.3 ms baseline range. Append, filter, and reverse distributions overlap; no change is claimed. Build distributions overlap; no build improvement is claimed. The deterministic cost is 127 B raw / 35 B aggregate gzip in `kudzu-list.js`.
|
|
107
|
+
|
|
108
|
+
```text
|
|
109
|
+
baseline build: [261.7,265.9,263.0,263.9,259.4,265.5,261.5,263.4,260.4,261.8,264.7,263.4,260.7,262.7,263.6,264.4,261.5,264.0,261.7,263.5,262.3]
|
|
110
|
+
candidate build: [265.4,271.2,265.2,263.6,266.1,266.7,264.4,317.3,298.2,269.4,269.2,264.1,266.9,268.9,273.6,265.5,262.4,259.4,262.1,265.6,262.0]
|
|
111
|
+
baseline append: [2.8,2.5,2.5,3.0,2.5,2.6,2.8,2.7,2.7,2.8,2.8,2.5,2.6,2.9,2.7,2.6,2.5,2.4,2.7,2.4,2.4]
|
|
112
|
+
candidate append: [2.8,3.0,2.7,2.5,2.7,2.6,2.8,2.8,2.7,2.6,2.8,2.8,3.0,2.8,2.4,2.4,2.4,2.9,2.9,2.7,2.6]
|
|
113
|
+
baseline filter: [4.5,4.5,5.0,4.6,4.5,4.5,4.9,4.8,4.5,4.6,4.7,4.3,4.7,4.8,4.7,4.7,4.6,4.5,4.9,4.4,4.2]
|
|
114
|
+
candidate filter: [4.7,4.8,4.6,4.6,4.7,4.4,4.7,4.5,4.6,4.4,4.7,4.9,4.9,4.6,4.4,4.6,4.6,4.8,4.8,4.3,4.4]
|
|
115
|
+
baseline restore: [26.5,27.3,26.5,26.5,25.8,26.7,26.3,26.4,26.2,26.4,26.6,26.2,27.0,26.6,25.9,25.7,25.9,26.3,26.0,25.9,26.3]
|
|
116
|
+
candidate restore: [21.3,21.7,21.8,21.6,20.4,20.8,20.6,21.5,20.4,20.8,21.8,21.4,21.9,21.0,21.5,20.9,20.8,21.0,21.1,21.1,21.2]
|
|
117
|
+
baseline reverse: [6.2,6.3,6.0,6.4,6.6,7.1,6.4,6.0,6.8,6.3,5.8,5.8,6.4,5.8,6.2,7.1,5.7,6.1,5.8,6.0,5.5]
|
|
118
|
+
candidate reverse: [5.8,6.0,6.2,5.8,5.8,6.1,6.2,6.2,6.3,6.0,6.1,6.2,6.2,6.0,6.3,6.2,5.9,6.3,6.0,5.9,6.3]
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
#### External 1,000-Product Build
|
|
122
|
+
|
|
123
|
+
The public [`SimYunSup/kudzu-based-bench`](https://github.com/SimYunSup/kudzu-based-bench) commerce fixture at `f2d5be1` generated 1,000 deterministic products and 1,011 complete Kudzu pages. On the same Apple M3 / Node 25.6.1 machine, clean `0.8.23` and the Goal B candidate received one warm-up and 21 alternating clean builds. Output and `.kudzu` cleanup remained outside timing.
|
|
124
|
+
|
|
125
|
+
The paired runner generates the catalog once, alternates the two compiler roots, cleans `dist` and `.kudzu` outside timing, compares relative output manifests and hashes, permits only the recorded `assets/kudzu-list.js` delta, and restores the external app's package symlink afterward:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
git clone https://github.com/SimYunSup/kudzu-based-bench.git /tmp/kudzu-based-bench
|
|
129
|
+
git -C /tmp/kudzu-based-bench checkout --detach f2d5be1a516c539e30f7125f6870d42b1dd02ecd
|
|
130
|
+
pnpm --dir /tmp/kudzu-based-bench install --force
|
|
131
|
+
pnpm --dir /tmp/kudzu-based-bench run build:commerce
|
|
132
|
+
git worktree add --detach /tmp/kudzu-0.8.23 v0.8.23
|
|
133
|
+
ln -s "$PWD/node_modules" /tmp/kudzu-0.8.23/node_modules
|
|
134
|
+
APP_ROOT=/tmp/kudzu-based-bench/apps/shop-kudzu \
|
|
135
|
+
BASELINE_ROOT=/tmp/kudzu-0.8.23 \
|
|
136
|
+
CANDIDATE_ROOT="$PWD" RUNS=21 CATALOG_SIZE=1000 \
|
|
137
|
+
npm run benchmark:commerce
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`applyNormalizationPasses()` previously called TypeScript's recursive parent-pointer repair after every pass, including validators and no-op passes returning the identical `SourceFile`. Every current pass was audited to use immutable factory updates for structural changes. Repairing only a changed root reduced the build median from 6,684.7 ms to 6,266.5 ms, a 6.26% improvement. Both builds emitted 3,056 files; only `assets/kudzu-list.js` differed because of the independently measured keyed restoration optimization. The normalization change itself adds no browser bytes.
|
|
141
|
+
|
|
142
|
+
```text
|
|
143
|
+
baseline: [6488.0,7095.6,6861.0,6647.7,6563.4,6613.3,6536.1,6529.2,6462.6,6655.9,6684.7,6623.2,6504.9,7618.5,8174.5,9309.1,7611.4,7942.3,8148.2,7191.0,7380.7]
|
|
144
|
+
candidate: [5898.6,5903.9,6108.5,6088.1,5966.4,5913.8,6266.5,6115.1,6014.6,6022.0,6223.1,6357.5,6429.5,6950.8,7887.1,8275.9,7208.9,7306.3,7861.9,6798.8,6859.3]
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
This external fixture is a candidate-finding and paired Kudzu regression benchmark, not a framework leaderboard result. It uses Kudzu-authored source; the catalog is generated once before timing and both revisions run the same complete Kudzu build. Its published cross-framework runs are sequential with only three build samples, and none of those framework numbers are used for this optimization claim. The paired command requires an existing symlink at `apps/shop-kudzu/node_modules/@kudzujs/core`. The external commit's pnpm 10.20 lock cannot install with `--frozen-lockfile`; `--force` accepts the unchanged lock and is a reproducibility limitation, although baseline and candidate use the same resulting dependency graph.
|
|
148
|
+
|
|
27
149
|
## 0.8.22 Versioned Compiler Foundation
|
|
28
150
|
|
|
29
|
-
Measured UTC 2026-08-09 on Apple M3, 8 logical CPUs, 8 GiB RAM, macOS 26.5.2 / Darwin 25.5.0, Node 25.6.1, and npm 11.18.0. Baseline tag `v0.8.21` at `ff38092` and the current `0.8.22` compiler candidate used the same local volume and identical installed dependencies.
|
|
151
|
+
Measured UTC 2026-08-09 on Apple M3, 8 logical CPUs, 8 GiB RAM, macOS 26.5.2 / Darwin 25.5.0, Node 25.6.1, and npm 11.18.0. Baseline tag `v0.8.21` at `ff38092` and the then-current `0.8.22` compiler candidate used the same local volume and identical installed dependencies.
|
|
30
152
|
|
|
31
|
-
The
|
|
153
|
+
The final `v0.8.21` to `v0.8.22` tagged implementation patch has reproducible SHA-256 `5fbdc3658b8c0d4d568c7ccdbf89c2c1c20c3a275ff9d0fd20e439b479d60530`, produced by:
|
|
32
154
|
|
|
33
155
|
```bash
|
|
34
|
-
git diff --binary v0.8.21 -- framework/build.mjs framework/compiler/list-runtime-codegen.mjs framework/compiler/param-codegen.mjs framework/compiler/route-capability-planner.mjs framework/compiler/runtime-codegen.mjs framework/core.mjs framework/core.d.ts | shasum -a 256
|
|
156
|
+
git diff --binary v0.8.21 v0.8.22 -- framework/build.mjs framework/compiler/list-runtime-codegen.mjs framework/compiler/param-codegen.mjs framework/compiler/route-capability-planner.mjs framework/compiler/runtime-codegen.mjs framework/core.mjs framework/core.d.ts | shasum -a 256
|
|
35
157
|
```
|
|
36
158
|
|
|
159
|
+
The previously recorded candidate hash does not match this tagged patch, and no intermediate commit exists between the two release tags. The measured candidate is therefore not independently identifiable as the final `v0.8.22` tree from repository history.
|
|
160
|
+
|
|
37
161
|
Both targets received one warm-up followed by seven clean `worker-effects` production builds in alternating round-robin order. Cleanup remained outside timing. Both medians were exactly 250.1 ms; the distributions overlap and establish no material change.
|
|
38
162
|
|
|
39
163
|
| Target | Build median | Worker raw / gzip | Window raw / gzip |
|
|
@@ -50,14 +174,16 @@ Before release-content updates, the complete 135-page site and the `bindings`, `
|
|
|
50
174
|
|
|
51
175
|
## 0.8.21 Explicit Effect Ownership
|
|
52
176
|
|
|
53
|
-
Measured UTC 2026-08-09 on Apple M3, 8 logical CPUs, 8 GiB RAM, macOS 26.5.2 / Darwin 25.5.0, Node 25.6.1, and npm 11.18.0. Baseline tag `v0.8.20` at `7fb6e37` and the current `0.8.21` compiler candidate used the same local volume and identical installed dependencies.
|
|
177
|
+
Measured UTC 2026-08-09 on Apple M3, 8 logical CPUs, 8 GiB RAM, macOS 26.5.2 / Darwin 25.5.0, Node 25.6.1, and npm 11.18.0. Baseline tag `v0.8.20` at `7fb6e37` and the then-current `0.8.21` compiler candidate used the same local volume and identical installed dependencies.
|
|
54
178
|
|
|
55
|
-
The
|
|
179
|
+
The final `v0.8.20` to `v0.8.21` tagged implementation patch has reproducible SHA-256 `c78159ccce1f88a5ed06445d5e0b113953576529a5845e7772ab386b8adf166a`, produced by:
|
|
56
180
|
|
|
57
181
|
```bash
|
|
58
|
-
git diff --binary v0.8.20 -- framework/build.mjs framework/compiler/descriptor-session.mjs framework/compiler/effect-analysis.mjs framework/compiler/ir/module-ir.mjs framework/compiler/worker-compiler.mjs framework/core.d.ts | shasum -a 256
|
|
182
|
+
git diff --binary v0.8.20 v0.8.21 -- framework/build.mjs framework/compiler/descriptor-session.mjs framework/compiler/effect-analysis.mjs framework/compiler/ir/module-ir.mjs framework/compiler/worker-compiler.mjs framework/core.d.ts | shasum -a 256
|
|
59
183
|
```
|
|
60
184
|
|
|
185
|
+
The previously recorded candidate hash does not match this tagged patch, and no intermediate commit exists between the two release tags. The measured candidate is therefore not independently identifiable as the final `v0.8.21` tree from repository history.
|
|
186
|
+
|
|
61
187
|
Both targets received one warm-up followed by seven clean `worker-effects` production builds in alternating round-robin order. Cleanup remained outside timing. The distributions overlap; the 1.02% lower candidate median does not establish a material change.
|
|
62
188
|
|
|
63
189
|
| Target | Build median | Worker raw / gzip | Window raw / gzip |
|
|
@@ -420,4 +546,4 @@ React toggle: [5.7, 6.1, 5.8, 5.8, 5.6, 5.8, 5.7]
|
|
|
420
546
|
Kudzu toggle: [5.2, 5.4, 5.3, 5.6, 5.3, 5.2, 5.3]
|
|
421
547
|
```
|
|
422
548
|
|
|
423
|
-
The
|
|
549
|
+
The original fixture and harness were stored in an excluded local workspace and are unavailable from this checkout. The arrays above remain historical provenance, not an independently reproducible current claim.
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Kudzu compiles ordinary React-shaped TypeScript and TSX into complete static HTM
|
|
|
14
14
|
|
|
15
15
|
> Experimental `0.8.x`: the compiler API and supported TSX surface may change.
|
|
16
16
|
|
|
17
|
-
**Latest release: 0.8.
|
|
17
|
+
**Latest release: 0.8.25 - Exact route-entry reuse.** Byte-identical generated route entries share one build-local transform result, compiler boundaries fail closed, and package release gates verify what is installed and published. The 1,011-page benchmark measures a 9.17% build improvement with identical deploy output. Read the [release notes](./RELEASES.md#0825---exact-route-entry-reuse), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.8.25), or follow the [architecture packet](./docs/next-architecture/README.md).
|
|
18
18
|
|
|
19
19
|
- [Documentation](https://kudzujs.cloud/docs)
|
|
20
20
|
- [Installation guide](https://kudzujs.cloud/docs#install)
|
|
@@ -123,7 +123,7 @@ React-shaped TypeScript/TSX
|
|
|
123
123
|
-> complete HTML + route-specific capability ESM
|
|
124
124
|
```
|
|
125
125
|
|
|
126
|
-
- `framework/compiler/normalization-pipeline.mjs` owns pass order and repairs AST parent pointers after
|
|
126
|
+
- `framework/compiler/normalization-pipeline.mjs` owns pass order and repairs AST parent pointers after structurally changed transforms.
|
|
127
127
|
- Focused passes own React, React Router, browser-signal, animation-frame, timer, render-control, and Worker validation.
|
|
128
128
|
- The main transformer produces descriptors; effect and handler codegen modules turn them into route-specific ESM.
|
|
129
129
|
- Unsupported nearby syntax fails with source-located diagnostics instead of falling back to React or a generic runtime.
|
package/RELEASES.md
CHANGED
|
@@ -1,5 +1,70 @@
|
|
|
1
1
|
# Kudzu Releases
|
|
2
2
|
|
|
3
|
+
## 0.8.25 - Exact route-entry reuse
|
|
4
|
+
|
|
5
|
+
Kudzu 0.8.25 removes measured repeated esbuild work from large multi-route builds, strengthens compiler boundary validation, and makes package publication gates explicit without changing generated deploy bytes or browser behavior.
|
|
6
|
+
|
|
7
|
+
### Changed in 0.8.25
|
|
8
|
+
|
|
9
|
+
- Generated native, parameter, and effect route entries reuse one build-local esbuild result when their complete source is byte-identical after route-relative URLs are resolved.
|
|
10
|
+
- Normalization passes now declare an immutable-root contract and reject any result that is not a TypeScript `SourceFile`.
|
|
11
|
+
- Finalized ModuleIR rejects invalid local slots and dangling signal, handler, derived, keyed-parent, keyed-child, keyed-selector, and ownership references.
|
|
12
|
+
- CI covers the minimum Node 22 runtime and the Node 24 Chrome suite, while release jobs exercise the packed package and verify package, lockfile, tag, and registry versions.
|
|
13
|
+
- Missing `0.8.21`, `0.8.22`, and `0.8.23` release pages and their canonical sitemap entries are restored.
|
|
14
|
+
|
|
15
|
+
### Performance
|
|
16
|
+
|
|
17
|
+
- Seven alternating clean builds of the public 1,000-product, 1,011-page fixture measured 12,581.4 ms with exact-source reuse versus 13,851.0 ms with repeated transformation, a 9.17% improvement.
|
|
18
|
+
- Every deploy path and SHA-256 hash matched. The map is scoped to one build and only the three generated route-entry families; no persistent or generalized transform cache was added.
|
|
19
|
+
- Raw arrays, environment, fixture revision, methodology, and limitations are recorded in `PERFORMANCE.md`.
|
|
20
|
+
|
|
21
|
+
### Validation
|
|
22
|
+
|
|
23
|
+
- `npm run check`, `npm run test:package`, and all 174 tests pass.
|
|
24
|
+
- The packed-package smoke test installs the produced tarball into a temporary consumer, imports both public entry points, runs the packed CLI, and verifies generated HTML.
|
|
25
|
+
- The external 1,011-page benchmark produced byte-identical deploy manifests across both targets.
|
|
26
|
+
- No accepted syntax, public runtime API, VDOM, hydration, scheduler, retained browser component tree, or `create-kudzu` template change was added.
|
|
27
|
+
|
|
28
|
+
### Upgrade
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install @kudzujs/core@^0.8.25
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 0.8.24 - Measured Goal B optimizations
|
|
35
|
+
|
|
36
|
+
Kudzu 0.8.24 starts the architecture optimization Goal B with two benchmark-proven optimizations for large keyed restoration and large multi-route builds while preserving complete HTML, direct DOM ownership, and existing compiler output. This is distinct from the completed historical Worker capability milestone in `GOAL_B.md`.
|
|
37
|
+
|
|
38
|
+
### Changed in 0.8.24
|
|
39
|
+
|
|
40
|
+
- A maintained `npm run benchmark:keyed` fixture measures 2,000 keyed rows with row-local state, reactive slicing/search, bulk restoration, retained-heavy append, reversal, DOM identity, reset state, and restored handlers across fresh Chrome profiles.
|
|
41
|
+
- Top-level flat lists batch mount-hook discovery through their connected parent only when more than 32 additions dominate both the next list and parent children. Nested lists, shared containers, and small or retained-heavy additions keep per-root mounting.
|
|
42
|
+
- `applyNormalizationPasses()` repairs TypeScript parent pointers only after a pass returns a structurally changed `SourceFile`; validators and no-op passes no longer walk the complete AST again.
|
|
43
|
+
- A focused test proves changed normalization output has repaired parent links before the next pass runs.
|
|
44
|
+
- Goal B records identical generated route entries as the next measured esbuild-transform investigation; no cache or generalized build system was added without isolated evidence.
|
|
45
|
+
|
|
46
|
+
### Performance
|
|
47
|
+
|
|
48
|
+
- Twenty-one fresh Chrome profiles measured 2,000-row restoration at 21.1 ms versus 26.3 ms for `0.8.23`, a 19.77% improvement. A retained-heavy 33-row append showed no material change at 2.7 ms versus 2.6 ms.
|
|
49
|
+
- The keyed route adds 127 B raw / 35 B aggregate gzip JavaScript. Filter, reverse, and clean-build distributions establish no material regression.
|
|
50
|
+
- Twenty-one alternating clean builds of [`SimYunSup/kudzu-based-bench`](https://github.com/SimYunSup/kudzu-based-bench)'s 1,000-product, 1,011-page Kudzu fixture measured 6,266.5 ms versus 6,684.7 ms for `0.8.23`, a 6.26% improvement.
|
|
51
|
+
- Both commerce builds emitted the same 3,056 files. Only `assets/kudzu-list.js` changed for the independently measured runtime optimization; normalization changed no output bytes.
|
|
52
|
+
- Exact keyed worktree setup and the checked-in `benchmark:commerce` paired runner are documented in `PERFORMANCE.md` with raw arrays and limitations.
|
|
53
|
+
|
|
54
|
+
### Validation
|
|
55
|
+
|
|
56
|
+
- `npm run check` and all 172 tests pass, including the new normalization boundary check.
|
|
57
|
+
- Node 22 focused compiler checks and the maintained Worker benchmark pass.
|
|
58
|
+
- Chrome-backed keyed list, selector, row-hook, Worker ownership, and the new large keyed benchmark checks pass.
|
|
59
|
+
- Before release-content updates, the complete site plus representative list, keyed-row-hook, and navigation builds retained identical file lists; only `assets/kudzu-list.js` changed, and normalized `.kudzu` trees remained identical to `v0.8.23`.
|
|
60
|
+
- No accepted syntax, public API behavior, VDOM, hydration, scheduler, cache, retained browser component tree, or `create-kudzu` template change was added.
|
|
61
|
+
|
|
62
|
+
### Upgrade
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npm install @kudzujs/core@^0.8.24
|
|
66
|
+
```
|
|
67
|
+
|
|
3
68
|
## 0.8.23 - Source compiler boundary
|
|
4
69
|
|
|
5
70
|
Kudzu 0.8.23 completes the Goal A compiler foundation by moving source normalization, TSX semantic analysis, ModuleIR finalization, handler generation, and build-module generation behind one explicit no-write source compiler result.
|
|
@@ -7,7 +7,7 @@ The top-level [`GOAL_A.md`](../../GOAL_A.md) and [`GOAL_B.md`](../../GOAL_B.md)
|
|
|
7
7
|
| Goal | Decision | Start condition |
|
|
8
8
|
|---|---|---|
|
|
9
9
|
| A: compiler foundation | Complete in `0.8.23` | The no-write source compiler, RouteIR v1, CapabilityIR v1, generator boundaries, and output baseline are recorded. |
|
|
10
|
-
| B: optimization benchmarks |
|
|
10
|
+
| B: optimization benchmarks | Active | The first measured bulk keyed restoration optimization is retained; continue one reproduced loss at a time. |
|
|
11
11
|
| C: state/resource model | Research only | Reduced fixtures expose a limitation |
|
|
12
12
|
| D: routing compatibility | Current behavior preserved | Revisit only with migration evidence and invariant review |
|
|
13
13
|
|
|
@@ -27,7 +27,7 @@ The top-level [`GOAL_A.md`](../../GOAL_A.md) and [`GOAL_B.md`](../../GOAL_B.md)
|
|
|
27
27
|
2. [`goal-a-compiler-foundation.md`](./goal-a-compiler-foundation.md): approved extraction and generator work.
|
|
28
28
|
3. [`versioning.md`](./versioning.md): planned patch sequence and completion rules.
|
|
29
29
|
4. [`performance-gates.md`](./performance-gates.md): output, build, and browser gates.
|
|
30
|
-
5. [`goal-b-optimization-benchmarks.md`](./goal-b-optimization-benchmarks.md):
|
|
30
|
+
5. [`goal-b-optimization-benchmarks.md`](./goal-b-optimization-benchmarks.md): active measurements, retained optimizations, and continuation rules.
|
|
31
31
|
6. [`goal-c-state-resource-research.md`](./goal-c-state-resource-research.md): research boundary only.
|
|
32
32
|
7. [`goal-d-routing-compatibility-decisions.md`](./goal-d-routing-compatibility-decisions.md): routing and islands decisions.
|
|
33
33
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Current Compiler Architecture
|
|
2
2
|
|
|
3
|
-
This maps the completed `0.8.23` Goal A compiler foundation. File and function names are the stable references; line numbers are intentionally omitted because later work may still move code.
|
|
3
|
+
This maps the current `0.8.24` architecture built on the completed `0.8.23` Goal A compiler foundation. File and function names are the stable references; line numbers are intentionally omitted because later work may still move code.
|
|
4
4
|
|
|
5
5
|
## Responsibility Map
|
|
6
6
|
|
|
@@ -9,7 +9,7 @@ This maps the completed `0.8.23` Goal A compiler foundation. File and function n
|
|
|
9
9
|
| CLI entry | [`bin/kudzu.mjs`](../../bin/kudzu.mjs) | Dispatches build and development commands. |
|
|
10
10
|
| Build orchestration | [`framework/build.mjs`](../../framework/build.mjs), `build()` | Coordinates config, discovery, source compilation, RouteIR rendering, CapabilityIR projection, generator invocation, artifact emission, and `afterBuild`. |
|
|
11
11
|
| Reachability/import resolution | [`framework/compiler/source-compiler.mjs`](../../framework/compiler/source-compiler.mjs), `reachableSourceFiles()`; [`framework/compiler/source-graph.mjs`](../../framework/compiler/source-graph.mjs), `resolveSourceImport()` | Starts from page entries, follows relative runtime imports/re-exports and validated Worker references, and excludes unreachable migration source. |
|
|
12
|
-
| Ordered normalization | [`framework/compiler/normalization-pipeline.mjs`](../../framework/compiler/normalization-pipeline.mjs), `applyNormalizationPasses()`; [`framework/compiler/source-compiler.mjs`](../../framework/compiler/source-compiler.mjs), `normalizeCompilerSource()` | Applies migration/resource passes in order and repairs TypeScript parent pointers after every
|
|
12
|
+
| Ordered normalization | [`framework/compiler/normalization-pipeline.mjs`](../../framework/compiler/normalization-pipeline.mjs), `applyNormalizationPasses()`; [`framework/compiler/source-compiler.mjs`](../../framework/compiler/source-compiler.mjs), `normalizeCompilerSource()` | Applies migration/resource passes in order and repairs TypeScript parent pointers after every structural change. Imported source uses the same pipeline. |
|
|
13
13
|
| Focused normalization passes | [`framework/compiler/`](../../framework/compiler/) | React, Router, browser signals, animation-frame refs, custom-hook timers, Zustand, and render control each validate and lower a narrow source shape. |
|
|
14
14
|
| Shared AST/scope helpers | [`framework/compiler/ast-helpers.mjs`](../../framework/compiler/ast-helpers.mjs) | Binding, scope, reference, effect-return, and source-location analysis. |
|
|
15
15
|
| Pure collection language | [`framework/compiler/collection-analysis.mjs`](../../framework/compiler/collection-analysis.mjs) | Analyzes collection roots/selectors and serializes the allowed pure expression language used by lists and derived dependencies. |
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Status
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Active from the completed Goal A baseline at `0.8.23` commit `39a065b`. Maintained keyed restoration and external 1,000-product build measurements authorized the first two narrow optimizations.
|
|
6
6
|
|
|
7
7
|
## Purpose
|
|
8
8
|
|
|
@@ -21,6 +21,18 @@ At Goal A completion, record one source revision and the current maintained fixt
|
|
|
21
21
|
|
|
22
22
|
Historical figures in [`PERFORMANCE.md`](../../PERFORMANCE.md) and the top-level Goal records are provenance, not an automatic current baseline. Reproduce a fixture before using it to approve work.
|
|
23
23
|
|
|
24
|
+
### First Baseline Pass
|
|
25
|
+
|
|
26
|
+
The maintained Worker benchmark measured a 289.9 ms clean-build median, 907 B raw / 477 B gzip Worker graph, and 12,148 B raw / 5,411 B gzip window graph on Node 22. Its Chrome throughput, cadence, stale-write, bounded-history, and repeated ownership gate passed.
|
|
27
|
+
|
|
28
|
+
The preserved external 1,000-row keyed fixture was used only to find a candidate, not as a maintained approval benchmark. Its operations were fast, so a repository-owned benchmark was added for 2,000 rows with local state and reactive search. It measures filtering to one retained row, restoring 1,999 fresh rows, and reversing all rows in fresh Chrome profiles while checking retained identity, released identity, reset state, and restored handlers.
|
|
29
|
+
|
|
30
|
+
The maintained benchmark measured a 26.3 ms restoration median. For top-level flat lists where more than 32 additions are a majority of both the next rows and their parent children, mounting the connected list parent once rather than invoking every mount hook for every new row reduced the median to 21.1 ms (19.77%). A retained-heavy 33-row append stayed on the per-root path and showed no material change at 2.7 ms versus 2.6 ms. The route added 127 B raw / 35 B aggregate gzip JavaScript.
|
|
31
|
+
|
|
32
|
+
[`SimYunSup/kudzu-based-bench`](https://github.com/SimYunSup/kudzu-based-bench) then supplied a 1,000-product, 1,011-page build fixture. Twenty-one alternating clean builds measured 6,684.7 ms for clean `0.8.23` and 6,266.5 ms after skipping parent-pointer repair for normalization passes that return the unchanged AST, a 6.26% improvement. The output retained 3,056 files and only the separately optimized `kudzu-list.js` changed. Full raw arrays and limitations are recorded in [`PERFORMANCE.md`](../../PERFORMANCE.md).
|
|
33
|
+
|
|
34
|
+
The `0.8.25` investigation isolated repeated esbuild work by comparing ordinary transformation with build-local reuse keyed by the complete generated route-entry source. Seven alternating 1,011-page builds measured 13,851.0 ms and 12,581.4 ms medians, a 9.17% improvement, with identical emitted paths and hashes. The retained implementation applies only to native, parameter, and effect route entries and keeps no data beyond one build.
|
|
35
|
+
|
|
24
36
|
## Candidate Order
|
|
25
37
|
|
|
26
38
|
1. Profile the largest measured loss in a maintained fixture.
|
|
@@ -30,6 +42,8 @@ Historical figures in [`PERFORMANCE.md`](../../PERFORMANCE.md) and the top-level
|
|
|
30
42
|
|
|
31
43
|
Known historical pressure points include large keyed-list removal/reconciliation and broad runtime specialization, but neither is authorized without a reproduced current loss.
|
|
32
44
|
|
|
45
|
+
Repeated esbuild transformation of byte-identical generated route entries cleared the materiality and output gates. Further optimization is not authorized until another current fixture isolates a measured loss; do not broaden the route-entry map into a generalized cache.
|
|
46
|
+
|
|
33
47
|
## Benchmark Contract
|
|
34
48
|
|
|
35
49
|
- Match visible content, behavior, errors, accessibility, and navigation semantics.
|
|
@@ -51,9 +65,9 @@ Reject an optimization that:
|
|
|
51
65
|
|
|
52
66
|
## Continuation Checklist
|
|
53
67
|
|
|
54
|
-
- [
|
|
55
|
-
- [
|
|
56
|
-
- [
|
|
57
|
-
- [
|
|
58
|
-
- [
|
|
68
|
+
- [x] Verify Goal A acceptance is complete.
|
|
69
|
+
- [x] Freeze and record the post-Goal-A baseline revision.
|
|
70
|
+
- [x] Select one measured loss, not a speculative hotspot.
|
|
71
|
+
- [x] Add one minimal benchmark or reuse a maintained one.
|
|
72
|
+
- [x] Record before/after raw arrays and artifacts.
|
|
59
73
|
- [ ] Revert experiments that do not clear correctness and materiality gates.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Planned Version Sequence
|
|
2
2
|
|
|
3
|
-
This is an execution sequence, not release history. `0.8.16` through `0.8.
|
|
3
|
+
This is an execution sequence, not release history. `0.8.16` through `0.8.24` are completed scopes represented by package/release records.
|
|
4
4
|
|
|
5
5
|
Keep each patch behavior-preserving and independently reviewable. If a boundary proves inseparable, revise this plan before combining releases; do not silently broaden a patch.
|
|
6
6
|
|
|
@@ -14,10 +14,11 @@ Keep each patch behavior-preserving and independently reviewable. If a boundary
|
|
|
14
14
|
| `0.8.21` | Replace effect AST side tables with EffectIR covering setup, cleanup, dependencies, ownership, source, and Worker edges. | Route/layout/conditional/list lifetime, stale-write isolation, and exact resource cleanup remain unchanged. |
|
|
15
15
|
| `0.8.22` | Version the existing RouteIR and CapabilityIR, formalize numeric slots plus readable debug metadata, and extract focused runtime generators. | RouteIR and CapabilityIR reject unsupported versions; runtime generator contracts, output gates, and the recorded benchmark pass. |
|
|
16
16
|
| `0.8.23` | Forward-fix Goal A by moving source normalization, semantic analysis, ModuleIR finalization, handler generation, and build-module generation behind one no-write source compiler result. | `build.mjs` contains no TSX feature analysis; source results are JSON-safe and project-relative; diagnostics, deploy bytes, tests, and build performance remain equivalent. |
|
|
17
|
+
| `0.8.24` | Start Goal B with measured large keyed restoration and no-op normalization optimizations. | Fresh-profile keyed restoration improves materially; alternating 1,000-product builds improve materially; correctness, output, and byte deltas are recorded. |
|
|
17
18
|
|
|
18
19
|
## Sequence Rules
|
|
19
20
|
|
|
20
|
-
- Goal A is complete. Goal B
|
|
21
|
+
- Goal A is complete. Goal B proceeds from the `0.8.23` baseline one repeatable measured loss at a time.
|
|
21
22
|
- Keep Goal C research out of these patches and publish no store/resource API.
|
|
22
23
|
- Keep Goal D behavior unchanged: complete HTML, native default, opt-in groups, no islands.
|
|
23
24
|
- A planned patch may ship under a different actual version only if this table and release-facing documentation are updated before release.
|
package/framework/README.md
CHANGED
|
@@ -38,7 +38,7 @@ Reduced Zustand migration stores lower to one ordinary layout-lifetime state slo
|
|
|
38
38
|
- `compiler/collection-analysis.mjs`: pure collection expression IR, selector pipeline, alias, and imported-transform analysis.
|
|
39
39
|
- `compiler/descriptor-session.mjs`: private source-local descriptor registration and deterministic JSON-safe HandlerIR, BindingIR, DerivedIR, KeyedBlockIR, EffectIR, import, and client-root finalization.
|
|
40
40
|
- `compiler/effect-analysis.mjs`: ordered effect dependency classification and cleanup-owned browser resource validation before EffectIR registration.
|
|
41
|
-
- `compiler/normalization-pipeline.mjs`: ordered source normalization with parent-pointer repair after
|
|
41
|
+
- `compiler/normalization-pipeline.mjs`: ordered source normalization with parent-pointer repair after each structurally changed source root.
|
|
42
42
|
- `compiler/param-codegen.mjs`: runtime pathname and search-parameter entry generation from rendered route descriptors.
|
|
43
43
|
- `compiler/react-migration-pass.mjs`: React import, memo, callback, `forwardRef`, and `useId` migration validation and lowering.
|
|
44
44
|
- `compiler/custom-hook-timer-pass.mjs`: private custom-hook timeout-ref validation and compiler-owned timer-state lowering.
|
|
@@ -90,4 +90,4 @@ A direct setter may cross one same-file or relative-imported component boundary
|
|
|
90
90
|
|
|
91
91
|
`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.
|
|
92
92
|
|
|
93
|
-
Cross-framework performance tables are historical snapshots from
|
|
93
|
+
Cross-framework performance tables are historical snapshots from excluded workspaces and are not current rankings. `npm run benchmark` tracks the Worker build/graph fixture; `RUNS=21 npm run benchmark:keyed` tracks large keyed restoration with fresh Chrome profiles. Current methodology, raw arrays, external-fixture limits, and artifact deltas live in `PERFORMANCE.md` and the web docs.
|
package/framework/build.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { build as bundle, transform } from "esbuild"
|
|
|
6
6
|
import { createEffectCodegen } from "./compiler/effect-codegen.mjs"
|
|
7
7
|
import { generateListRuntime } from "./compiler/list-runtime-codegen.mjs"
|
|
8
8
|
import { assetPath, browserPath, relativeModulePath, withBase } from "./compiler/path-helpers.mjs"
|
|
9
|
-
import {
|
|
9
|
+
import { collectClientModules, compileClientModule, compiledPath, compileSource, layoutExportError, orderSourceStyles, reachableSourceFiles, safeStaticFiles } from "./compiler/source-compiler.mjs"
|
|
10
10
|
import { createParamCodegen } from "./compiler/param-codegen.mjs"
|
|
11
11
|
import { planRouteCapabilities, usesRouteDependencyRuntime } from "./compiler/route-capability-planner.mjs"
|
|
12
12
|
import { generateBindingRuntime, generateCoreRuntime, generateEffectRuntime, generateNativeRuntime, generateNavigationRuntime, specializeRuntime } from "./compiler/runtime-codegen.mjs"
|
|
@@ -95,6 +95,7 @@ export async function build({ quiet = false, minify = true } = {}) {
|
|
|
95
95
|
const effectEntries = []
|
|
96
96
|
const nativeEntries = []
|
|
97
97
|
const paramEntries = []
|
|
98
|
+
const routeEntryTransforms = new Map()
|
|
98
99
|
const rewrites = []
|
|
99
100
|
const emittedRoutes = new Set()
|
|
100
101
|
const emittedApplicationRoutes = new Set()
|
|
@@ -264,7 +265,7 @@ export async function build({ quiet = false, minify = true } = {}) {
|
|
|
264
265
|
if (hasNativeHandlers) {
|
|
265
266
|
const generated = generateNativeRuntime(await readFile(new URL("./native-runtime.js", import.meta.url), "utf8"), capabilityIR)
|
|
266
267
|
await writeJavaScript(join(assetsDirectory, "kudzu-native.js"), generated.source, minify, generated.define)
|
|
267
|
-
for (const entry of nativeEntries) await printNativeEntry(entry, assetsDirectory, base, minify)
|
|
268
|
+
for (const entry of nativeEntries) await printNativeEntry(entry, assetsDirectory, base, minify, routeEntryTransforms)
|
|
268
269
|
}
|
|
269
270
|
if (navigationGroups.length) {
|
|
270
271
|
const navigationSource = await readFile(new URL("./navigation-runtime.js", import.meta.url), "utf8")
|
|
@@ -280,12 +281,12 @@ export async function build({ quiet = false, minify = true } = {}) {
|
|
|
280
281
|
for (const entry of paramEntries) {
|
|
281
282
|
const output = join(assetsDirectory, entry.path)
|
|
282
283
|
await mkdir(dirname(output), { recursive: true })
|
|
283
|
-
await
|
|
284
|
+
await writeRouteEntry(output, printParamEntry(entry.schema, entry.params, entry.searchParams, entry.searchParamsWritable, output, assetsDirectory, base, runtimeName(entry.usesDependencyRuntime), entry.navigable), minify, routeEntryTransforms)
|
|
284
285
|
}
|
|
285
286
|
for (const entry of effectEntries) {
|
|
286
287
|
const output = join(assetsDirectory, entry.path)
|
|
287
288
|
await mkdir(dirname(output), { recursive: true })
|
|
288
|
-
await
|
|
289
|
+
await writeRouteEntry(output, printEffectEntry(entry.effects, output, emittedHandlerModules, assetsDirectory, base, entry.paramPath, runtimeName(entry.usesDependencyRuntime), entry.navigable), minify, routeEntryTransforms)
|
|
289
290
|
}
|
|
290
291
|
const clientModules = await collectClientModules(emittedHandlerModules.flatMap(module => module.clientImports).map(file => resolve(root, file)), sourceFileSet)
|
|
291
292
|
for (const file of clientModules) {
|
|
@@ -358,13 +359,13 @@ function preloadModules(html) {
|
|
|
358
359
|
return html.replace(scripts[0][0], `${links}${scripts[0][0]}`)
|
|
359
360
|
}
|
|
360
361
|
|
|
361
|
-
async function printNativeEntry(entry, assetsDirectory, base, minify) {
|
|
362
|
+
async function printNativeEntry(entry, assetsDirectory, base, minify, transforms) {
|
|
362
363
|
const output = join(assetsDirectory, entry.path)
|
|
363
364
|
await mkdir(dirname(output), { recursive: true })
|
|
364
365
|
const imports = entry.modules.map((module, index) => `import * as __kNativeModule${index} from ${JSON.stringify(module)}`).join("\n")
|
|
365
366
|
const registrations = entry.modules.map((module, index) => `[${JSON.stringify(module)}, __kNativeModule${index}]`).join(",")
|
|
366
367
|
const runtime = assetPath(base, "assets/kudzu-native.js")
|
|
367
|
-
await
|
|
368
|
+
await writeRouteEntry(output, `import { registerNativeModules } from ${JSON.stringify(runtime)}\n${imports}\nregisterNativeModules([${registrations}])`, minify, transforms)
|
|
368
369
|
}
|
|
369
370
|
|
|
370
371
|
function runtimeEffects(effects, lifetimes = false) {
|
|
@@ -388,6 +389,15 @@ async function writeJavaScript(file, source, minify, define) {
|
|
|
388
389
|
await writeFile(file, code)
|
|
389
390
|
}
|
|
390
391
|
|
|
392
|
+
async function writeRouteEntry(file, source, minify, transforms) {
|
|
393
|
+
let code = transforms.get(source)
|
|
394
|
+
if (code === undefined) {
|
|
395
|
+
code = minify ? (await transform(source, { format: "esm", legalComments: "none", minify, target: "es2022" })).code : source
|
|
396
|
+
transforms.set(source, code)
|
|
397
|
+
}
|
|
398
|
+
await writeFile(file, code)
|
|
399
|
+
}
|
|
400
|
+
|
|
391
401
|
async function writeBundledJavaScript(file, source, minify, define) {
|
|
392
402
|
const result = await bundle({
|
|
393
403
|
stdin: { contents: source, resolveDir: dirname(file), sourcefile: file },
|
|
@@ -2,7 +2,7 @@ import ts from "typescript"
|
|
|
2
2
|
import { createComponentAnalysis } from "./analysis/component-analysis.mjs"
|
|
3
3
|
import { bindingNames, isFunctionLike, isReferenceIdentifier, isShadowedByParameter, isShadowedIdentifier, unwrapExpression } from "./ast-helpers.mjs"
|
|
4
4
|
import { generateCommandBehavior } from "./codegen/command-codegen.mjs"
|
|
5
|
-
import { createModuleIR, registerBinding, registerCommandHandler, registerDerived, registerEffect, registerKeyedBlock, registerModuleHandler } from "./ir/module-ir.mjs"
|
|
5
|
+
import { assertModuleIRReferences, createModuleIR, registerBinding, registerCommandHandler, registerDerived, registerEffect, registerKeyedBlock, registerModuleHandler } from "./ir/module-ir.mjs"
|
|
6
6
|
|
|
7
7
|
export function createSemanticArtifact(file) {
|
|
8
8
|
return { componentAnalysis: createComponentAnalysis(file), moduleIR: createModuleIR(file) }
|
|
@@ -222,6 +222,7 @@ export function createDescriptorSession({ semantic, handlerUrl, factory, context
|
|
|
222
222
|
const imports = [...callbacks, ...reactiveBindings].flatMap(entry => entry.imports ?? []).map(importRecord)
|
|
223
223
|
moduleIR.imports = [...new Map(imports.map(entry => [`${entry.target}:${entry.kind}:${entry.imported ?? ""}:${entry.local}`, entry])).values()]
|
|
224
224
|
moduleIR.clientModules = [...clientModules]
|
|
225
|
+
assertModuleIRReferences(moduleIR)
|
|
225
226
|
}
|
|
226
227
|
|
|
227
228
|
function source(node) {
|
|
@@ -2,6 +2,33 @@ export function createModuleIR(file) {
|
|
|
2
2
|
return { version: 1, file, signals: [], handlers: [], bindings: [], derived: [], effects: [], keyedBlocks: [], imports: [], clientModules: [] }
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
+
export function assertModuleIRReferences(moduleIR) {
|
|
6
|
+
const slot = (records, value, label) => {
|
|
7
|
+
if (!Number.isInteger(value) || value < 0 || value >= records.length) throw new Error(`ModuleIR ${label} references missing slot ${JSON.stringify(value)}`)
|
|
8
|
+
}
|
|
9
|
+
for (const [name, records] of [["signal", moduleIR.signals], ["handler", moduleIR.handlers], ["binding", moduleIR.bindings], ["derived", moduleIR.derived], ["effect", moduleIR.effects], ["keyed block", moduleIR.keyedBlocks]]) {
|
|
10
|
+
records.forEach((record, index) => {
|
|
11
|
+
if (record.slot !== index) throw new Error(`ModuleIR ${name} slot ${JSON.stringify(record.slot)} must equal its index ${index}`)
|
|
12
|
+
})
|
|
13
|
+
}
|
|
14
|
+
for (const handler of moduleIR.handlers) {
|
|
15
|
+
for (const command of handler.commands ?? []) slot(moduleIR.signals, command.signal, `handler ${handler.slot} command signal`)
|
|
16
|
+
if (handler.keyedBlock !== undefined) slot(moduleIR.keyedBlocks, handler.keyedBlock, `handler ${handler.slot} keyed block`)
|
|
17
|
+
}
|
|
18
|
+
for (const binding of moduleIR.bindings) if (binding.keyedBlock !== undefined) slot(moduleIR.keyedBlocks, binding.keyedBlock, `binding ${binding.slot} keyed block`)
|
|
19
|
+
for (const effect of moduleIR.effects) {
|
|
20
|
+
slot(moduleIR.handlers, effect.setup?.handler, `effect ${effect.slot} setup handler`)
|
|
21
|
+
for (const dependency of effect.dependencies ?? []) if (dependency.kind === "derived") slot(moduleIR.derived, dependency.derived, `effect ${effect.slot} derived dependency`)
|
|
22
|
+
if (effect.ownership?.keyedBlock !== undefined) slot(moduleIR.keyedBlocks, effect.ownership.keyedBlock, `effect ${effect.slot} keyed block`)
|
|
23
|
+
}
|
|
24
|
+
for (const block of moduleIR.keyedBlocks) {
|
|
25
|
+
if (block.parent !== undefined) slot(moduleIR.keyedBlocks, block.parent, `keyed block ${block.slot} parent`)
|
|
26
|
+
for (const child of block.children ?? []) slot(moduleIR.keyedBlocks, child, `keyed block ${block.slot} child`)
|
|
27
|
+
if (block.selector !== undefined) slot(moduleIR.derived, block.selector, `keyed block ${block.slot} selector`)
|
|
28
|
+
}
|
|
29
|
+
return moduleIR
|
|
30
|
+
}
|
|
31
|
+
|
|
5
32
|
export function registerCommandHandler(moduleIR, commands, source, scope = "module") {
|
|
6
33
|
const slots = new Map(moduleIR.signals.map(signal => [signal.key, signal.slot]))
|
|
7
34
|
for (const { state, owner = scope } of commands) {
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import ts from "typescript"
|
|
2
2
|
|
|
3
3
|
export function applyNormalizationPasses(sourceFile, passes) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
// Passes treat their input root as immutable and return the SourceFile used by the next pass.
|
|
5
|
+
for (const [index, pass] of passes.entries()) {
|
|
6
|
+
const next = pass(sourceFile)
|
|
7
|
+
if (!next || !ts.isSourceFile(next)) throw new TypeError(`Normalization pass ${index + 1} must return a TypeScript SourceFile`)
|
|
8
|
+
if (next !== sourceFile) ts.setParentRecursive(next, false)
|
|
9
|
+
sourceFile = next
|
|
7
10
|
}
|
|
8
11
|
return sourceFile
|
|
9
12
|
}
|
|
@@ -282,7 +282,8 @@ function updateList(list) {
|
|
|
282
282
|
if (run.firstChild) parent.insertBefore(run, list.boundary)
|
|
283
283
|
ordered = true
|
|
284
284
|
} else parent.insertBefore(additions, list.boundary)
|
|
285
|
-
if (addedNodes
|
|
285
|
+
if (addedNodes?.length > 32 && addedNodes.length * 2 > next.length && addedNodes.length * 2 > parent.children.length && !list.descriptor.children && !list.descriptor.ownerField) mountDom(parent)
|
|
286
|
+
else if (addedNodes) for (const node of addedNodes) mountDom(node)
|
|
286
287
|
list.container ??= parent
|
|
287
288
|
}
|
|
288
289
|
let anchor = list.boundary
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kudzujs/core",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.25",
|
|
4
4
|
"description": "HTML-first TSX framework with synchronous state semantics and no virtual DOM",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -54,7 +54,10 @@
|
|
|
54
54
|
"dev": "node ./bin/kudzu.mjs dev",
|
|
55
55
|
"check": "tsc --noEmit && tsc -p test/fixtures/tsconfig.json --noEmit && node ./bin/kudzu.mjs build",
|
|
56
56
|
"test": "node --test test/*.test.mjs",
|
|
57
|
+
"test:package": "node test/package-smoke.mjs",
|
|
57
58
|
"benchmark": "node test/performance.mjs",
|
|
59
|
+
"benchmark:keyed": "node test/keyed-performance.mjs",
|
|
60
|
+
"benchmark:commerce": "node test/commerce-build-performance.mjs",
|
|
58
61
|
"prepublishOnly": "npm run check && npm test",
|
|
59
62
|
"deploy": "wrangler deploy",
|
|
60
63
|
"preview": "wrangler dev"
|