@kudzujs/core 0.8.24 → 0.8.26
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/MIGRATION_ROADMAP.md +12 -0
- package/PERFORMANCE.md +36 -7
- package/README.md +1 -1
- package/RELEASES.md +60 -0
- package/docs/next-architecture/README.md +1 -1
- package/docs/next-architecture/compiler-current-architecture.md +4 -3
- package/docs/next-architecture/goal-b-optimization-benchmarks.md +17 -2
- package/docs/next-architecture/versioning.md +3 -1
- 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 +3 -1
- package/package.json +2 -1
package/MIGRATION_ROADMAP.md
CHANGED
|
@@ -250,6 +250,18 @@ This queue orders the next investigations by general migration value. Start only
|
|
|
250
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
251
|
- Identical generated route-entry transformation remains the next evidence-backed Goal B investigation; it is not implemented without an isolated benchmark.
|
|
252
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
|
+
|
|
259
|
+
### Completed In 0.8.26
|
|
260
|
+
|
|
261
|
+
- The maintained commerce runner requires byte-identical output by default while explicit expected deltas preserve historical benchmark reproduction.
|
|
262
|
+
- The standard suite counts exact route-entry transforms across repeated, distinct, and new-build sources and protects the safe keyed bulk-mount guard plus its per-root fallback.
|
|
263
|
+
- Goal B records which build, output, keyed, Worker, integration, and heap evidence exists; no new optimization is authorized without another reproduced material loss.
|
|
264
|
+
|
|
253
265
|
## Cross-Cutting Performance Gates
|
|
254
266
|
|
|
255
267
|
Every migration feature must preserve:
|
package/PERFORMANCE.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
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
4
|
|
|
5
|
+
## 0.8.26 Goal B Benchmark Hardening
|
|
6
|
+
|
|
7
|
+
The checked-in `benchmark:commerce` runner now requires byte-identical candidate output by default. `EXPECTED_CHANGES` is reserved for explicitly recorded historical comparisons; the route-entry reuse path also has a focused transform-count regression test in the ordinary suite.
|
|
8
|
+
|
|
9
|
+
After that runner update, seven alternating `v0.8.24` versus current-tree builds on the same Linux machine verified 1,011 pages, 3,056 files, and no changed deploy hash. The 14,766.5 ms and 12,202.4 ms medians validate the maintained runner and released-tree direction, but are not attributed to route-entry reuse alone because the compared revisions include all `0.8.25` compiler-boundary changes.
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
v0.8.24: [14311.1,14766.5,15294.2,14718.0,13440.6,15245.2,15007.0]
|
|
13
|
+
current: [12202.4,12243.2,11701.2,12320.8,11994.6,12461.4,12073.3]
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## 0.8.25 Route Entry Transform Reuse
|
|
17
|
+
|
|
18
|
+
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.
|
|
19
|
+
|
|
20
|
+
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.
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
repeated transform: [13437.4,13851.0,13844.9,13440.8,13906.1,14310.0,14901.0]
|
|
24
|
+
exact-source reuse: [12480.2,12826.9,12777.9,12615.9,11869.7,12121.3,12581.4]
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
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.
|
|
28
|
+
|
|
5
29
|
## 0.8.24 Measured Goal B Optimizations
|
|
6
30
|
|
|
7
31
|
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.
|
|
@@ -109,7 +133,7 @@ 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,
|
|
|
109
133
|
|
|
110
134
|
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.
|
|
111
135
|
|
|
112
|
-
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
|
|
136
|
+
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 explicitly configured historical `assets/kudzu-list.js` delta, and restores the external app's package symlink afterward:
|
|
113
137
|
|
|
114
138
|
```bash
|
|
115
139
|
git clone https://github.com/SimYunSup/kudzu-based-bench.git /tmp/kudzu-based-bench
|
|
@@ -121,6 +145,7 @@ ln -s "$PWD/node_modules" /tmp/kudzu-0.8.23/node_modules
|
|
|
121
145
|
APP_ROOT=/tmp/kudzu-based-bench/apps/shop-kudzu \
|
|
122
146
|
BASELINE_ROOT=/tmp/kudzu-0.8.23 \
|
|
123
147
|
CANDIDATE_ROOT="$PWD" RUNS=21 CATALOG_SIZE=1000 \
|
|
148
|
+
EXPECTED_CHANGES=assets/kudzu-list.js \
|
|
124
149
|
npm run benchmark:commerce
|
|
125
150
|
```
|
|
126
151
|
|
|
@@ -135,14 +160,16 @@ This external fixture is a candidate-finding and paired Kudzu regression benchma
|
|
|
135
160
|
|
|
136
161
|
## 0.8.22 Versioned Compiler Foundation
|
|
137
162
|
|
|
138
|
-
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.
|
|
163
|
+
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.
|
|
139
164
|
|
|
140
|
-
The
|
|
165
|
+
The final `v0.8.21` to `v0.8.22` tagged implementation patch has reproducible SHA-256 `5fbdc3658b8c0d4d568c7ccdbf89c2c1c20c3a275ff9d0fd20e439b479d60530`, produced by:
|
|
141
166
|
|
|
142
167
|
```bash
|
|
143
|
-
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
|
|
168
|
+
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
|
|
144
169
|
```
|
|
145
170
|
|
|
171
|
+
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.
|
|
172
|
+
|
|
146
173
|
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.
|
|
147
174
|
|
|
148
175
|
| Target | Build median | Worker raw / gzip | Window raw / gzip |
|
|
@@ -159,14 +186,16 @@ Before release-content updates, the complete 135-page site and the `bindings`, `
|
|
|
159
186
|
|
|
160
187
|
## 0.8.21 Explicit Effect Ownership
|
|
161
188
|
|
|
162
|
-
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.
|
|
189
|
+
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.
|
|
163
190
|
|
|
164
|
-
The
|
|
191
|
+
The final `v0.8.20` to `v0.8.21` tagged implementation patch has reproducible SHA-256 `c78159ccce1f88a5ed06445d5e0b113953576529a5845e7772ab386b8adf166a`, produced by:
|
|
165
192
|
|
|
166
193
|
```bash
|
|
167
|
-
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
|
|
194
|
+
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
|
|
168
195
|
```
|
|
169
196
|
|
|
197
|
+
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.
|
|
198
|
+
|
|
170
199
|
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.
|
|
171
200
|
|
|
172
201
|
| Target | Build median | Worker raw / gzip | Window raw / gzip |
|
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.26 - Goal B benchmark hardening.** Commerce comparisons now require byte-identical output by default, and the standard suite protects exact route-entry transform reuse plus safe keyed bulk-mount selection. Read the [release notes](./RELEASES.md#0826---goal-b-benchmark-hardening), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.8.26), 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)
|
package/RELEASES.md
CHANGED
|
@@ -1,5 +1,65 @@
|
|
|
1
1
|
# Kudzu Releases
|
|
2
2
|
|
|
3
|
+
## 0.8.26 - Goal B benchmark hardening
|
|
4
|
+
|
|
5
|
+
Kudzu 0.8.26 makes the retained Goal B optimizations reproducible and directly regression-tested without changing generated runtime behavior or authoring support.
|
|
6
|
+
|
|
7
|
+
### Changed in 0.8.26
|
|
8
|
+
|
|
9
|
+
- `benchmark:commerce` requires byte-identical candidate output by default; `EXPECTED_CHANGES` explicitly preserves historical comparisons with known artifact deltas.
|
|
10
|
+
- The standard suite verifies that exact route-entry sources transform once per build, distinct sources do not collide, and a new build owns a fresh transform map.
|
|
11
|
+
- List runtime generation tests retain the measured bulk-mount threshold, majority guards, nested/owner exclusions, and per-root fallback.
|
|
12
|
+
- The architecture packet now covers `0.8.25`, records Goal B baseline evidence and deliberate gaps, and closes the retained-experiment checklist.
|
|
13
|
+
|
|
14
|
+
### Performance Validation
|
|
15
|
+
|
|
16
|
+
- Seven alternating `v0.8.24` and current-tree commerce builds emitted 1,011 pages and 3,056 files with no changed deploy hash.
|
|
17
|
+
- The measured medians were 14,766.5 ms and 12,202.4 ms. This validates the maintained runner and release direction but is not attributed to one optimization because the revisions include all `0.8.25` compiler-boundary changes.
|
|
18
|
+
- No new runtime bytes, scheduler, persistent cache, retained tree, or public browser capability was added.
|
|
19
|
+
|
|
20
|
+
### Validation
|
|
21
|
+
|
|
22
|
+
- `npm run check`, `npm run test:package`, and all 175 tests pass.
|
|
23
|
+
- The exact-output commerce runner passed seven alternating 1,011-page builds.
|
|
24
|
+
- No accepted syntax, runtime behavior, DOM ownership, effect cleanup, or `create-kudzu` template changed.
|
|
25
|
+
|
|
26
|
+
### Upgrade
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install @kudzujs/core@^0.8.26
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## 0.8.25 - Exact route-entry reuse
|
|
33
|
+
|
|
34
|
+
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.
|
|
35
|
+
|
|
36
|
+
### Changed in 0.8.25
|
|
37
|
+
|
|
38
|
+
- 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.
|
|
39
|
+
- Normalization passes now declare an immutable-root contract and reject any result that is not a TypeScript `SourceFile`.
|
|
40
|
+
- Finalized ModuleIR rejects invalid local slots and dangling signal, handler, derived, keyed-parent, keyed-child, keyed-selector, and ownership references.
|
|
41
|
+
- 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.
|
|
42
|
+
- Missing `0.8.21`, `0.8.22`, and `0.8.23` release pages and their canonical sitemap entries are restored.
|
|
43
|
+
|
|
44
|
+
### Performance
|
|
45
|
+
|
|
46
|
+
- 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.
|
|
47
|
+
- 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.
|
|
48
|
+
- Raw arrays, environment, fixture revision, methodology, and limitations are recorded in `PERFORMANCE.md`.
|
|
49
|
+
|
|
50
|
+
### Validation
|
|
51
|
+
|
|
52
|
+
- `npm run check`, `npm run test:package`, and all 174 tests pass.
|
|
53
|
+
- 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.
|
|
54
|
+
- The external 1,011-page benchmark produced byte-identical deploy manifests across both targets.
|
|
55
|
+
- No accepted syntax, public runtime API, VDOM, hydration, scheduler, retained browser component tree, or `create-kudzu` template change was added.
|
|
56
|
+
|
|
57
|
+
### Upgrade
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npm install @kudzujs/core@^0.8.25
|
|
61
|
+
```
|
|
62
|
+
|
|
3
63
|
## 0.8.24 - Measured Goal B optimizations
|
|
4
64
|
|
|
5
65
|
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`.
|
|
@@ -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 | Active |
|
|
10
|
+
| B: optimization benchmarks | Active | Keyed restoration, no-op normalization, and exact route-entry transform reuse are retained; continue only when another current fixture reproduces a material loss. |
|
|
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
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Current Compiler Architecture
|
|
2
2
|
|
|
3
|
-
This maps the current `0.8.
|
|
3
|
+
This maps the current `0.8.26` 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
|
|
|
@@ -15,7 +15,7 @@ This maps the current `0.8.24` architecture built on the completed `0.8.23` Goal
|
|
|
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. |
|
|
16
16
|
| Main semantic analysis | [`framework/compiler/source-compiler.mjs`](../../framework/compiler/source-compiler.mjs), `createKudzuTransformer()` | Produces transformed source plus explicit component, handler, binding, derived, keyed, and effect ownership results. |
|
|
17
17
|
| Component ownership analysis | [`framework/compiler/analysis/component-analysis.mjs`](../../framework/compiler/analysis/component-analysis.mjs) | Retains ordered JSON-safe owner and specialization records for state, setters, props, refs, IDs, direct signal links, and source provenance; AST identity remains private to its source-local session. |
|
|
18
|
-
| Per-source descriptor registration | [`framework/compiler/descriptor-session.mjs`](../../framework/compiler/descriptor-session.mjs), `createSemanticArtifact()`, `createDescriptorSession()` | Keeps AST descriptors private during analysis, then finalizes deterministic JSON-safe HandlerIR, BindingIR, DerivedIR, KeyedBlockIR, EffectIR, imports, and client roots into ModuleIR. |
|
|
18
|
+
| Per-source descriptor registration | [`framework/compiler/descriptor-session.mjs`](../../framework/compiler/descriptor-session.mjs), `createSemanticArtifact()`, `createDescriptorSession()` | Keeps AST descriptors private during analysis, then finalizes deterministic JSON-safe HandlerIR, BindingIR, DerivedIR, KeyedBlockIR, EffectIR, imports, and client roots into ModuleIR and validates every local slot reference. |
|
|
19
19
|
| Command IR and codegen | [`framework/compiler/optimize/command-specialization.mjs`](../../framework/compiler/optimize/command-specialization.mjs), [`framework/compiler/ir/module-ir.mjs`](../../framework/compiler/ir/module-ir.mjs), [`framework/compiler/codegen/command-codegen.mjs`](../../framework/compiler/codegen/command-codegen.mjs) | Supported command handlers specialize to JSON-safe ModuleIR, then emit the existing `__kBehavior` AST without changing route plans. |
|
|
20
20
|
| Source compilation | [`framework/compiler/source-compiler.mjs`](../../framework/compiler/source-compiler.mjs), `compileSource()` | Runs TypeScript with the Kudzu transformer, rejects surviving React/Router references, and returns a JSON-safe project-relative build module, component analysis, ModuleIR, optional handler module, and imported assets without filesystem writes. |
|
|
21
21
|
| Handler/evaluator lowering | [`framework/compiler/handler-lowering.mjs`](../../framework/compiler/handler-lowering.mjs) | Completes source-local callback/binding/list AST rewriting and diagnostics before the JSON-safe IR boundary. |
|
|
@@ -27,7 +27,7 @@ This maps the current `0.8.24` architecture built on the completed `0.8.23` Goal
|
|
|
27
27
|
| Route capability projection | [`framework/compiler/route-capability-planner.mjs`](../../framework/compiler/route-capability-planner.mjs), `planRouteCapabilities()` | Validates RouteIR v1 and purely folds rendered plans and route facts into CapabilityIR v1. |
|
|
28
28
|
| Effect entry generation | [`framework/compiler/effect-codegen.mjs`](../../framework/compiler/effect-codegen.mjs) | Generates ordinary, dependency, owned, and navigable effect entries from rendered descriptors. |
|
|
29
29
|
| Runtime generation | [`framework/compiler/runtime-codegen.mjs`](../../framework/compiler/runtime-codegen.mjs), [`framework/compiler/list-runtime-codegen.mjs`](../../framework/compiler/list-runtime-codegen.mjs), [`framework/compiler/param-codegen.mjs`](../../framework/compiler/param-codegen.mjs) | Consumes versioned contracts, specializes authored capability sources with fail-closed anchors, and returns source/define results without filesystem ownership. |
|
|
30
|
-
| Artifact emission | `framework/build.mjs` | Selects required files from CapabilityIR and writes or bundles generated sources with esbuild. |
|
|
30
|
+
| Artifact emission | `framework/build.mjs` | Selects required files from CapabilityIR and writes or bundles generated sources with esbuild. Byte-identical native, parameter, and effect route entries reuse one exact-source transform result within the current build only. |
|
|
31
31
|
| Browser capabilities | [`framework/*.js`](../../framework/) | Small optional modules for commands, bindings, lists, effects, native handlers, serialization, parameters, and navigation; no component runtime. |
|
|
32
32
|
| Opt-in navigation | [`framework/navigation-runtime.js`](../../framework/navigation-runtime.js) plus `framework/build.mjs` navigation configuration/emission | Fetches and validates complete same-origin documents, replaces only the marked route range, manages route/layout disposal, history, focus, finite prefetch retention, and native fallback. |
|
|
33
33
|
| Development serving | [`framework/dev-server.mjs`](../../framework/dev-server.mjs) and [`framework/dev-state.js`](../../framework/dev-state.js) | Rebuild/watch/SSE and response-only short-lived state restoration; never changes production `dist/`. |
|
|
@@ -50,6 +50,7 @@ src/pages entries + config
|
|
|
50
50
|
-> planRouteCapabilities(RouteIR records, route facts)
|
|
51
51
|
-> CapabilityIR v1
|
|
52
52
|
-> specialize and emit only selected runtime/capability ESM
|
|
53
|
+
-> reuse exact generated route-entry transforms within this build
|
|
53
54
|
-> write route index.html, CSS/assets, Worker graphs, rewrites, and .kudzu/kudzu-plan.json
|
|
54
55
|
-> optional afterBuild()
|
|
55
56
|
```
|
|
@@ -31,6 +31,21 @@ The maintained benchmark measured a 26.3 ms restoration median. For top-level fl
|
|
|
31
31
|
|
|
32
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
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
|
+
|
|
36
|
+
### Baseline Coverage Status
|
|
37
|
+
|
|
38
|
+
| Area | Current evidence | Status |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| Build and artifact graph | Worker and commerce clean builds, emitted manifests, deterministic hashes, raw/gzip JavaScript and Worker bytes | Recorded |
|
|
41
|
+
| Static exclusion | Complete-site and representative fixture comparisons plus static zero-JavaScript assertions | Recorded |
|
|
42
|
+
| Keyed operations | Append, filter, bulk restoration, reverse, retained/released identity, state reset, and handler checks | Recorded |
|
|
43
|
+
| Worker ownership | Throughput, cadence, bounded history, stale writes, and repeated start/termination/listener ownership | Recorded |
|
|
44
|
+
| Command, binding, effect, and navigation semantics | Maintained integration and Chrome behavior tests | Correctness recorded; no standalone timing baseline because no material loss is currently reproduced |
|
|
45
|
+
| Heap trend | Repeated ownership counters and bounded fixture data | No general heap series; add one only when a reproduced ownership or memory loss requires it |
|
|
46
|
+
|
|
47
|
+
The broad baseline list is an investigation inventory, not a requirement to manufacture timings without a candidate. New timing or heap work begins only when a current fixture reproduces the corresponding loss.
|
|
48
|
+
|
|
34
49
|
## Candidate Order
|
|
35
50
|
|
|
36
51
|
1. Profile the largest measured loss in a maintained fixture.
|
|
@@ -40,7 +55,7 @@ The maintained benchmark measured a 26.3 ms restoration median. For top-level fl
|
|
|
40
55
|
|
|
41
56
|
Known historical pressure points include large keyed-list removal/reconciliation and broad runtime specialization, but neither is authorized without a reproduced current loss.
|
|
42
57
|
|
|
43
|
-
|
|
58
|
+
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.
|
|
44
59
|
|
|
45
60
|
## Benchmark Contract
|
|
46
61
|
|
|
@@ -68,4 +83,4 @@ Reject an optimization that:
|
|
|
68
83
|
- [x] Select one measured loss, not a speculative hotspot.
|
|
69
84
|
- [x] Add one minimal benchmark or reuse a maintained one.
|
|
70
85
|
- [x] Record before/after raw arrays and artifacts.
|
|
71
|
-
- [
|
|
86
|
+
- [x] Keep no experiment that fails correctness or materiality gates in the release tree.
|
|
@@ -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.26` 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
|
|
|
@@ -15,6 +15,8 @@ Keep each patch behavior-preserving and independently reviewable. If a boundary
|
|
|
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
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. |
|
|
18
|
+
| `0.8.25` | Reuse byte-identical generated native, parameter, and effect route-entry transforms within one build and strengthen compiler/release boundaries. | Alternating 1,011-page builds improve materially with identical deploy hashes; normalization and ModuleIR fail closed; Node, Chrome, package, and registry gates pass. |
|
|
19
|
+
| `0.8.26` | Harden Goal B benchmark reproducibility and ordinary regression coverage without changing runtime behavior. | Commerce comparison defaults to exact output; route transform counts and keyed bulk/fallback guards are protected by the standard suite; baseline coverage and limits are explicit. |
|
|
18
20
|
|
|
19
21
|
## Sequence Rules
|
|
20
22
|
|
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
|
+
export async function writeRouteEntry(file, source, minify, transforms, transformSource = transform, write = writeFile) {
|
|
393
|
+
let code = transforms.get(source)
|
|
394
|
+
if (code === undefined) {
|
|
395
|
+
code = minify ? (await transformSource(source, { format: "esm", legalComments: "none", minify, target: "es2022" })).code : source
|
|
396
|
+
transforms.set(source, code)
|
|
397
|
+
}
|
|
398
|
+
await write(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,8 +1,10 @@
|
|
|
1
1
|
import ts from "typescript"
|
|
2
2
|
|
|
3
3
|
export function applyNormalizationPasses(sourceFile, passes) {
|
|
4
|
-
|
|
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()) {
|
|
5
6
|
const next = pass(sourceFile)
|
|
7
|
+
if (!next || !ts.isSourceFile(next)) throw new TypeError(`Normalization pass ${index + 1} must return a TypeScript SourceFile`)
|
|
6
8
|
if (next !== sourceFile) ts.setParentRecursive(next, false)
|
|
7
9
|
sourceFile = next
|
|
8
10
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kudzujs/core",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.26",
|
|
4
4
|
"description": "HTML-first TSX framework with synchronous state semantics and no virtual DOM",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -54,6 +54,7 @@
|
|
|
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",
|
|
58
59
|
"benchmark:keyed": "node test/keyed-performance.mjs",
|
|
59
60
|
"benchmark:commerce": "node test/commerce-build-performance.mjs",
|