@kudzujs/core 0.8.24 → 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/MIGRATION_ROADMAP.md +6 -0
- package/PERFORMANCE.md +23 -6
- package/README.md +1 -1
- package/RELEASES.md +31 -0
- package/docs/next-architecture/goal-b-optimization-benchmarks.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,12 @@ 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
|
+
|
|
253
259
|
## Cross-Cutting Performance Gates
|
|
254
260
|
|
|
255
261
|
Every migration feature must preserve:
|
package/PERFORMANCE.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
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.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
|
+
|
|
5
18
|
## 0.8.24 Measured Goal B Optimizations
|
|
6
19
|
|
|
7
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.
|
|
@@ -135,14 +148,16 @@ This external fixture is a candidate-finding and paired Kudzu regression benchma
|
|
|
135
148
|
|
|
136
149
|
## 0.8.22 Versioned Compiler Foundation
|
|
137
150
|
|
|
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.
|
|
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.
|
|
139
152
|
|
|
140
|
-
The
|
|
153
|
+
The final `v0.8.21` to `v0.8.22` tagged implementation patch has reproducible SHA-256 `5fbdc3658b8c0d4d568c7ccdbf89c2c1c20c3a275ff9d0fd20e439b479d60530`, produced by:
|
|
141
154
|
|
|
142
155
|
```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
|
|
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
|
|
144
157
|
```
|
|
145
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
|
+
|
|
146
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.
|
|
147
162
|
|
|
148
163
|
| Target | Build median | Worker raw / gzip | Window raw / gzip |
|
|
@@ -159,14 +174,16 @@ Before release-content updates, the complete 135-page site and the `bindings`, `
|
|
|
159
174
|
|
|
160
175
|
## 0.8.21 Explicit Effect Ownership
|
|
161
176
|
|
|
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.
|
|
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.
|
|
163
178
|
|
|
164
|
-
The
|
|
179
|
+
The final `v0.8.20` to `v0.8.21` tagged implementation patch has reproducible SHA-256 `c78159ccce1f88a5ed06445d5e0b113953576529a5845e7772ab386b8adf166a`, produced by:
|
|
165
180
|
|
|
166
181
|
```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
|
|
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
|
|
168
183
|
```
|
|
169
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
|
+
|
|
170
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.
|
|
171
188
|
|
|
172
189
|
| 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.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)
|
package/RELEASES.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
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
|
+
|
|
3
34
|
## 0.8.24 - Measured Goal B optimizations
|
|
4
35
|
|
|
5
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`.
|
|
@@ -31,6 +31,8 @@ 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
|
+
|
|
34
36
|
## Candidate Order
|
|
35
37
|
|
|
36
38
|
1. Profile the largest measured loss in a maintained fixture.
|
|
@@ -40,7 +42,7 @@ The maintained benchmark measured a 26.3 ms restoration median. For top-level fl
|
|
|
40
42
|
|
|
41
43
|
Known historical pressure points include large keyed-list removal/reconciliation and broad runtime specialization, but neither is authorized without a reproduced current loss.
|
|
42
44
|
|
|
43
|
-
|
|
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.
|
|
44
46
|
|
|
45
47
|
## Benchmark Contract
|
|
46
48
|
|
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,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.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,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",
|