@kudzujs/core 0.8.44 → 0.8.46

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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  This document is the source of truth for Kudzu's product direction, architecture invariants, and future development order. Read it before extending React-shaped syntax or browser capabilities.
4
4
 
5
- The executable post-`0.8.44` compiler and large-application sequence is maintained in [`docs/next-architecture/large-application-ai-native-roadmap.md`](./docs/next-architecture/large-application-ai-native-roadmap.md). Follow its PR dependencies for implementation work; this document remains authoritative when selecting or accepting a migration capability.
5
+ The executable post-`0.8.46` compiler and large-application sequence is maintained in [`docs/next-architecture/large-application-ai-native-roadmap.md`](./docs/next-architecture/large-application-ai-native-roadmap.md). Follow its PR dependencies for implementation work; this document remains authoritative when selecting or accepting a migration capability.
6
6
 
7
7
  [`GOAL_A.md`](./GOAL_A.md) and [`GOAL_B.md`](./GOAL_B.md) are completed capability-validation records. Their commerce and realtime dashboard fixtures prove general lifecycle, navigation, async-workflow, and Worker capabilities; they are not separate product verticals or future priority lists.
8
8
 
@@ -143,6 +143,7 @@ This queue orders the next investigations by general migration value. Start only
143
143
 
144
144
  - A React Notes migration preserves its Provider, relative `useNotes()` alias, consumer components, and parameterized CRUD actions. A direct custom-hook `useContext(Context)` return resolves one local or named relative Context module whose Provider value exposes direct state/setter pairs and synchronous actions.
145
145
  - Context actions inline into existing route handler ESM and compile to concrete state operations. Private captures, dynamic Provider objects, and multiple Provider implementations remain diagnosed; no browser Context tree, callback registry, or shared runtime was added.
146
+ - In `0.8.46`, the same Notes migration may omit action-only setters from its public Context type and authored Provider value when the corresponding state remains exposed. Kudzu restores those setters only in build scratch so existing concrete state operations, RouteIR signals, CRUD behavior, and zero-Context runtime output remain unchanged. Fully hidden action state and exposed setters without state remain diagnosed.
146
147
 
147
148
  ### Completed In 0.8.10
148
149
 
package/PERFORMANCE.md CHANGED
@@ -1,8 +1,20 @@
1
1
  # Performance Records
2
2
 
3
- Reproducibility classes: `npm run benchmark`, `npm run benchmark:keyed`, `npm run benchmark:native`, and `npm run benchmark:module-cache` 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.
3
+ Reproducibility classes: `npm run benchmark`, `npm run benchmark:keyed`, `npm run benchmark:native`, `npm run benchmark:module-cache`, and `npm run benchmark:source-scale` 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
- ## Current 0.8.44 Release Snapshot
5
+ `npm run benchmark:source-scale` generates its fixture outside the repository so 50,000 lines of synthetic source are not tracked. The default topology is 50 pages plus 450 route-owned imported modules. Generation is excluded from timing; fresh-process samples separately report source reads, reachable-graph discovery, source compilation, clean production build, compiler-result and deploy digests, output files/bytes, cache counters, and peak RSS. `ROUTES`, `MODULES_PER_ROUTE`, `FILLER_LINES`, `WARMUPS`, and `RUNS` may reduce or expand the fixture without changing the default acceptance floor. `TARGET_ROOT` measures another checkout; `BASELINE_ROOT` alternates that checkout with the current tree and requires identical deploy output.
6
+
7
+ The maintained 2026-08-13 comparison used Node 24.14.0 and an Intel Core i5-9500 Linux x64 host, one warm-up, and seven alternating fresh-process samples against clean `v0.8.44`. A narrow fast path skips Kudzu semantic transformation for 450 plain `.ts` modules whose runtime edges are exclusively resolvable relative TypeScript imports or exports; all other modules retain the existing transformer. Compile median fell from 2,323.9 ms to 1,413.2 ms (39.2%) and clean-build median from 3,325.3 ms to 2,382.4 ms (28.4%); every paired sample improved. Compile peak-RSS median fell from 571.2 MiB to 552.6 MiB, while build peak RSS was 570.9 MiB versus 568.8 MiB. Compiler scratch fell from 7,328,390 to 1,971,061 bytes. Both targets emitted the same 50 static HTML files, 10,980 bytes, and deploy SHA-256 `e107d78a7f55bc8a1af0ea6e53efeffa19b3d44d21c892484d103fa346e7ba7b`. This is a source-scale compiler comparison, not a cross-framework result.
8
+
9
+ ## Current 0.8.46 Release Snapshot
10
+
11
+ Kudzu 0.8.46 changes compiler-only Context Provider scratch and retains the same concrete Notes state operations, RouteIR signals, runtime modules, and static sibling output. No new performance claim is made.
12
+
13
+ ## Maintained 0.8.45 Optimization Snapshot
14
+
15
+ Kudzu 0.8.45 adds the plain TypeScript fast path and maintained paired source-scale runner described above. It changes compiler scratch only: the measured deploy graph is byte-identical to `v0.8.44`, and no browser runtime or public API changed.
16
+
17
+ ## Maintained 0.8.44 Release Snapshot
6
18
 
