@kudzujs/core 0.8.33 → 0.8.35
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 +14 -1
- package/PERFORMANCE.md +90 -1
- package/README.md +1 -1
- package/RELEASES.md +68 -0
- package/docs/next-architecture/README.md +1 -1
- package/docs/next-architecture/compiler-current-architecture.md +6 -5
- package/docs/next-architecture/large-application-ai-native-roadmap.md +9 -4
- package/docs/next-architecture/versioning.md +3 -1
- package/framework/compiler/analysis/component-analysis.mjs +2 -2
- package/framework/compiler/project-session.mjs +168 -4
- package/framework/compiler/source-compiler.mjs +33 -52
- package/framework/compiler/worker-compiler.mjs +3 -3
- package/package.json +2 -1
package/MIGRATION_ROADMAP.md
CHANGED
|
@@ -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.
|
|
5
|
+
The executable post-`0.8.35` 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
|
|
|
@@ -308,6 +308,19 @@ This queue orders the next investigations by general migration value. Start only
|
|
|
308
308
|
- Programmatic build and development entry points accept an explicit root while omitted roots preserve call-time CWD and existing CLI behavior.
|
|
309
309
|
- One process compiles two independent roots with identical module names and verifies isolated config, HTML, `.kudzu`, source results, and Worker output; no browser runtime or migration syntax changes.
|
|
310
310
|
|
|
311
|
+
### Completed In 0.8.34
|
|
312
|
+
|
|
313
|
+
- Each unchanged source module is parsed and export-summarized once per ProjectSession, with source-text invalidation and no cross-project cache sharing.
|
|
314
|
+
- Read-only graph consumers share a canonical AST while importer normalization deep-clones every node and repairs independent parent links before transformation.
|
|
315
|
+
- A 100-importer fixture proves bounded parse/summary work and identical source results without changing accepted migration syntax or browser output.
|
|
316
|
+
|
|
317
|
+
### Completed In 0.8.35
|
|
318
|
+
|
|
319
|
+
- Cross-module declarations resolve through stable project-relative ModuleSymbol records rather than transformed AST identity or readable names.
|
|
320
|
+
- Source-local SiteIds identify imports, re-exports, component calls, hooks, keyed lists, effects, and ownership records across private transformer clones.
|
|
321
|
+
- Default/named exports, aliases, barrel chains, `export *`, cycles, ambiguity, repeated compilation, and source-dependent cache invalidation are covered without changing migration syntax or browser runtime behavior.
|
|
322
|
+
- The 100-importer fixture reduces private clones from 200 to 100 and records a small directional build improvement; current React/Vue/Svelte/Astro whole-build and artifact context is retained in `PERFORMANCE.md`.
|
|
323
|
+
|
|
311
324
|
## Cross-Cutting Performance Gates
|
|
312
325
|
|
|
313
326
|
Every migration feature must preserve:
|
package/PERFORMANCE.md
CHANGED
|
@@ -1,6 +1,95 @@
|
|
|
1
1
|
# Performance Records
|
|
2
2
|
|
|
3
|
-
Reproducibility classes: `npm run benchmark`, `npm run benchmark:keyed`, and `npm run benchmark:
|
|
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.
|
|
4
|
+
|
|
5
|
+
## P0.8 Stable ModuleSymbol And SiteId
|
|
6
|
+
|
|
7
|
+
Measured UTC 2026-08-11 on the same Intel Core i5-9500 Linux x64 host with Node 24.14.0. The baseline was clean tag `v0.8.34` at `007fcb6e23c7d5bc742fa37c28388d070da9f598`. The compiler and maintained-check patch had SHA-256 `10ed6beb448b9e86961afab0b798f010932b8a29f98475d41f7bd8cfad04a872`, produced by:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
git diff --binary v0.8.34 -- framework/compiler/project-session.mjs framework/compiler/source-compiler.mjs framework/compiler/analysis/component-analysis.mjs test/compiler-passes.test.mjs test/module-cache-performance.mjs | sha256sum
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The maintained 100-importer fixture used three warm-ups and 21 alternating fresh-process samples. Because P0.8 intentionally adds source-local `site` metadata, the runner removes only `site` keys for output-equivalence hashing and reports the unmodified source-result size separately. The normalized 382,603-byte graph retained SHA-256 `8c35b3f6d2c571306bd97c4d51d4af76ca244badd36c57363bf579ef961f41aa`; parse and summary misses remained 103 each. Stable symbol traversal removed unnecessary normalization of intermediate barrel modules, reducing importer-local clones from 200 to 100.
|
|
14
|
+
|
|
15
|
+
| Target | Compiler median | Range | Peak RSS median | Source-result bytes | Parse / summary / clone misses |
|
|
16
|
+
|---|---:|---:|---:|---:|---:|
|
|
17
|
+
| `v0.8.34` | 760.967 ms | 696.626-796.908 ms | 256.9 MiB | 382,603 B | not instrumented |
|
|
18
|
+
| `0.8.35` | 755.595 ms | 692.365-804.376 ms | 257.8 MiB | 395,346 B | 103 / 103 / 100 |
|
|
19
|
+
|
|
20
|
+
The candidate's unpaired median is 0.71% lower. Round-paired candidate-minus-baseline differences had a -2.971 ms median with the candidate faster in 13/21 pairs and the baseline faster in 8/21. This is a small directional improvement, not a material speedup claim. Peak RSS differs by 0.9 MiB and ranges overlap. The 12,743-byte source-result increase is deterministic SiteId metadata in build scratch, not deployed browser JavaScript.
|
|
21
|
+
|
|
22
|
+
Two measurements before removing intermediate barrel clones showed paired medians of +12.142 ms and +21.954 ms and RSS medians 11.7-12.3 MiB above baseline. Removing those clones reduced the paired median to +4.951 ms; caching repeated ModuleSymbol resolutions produced the final -2.971 ms result. The earlier measurements are not the final candidate record.
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
v0.8.34: [791.247,780.543,796.908,783.896,786.968,790.739,784.589,753.377,697.477,696.626,701.336,718.764,709.913,743.865,749.294,791.943,743.784,758.566,783.665,763.691,760.967]
|
|
26
|
+
candidate: [783.482,799.855,760.681,768.299,804.376,802.390,783.124,799.484,692.365,720.229,708.652,707.073,712.554,708.853,750.750,756.400,742.668,755.595,776.556,747.370,751.392]
|
|
27
|
+
paired candidate-baseline: [-7.765,19.312,-36.227,-15.597,17.408,11.651,-1.465,46.107,-5.112,23.603,7.316,-11.691,2.641,-35.012,1.456,-35.543,-1.116,-2.971,-7.109,-16.321,-9.575]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
git worktree add --detach /tmp/opencode/kudzu-v0.8.34 v0.8.34
|
|
32
|
+
ln -s "$PWD/node_modules" /tmp/opencode/kudzu-v0.8.34/node_modules
|
|
33
|
+
BASELINE_ROOT=/tmp/opencode/kudzu-v0.8.34 CANDIDATE_ROOT="$PWD" WARMUPS=3 RUNS=21 npm run benchmark:module-cache
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## P0.7 Parsed Module And Export Summary Cache
|
|
37
|
+
|
|
38
|
+
Measured UTC 2026-08-11 on an Intel Core i5-9500 with 6 physical/logical cores, 31.2 GiB RAM, Linux 6.17.0-19-generic x64, Node 24.14.0, and npm 11.9.0. The baseline was clean tag `v0.8.33` at `65c96b13802c73e9c9a109cebbaac88bae7704a7`; both targets used the same installed dependencies. The implementation and maintained benchmark patch had SHA-256 `bca61b37f14c77488c4a35b62856faa6b471dd01081abcb69c47027b3b4615d9`, produced by:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
git diff --binary v0.8.33..v0.8.34 -- framework/compiler/project-session.mjs framework/compiler/source-compiler.mjs framework/compiler/worker-compiler.mjs test/compiler-passes.test.mjs test/module-cache-performance.mjs package.json | sha256sum
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The maintained in-memory fixture has 100 page importers sharing one barrel component and helper, for 103 unique modules. Each sample starts a fresh Node process; fixture generation and result hashing occur outside timing. Three warm-ups followed by 21 alternating samples compiled every reachable module. The candidate parsed and summarized each unique module once and created 200 importer-local normalization clones. Baseline and candidate produced the same 382,603-byte serialized source-result graph with SHA-256 `8c35b3f6d2c571306bd97c4d51d4af76ca244badd36c57363bf579ef961f41aa`.
|
|
45
|
+
|
|
46
|
+
| Target | Compiler median | Range | Peak RSS median | Parse / summary / clone misses |
|
|
47
|
+
|---|---:|---:|---:|---:|
|
|
48
|
+
| `v0.8.33` | 2,386.735 ms | 1,183.385–3,249.719 ms | 261.3 MiB | not instrumented |
|
|
49
|
+
| P0.7 candidate | 2,188.745 ms | 1,321.352–2,957.883 ms | 257.1 MiB | 103 / 103 / 200 |
|
|
50
|
+
|
|
51
|
+
Both timing arrays drifted upward during the run, making their raw 8.29% median difference unsuitable as an improvement claim. Round-paired candidate-minus-baseline differences had a +33.324 ms median, with the candidate faster in 9/21 pairs and the baseline faster in 12/21. Peak RSS ranges also overlap. This establishes bounded parse/summary work and identical compiler output with no material performance conclusion on this machine; it does not establish a speedup.
|
|
52
|
+
|
|
53
|
+
```text
|
|
54
|
+
v0.8.33: [1512.096,1244.231,1183.385,1655.352,1669.380,2033.448,1820.868,1774.702,1847.652,1889.290,2386.735,2926.474,2853.159,3249.719,2716.963,2569.254,2531.427,2727.951,2483.411,2492.172,2597.774]
|
|
55
|
+
candidate: [1943.630,1416.969,1321.352,1603.967,1340.557,1440.993,1913.509,2060.240,1796.133,2011.796,2188.745,2888.145,2957.883,2947.417,2750.287,2745.883,2799.166,2620.866,2499.916,2541.054,2530.391]
|
|
56
|
+
paired candidate-baseline: [431.534,172.738,137.967,-51.385,-328.823,-592.455,92.641,285.538,-51.519,122.506,-197.990,-38.329,104.724,-302.302,33.324,176.629,267.739,-107.085,16.505,48.882,-67.383]
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
git worktree add --detach /tmp/opencode/kudzu-v0.8.33 v0.8.33
|
|
61
|
+
ln -s "$PWD/node_modules" /tmp/opencode/kudzu-v0.8.33/node_modules
|
|
62
|
+
BASELINE_ROOT=/tmp/opencode/kudzu-v0.8.33 CANDIDATE_ROOT="$PWD" WARMUPS=3 RUNS=21 npm run benchmark:module-cache
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Current Cross-Framework Whole-Build Context
|
|
66
|
+
|
|
67
|
+
Measured again after the P0.8 optimization on 2026-08-11. This separate local check is not a parsed-module-cache comparison or maintained ranking. The external `/tmp/opencode/kudzu-dependency-benchmark` workspace is not Git-provenanced. It used Kudzu 0.8.35, React 19.2.7, Vue 3.5.40, Svelte 5.56.6, Astro 7.1.1, and Vite 7.3.6. Every target emitted matched initial device content, and fresh headless Chrome runs passed detail fetch, list filters, empty state, superseded async command, stale-result rejection, and HTTP error behavior. One warm-up preceded seven rotated clean builds. React, Vue, and Svelte perform separate client, SSR, and prerender builds; Astro uses its native build and an application-specific imperative script; Kudzu runs one static compiler build, so build stages and artifact architectures differ.
|
|
68
|
+
|
|
69
|
+
| Target | Build median | HTML raw / gzip | JavaScript raw / gzip | Total output |
|
|
70
|
+
|---|---:|---:|---:|---:|
|
|
71
|
+
| Kudzu | 495.4 ms | 5,647 B / 1,324 B | 30,712 B / 12,233 B | 36,359 B |
|
|
72
|
+
| React SSR | 1,639.5 ms | 1,338 B / 562 B | 195,864 B / 61,488 B | 197,202 B |
|
|
73
|
+
| Vue SSR | 1,477.3 ms | 1,348 B / 567 B | 69,643 B / 27,756 B | 70,991 B |
|
|
74
|
+
| Svelte SSR | 2,033.1 ms | 1,399 B / 582 B | 40,781 B / 15,898 B | 42,180 B |
|
|
75
|
+
| Astro native | 974.5 ms | 3,307 B / 1,229 B | 2,201 B / 883 B | 3,307 B |
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
Kudzu: [474.3,482.6,493.4,495.4,498.0,503.5,504.9]
|
|
79
|
+
React SSR: [1589.6,1617.1,1638.4,1639.5,1640.7,1662.7,1667.5]
|
|
80
|
+
Vue SSR: [1433.8,1448.2,1450.0,1477.3,1486.0,1498.2,1500.5]
|
|
81
|
+
Svelte SSR: [1956.3,1993.7,2025.4,2033.1,2036.9,2060.2,2072.8]
|
|
82
|
+
Astro native: [943.3,945.9,964.7,974.5,988.8,996.4,999.5]
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
node /tmp/opencode/kudzu-dependency-benchmark/device-run.mjs
|
|
87
|
+
node /tmp/opencode/kudzu-dependency-benchmark/device-verify.mjs
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Kudzu's build median was 69.8% lower than React SSR, 66.5% lower than Vue SSR, 75.6% lower than Svelte SSR, and 49.2% lower than Astro native in this protocol. Kudzu shipped 80.1%, 55.9%, and 23.1% less JavaScript gzip than React, Vue, and Svelte respectively. Astro shipped only 883 B JavaScript gzip and a 3,307 B single-file output, so Kudzu's JavaScript gzip was 13.9 times and total output 11.0 times larger than Astro's direct imperative implementation. Kudzu's larger HTML carries static content and capability descriptors; its 1,324 B gzip was close to Astro's 1,229 B but larger than the hydrated SSR controls' 562-582 B HTML.
|
|
91
|
+
|
|
92
|
+
The cross-framework result provides current whole-build and deploy-artifact context only. It does not show that Kudzu's ModuleSymbol cache is faster than another framework's module cache: this fixture has one application module per target and exposes no equivalent parser/cache counters. A cache-specific cross-framework claim would require matched 100-importer module graphs, equivalent output, equal cold/warm policy, and framework-specific cache instrumentation. The browser run verifies behavior rather than interaction latency; the maintained larger keyed benchmarks below remain the runtime-performance evidence.
|
|
4
93
|
|
|
5
94
|
## 2026-08-11 React, Vue, And Svelte Check
|
|
6
95
|
|
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.35 - Stable module identity.** Cross-module declarations now resolve through stable ModuleSymbol and source-local SiteId records across aliases, barrel chains, `export *`, and cloned transformer trees. Read the [release notes](./RELEASES.md#0835---stable-module-identity), open the [release page](https://github.com/kudzujs/kudzu/releases/tag/v0.8.35), 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,73 @@
|
|
|
1
1
|
# Kudzu Releases
|
|
2
2
|
|
|
3
|
+
## 0.8.35 - Stable module identity
|
|
4
|
+
|
|
5
|
+
Kudzu 0.8.35 completes P0.8 by giving cross-module declarations and authored compiler sites stable identities that do not depend on transformed TypeScript AST objects or readable names.
|
|
6
|
+
|
|
7
|
+
### Changed in 0.8.35
|
|
8
|
+
|
|
9
|
+
- ProjectSession summarizes declaration, import, named re-export, and `export *` sites as source-local SiteId records.
|
|
10
|
+
- ModuleSymbol identity combines a project-relative module path with the authored declaration SiteId; readable names remain diagnostic metadata.
|
|
11
|
+
- Default and named exports, local aliases, barrel chains, `export *`, cycles, and ambiguous star exports resolve through one cycle-safe symbol graph.
|
|
12
|
+
- Imported semantic consumers resolve ModuleSymbols first, then locate the authored SiteId in their private normalized AST clone instead of rescanning by declaration name.
|
|
13
|
+
- Component owners, component calls, hooks, keyed lists, effects, states, refs, and IDs expose deterministic source-local SiteIds in compiler analysis records.
|
|
14
|
+
- Repeated symbol resolutions are cached against their source dependencies and invalidate when any dependency source changes.
|
|
15
|
+
|
|
16
|
+
### Performance
|
|
17
|
+
|
|
18
|
+
- The 100-importer fixture retains 103 parse and 103 summary misses while removing 100 unnecessary intermediate barrel clones, reducing importer-local clones from 200 to 100.
|
|
19
|
+
- Twenty-one alternating `v0.8.34`/candidate samples measured compiler medians of 760.967 ms and 755.595 ms. The paired candidate-minus-baseline median was -2.971 ms, with the candidate faster in 13/21 pairs; this is a small directional improvement, not a material speedup claim.
|
|
20
|
+
- Peak RSS medians were 256.9 MiB and 257.8 MiB. The 12,743-byte source-result increase is deterministic SiteId metadata in build scratch, not deployed JavaScript.
|
|
21
|
+
- A matched whole-build check measured Kudzu at 495.4 ms, Astro at 974.5 ms, Vue SSR at 1,477.3 ms, React SSR at 1,639.5 ms, and Svelte SSR at 2,033.1 ms. Kudzu shipped less JavaScript gzip than React, Vue, and Svelte; Astro's direct imperative implementation remained much smaller.
|
|
22
|
+
|
|
23
|
+
### Validation
|
|
24
|
+
|
|
25
|
+
- `npm run check`, `npm test`, `npm run test:package`, and all 195 tests pass.
|
|
26
|
+
- Focused coverage verifies default/named exports, aliases, barrel chains, `export *`, ambiguity, cycles, repeated-session IDs, repeated-compilation IDs, resolution-cache invalidation, and clone-local declaration lookup.
|
|
27
|
+
- All five cross-framework device targets pass the same Chrome checks for initial HTML, filtering, empty state, detail fetch, superseded commands, stale-result rejection, and HTTP failures.
|
|
28
|
+
- Existing browser runtime code, route capabilities, source syntax, and deployed JavaScript remain unchanged; the repository build emits 150 pages.
|
|
29
|
+
- P0.9 semantic state operations is next.
|
|
30
|
+
|
|
31
|
+
### Upgrade
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install @kudzujs/core@^0.8.35
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## 0.8.34 - Session-local module cache
|
|
38
|
+
|
|
39
|
+
Kudzu 0.8.34 completes P0.7 by making source parsing and supported-export summarization proportional to unique modules within one ProjectSession instead of repeated importer edges.
|
|
40
|
+
|
|
41
|
+
### Changed in 0.8.34
|
|
42
|
+
|
|
43
|
+
- ProjectSession caches one canonical read-only TypeScript AST and one narrow export summary for each file/source-text pair.
|
|
44
|
+
- Reachability, static collection, Zustand, client-helper, style, layout-diagnostic, and Worker graph consumers reuse canonical module records.
|
|
45
|
+
- Importer normalization receives a deep clone of every node with independent parent links; transformed mutable AST is never shared between transformer contexts.
|
|
46
|
+
- Source-text changes invalidate the parsed tree and export summary together, and independent ProjectSessions never share records.
|
|
47
|
+
- Export summaries preserve the existing supported direct, aliased, default, and named re-export forms. Stable ModuleSymbol/SiteId identity and `export *` expansion remain P0.8 work.
|
|
48
|
+
- Optional injected counters expose parse, summary, and clone misses to tests and the maintained benchmark without production logging.
|
|
49
|
+
|
|
50
|
+
### Performance
|
|
51
|
+
|
|
52
|
+
- A 100-importer fixture contains 103 unique page/barrel/component/helper modules. The candidate records exactly 103 parses, 103 export summaries, and 200 importer-local clones.
|
|
53
|
+
- Baseline and candidate produce the same 382,603-byte source-result graph and SHA-256 digest.
|
|
54
|
+
- The 21-pair timing run drifted materially on the measurement host. Paired candidate-minus-baseline differences had a +33.324 ms median, with the candidate faster in 9/21 pairs; no material speedup or regression is claimed.
|
|
55
|
+
- A separate matched SSR/native whole-build check measured Kudzu at 856.2 ms, Astro at 1,482.9 ms, Vue at 2,295.3 ms, React at 2,802.0 ms, and Svelte at 2,972.9 ms. Every target passed the same Chrome behavior validation; this is whole-build context, not a module-cache comparison.
|
|
56
|
+
|
|
57
|
+
### Validation
|
|
58
|
+
|
|
59
|
+
- `npm run check`, `npm test`, `npm run test:package`, and all 193 tests pass.
|
|
60
|
+
- Unit coverage verifies cache hits, source-text invalidation, export summaries, cross-session isolation, independent cloned node identity, and clone-local parent links.
|
|
61
|
+
- Existing imported components, re-export cycles, static collections, Zustand, client helpers, Workers, source diagnostics, and generated artifacts remain covered.
|
|
62
|
+
- The repository build emits 149 pages; unaffected static routes remain complete zero-JavaScript documents.
|
|
63
|
+
- P0.8 stable ModuleSymbol and SiteId is next.
|
|
64
|
+
|
|
65
|
+
### Upgrade
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm install @kudzujs/core@^0.8.34
|
|
69
|
+
```
|
|
70
|
+
|
|
3
71
|
## 0.8.33 - Project-scoped compilation
|
|
4
72
|
|
|
5
73
|
Kudzu 0.8.33 completes P0.6 by replacing import-time project globals with an explicit build-scoped ProjectSession. Independent roots can now compile in one Node process without sharing paths, source records, graph resolution, or Worker compiler ownership.
|
|
@@ -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.
|
|
14
|
+
The active post-`0.8.35` implementation sequence is [`large-application-ai-native-roadmap.md`](./large-application-ai-native-roadmap.md). P0.8 stable ModuleSymbol and SiteId is complete; P0.9 semantic state operations is next. 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.
|
|
3
|
+
This maps the current `0.8.35` 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
|
|
|
@@ -8,6 +8,7 @@ This maps the current `0.8.33` architecture built on the completed `0.8.23` Goal
|
|
|
8
8
|
|---|---|---|
|
|
9
9
|
| CLI entry | [`bin/kudzu.mjs`](../../bin/kudzu.mjs) | Dispatches build and development commands. |
|
|
10
10
|
| Project session | [`framework/compiler/project-session.mjs`](../../framework/compiler/project-session.mjs), `createProjectSession()` | Owns one absolute root, standard project paths, source records, bound graph operations, and Worker compiler for a build. Omitted roots resolve from call-time CWD. |
|
|
11
|
+
| Parsed module cache and symbols | [`framework/compiler/project-session.mjs`](../../framework/compiler/project-session.mjs) | Parses each unchanged source module once per ProjectSession and records source-local declaration/import/re-export sites. Stable ModuleSymbol records resolve direct, aliased, barrel, and `export *` exports with cycle and ambiguity checks; repeated resolutions are cached against their source dependencies, and normalization consumers locate the resolved SiteId in a fresh clone with independent parent links. |
|
|
11
12
|
| Build orchestration | [`framework/build.mjs`](../../framework/build.mjs), `build()` | Coordinates config, discovery, source compilation, RouteIR rendering, CapabilityIR projection, generator invocation, artifact emission, and `afterBuild`. |
|
|
12
13
|
| Reachability/import resolution | [`framework/compiler/source-compiler.mjs`](../../framework/compiler/source-compiler.mjs), `reachableSourceFiles()`; [`framework/compiler/source-graph.mjs`](../../framework/compiler/source-graph.mjs), `ordinaryRuntimeDependencies()`, `resolveSourceImport()` | Starts from page entries, follows relative runtime imports/re-exports and validated Worker references, excludes unreachable migration source, and fails unresolved ordinary edges or dynamic imports at the importer source location before code generation. |
|
|
13
14
|
| Ordered normalization | [`framework/compiler/normalization-pipeline.mjs`](../../framework/compiler/normalization-pipeline.mjs), `applyNormalizationPasses()`; [`framework/compiler/source-compiler.mjs`](../../framework/compiler/source-compiler.mjs), `normalizeCompilerSource()` | Applies migration/resource passes in order and repairs TypeScript parent pointers after every structural change. Imported source uses the same pipeline. |
|
|
@@ -16,7 +17,7 @@ This maps the current `0.8.33` architecture built on the completed `0.8.23` Goal
|
|
|
16
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. |
|
|
17
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. |
|
|
18
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. |
|
|
19
|
-
| 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. |
|
|
20
|
+
| 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, source-local SiteIds, and source provenance; AST identity remains private to its source-local session. |
|
|
20
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 deterministic JSON-safe HandlerIR, BindingIR, DerivedIR, KeyedBlockIR, EffectIR, imports, and client roots into ModuleIR and validates every local slot reference. |
|
|
21
22
|
| 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. |
|
|
22
23
|
| 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. |
|
|
@@ -38,7 +39,7 @@ This maps the current `0.8.33` architecture built on the completed `0.8.23` Goal
|
|
|
38
39
|
|
|
39
40
|
```text
|
|
40
41
|
src/pages entries + config
|
|
41
|
-
-> ProjectSession(root) with project paths, source records, graph, and Worker compiler
|
|
42
|
+
-> ProjectSession(root) with project paths, source records, parsed/symbol caches, graph, and Worker compiler
|
|
42
43
|
-> project discovery and reachable relative graph
|
|
43
44
|
-> compileSource()
|
|
44
45
|
-> ordered normalization and parent repair
|
|
@@ -70,8 +71,8 @@ The browser consumes static HTML first. State seeds and descriptors in that HTML
|
|
|
70
71
|
- Transient component rewrite indexes remain source-local AST indexes; handler, binding, derived, keyed, effect, and component ownership now have explicit JSON-safe source results.
|
|
71
72
|
- `build()` still owns explicit artifact selection and filesystem writes after generator results are produced.
|
|
72
73
|
- Runtime generators intentionally specialize readable authored sources through exact anchors; every required anchor fails closed, but a future generator format may remove this transitional dependency.
|
|
73
|
-
- Source reachability and source compilation remain in one session-bound compiler factory because both consume the same normalization and import graph contracts
|
|
74
|
-
- Imported
|
|
74
|
+
- Source reachability and source compilation remain in one session-bound compiler factory because both consume the same normalization and import graph contracts. ModuleSymbol resolution is stable across canonical and cloned trees; unsupported export syntax remains intentionally narrow.
|
|
75
|
+
- Imported declarations resolve by ModuleSymbol and source-local SiteId. Specialized and compiler-synthesized trees still use conservative name/scope fallback where the source-local binding index does not own the complete AST.
|
|
75
76
|
|
|
76
77
|
These are future simplification opportunities, not incomplete Goal A contracts. Goal A changed no source support, browser output semantics, or browser architecture.
|
|
77
78
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Status
|
|
4
4
|
|
|
5
|
-
Active execution plan after `0.8.
|
|
5
|
+
Active execution plan after `0.8.35`. This document turns the current compiler audit into an ordered implementation program. It does not mark any planned capability as supported and does not authorize a React runtime, VDOM, hydration, retained browser component tree, generic rerenderer, public store/query/resource API, SPA router, or islands.
|
|
6
6
|
|
|
7
7
|
[`MIGRATION_ROADMAP.md`](../../MIGRATION_ROADMAP.md) remains authoritative for product invariants and fixture-driven feature selection. This plan is authoritative for the order and completion evidence of compiler generalization, large-application foundations, compatibility boundaries, AI tooling, and scale validation. If implementation evidence changes a boundary, update this document before broadening a patch.
|
|
8
8
|
|
|
@@ -34,7 +34,7 @@ The current limiting architecture is observable in these files:
|
|
|
34
34
|
|
|
35
35
|
| Concern | Current owner | Limitation to remove |
|
|
36
36
|
|---|---|---|
|
|
37
|
-
| Project graph | `framework/compiler/project-session.mjs`, `source-graph.mjs`, `build.mjs` | Explicit build-scoped root and source ownership;
|
|
37
|
+
| Project graph | `framework/compiler/project-session.mjs`, `source-graph.mjs`, `build.mjs` | Explicit build-scoped root, parsed-module/export-summary cache, and source ownership; narrow export resolution and no incremental invalidation remain |
|
|
38
38
|
| Normalization | `framework/compiler/source-compiler.mjs`, focused passes | Pass order and package ownership are implicit; semantically equal source often follows different shape-specific paths |
|
|
39
39
|
| Component/state analysis | `framework/compiler/source-compiler.mjs`, `analysis/component-analysis.mjs` | AST identity, identifier text, source offsets, and caller-side AST specialization remain central |
|
|
40
40
|
| Handler/binding analysis | `framework/compiler/descriptor-session.mjs`, `handler-lowering.mjs` | Capture/import/state discovery is name-based and arbitrary handlers become code before IR finalization |
|
|
@@ -135,7 +135,8 @@ This is an incremental evolution of the current repository:
|
|
|
135
135
|
- [x] P0.4 Async native handler ownership is complete in `0.8.31`. Mounted native registrations invalidate direct/captured setters, queued commits, and captured refs before listener removal. Chrome route, keyed-row, document-disposal, and synchronous event-dispatch checks, all 189 tests, and packed-package smoke pass without cancelling application promises or adding a scheduler.
|
|
136
136
|
- [x] P0.5 Staged and collision-safe output is complete in `0.8.32`. Production output completes in one project-local staging tree, public files are compared against generated route/runtime/handler/chunk/Worker/CSS paths while copying, `afterBuild` runs before rollback-safe promotion, and ordinary failures preserve the prior `dist`. Same-root overlap and stale locks fail closed; after stale-lock removal, an interrupted promotion backup recovers on the next admitted build. Focused collision/replacement/dev checks pass with equivalent `v0.8.31` commerce deploy output; `.kudzu` remains compiler scratch for P0.6.
|
|
137
137
|
- [x] P0.6 Explicit ProjectSession is complete in `0.8.33`. Each build owns an absolute root, project paths, source records, bound graph operations, and Worker compiler. Explicit-root build/dev entry points preserve omitted-root CLI CWD behavior. One imported build function compiles two same-shaped roots with isolated config, HTML, `.kudzu`, source results, and Worker bundles; all 191 tests and package checks pass without browser or source-syntax changes.
|
|
138
|
-
- [
|
|
138
|
+
- [x] P0.7 Parsed module and export summary caching is complete in `0.8.34`. Canonical read-only source trees and narrow export summaries are invalidated together by source text and remain ProjectSession-local; every normalization context receives a deep clone with independent parent links. A 100-importer fixture parses and summarizes 103 unique page/barrel/component/helper modules exactly once and creates 200 importer-local clones. The maintained paired benchmark preserves the complete source-result digest and establishes no material timing or peak-RSS conclusion; all 193 tests and package checks pass without broadening exports, source syntax, or browser output.
|
|
139
|
+
- [x] P0.8 Stable ModuleSymbol and SiteId is complete in `0.8.35`. ProjectSession records source-local declaration, import, and re-export sites and resolves stable ModuleSymbol records through default/named exports, aliases, barrel chains, `export *`, ambiguity, and cycles. Cross-module compiler consumers locate resolved declarations by SiteId in their private normalized clones, while component calls, hooks, keyed blocks, effects, and ownership records expose deterministic source-local SiteIds. Repeated sessions and compilations preserve IDs, and symbol-only barrel traversal avoids cloning intermediate modules without changing browser artifacts or source syntax.
|
|
139
140
|
|
|
140
141
|
### P0: Semantic Correctness And Compiler Foundation
|
|
141
142
|
|
|
@@ -309,6 +310,8 @@ Every result carries a stable source-local binding slot, debug name, declaration
|
|
|
309
310
|
|
|
310
311
|
**Done condition:** each unchanged module is parsed and summarized once per project session; transformed mutable AST is not shared across transformer contexts.
|
|
311
312
|
|
|
313
|
+
**Completed in `0.8.34`:** ProjectSession caches one canonical parsed tree and one supported-export summary per file/source-text pair. Reachability, static collection, Zustand, client-helper, style, layout-diagnostic, and Worker graph reads reuse canonical trees; importer normalization deep-clones every node and repairs clone-local parents. Optional injected counters add no production logging. Unit coverage proves source invalidation and cross-session isolation; 100 pages sharing one barrel component and helper produce 103 parse misses, 103 summary misses, and 200 independent normalization clones.
|
|
314
|
+
|
|
312
315
|
### PR 8: Stable ModuleSymbol And SiteId
|
|
313
316
|
|
|
314
317
|
**Objective:** represent declarations, imports, re-exports, component calls, hooks, and ownership sites independently of transformed AST identity.
|
|
@@ -317,6 +320,8 @@ Every result carries a stable source-local binding slot, debug name, declaration
|
|
|
317
320
|
|
|
318
321
|
**Done condition:** cross-module semantic consumers use ModuleSymbol records and source-local SiteId values; readable names remain diagnostic metadata.
|
|
319
322
|
|
|
323
|
+
**Completed in `0.8.35`:** ProjectSession export summaries now include stable declaration/import/re-export records. ModuleSymbol identity is project-relative module path plus declaration SiteId and excludes readable names; cycle-safe resolution supports direct/default/named/aliased/barrel/`export *` paths and rejects ambiguity. Imported semantic consumers resolve symbols first, then find the authored SiteId in an importer-private normalized clone. Component analysis and ModuleIR ownership records carry deterministic owner, component-call, hook, keyed-list, and effect SiteIds. Focused fixtures verify default/named aliases, barrels, `export *`, cycles, ambiguity, repeated-session determinism, repeated-compilation determinism, and source-dependent resolution-cache invalidation. The 100-importer fixture retains 103 parse and summary misses while removing 100 intermediate barrel clones.
|
|
324
|
+
|
|
320
325
|
### PR 9: Semantic State Operations
|
|
321
326
|
|
|
322
327
|
**Objective:** lower semantically equivalent state updates to the existing structured HandlerIR path.
|
|
@@ -463,4 +468,4 @@ The first comparison is Kudzu versus React + Vite using the same agent, model, t
|
|
|
463
468
|
|
|
464
469
|
## Immediate Decision
|
|
465
470
|
|
|
466
|
-
PR 1 through PR
|
|
471
|
+
PR 1 through PR 8 are complete. The next PR is **PR 9: Semantic State Operations**. 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.
|
|
3
|
+
This is an execution sequence, not release history. `0.8.16` through `0.8.35` 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
|
|
|
@@ -24,6 +24,8 @@ Keep each patch behavior-preserving and independently reviewable. If a boundary
|
|
|
24
24
|
| `0.8.31` | Invalidate pending native-handler contexts when their route, keyed, conditional, or document DOM owner is released. | Late setters, queued commits, and captured refs cannot mutate replacement ownership; synchronous dispatch remains within the performance gate and runtime bytes are recorded. |
|
|
25
25
|
| `0.8.32` | Stage and validate production output before rollback-safe promotion, reject same-root overlap, and tighten keyed browser paths. | Build/hook failures preserve the prior output, stale locks fail closed, interrupted backups recover on the next admitted build after lock removal, public/generated collisions fail, and deploy output remains equivalent. |
|
|
26
26
|
| `0.8.33` | Move root, graph, source records, compiler paths, and Worker ownership into an explicit build-scoped ProjectSession. | Two independent roots compile through one imported build entry without config, source, `.kudzu`, Worker, or output leakage; CLI CWD behavior remains unchanged. |
|
|
27
|
+
| `0.8.34` | Cache canonical parsed modules and narrow export summaries within one ProjectSession while cloning transformer input. | A 100-importer fixture parses and summarizes 103 unique modules once, creates independent normalization trees, preserves the complete source-result digest, and leaks no cache state across projects. |
|
|
28
|
+
| `0.8.35` | Resolve cross-module declarations through stable ModuleSymbol and source-local SiteId records. | Default/named exports, aliases, barrel chains, `export *`, cycles, ambiguity, repeated compilation, source invalidation, private clones, and measured build behavior remain deterministic. |
|
|
27
29
|
|
|
28
30
|
## Sequence Rules
|
|
29
31
|
|
|
@@ -8,7 +8,7 @@ export function createComponentAnalysisSession(analysis) {
|
|
|
8
8
|
function registerOwner(identity, descriptor = {}) {
|
|
9
9
|
let owner = owners.get(identity)
|
|
10
10
|
if (!owner) {
|
|
11
|
-
owner = { slot: analysis.owners.length, kind: descriptor.kind ?? "component", name: descriptor.name ?? "anonymous", props: descriptor.props ?? [], states: [], setters: [], refs: [], ids: [], ...(descriptor.source ? { source: descriptor.source } : {}) }
|
|
11
|
+
owner = { slot: analysis.owners.length, kind: descriptor.kind ?? "component", name: descriptor.name ?? "anonymous", props: descriptor.props ?? [], states: [], setters: [], refs: [], ids: [], ...(descriptor.site ? { site: descriptor.site } : {}), ...(descriptor.source ? { source: descriptor.source } : {}) }
|
|
12
12
|
owners.set(identity, owner)
|
|
13
13
|
analysis.owners.push(owner)
|
|
14
14
|
}
|
|
@@ -20,7 +20,7 @@ export function createComponentAnalysisSession(analysis) {
|
|
|
20
20
|
if (!owner) throw new Error("Component owner must be registered before its state")
|
|
21
21
|
let state = owner.states.find(entry => entry.name === descriptor.name && entry.owner === descriptor.owner)
|
|
22
22
|
if (!state) {
|
|
23
|
-
state = { slot: owner.states.length, name: descriptor.name, kind: descriptor.kind, ...(descriptor.owner ? { owner: descriptor.owner } : {}), ...(descriptor.source ? { source: descriptor.source } : {}) }
|
|
23
|
+
state = { slot: owner.states.length, name: descriptor.name, kind: descriptor.kind, ...(descriptor.owner ? { owner: descriptor.owner } : {}), ...(descriptor.site ? { site: descriptor.site } : {}), ...(descriptor.source ? { source: descriptor.source } : {}) }
|
|
24
24
|
owner.states.push(state)
|
|
25
25
|
}
|
|
26
26
|
if (descriptor.setter && !owner.setters.some(entry => entry.name === descriptor.setter)) owner.setters.push({ name: descriptor.setter, signal: state.slot, kind: descriptor.kind })
|
|
@@ -1,21 +1,185 @@
|
|
|
1
|
-
import { join, resolve } from "node:path"
|
|
1
|
+
import { join, relative, resolve, sep } from "node:path"
|
|
2
|
+
import ts from "typescript"
|
|
3
|
+
import { sourceNodeError } from "./ast-helpers.mjs"
|
|
2
4
|
import { assetPath } from "./path-helpers.mjs"
|
|
3
5
|
import { createSourceGraph } from "./source-graph.mjs"
|
|
4
6
|
import { createWorkerCompiler } from "./worker-compiler.mjs"
|
|
5
7
|
|
|
6
|
-
export function createProjectSession(projectRoot = process.cwd()) {
|
|
8
|
+
export function createProjectSession(projectRoot = process.cwd(), { counters, sourceIndex = new Map() } = {}) {
|
|
7
9
|
const root = resolve(projectRoot)
|
|
8
10
|
const sourceDirectory = join(root, "src")
|
|
9
11
|
const graph = createSourceGraph(root)
|
|
12
|
+
const modules = createModuleCache(root, sourceIndex, graph, counters)
|
|
10
13
|
return {
|
|
11
14
|
root,
|
|
12
15
|
sourceDirectory,
|
|
13
16
|
pagesDirectory: join(sourceDirectory, "pages"),
|
|
14
17
|
workDirectory: join(root, ".kudzu"),
|
|
15
18
|
outputDirectory: join(root, "dist"),
|
|
16
|
-
sourceIndex
|
|
19
|
+
sourceIndex,
|
|
17
20
|
sourceFiles: new Set(),
|
|
18
21
|
graph,
|
|
19
|
-
|
|
22
|
+
modules,
|
|
23
|
+
workerCompiler: createWorkerCompiler({ root, sourceDirectory, assetPath, readSourceModule: modules.read, ...graph })
|
|
20
24
|
}
|
|
21
25
|
}
|
|
26
|
+
|
|
27
|
+
function createModuleCache(root, sourceIndex, graph, counters) {
|
|
28
|
+
const records = new Map()
|
|
29
|
+
let resolvedExports = new WeakMap()
|
|
30
|
+
const moduleName = file => relative(root, file).replaceAll(sep, "/")
|
|
31
|
+
const siteId = (node, role = "site") => {
|
|
32
|
+
const original = ts.getOriginalNode(node)
|
|
33
|
+
return `${role}:${original.getStart(original.getSourceFile())}:${original.end}`
|
|
34
|
+
}
|
|
35
|
+
const moduleSymbol = (file, node, name) => {
|
|
36
|
+
const site = siteId(node, "declaration")
|
|
37
|
+
const module = moduleName(file)
|
|
38
|
+
return { id: `${module}#${site}`, module, site, name }
|
|
39
|
+
}
|
|
40
|
+
const increment = name => {
|
|
41
|
+
if (counters) counters[name] = (counters[name] ?? 0) + 1
|
|
42
|
+
}
|
|
43
|
+
const read = (file, source = sourceIndex.get(file)) => {
|
|
44
|
+
if (typeof source !== "string") throw new Error(`Source text is unavailable for ${file}`)
|
|
45
|
+
const cached = records.get(file)
|
|
46
|
+
if (cached?.source === source) return cached
|
|
47
|
+
const sourceFile = graph.parseSourceFile(file, source)
|
|
48
|
+
increment("parsedModules")
|
|
49
|
+
const summary = summarizeModule(file, sourceFile, moduleSymbol, siteId)
|
|
50
|
+
increment("exportSummaries")
|
|
51
|
+
const record = { source, sourceFile, ...summary }
|
|
52
|
+
records.set(file, record)
|
|
53
|
+
resolvedExports = new WeakMap()
|
|
54
|
+
return record
|
|
55
|
+
}
|
|
56
|
+
const clone = (file, factory, context) => {
|
|
57
|
+
const visit = node => {
|
|
58
|
+
const copy = factory.cloneNode(node)
|
|
59
|
+
ts.setTextRange(copy, node)
|
|
60
|
+
ts.setOriginalNode(copy, node)
|
|
61
|
+
return ts.visitEachChild(copy, visit, context)
|
|
62
|
+
}
|
|
63
|
+
const sourceFile = visit(read(file).sourceFile)
|
|
64
|
+
ts.setParentRecursive(sourceFile, false)
|
|
65
|
+
increment("clonedModules")
|
|
66
|
+
return sourceFile
|
|
67
|
+
}
|
|
68
|
+
const resolveExport = (file, exportName, sourceFiles) => {
|
|
69
|
+
let cached = resolvedExports.get(sourceFiles)
|
|
70
|
+
const cacheKey = `${file}:${exportName}`
|
|
71
|
+
const cachedResolution = cached?.get(cacheKey)
|
|
72
|
+
if (cachedResolution?.dependencies.every(([target, source]) => sourceIndex.get(target) === source)) return cachedResolution.symbol
|
|
73
|
+
const dependencies = new Set()
|
|
74
|
+
const resolveEntry = (currentFile, name, trail) => {
|
|
75
|
+
const key = `${currentFile}:${name}`
|
|
76
|
+
if (trail.includes(key)) return { cycle: [...trail, key] }
|
|
77
|
+
const record = read(currentFile)
|
|
78
|
+
dependencies.add(currentFile)
|
|
79
|
+
const entry = record.exports.get(name)
|
|
80
|
+
const nextTrail = [...trail, key]
|
|
81
|
+
if (entry?.kind === "local") {
|
|
82
|
+
const symbol = entry.symbol ?? record.declarations.get(entry.local)
|
|
83
|
+
return symbol ? { symbol } : {}
|
|
84
|
+
}
|
|
85
|
+
if (entry?.kind === "reexport") {
|
|
86
|
+
if (!entry.specifier.startsWith(".")) throw sourceNodeError(entry.node, record.sourceFile, "Imported keyed list components must use relative TypeScript re-exports")
|
|
87
|
+
return resolveEntry(graph.resolveSourceImport(currentFile, entry.specifier, sourceFiles), entry.imported, nextTrail)
|
|
88
|
+
}
|
|
89
|
+
if (name === "default") return {}
|
|
90
|
+
const matches = new Map()
|
|
91
|
+
let cycle
|
|
92
|
+
for (const star of record.exportStars) {
|
|
93
|
+
if (!star.specifier.startsWith(".")) continue
|
|
94
|
+
const result = resolveEntry(graph.resolveSourceImport(currentFile, star.specifier, sourceFiles), name, nextTrail)
|
|
95
|
+
if (result.symbol) matches.set(result.symbol.id, result.symbol)
|
|
96
|
+
else cycle ??= result.cycle
|
|
97
|
+
}
|
|
98
|
+
if (matches.size > 1) throw new Error(`${moduleName(currentFile)} has an ambiguous export named ${JSON.stringify(name)}`)
|
|
99
|
+
return matches.size ? { symbol: matches.values().next().value } : cycle ? { cycle } : {}
|
|
100
|
+
}
|
|
101
|
+
const result = resolveEntry(file, exportName, [])
|
|
102
|
+
if (result.cycle) throw new Error(`Imported keyed list component re-export cycle: ${result.cycle.map(entry => moduleName(entry.slice(0, entry.lastIndexOf(":")))).join(" -> ")}`)
|
|
103
|
+
cached = resolvedExports.get(sourceFiles) ?? new Map()
|
|
104
|
+
cached.set(cacheKey, { symbol: result.symbol, dependencies: [...dependencies].map(target => [target, sourceIndex.get(target)]) })
|
|
105
|
+
resolvedExports.set(sourceFiles, cached)
|
|
106
|
+
return result.symbol
|
|
107
|
+
}
|
|
108
|
+
const declaration = (symbol, sourceFile) => {
|
|
109
|
+
const file = resolve(root, symbol.module)
|
|
110
|
+
const record = read(file)
|
|
111
|
+
if (!sourceFile || sourceFile === record.sourceFile) return record.declarationSites.get(symbol.site)
|
|
112
|
+
for (const statement of sourceFile.statements) {
|
|
113
|
+
if (ts.isFunctionDeclaration(statement) && siteId(statement, "declaration") === symbol.site) return statement
|
|
114
|
+
if (ts.isVariableStatement(statement)) for (const entry of statement.declarationList.declarations) {
|
|
115
|
+
if (siteId(entry, "declaration") === symbol.site) return entry
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return { clone, declaration, read, resolveExport, siteId, symbol: moduleSymbol }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function summarizeModule(file, sourceFile, moduleSymbol, siteId) {
|
|
123
|
+
const exports = new Map()
|
|
124
|
+
const imports = new Map()
|
|
125
|
+
const declarations = new Map()
|
|
126
|
+
const declarationSites = new Map()
|
|
127
|
+
const exportStars = []
|
|
128
|
+
const add = (name, value) => {
|
|
129
|
+
if (!exports.has(name)) exports.set(name, value)
|
|
130
|
+
}
|
|
131
|
+
const declare = (name, node) => {
|
|
132
|
+
const symbol = moduleSymbol(file, node, name)
|
|
133
|
+
if (!declarations.has(name)) declarations.set(name, symbol)
|
|
134
|
+
declarationSites.set(symbol.site, node)
|
|
135
|
+
return symbol
|
|
136
|
+
}
|
|
137
|
+
for (const statement of sourceFile.statements) {
|
|
138
|
+
if (ts.isImportDeclaration(statement) && !statement.importClause?.isTypeOnly && ts.isStringLiteral(statement.moduleSpecifier)) {
|
|
139
|
+
const specifier = statement.moduleSpecifier.text
|
|
140
|
+
const clause = statement.importClause
|
|
141
|
+
if (clause?.name) imports.set(clause.name.text, { kind: "default", imported: "default", specifier, site: siteId(clause.name, "import") })
|
|
142
|
+
if (clause?.namedBindings && ts.isNamespaceImport(clause.namedBindings)) imports.set(clause.namedBindings.name.text, { kind: "namespace", imported: "*", specifier, site: siteId(clause.namedBindings, "import") })
|
|
143
|
+
if (clause?.namedBindings && ts.isNamedImports(clause.namedBindings)) for (const element of clause.namedBindings.elements) {
|
|
144
|
+
if (!element.isTypeOnly) imports.set(element.name.text, { kind: "named", imported: (element.propertyName ?? element.name).text, specifier, site: siteId(element, "import") })
|
|
145
|
+
}
|
|
146
|
+
continue
|
|
147
|
+
}
|
|
148
|
+
if (ts.isFunctionDeclaration(statement)) {
|
|
149
|
+
const exported = statement.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword)
|
|
150
|
+
const isDefault = statement.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword)
|
|
151
|
+
const name = statement.name?.text ?? (isDefault ? "default" : undefined)
|
|
152
|
+
const symbol = name ? declare(name, statement) : undefined
|
|
153
|
+
if (isDefault && symbol) add("default", { kind: "local", symbol })
|
|
154
|
+
if (exported && !isDefault && statement.name && symbol) add(statement.name.text, { kind: "local", local: statement.name.text, symbol })
|
|
155
|
+
continue
|
|
156
|
+
}
|
|
157
|
+
if (ts.isVariableStatement(statement)) {
|
|
158
|
+
const exported = statement.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword)
|
|
159
|
+
for (const declaration of statement.declarationList.declarations) if (ts.isIdentifier(declaration.name)) {
|
|
160
|
+
const symbol = declare(declaration.name.text, declaration)
|
|
161
|
+
if (exported) add(declaration.name.text, { kind: "local", local: declaration.name.text, symbol })
|
|
162
|
+
}
|
|
163
|
+
continue
|
|
164
|
+
}
|
|
165
|
+
if (ts.isExportAssignment(statement) && !statement.isExportEquals && ts.isIdentifier(statement.expression)) {
|
|
166
|
+
add("default", { kind: "local", local: statement.expression.text })
|
|
167
|
+
continue
|
|
168
|
+
}
|
|
169
|
+
if (!ts.isExportDeclaration(statement)) continue
|
|
170
|
+
if (!statement.exportClause && statement.moduleSpecifier && ts.isStringLiteral(statement.moduleSpecifier)) {
|
|
171
|
+
exportStars.push({ specifier: statement.moduleSpecifier.text, site: siteId(statement, "reexport") })
|
|
172
|
+
continue
|
|
173
|
+
}
|
|
174
|
+
if (!statement.exportClause || !ts.isNamedExports(statement.exportClause)) continue
|
|
175
|
+
for (const element of statement.exportClause.elements) {
|
|
176
|
+
if (element.isTypeOnly) continue
|
|
177
|
+
const name = element.name.text
|
|
178
|
+
const imported = (element.propertyName ?? element.name).text
|
|
179
|
+
add(name, statement.moduleSpecifier && ts.isStringLiteral(statement.moduleSpecifier)
|
|
180
|
+
? { kind: "reexport", imported, specifier: statement.moduleSpecifier.text, site: siteId(element, "reexport"), node: statement }
|
|
181
|
+
: { kind: "local", local: imported })
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return { declarations, declarationSites, exports, exportStars, imports }
|
|
185
|
+
}
|
|
@@ -22,8 +22,9 @@ 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 } = project
|
|
26
|
-
const { ordinaryRuntimeDependencies,
|
|
25
|
+
const { root, sourceDirectory, pagesDirectory, workDirectory, workerCompiler, modules } = project
|
|
26
|
+
const { ordinaryRuntimeDependencies, resolveSourceImport, runtimeModuleReference } = project.graph
|
|
27
|
+
const parseSourceFile = (file, source) => modules.read(file, source).sourceFile
|
|
27
28
|
const staticAssetExtensions = new Set([".avif", ".gif", ".ico", ".jpeg", ".jpg", ".otf", ".png", ".svg", ".ttf", ".webp", ".woff", ".woff2"])
|
|
28
29
|
|
|
29
30
|
function compileSource(file, sourceFiles, sourceIndex, staticFiles, cssModules, base) {
|
|
@@ -282,7 +283,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
282
283
|
const importedSource = target => {
|
|
283
284
|
let result = importedSourceCache.get(target)
|
|
284
285
|
if (!result) {
|
|
285
|
-
result = normalizeCompilerSource(
|
|
286
|
+
result = normalizeCompilerSource(modules.clone(target, context.factory, context), { base, context, file: target, sourceFiles, sourceIndex })
|
|
286
287
|
importedSourceCache.set(target, result)
|
|
287
288
|
}
|
|
288
289
|
return result.sourceFile
|
|
@@ -323,6 +324,10 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
323
324
|
const original = ts.getOriginalNode(node)
|
|
324
325
|
return original.pos >= 0 && original.end >= 0 ? { file: sourceName(original.getSourceFile()), start: original.getStart(), end: original.end } : undefined
|
|
325
326
|
}
|
|
327
|
+
const analysisSite = (node, role) => {
|
|
328
|
+
const original = ts.getOriginalNode(node)
|
|
329
|
+
return original.pos >= 0 && original.end >= 0 ? modules.siteId(original, role) : undefined
|
|
330
|
+
}
|
|
326
331
|
const analyzedProps = owner => {
|
|
327
332
|
if (owner.parameters.length !== 1 || !ts.isObjectBindingPattern(owner.parameters[0].name)) return []
|
|
328
333
|
return owner.parameters[0].name.elements.map(element => ({
|
|
@@ -333,14 +338,14 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
333
338
|
}))
|
|
334
339
|
}
|
|
335
340
|
const ownerName = owner => owner.name?.text ?? (ts.isVariableDeclaration(owner.parent) && ts.isIdentifier(owner.parent.name) ? owner.parent.name.text : "anonymous")
|
|
336
|
-
const ensureOwner = (owner, kind = "component") => componentAnalysis.registerOwner(owner, { kind, name: ownerName(owner), props: analyzedProps(owner), source: analysisSource(owner) })
|
|
341
|
+
const ensureOwner = (owner, kind = "component") => componentAnalysis.registerOwner(owner, { kind, name: ownerName(owner), props: analyzedProps(owner), site: analysisSite(owner, "owner"), source: analysisSource(owner) })
|
|
337
342
|
const registerState = (owner, state, setter, kind, node, externalOwner) => {
|
|
338
343
|
const ownerRecord = ensureOwner(owner)
|
|
339
344
|
const stateOwner = externalOwner ?? `owner:${ownerRecord.slot}`
|
|
340
345
|
const stateOwners = stateOwnersByFunction.get(owner) ?? new Map()
|
|
341
346
|
stateOwners.set(state, stateOwner)
|
|
342
347
|
stateOwnersByFunction.set(owner, stateOwners)
|
|
343
|
-
return componentAnalysis.registerState(owner, { name: state, setter, kind, ...(externalOwner ? { owner: externalOwner } : {}), source: analysisSource(node) })
|
|
348
|
+
return componentAnalysis.registerState(owner, { name: state, setter, kind, ...(externalOwner ? { owner: externalOwner } : {}), site: analysisSite(node, "hook"), source: analysisSource(node) })
|
|
344
349
|
}
|
|
345
350
|
const stateOwnersForNode = node => {
|
|
346
351
|
for (let current = node.parent; current; current = current.parent) {
|
|
@@ -642,12 +647,12 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
642
647
|
if (!nullInitializer && owner.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword)) throw sourceNodeError(node.initializer, sourceFile, "Mutable value useRef() is unsupported except for an effect-owned useRef(0) animation-frame handle; otherwise keep resource-private mutable values inside the owning effect")
|
|
643
648
|
if (nullInitializer) {
|
|
644
649
|
ensureOwner(owner)
|
|
645
|
-
componentAnalysis.registerRef(owner, { name: node.name.text, source: analysisSource(node) })
|
|
650
|
+
componentAnalysis.registerRef(owner, { name: node.name.text, site: analysisSite(node, "hook"), source: analysisSource(node) })
|
|
646
651
|
}
|
|
647
652
|
}
|
|
648
653
|
if (owner && node.initializer.expression.text === "useId" && node.initializer.arguments.length === 0) {
|
|
649
654
|
ensureOwner(owner)
|
|
650
|
-
componentAnalysis.registerId(owner, { name: node.name.text, source: analysisSource(node) })
|
|
655
|
+
componentAnalysis.registerId(owner, { name: node.name.text, site: analysisSite(node, "hook"), source: analysisSource(node) })
|
|
651
656
|
}
|
|
652
657
|
}
|
|
653
658
|
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && ts.isCallExpression(node.initializer) && ts.isIdentifier(node.initializer.expression) && node.initializer.expression.text === "createContext") contexts.add(node.name.text)
|
|
@@ -833,6 +838,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
833
838
|
result.analysis = componentAnalysis.registerSpecialization({
|
|
834
839
|
kind: label,
|
|
835
840
|
...(owner ? { owner: ensureOwner(owner).slot } : {}),
|
|
841
|
+
...(analysisSite(call, "component-call") ? { site: analysisSite(call, "component-call") } : {}),
|
|
836
842
|
...(analysisSource(call) ? { source: analysisSource(call) } : {}),
|
|
837
843
|
props: result.props.map(prop => {
|
|
838
844
|
const expression = result.propExpressions.get(prop.name)
|
|
@@ -840,11 +846,11 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
840
846
|
return { ...prop, ...(signals.length ? { signals } : {}) }
|
|
841
847
|
}),
|
|
842
848
|
states: [
|
|
843
|
-
...result.rowStates.map(({ state, setter, source }) => ({ name: state, setter, kind: "row", ...(analysisSource(source) ? { source: analysisSource(source) } : {}) })),
|
|
844
|
-
...result.ordinaryStates.map(({ state, setter, source }) => ({ name: state, setter, kind: "component", ...(analysisSource(source) ? { source: analysisSource(source) } : {}) }))
|
|
849
|
+
...result.rowStates.map(({ state, setter, source }) => ({ name: state, setter, kind: "row", ...(analysisSite(source, "hook") ? { site: analysisSite(source, "hook") } : {}), ...(analysisSource(source) ? { source: analysisSource(source) } : {}) })),
|
|
850
|
+
...result.ordinaryStates.map(({ state, setter, source }) => ({ name: state, setter, kind: "component", ...(analysisSite(source, "hook") ? { site: analysisSite(source, "hook") } : {}), ...(analysisSource(source) ? { source: analysisSource(source) } : {}) }))
|
|
845
851
|
],
|
|
846
|
-
refs: [...result.rowRefs.map(({ name, source }) => ({ name, kind: "row", ...(analysisSource(source) ? { source: analysisSource(source) } : {}) })), ...result.ordinaryRefs.map(({ name, source }) => ({ name, kind: "component", ...(analysisSource(source) ? { source: analysisSource(source) } : {}) }))],
|
|
847
|
-
ids: result.ordinaryIds.map(({ name, source }) => ({ name, ...(analysisSource(source) ? { source: analysisSource(source) } : {}) }))
|
|
852
|
+
refs: [...result.rowRefs.map(({ name, source }) => ({ name, kind: "row", ...(analysisSite(source, "hook") ? { site: analysisSite(source, "hook") } : {}), ...(analysisSource(source) ? { source: analysisSource(source) } : {}) })), ...result.ordinaryRefs.map(({ name, source }) => ({ name, kind: "component", ...(analysisSite(source, "hook") ? { site: analysisSite(source, "hook") } : {}), ...(analysisSource(source) ? { source: analysisSource(source) } : {}) }))],
|
|
853
|
+
ids: result.ordinaryIds.map(({ name, source }) => ({ name, ...(analysisSite(source, "hook") ? { site: analysisSite(source, "hook") } : {}), ...(analysisSource(source) ? { source: analysisSource(source) } : {}) }))
|
|
848
854
|
})
|
|
849
855
|
for (const state of [...result.rowStates, ...result.ordinaryStates]) state.analysisOwner = `specialization:${result.analysis.slot}`
|
|
850
856
|
for (const ref of [...result.rowRefs, ...result.ordinaryRefs]) ref.analysisOwner = `specialization:${result.analysis.slot}`
|
|
@@ -1426,6 +1432,7 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1426
1432
|
const rowRefs = (listParts.rowRefs ?? []).map(ref => ({ name: ref.name, owner: ref.analysisOwner, ...(analysisSource(ref.source) ? { source: analysisSource(ref.source) } : {}) }))
|
|
1427
1433
|
const specializations = [...new Set([...(listParts.specializations ?? []), ...rowStates.map(state => state.owner), ...rowRefs.map(ref => ref.owner)].filter(value => value !== undefined).map(value => typeof value === "string" ? Number(value.slice(value.lastIndexOf(":") + 1)) : value))]
|
|
1428
1434
|
const block = descriptors.registerKeyedBlock({
|
|
1435
|
+
...(analysisSite(node, "keyed-list") ? { site: analysisSite(node, "keyed-list") } : {}),
|
|
1429
1436
|
...(analysisSource(node) ? { source: analysisSource(node) } : {}),
|
|
1430
1437
|
...(parent ? { parent: parent.slot } : {}),
|
|
1431
1438
|
children: [],
|
|
@@ -1606,9 +1613,10 @@ function createKudzuTransformer({ semantic, handlerUrl, file, sourceFiles, sourc
|
|
|
1606
1613
|
ownership: {
|
|
1607
1614
|
kind: activeKeyedBlock ? "keyed" : "component",
|
|
1608
1615
|
...(activeKeyedBlock ? { keyedBlock: activeKeyedBlock.block.slot } : {}),
|
|
1609
|
-
...(lexicalOwner ? { component: { name: ownerName(lexicalOwner), ...(analysisSource(lexicalOwner) ? { source: analysisSource(lexicalOwner) } : {}) } } : {})
|
|
1616
|
+
...(lexicalOwner ? { component: { name: ownerName(lexicalOwner), ...(analysisSite(lexicalOwner, "owner") ? { site: analysisSite(lexicalOwner, "owner") } : {}), ...(analysisSource(lexicalOwner) ? { source: analysisSource(lexicalOwner) } : {}) } } : {})
|
|
1610
1617
|
},
|
|
1611
1618
|
workers,
|
|
1619
|
+
...(analysisSite(effectSource, "hook") ? { site: analysisSite(effectSource, "hook") } : {}),
|
|
1612
1620
|
...(analysisSource(effectSource) ? { source: analysisSource(effectSource) } : {})
|
|
1613
1621
|
})
|
|
1614
1622
|
const dependencyExpressions = effect.dependencies.map(dependency => dependency.kind === "derived" ? moduleIR.derived[dependency.derived].expression : ["state", dependency.name])
|
|
@@ -2718,39 +2726,12 @@ function normalizeImportedStaticCollections(sourceFile, collections, factory, co
|
|
|
2718
2726
|
return ts.visitNode(sourceFile, visitor)
|
|
2719
2727
|
}
|
|
2720
2728
|
|
|
2721
|
-
function resolveComponentExport(file, exportName, getSource, sourceFiles
|
|
2722
|
-
const
|
|
2723
|
-
|
|
2724
|
-
const
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
for (const statement of sourceFile.statements) {
|
|
2728
|
-
if (ts.isFunctionDeclaration(statement)) {
|
|
2729
|
-
const isDefault = statement.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.DefaultKeyword)
|
|
2730
|
-
const isExported = statement.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword)
|
|
2731
|
-
if (exportName === "default" && isDefault || exportName !== "default" && isExported && statement.name?.text === exportName) return statement
|
|
2732
|
-
}
|
|
2733
|
-
if (ts.isVariableStatement(statement) && statement.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword) && exportName !== "default") {
|
|
2734
|
-
const declaration = statement.declarationList.declarations.find(entry => ts.isIdentifier(entry.name) && entry.name.text === exportName)
|
|
2735
|
-
if (declaration?.initializer && (ts.isArrowFunction(declaration.initializer) || ts.isFunctionExpression(declaration.initializer))) return declaration.initializer
|
|
2736
|
-
}
|
|
2737
|
-
if (exportName === "default" && ts.isExportAssignment(statement) && !statement.isExportEquals && ts.isIdentifier(statement.expression)) {
|
|
2738
|
-
const component = localComponentDeclaration(sourceFile, statement.expression.text)
|
|
2739
|
-
if (component) return component
|
|
2740
|
-
}
|
|
2741
|
-
if (ts.isExportDeclaration(statement) && ts.isNamedExports(statement.exportClause)) {
|
|
2742
|
-
const entry = statement.exportClause.elements.find(element => !element.isTypeOnly && element.name.text === exportName)
|
|
2743
|
-
if (!entry) continue
|
|
2744
|
-
const imported = (entry.propertyName ?? entry.name).text
|
|
2745
|
-
if (statement.moduleSpecifier && ts.isStringLiteral(statement.moduleSpecifier)) {
|
|
2746
|
-
if (!statement.moduleSpecifier.text.startsWith(".")) throw sourceNodeError(statement, sourceFile, "Imported keyed list components must use relative TypeScript re-exports")
|
|
2747
|
-
const target = resolveSourceImport(file, statement.moduleSpecifier.text, sourceFiles)
|
|
2748
|
-
return resolveComponentExport(target, imported, getSource, sourceFiles, nextTrail)
|
|
2749
|
-
}
|
|
2750
|
-
const component = localComponentDeclaration(sourceFile, imported)
|
|
2751
|
-
if (component) return component
|
|
2752
|
-
}
|
|
2753
|
-
}
|
|
2729
|
+
function resolveComponentExport(file, exportName, getSource, sourceFiles) {
|
|
2730
|
+
const symbol = modules.resolveExport(file, exportName, sourceFiles)
|
|
2731
|
+
const target = symbol && resolve(root, symbol.module)
|
|
2732
|
+
const declaration = symbol && modules.declaration(symbol, getSource(target))
|
|
2733
|
+
if (declaration && ts.isFunctionDeclaration(declaration)) return declaration
|
|
2734
|
+
if (declaration && ts.isVariableDeclaration(declaration) && declaration.initializer && (ts.isArrowFunction(declaration.initializer) || ts.isFunctionExpression(declaration.initializer))) return declaration.initializer
|
|
2754
2735
|
throw new Error(`${relative(root, file)} does not export a statically analyzable keyed list component named ${JSON.stringify(exportName)}`)
|
|
2755
2736
|
}
|
|
2756
2737
|
|
|
@@ -2771,8 +2752,8 @@ async function collectClientModules(entries, sourceFiles) {
|
|
|
2771
2752
|
while (queue.length) {
|
|
2772
2753
|
const file = queue.shift()
|
|
2773
2754
|
if (modules.has(file)) continue
|
|
2774
|
-
const source = await readFile(file, "utf8")
|
|
2775
|
-
const sourceFile =
|
|
2755
|
+
const source = project.sourceIndex.get(file) ?? await readFile(file, "utf8")
|
|
2756
|
+
const sourceFile = project.modules.read(file, source).sourceFile
|
|
2776
2757
|
workerCompiler.rejectConstructions(sourceFile, sourceFile, "Relative TypeScript Worker construction is only supported directly inside an inline useEffect() callback, not imported client helpers")
|
|
2777
2758
|
if (containsJsx(sourceFile)) throw new Error(`${relative(root, file)} Imported client helpers must not contain JSX`)
|
|
2778
2759
|
rejectUnsupportedClientImports(sourceFile, file)
|
|
@@ -2974,12 +2955,12 @@ const normalizeReactRouterSyntax = createRouterPass({ withBase })
|
|
|
2974
2955
|
return { collectClientModules, compileClientModule, compiledPath, compileSource, layoutExportError, orderSourceStyles, reachableSourceFiles, safeStaticFiles }
|
|
2975
2956
|
}
|
|
2976
2957
|
|
|
2977
|
-
const currentCompiler =
|
|
2958
|
+
const currentCompiler = sourceIndex => createSourceCompiler(createProjectSession(process.cwd(), { sourceIndex }))
|
|
2978
2959
|
export const collectClientModules = (...arguments_) => currentCompiler().collectClientModules(...arguments_)
|
|
2979
2960
|
export const compileClientModule = (...arguments_) => currentCompiler().compileClientModule(...arguments_)
|
|
2980
2961
|
export const compiledPath = (...arguments_) => currentCompiler().compiledPath(...arguments_)
|
|
2981
|
-
export const compileSource = (...arguments_) => currentCompiler().compileSource(...arguments_)
|
|
2982
|
-
export const layoutExportError = (...arguments_) => currentCompiler().layoutExportError(...arguments_)
|
|
2983
|
-
export const orderSourceStyles = (...arguments_) => currentCompiler().orderSourceStyles(...arguments_)
|
|
2984
|
-
export const reachableSourceFiles = (...arguments_) => currentCompiler().reachableSourceFiles(...arguments_)
|
|
2962
|
+
export const compileSource = (...arguments_) => currentCompiler(arguments_[2]).compileSource(...arguments_)
|
|
2963
|
+
export const layoutExportError = (...arguments_) => currentCompiler(new Map([[arguments_[0], arguments_[1]]])).layoutExportError(...arguments_)
|
|
2964
|
+
export const orderSourceStyles = (...arguments_) => currentCompiler(arguments_[2]).orderSourceStyles(...arguments_)
|
|
2965
|
+
export const reachableSourceFiles = (...arguments_) => currentCompiler(arguments_[2]).reachableSourceFiles(...arguments_)
|
|
2985
2966
|
export const safeStaticFiles = (...arguments_) => currentCompiler().safeStaticFiles(...arguments_)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto"
|
|
2
|
-
import { mkdir
|
|
2
|
+
import { mkdir } from "node:fs/promises"
|
|
3
3
|
import { dirname, relative, resolve, sep } from "node:path"
|
|
4
4
|
import { build as bundle } from "esbuild"
|
|
5
5
|
import ts from "typescript"
|
|
@@ -9,7 +9,7 @@ export function createWorkerCompiler({
|
|
|
9
9
|
root,
|
|
10
10
|
sourceDirectory,
|
|
11
11
|
assetPath,
|
|
12
|
-
|
|
12
|
+
readSourceModule,
|
|
13
13
|
resolveSourceImport,
|
|
14
14
|
runtimeModuleReference
|
|
15
15
|
}) {
|
|
@@ -106,7 +106,7 @@ export function createWorkerCompiler({
|
|
|
106
106
|
const file = queue.shift()
|
|
107
107
|
if (visited.has(file)) continue
|
|
108
108
|
visited.add(file)
|
|
109
|
-
const sourceFile =
|
|
109
|
+
const sourceFile = readSourceModule(file).sourceFile
|
|
110
110
|
if (containsJsx(sourceFile)) throw sourceNodeError(sourceFile, sourceFile, "Worker modules must not contain JSX")
|
|
111
111
|
const visit = node => {
|
|
112
112
|
if (ts.isImportEqualsDeclaration(node)) throw sourceNodeError(node, sourceFile, "TypeScript import-equals declarations are not supported in Worker modules; use a relative ESM import")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kudzujs/core",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.35",
|
|
4
4
|
"description": "HTML-first TSX framework with synchronous state semantics and no virtual DOM",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"benchmark": "node test/performance.mjs",
|
|
59
59
|
"benchmark:keyed": "node test/keyed-performance.mjs",
|
|
60
60
|
"benchmark:commerce": "node test/commerce-build-performance.mjs",
|
|
61
|
+
"benchmark:module-cache": "node test/module-cache-performance.mjs",
|
|
61
62
|
"benchmark:native": "node test/native-performance.mjs",
|
|
62
63
|
"prepublishOnly": "npm run check && npm test",
|
|
63
64
|
"deploy": "wrangler deploy",
|