7
19
  Kudzu 0.8.44 changes compiler-only naming for action-private Context state and setters. The maintained Context browser fixture retains the same emitted concrete state operations and CRUD behavior while a same-named consumer local remains ordinary static content. No browser runtime module or public API changed, and no new performance claim is made.
8
20
 
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.44 - Collision-free Context actions.** Context actions can now use Provider state and setters whose names also exist as ordinary consumer locals; Kudzu assigns compiler-owned aliases and still emits concrete state operations without a Context runtime. Read the [release notes](./RELEASES.md#0844---collision-free-context-actions), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.8.44), or follow the [architecture packet](./docs/next-architecture/README.md).
17
+ **Latest release: 0.8.46 - Private Context action setters.** Context actions may use Provider setters without exposing those implementation details in the authored Provider value or public Context type. Kudzu restores required setters only in compiler scratch and still emits concrete state operations without a Context runtime. Read the [release notes](./RELEASES.md#0846---private-context-action-setters), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.8.46), 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,63 @@
1
1
  # Kudzu Releases
2
2
 
3
+ ## 0.8.46 - Private Context action setters
4
+
5
+ Kudzu 0.8.46 lets relative Context-hook actions use Provider-owned setters without forcing those implementation-only setters into the authored Provider value or public Context type.
6
+
7
+ ### Changed in 0.8.46
8
+
9
+ - An action-required setter may be omitted when its corresponding Provider state value remains exposed.
10
+ - Kudzu adds only the required setter fields to compiler build scratch so existing setter identity and HandlerIR lowering remain unchanged.
11
+ - Consumer scratch receives collision-free private bindings for action lowering without changing authored hook destructuring.
12
+ - Fully hidden action state and Provider setters exposed without their state remain source-diagnosed.
13
+
14
+ ### Output and performance
15
+
16
+ - The Notes migration retains the same two RouteIR signals and concrete `notes` and `activeId` state operations.
17
+ - Context objects, Provider trees, action functions, and a setter registry remain absent from browser output; the static sibling remains zero JavaScript.
18
+ - No browser runtime module, public Kudzu API, or new performance claim is added.
19
+
20
+ ### Validation
21
+
22
+ - `npm run check`, `npm test`, and `npm run test:package` pass with all 210 tests and 161 generated pages.
23
+ - The maintained Chrome Notes journey passes create, rename, select, delete, list identity, and active-note updates with authored setters omitted.
24
+ - Focused negative fixtures retain diagnostics for fully hidden action state, setter-only exposure, private captures, and indirect action references.
25
+
26
+ ### Upgrade
27
+
28
+ ```bash
29
+ npm install @kudzujs/core@^0.8.46
30
+ ```
31
+
32
+ ## 0.8.45 - Plain TypeScript fast path
33
+
34
+ Kudzu 0.8.45 reduces large-project compiler work by sending proven plain `.ts` modules through TypeScript transpilation and relative ESM path rewriting without running Kudzu's TSX semantic transformer.
35
+
36
+ ### Changed in 0.8.45
37
+
38
+ - The fast path is limited to `.ts` files whose runtime imports and exports are resolvable relative TypeScript modules.
39
+ - TSX, package imports, static assets, unresolved edges, and all uncertain module shapes retain the existing Kudzu transformer.
40
+ - `npm run benchmark:source-scale` deterministically generates 500 reachable modules and supports alternating baseline/candidate measurements with deploy-output equivalence checks.
41
+ - Compiler counters report the number of plain modules selected.
42
+
43
+ ### Output and performance
44
+
45
+ - Seven alternating samples against clean `v0.8.44` reduced compile median from 2,323.9 ms to 1,413.2 ms (39.2%) and clean-build median from 3,325.3 ms to 2,382.4 ms (28.4%) on the maintained source-scale fixture.
46
+ - Compiler scratch decreased from 7,328,390 to 1,971,061 bytes and compile peak-RSS median decreased from 571.2 MiB to 552.6 MiB.
47
+ - Both targets emitted the same 50 HTML files, 10,980 deploy bytes, and SHA-256 digest. No browser runtime or public API changed.
48
+
49
+ ### Validation
50
+
51
+ - `npm run check`, `npm test`, and `npm run test:package` pass with all 209 tests and 160 generated pages.
52
+ - A reduced deterministic source-scale run remains in the standard suite.
53
+ - The maintained benchmark alternates target order and rejects any deploy digest, file-count, page-count, or byte-count difference.
54
+
55
+ ### Upgrade
56
+
57
+ ```bash
58
+ npm install @kudzujs/core@^0.8.45
59
+ ```
60
+
3
61
  ## 0.8.44 - Collision-free Context actions
4
62
 
5
63
  Kudzu 0.8.44 removes a compiler-only naming restriction from relative Context-hook actions: Provider state and setter fields needed privately by an action may now share names with ordinary consumer locals.
@@ -11,7 +11,7 @@ The top-level [`GOAL_A.md`](../../GOAL_A.md) and [`GOAL_B.md`](../../GOAL_B.md)
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
 
14
- The active post-`0.8.44` implementation sequence is [`large-application-ai-native-roadmap.md`](./large-application-ai-native-roadmap.md). P0.12 validation, property-level object-state dependencies, direct three-boundary callback/ref ownership, collision-free Context action-private state, and the measured route-entry output optimization are complete; broader prop, callback, ref, and Context dataflow remains migration-backed work. The plan orders compiler semantic generalization, large-application foundations, compatibility boundaries, AI tooling, and production validation without changing the invariants below.
14
+ The active post-`0.8.46` implementation sequence is [`large-application-ai-native-roadmap.md`](./large-application-ai-native-roadmap.md). P0.12 validation, property-level object-state dependencies, direct three-boundary callback/ref ownership, collision-free and private Context action setters, measured route-entry output optimization, and the plain TypeScript source-scale fast path are complete; broader prop, callback, ref, and Context dataflow remains migration-backed work. The plan orders compiler semantic generalization, large-application foundations, compatibility boundaries, AI tooling, and production validation without changing the invariants below.
15
15
 
16
16
  ## Required Invariants
17
17
 
@@ -1,6 +1,6 @@
1
1
  # Current Compiler Architecture
2
2
 
3
- This maps the current `0.8.44` 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.
3
+ This maps the current `0.8.46` 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
 
@@ -17,7 +17,7 @@ This maps the current `0.8.44` architecture, built on the completed `0.8.23` Goa
17
17
  | Source-local binding index | [`framework/compiler/analysis/binding-index.mjs`](../../framework/compiler/analysis/binding-index.mjs) | After normalization, assigns deterministic lexical slots and classifies local, parameter, import, capture, global, and unresolved references. Native handler, effect, binding, list evaluator, optimized-command, and effect-resource consumers use complete index-owned AST; synthesized expressions retain the existing fallback. |
18
18
  | 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. |
19
19
  | 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. |
20
- | Component ownership analysis | [`framework/compiler/analysis/component-analysis.mjs`](../../framework/compiler/analysis/component-analysis.mjs) | Produces ComponentAnalysis v2 with ordered JSON-safe owner and specialization slots for state, setters, props, refs, IDs, direct SignalIR links, structural OwnerRefs, source-local SiteIds, and source provenance. Three direct callback/ref component boundaries specialize into the same parent signal and intrinsic ownership; Context action-private state fields receive collision-free consumer-local aliases; AST identity remains private to its source-local session. |
20
+ | Component ownership analysis | [`framework/compiler/analysis/component-analysis.mjs`](../../framework/compiler/analysis/component-analysis.mjs) | Produces ComponentAnalysis v2 with ordered JSON-safe owner and specialization slots for state, setters, props, refs, IDs, direct SignalIR links, structural OwnerRefs, source-local SiteIds, and source provenance. Three direct callback/ref component boundaries specialize into the same parent signal and intrinsic ownership; Context action-private setters may remain compiler-only and receive collision-free consumer-local aliases; AST identity remains private to its source-local session. |
21
21
  | Per-source descriptor registration | [`framework/compiler/descriptor-session.mjs`](../../framework/compiler/descriptor-session.mjs), `createSemanticArtifact()`, `createDescriptorSession()` | Keeps AST descriptors private during analysis, then finalizes ModuleIR v2 with deterministic SymbolRef, SignalIR, HandlerIR, BindingIR, DerivedIR, EffectIR, KeyedBlockIR, and ImportIR slots. One fail-closed boundary validates every source-local and component ownership edge before build-module generation. |
22
22
  | Route artifact graph | [`framework/compiler/route-build-record.mjs`](../../framework/compiler/route-build-record.mjs), `createRouteBuildRecord()`, `planRouteArtifacts()` | Validates each rendered route's RouteIR, capabilities, entry paths, styles, and exact handler/effect references. Handler modules, Workers, package-client modules, and bundle entry/chunk closure derive from structural edges without serialized output searches or formatted composite keys. |
23
23
  | Route contract validation | [`framework/compiler/route-ir.mjs`](../../framework/compiler/route-ir.mjs), `assertRouteIR()` | Fails before artifact selection for invalid state/parameter identity, commands, native/effect captures and dependencies, reactive descriptors, conditions, keyed-list identity/ownership, marker fields, or JSON safety. Immutable in-memory contracts validate once by identity. |
@@ -141,6 +141,7 @@ This is an incremental evolution of the current repository:
141
141
  - [x] P0.10 ModuleIR Reference Unification is complete in `0.8.37`. ModuleIR and ComponentAnalysis v2 assign deterministic slots to symbols, signals, handlers, bindings, derived values, effects, keyed blocks, imports, owners, specializations, states, refs, and IDs. State, capture, import, effect, collection, parent/child, specialization, and row edges use structural slots or ModuleSymbol records while readable names remain codegen/debug metadata. A fail-closed pre-codegen validator rejects malformed slots, unsupported versions, duplicate exports, broken reciprocity, and ownership cycles; focused JSON round-trip checks and all 198 tests pass without changing browser runtime behavior.
142
142
  - [x] P0.11 RouteBuildRecord and Artifact Graph is complete in `0.8.38`. Each rendered route records RouteIR, capability facts, route-entry paths, styles, and exact handler/effect references. Build orchestration derives Handler ESM, Worker, package-client, and chunk closure from those edges; serialized HTML/plan `includes()` searches, formatted effect keys, and parallel route-fact/entry arrays are removed. Focused malformed-reference and JSON round-trip checks plus the standard suite preserve the exact 173-file deploy digest and bytes.
143
143
  - [x] P0.12 Deep RouteIR and CapabilityIR Validation is complete in `0.8.39`. RouteIR v1 validates state/parameter identity, commands, captures, effects, bindings, conditions, keyed-list ownership, and JSON safety before artifact selection. RouteBuildRecord checks concrete capability reciprocity, and CapabilityIR validates standalone implications plus exact projection from route records before codegen. Identity caching avoids repeated validation of immutable contracts; all 203 tests and byte-identical deploy checks pass without runtime or accepted-source changes.
144
+ - [x] The P1 source-scale benchmark foundation is complete after `0.8.44`. `npm run benchmark:source-scale` generates 500 reachable modules, 50,550 TS/TSX lines, and 50 routes outside the repository, then measures source read, graph discovery, compile, clean build, deterministic output, cache counters, and peak RSS in fresh processes. Its paired mode alternates another checkout with the current tree and rejects deploy-output differences. The first measured specialization keeps Kudzu transformation for TSX, package imports, assets, and unresolved edges while plain relative-only `.ts` modules receive path rewriting alone; seven alternating samples against `v0.8.44` reduced compile median 39.2% and clean-build median 28.4% with identical deploy output. A reduced deterministic run remains in the standard suite.
144
145
 
145
146
  ### P0: Semantic Correctness And Compiler Foundation
146
147
 
@@ -175,7 +176,7 @@ P0 creates the semantic base required by every later large-application capabilit
175
176
  | Route capability and chunk closure | Every route reports exact runtime, handler, package, Worker, and shared chunk edges |
176
177
  | Route/layout CSS closure | Unrelated feature CSS is absent from a route unless configured global |
177
178
  | Incremental development build | A source change recompiles and rerenders only affected modules/routes while preserving full reload correctness |
178
- | Source-scale benchmark | At least 500 reachable modules, 50,000 TS/TSX lines, 50 routes, phase timings, and peak RSS |
179
+ | Source-scale benchmark | `npm run benchmark:source-scale` deterministically generates at least 500 reachable modules, 50,000 TS/TSX lines, and 50 routes, then reports source-read, graph, compile, clean-build, output, and peak-RSS measurements from fresh processes |
179
180
 
180
181
  ### P2: Compatibility And Migration
181
182
 
@@ -378,7 +379,7 @@ function increment(value) { setCount(value + 1) }; increment(count)
378
379
  After the relevant P0 foundations, investigate capabilities in this order:
379
380
 
380
381
  1. Property-level derived dependencies over ordinary object state. **Completed in `0.8.40`:** direct property paths and top-level immutable primitive locals over object state reuse tagged DerivedIR, subscribe to the source signal, and compare selected values with `Object.is`; whole-object and dynamic dependencies remain rejected.
381
- 2. Multi-boundary component/prop/callback/ref/context dataflow. **Three-boundary callback/ref ownership completed in `0.8.43`; collision-free Context action-private state completed in `0.8.44`:** forwarding preserves parent SignalIR and Context action lowering uses compiler-owned aliases when consumer locals reuse Provider state/setter names. A fourth callback boundary remains fail-closed. Broader prop, callback, ref, and Context graphs remain migration-led work.
382
+ 2. Multi-boundary component/prop/callback/ref/context dataflow. **Three-boundary callback/ref ownership completed in `0.8.43`; collision-free Context action-private state completed in `0.8.44`; action-only Provider setter exposure removed in `0.8.46`:** forwarding preserves parent SignalIR, Context action lowering uses compiler-owned aliases when consumer locals reuse Provider state/setter names, and action-required setters may remain compiler-only when their state is publicly exposed. A fourth callback boundary and fully hidden Context state remain fail-closed. Broader prop, callback, ref, and Context graphs remain migration-led work.
382
383
  3. Package-neutral shared state/actions and migration of current Zustand internals.
383
384
  4. Browser-only package imports in owned effect/resource modules.
384
385
  5. ResourceIR from at least two independent WebSocket/SSE/SDK fixtures with the same semantics.
@@ -480,4 +481,4 @@ The first comparison is Kudzu versus React + Vite using the same agent, model, t
480
481
 
481
482
  ## Immediate Decision
482
483
 
483
- PR 1 through PR 12, the `0.8.40` property-dependency slice, the `0.8.41` and `0.8.43` direct multi-boundary callback/ref slices, the `0.8.44` Context alias slice, and the `0.8.42` measured route-output optimization are complete. Continue broader **component/prop/callback/ref/context dataflow** only from the next real migration blocker. Do not skip directly to a store, resource, router, virtualization, or ecosystem package feature.
484
+ PR 1 through PR 12, the `0.8.40` property-dependency slice, the `0.8.41` and `0.8.43` direct multi-boundary callback/ref slices, the `0.8.44` Context alias slice, the `0.8.46` action-only Provider setter slice, and the `0.8.42` measured route-output optimization are complete. Continue broader **component/prop/callback/ref/context dataflow** only from the next real migration blocker. Do not skip directly to a store, resource, router, virtualization, or ecosystem package feature.
@@ -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.44` are completed scopes represented by package/release records.
3
+ This is an execution sequence, not release history. `0.8.16` through `0.8.46` 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
 
@@ -35,6 +35,8 @@ Keep each patch behavior-preserving and independently reviewable. If a boundary
35
35
  | `0.8.42` | Share byte-identical generated route entries and accelerate one proven read-only query form carry shape. | A static-path fixture emits one native/effect file for three routes; hidden GET carry initializes before deferred parameter ESM; static siblings remain zero-JavaScript; the losing catalog navigation experiment is excluded. |
36
36
  | `0.8.43` | Preserve callback, setter, and ref ownership through a third direct component boundary. | Parent SignalIR, child state/effects/IDs, object refs, conditional cleanup, fresh remount, and static zero-JavaScript output pass in Chrome; a fourth callback boundary fails closed. |
37
37
  | `0.8.44` | Remove consumer-local name collisions from specialized Context actions. | Action-private Provider state/setter fields receive deterministic collision-free aliases; CRUD browser behavior, direct state operations, diagnostics, and zero Context runtime output pass. |
38
+ | `0.8.45` | Add a source-scale benchmark and skip Kudzu semantic transformation for proven plain TypeScript modules. | Seven alternating samples improve compile and clean-build medians with identical deploy output; uncertain modules retain the existing transformer. |
39
+ | `0.8.46` | Remove action-only setter fields from authored Context Provider APIs. | Compiler scratch restores required setters while Notes CRUD, RouteIR signals, concrete state operations, diagnostics, and zero Context runtime output pass. |
38
40
 
39
41
  ## Sequence Rules
40
42
 
@@ -11,6 +11,7 @@ export function createProjectSession(projectRoot = process.cwd(), { counters, so
11
11
  const graph = createSourceGraph(root)
12
12
  const modules = createModuleCache(root, sourceIndex, graph, counters)
13
13
  return {
14
+ counters,
14
15
  root,
15
16
  sourceDirectory,
16
17
  pagesDirectory: join(sourceDirectory, "pages"),
@@ -22,7 +22,7 @@ import { createProjectSession } from "./project-session.mjs"
22
22
  import { createZustandPass } from "./zustand-pass.mjs"
23
23
 
24
24
  export function createSourceCompiler(project) {
25
- const { root, sourceDirectory, pagesDirectory, workDirectory, workerCompiler, modules } = project
25
+ const { root, sourceDirectory, pagesDirectory, workDirectory, workerCompiler, modules, counters } = project
26
26
  const { ordinaryRuntimeDependencies, resolveSourceImport, runtimeModuleReference } = project.graph
27
27
  const parseSourceFile = (file, source) => modules.read(file, source).sourceFile
28
28
  const staticAssetExtensions = new Set([".avif", ".gif", ".ico", ".jpeg", ".jpg", ".otf", ".png", ".svg", ".ttf", ".webp", ".woff", ".woff2"])
@@ -32,6 +32,8 @@ function compileSource(file, sourceFiles, sourceIndex, staticFiles, cssModules,
32
32
  const source = sourceIndex.get(file)
33
33
  const semantic = createSemanticArtifact(relative(root, file).replaceAll(sep, "/"))
34
34
  const handlerPath = `handlers/${relative(sourceDirectory, file).replaceAll(sep, "/").replace(/\.(?:ts|tsx)$/, ".js")}`
35
+ const plain = plainTypeScriptModule(file, source, sourceFiles)
36
+ if (plain && counters) counters.plainModules = (counters.plainModules ?? 0) + 1
35
37
  const result = ts.transpileModule(source, {
36
38
  fileName: file,
37
39
  compilerOptions: {
@@ -40,7 +42,7 @@ function compileSource(file, sourceFiles, sourceIndex, staticFiles, cssModules,
40
42
  jsx: ts.JsxEmit.ReactJSX,
41
43
  jsxImportSource: "@kudzujs/core"
42
44
  },
43
- transformers: { before: [createKudzuTransformer({ semantic, handlerUrl: assetPath(base, `assets/${handlerPath}`), file, sourceFiles, sourceIndex, staticFiles, importedAssets, cssModules, base })] },
45
+ transformers: { before: [plain ? createPlainModuleTransformer(file, sourceFiles) : createKudzuTransformer({ semantic, handlerUrl: assetPath(base, `assets/${handlerPath}`), file, sourceFiles, sourceIndex, staticFiles, importedAssets, cssModules, base })] },
44
46
  reportDiagnostics: true
45
47
  })
46
48
 
@@ -71,6 +73,27 @@ function compileSource(file, sourceFiles, sourceIndex, staticFiles, cssModules,
71
73
  return sourceResult
72
74
  }
73
75
 
76
+ function plainTypeScriptModule(file, source, sourceFiles) {
77
+ if (!file.endsWith(".ts")) return false
78
+ const sourceFile = parseSourceFile(file, source)
79
+ for (const statement of sourceFile.statements) {
80
+ if ((!ts.isImportDeclaration(statement) && !ts.isExportDeclaration(statement)) || !runtimeModuleReference(statement)) continue
81
+ if (!statement.moduleSpecifier || !ts.isStringLiteral(statement.moduleSpecifier) || !statement.moduleSpecifier.text.startsWith(".") || isStaticImport(statement.moduleSpecifier.text)) return false
82
+ try { resolveSourceImport(file, statement.moduleSpecifier.text, sourceFiles) } catch { return false }
83
+ }
84
+ return true
85
+ }
86
+
87
+ function createPlainModuleTransformer(file, sourceFiles) {
88
+ return context => sourceFile => context.factory.updateSourceFile(sourceFile, sourceFile.statements.map(statement => {
89
+ if ((!ts.isImportDeclaration(statement) && !ts.isExportDeclaration(statement)) || !runtimeModuleReference(statement) || !statement.moduleSpecifier || !ts.isStringLiteral(statement.moduleSpecifier) || !statement.moduleSpecifier.text.startsWith(".")) return statement
90
+ const target = resolveSourceImport(file, statement.moduleSpecifier.text, sourceFiles)
91
+ const specifier = context.factory.createStringLiteral(relativeModulePath(compiledPath(file), compiledPath(target)))
92
+ if (ts.isImportDeclaration(statement)) return context.factory.updateImportDeclaration(statement, statement.modifiers, statement.importClause, specifier, statement.attributes)
93
+ return context.factory.updateExportDeclaration(statement, statement.modifiers, statement.isTypeOnly, statement.exportClause, specifier, statement.attributes)
94
+ }))
95
+ }
96
+
74
97
  function normalizeModulePaths(moduleIR) {
75
98
  const normalize = target => isAbsolute(target) ? relative(root, target).replaceAll(sep, "/") : target
76
99
  moduleIR.imports = moduleIR.imports.map(entry => entry.package ? entry : { ...entry, target: normalize(entry.target) })
@@ -336,6 +359,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
336
359
  const functions = new Map()
337
360
  const customHookFunctionsByOwner = new Map()
338
361
  const customHookPrivateFields = new WeakMap()
362
+ const contextProviderPrivateSetters = new WeakMap()
339
363
  const components = new Map()
340
364
  const contexts = new Set()
341
365
  const customHooks = new Map()
@@ -467,7 +491,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
467
491
  fields.add(name)
468
492
  }
469
493
  for (const [setter, state] of states) {
470
- if (fields.has(setter) !== fields.has(state)) throw sourceNodeError(value, providerSource, `Context Provider state ${JSON.stringify(state)} and setter ${JSON.stringify(setter)} must be exposed together`)
494
+ if (fields.has(setter) && !fields.has(state)) throw sourceNodeError(value, providerSource, `Context Provider setter ${JSON.stringify(setter)} requires exposed state ${JSON.stringify(state)}`)
471
495
  }
472
496
  for (const [name, callback] of callbacks) {
473
497
  if (!fields.has(name)) continue
@@ -476,7 +500,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
476
500
  if (capture) throw sourceNodeError(callback, providerSource, `Context action ${JSON.stringify(name)} cannot capture private binding ${JSON.stringify(capture)}`)
477
501
  for (const state of referencedStateNames(callback.body, states, callback)) {
478
502
  const setter = [...states].find(([, candidate]) => candidate === state)?.[0]
479
- if (!setter || !fields.has(state) || !fields.has(setter)) throw sourceNodeError(callback, providerSource, `Context action ${JSON.stringify(name)} requires exposed state and setter fields for ${JSON.stringify(state)}`)
503
+ if (!setter || !fields.has(state)) throw sourceNodeError(callback, providerSource, `Context action ${JSON.stringify(name)} requires exposed state field ${JSON.stringify(state)}`)
480
504
  }
481
505
  }
482
506
  return { callbacks: new Map([...callbacks].filter(([name]) => fields.has(name))), context: true, fields, privateStates: new Set(), stateOwner, stateSymbols, states }
@@ -716,6 +740,32 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
716
740
  ts.forEachChild(node, collect)
717
741
  }
718
742
  collect(sourceFile)
743
+ const collectContextProviderPrivateSetters = node => {
744
+ if (ts.isJsxAttribute(node) && isContextProviderValue(node, contexts)) {
745
+ const value = node.initializer && ts.isJsxExpression(node.initializer) && node.initializer.expression ? unwrapExpression(node.initializer.expression) : undefined
746
+ const owner = nearestFunction(node)
747
+ if (value && ts.isObjectLiteralExpression(value) && owner) {
748
+ const states = new Map()
749
+ const callbacks = new Map()
750
+ const visitOwner = current => {
751
+ if (ts.isVariableDeclaration(current) && nearestFunction(current) === owner) {
752
+ if (ts.isArrayBindingPattern(current.name) && current.initializer && ts.isCallExpression(current.initializer) && ts.isIdentifier(current.initializer.expression) && current.initializer.expression.text === "useState") {
753
+ const [state, setter] = current.name.elements
754
+ if (current.name.elements.length === 2 && state && setter && ts.isBindingElement(state) && ts.isBindingElement(setter) && ts.isIdentifier(state.name) && ts.isIdentifier(setter.name)) states.set(setter.name.text, state.name.text)
755
+ }
756
+ if (ts.isIdentifier(current.name) && current.initializer && (ts.isArrowFunction(current.initializer) || ts.isFunctionExpression(current.initializer))) callbacks.set(current.name.text, current.initializer)
757
+ }
758
+ ts.forEachChild(current, visitOwner)
759
+ }
760
+ visitOwner(owner.body)
761
+ const fields = new Set(value.properties.filter(ts.isShorthandPropertyAssignment).map(property => property.name.text))
762
+ const missing = [...states].filter(([setter, state]) => fields.has(state) && !fields.has(setter) && [...callbacks].some(([name, callback]) => fields.has(name) && referencedStateNames(callback.body, states, callback).has(state))).map(([setter]) => setter)
763
+ if (missing.length) contextProviderPrivateSetters.set(node, missing)
764
+ }
765
+ }
766
+ ts.forEachChild(node, collectContextProviderPrivateSetters)
767
+ }
768
+ collectContextProviderPrivateSetters(sourceFile)
719
769
  const functionsForNode = node => {
720
770
  const callbacks = customHookFunctionsByOwner.get(nearestFunction(node))
721
771
  return callbacks ? new Map([...functions, ...callbacks]) : functions
@@ -1558,6 +1608,11 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
1558
1608
  return factory.updateJsxExpression(node, factory.createCallExpression(factory.createIdentifier("__kList"), undefined, arguments_))
1559
1609
  }
1560
1610
  const visitor = node => {
1611
+ if (ts.isJsxAttribute(node) && contextProviderPrivateSetters.has(node)) {
1612
+ const expression = unwrapExpression(node.initializer.expression)
1613
+ const value = factory.updateObjectLiteralExpression(expression, [...expression.properties, ...contextProviderPrivateSetters.get(node).map(name => factory.createShorthandPropertyAssignment(name))])
1614
+ return factory.updateJsxAttribute(node, node.name, factory.updateJsxExpression(node.initializer, value))
1615
+ }
1561
1616
  if (ts.isVariableDeclaration(node) && ts.isObjectBindingPattern(node.name) && customHookPrivateFields.has(node)) {
1562
1617
  const privateFields = customHookPrivateFields.get(node)
1563
1618
  return factory.updateVariableDeclaration(node, factory.updateObjectBindingPattern(node.name, [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kudzujs/core",
3
- "version": "0.8.44",
3
+ "version": "0.8.46",
4
4
  "description": "HTML-first TSX framework with synchronous state semantics and no virtual DOM",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -60,6 +60,7 @@
60
60
  "benchmark:commerce": "node test/commerce-build-performance.mjs",
61
61
  "benchmark:module-cache": "node test/module-cache-performance.mjs",
62
62
  "benchmark:native": "node test/native-performance.mjs",
63
+ "benchmark:source-scale": "node test/source-scale-performance.mjs",
63
64
  "prepublishOnly": "npm run check && npm test",
64
65
  "deploy": "wrangler deploy",
65
66
  "preview": "wrangler dev"