sinwan-compiler 0.0.0 → 0.2.0

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.
@@ -0,0 +1,75 @@
1
+ # Architecture
2
+
3
+ ## File layout
4
+
5
+ ```
6
+ src/
7
+ index.ts Public exports
8
+ transform.ts JSX transform and template hoisting
9
+ reactive-wrap.ts Reactive source detection, call graph, propagation
10
+ analyze.ts Cross-file analyzer, cache, import resolution, CLI
11
+ cli.ts CLI entry point
12
+ ```
13
+
14
+ ## Data flow
15
+
16
+ 1. **Transform**: receives source code and optional reactive-prop metadata.
17
+ 2. **Analyzer (dev)**: builds `AnalyzerCache` incrementally as files are transformed.
18
+ 3. **Analyzer (production)**: `analyzeProject()` scans the whole project and writes metadata.
19
+ 4. **Propagation**: `propagateReactiveProps()` runs a fixed-point algorithm over the call graph.
20
+
21
+ ## Key data structures
22
+
23
+ ### `CallSite`
24
+
25
+ ```ts
26
+ {
27
+ callee: t.Function;
28
+ props: { name: string; value: t.Expression }[];
29
+ spreads: t.Expression[];
30
+ }
31
+ ```
32
+
33
+ `spreads` stores the spread expressions. Object-literal spreads are analyzed precisely; unknown spreads are treated conservatively.
34
+
35
+ ### `ModuleAnalysis`
36
+
37
+ Per-file analysis including:
38
+
39
+ - imports
40
+ - exports
41
+ - default export
42
+ - component functions
43
+ - local scopes
44
+ - local call graph
45
+ - imported call sites
46
+
47
+ ### `AnalyzerCache`
48
+
49
+ - Stores `ModuleAnalysis` objects keyed by absolute file path.
50
+ - Maintains `reactiveProps: Map<filePath, Map<exportName, Set<propName>>>`.
51
+ - Maintains a reverse importer index for targeted incremental propagation.
52
+ - Serializes to JSON for persistence across process restarts.
53
+
54
+ ## Propagation algorithm
55
+
56
+ 1. Initialize every exported component with an empty reactive-prop set.
57
+ 2. For each component function, build a full scope that includes its own bindings and the reactive props of its parent.
58
+ 3. For each call site, check every named prop and object-literal spread key. If the value contains a reactive read, add the prop name to the callee's reactive set.
59
+ 4. For unknown spreads, add all known callee prop names.
60
+ 5. If a callee's reactive set grows, add the callee to the worklist and repeat until stable.
61
+
62
+ ## Import resolution
63
+
64
+ The resolver combines multiple sources:
65
+
66
+ - `tsconfig.json` `paths`
67
+ - `bunfig.toml` aliases
68
+ - Relative imports
69
+ - Custom resolver
70
+
71
+ Resolution order: tsconfig → bunfig → relative → custom.
72
+
73
+ ## Serialization
74
+
75
+ The cache serializes modules using function start/end positions as stable identifiers. When restoring, the source file is re-parsed and functions are matched by position. This allows the cache to survive source edits as long as the analyzed functions remain at the same positions.
package/docs/cli.md ADDED
@@ -0,0 +1,46 @@
1
+ # CLI
2
+
3
+ The `sinwan-compiler` package ships a small CLI for production analysis.
4
+
5
+ ## Command
6
+
7
+ ```bash
8
+ bunx sinwan-compiler analyze [root] [outFile] [options]
9
+ ```
10
+
11
+ - `root` — project root to scan (default: current working directory).
12
+ - `outFile` — path for the output JSON (default: `<root>/.sinwan/reactive-props.json`).
13
+
14
+ ## Options
15
+
16
+ | Flag | Description |
17
+ | -------------------- | --------------------------------------------------------------------------------- |
18
+ | `--tsconfig`, `-t` | Path to `tsconfig.json` for path alias resolution. |
19
+ | `--bunfig`, `-b` | Path to `bunfig.toml` for Bun alias resolution. |
20
+ | `--workspaces`, `-w` | Path to a `package.json` or `pnpm-workspace.yaml` to discover workspace packages. |
21
+
22
+ ## Example
23
+
24
+ ```bash
25
+ bunx sinwan-compiler analyze ./src ./.sinwan/reactive-props.json --tsconfig ./tsconfig.json --workspaces ../../package.json
26
+ ```
27
+
28
+ ## Output format
29
+
30
+ The CLI writes a JSON file mapping each source file to its exported component names and their reactive props:
31
+
32
+ ```json
33
+ {
34
+ "/project/src/Child.tsx": {
35
+ "Child": ["title"]
36
+ }
37
+ }
38
+ ```
39
+
40
+ ## Use in plugins
41
+
42
+ Pass the generated JSON to the Bun or Vite plugin via the `analyze` option:
43
+
44
+ ```ts
45
+ sinwan({ analyze: "./.sinwan/reactive-props.json" });
46
+ ```
@@ -0,0 +1,60 @@
1
+ # Bun and Vite plugins
2
+
3
+ Both plugins use the shared compiler package. The cache is optional and enabled by passing the `cache` option.
4
+
5
+ ## Bun plugin
6
+
7
+ ```ts
8
+ import { sinwan } from "bun-plugin-sinwan";
9
+
10
+ Bun.build({
11
+ entrypoints: ["./src/index.tsx"],
12
+ plugins: [
13
+ sinwan({
14
+ hoist: true,
15
+ cache: {
16
+ root: "./src",
17
+ tsConfigPath: "./tsconfig.json",
18
+ bunfigPath: "./bunfig.toml",
19
+ workspaces: "../../package.json",
20
+ cachePath: "./.sinwan/cache.json",
21
+ },
22
+ }),
23
+ ],
24
+ });
25
+ ```
26
+
27
+ ## Vite plugin
28
+
29
+ ```ts
30
+ import { sinwan } from "vite-plugin-sinwan";
31
+
32
+ export default {
33
+ plugins: [
34
+ sinwan({
35
+ hoist: true,
36
+ cache: {
37
+ tsConfigPath: "./tsconfig.json",
38
+ bunfigPath: "./bunfig.toml",
39
+ workspaces: "../../package.json",
40
+ cachePath: "./.sinwan/cache.json",
41
+ },
42
+ }),
43
+ ],
44
+ };
45
+ ```
46
+
47
+ ## Cache options
48
+
49
+ | Option | Description |
50
+ | -------------- | ----------------------------------------------------------------------------------------------------- |
51
+ | `root` | Project root for the analyzer. |
52
+ | `tsConfigPath` | Path to `tsconfig.json`. |
53
+ | `bunfigPath` | Path to `bunfig.toml`. If omitted, the plugin tries to auto-detect `bunfig.toml` in the project root. |
54
+ | `workspaces` | Path to a workspace file (`package.json` or `pnpm-workspace.yaml`) or explicit package paths/globs. |
55
+ | `cachePath` | Path to a JSON file for persistent cache. |
56
+
57
+ ## Dev vs production
58
+
59
+ - In **dev**, enable the `cache` option for incremental HMR analysis.
60
+ - In **production**, run the CLI ahead of time and pass the generated JSON via the `analyze` option for deterministic builds.
@@ -0,0 +1,64 @@
1
+ # JSX transform
2
+
3
+ The transform rewrites JSX so the Sinwan runtime can wrap reactive reads in effects at render time.
4
+
5
+ ## Reactive wrapping
6
+
7
+ The compiler wraps expressions that read reactive values in zero-arity arrow functions. For example:
8
+
9
+ ```tsx
10
+ <p>{state.name}</p>
11
+ ```
12
+
13
+ becomes:
14
+
15
+ ```tsx
16
+ <p>{() => state.name}</p>
17
+ ```
18
+
19
+ The runtime calls these functions inside an effect so the DOM updates when the value changes.
20
+
21
+ ## What is reactive
22
+
23
+ The compiler recognizes values from:
24
+
25
+ - `createMutable` / `createStore` from `sinwan/store`
26
+ - `signal` / `computed` from `sinwan/reactivity`
27
+ - `useState` from `sinwan/react-client`
28
+
29
+ Reactive reads include:
30
+
31
+ - Member expressions: `state.name`, `signal.value`
32
+ - Function calls: `getCount()`
33
+ - Derived expressions: `count() + 1`
34
+
35
+ ## What is NOT wrapped
36
+
37
+ - Plain literals: `"Hello"`, `123`
38
+ - Event handlers: `onClick={handleClick}`
39
+ - Static bindings that the analyzer proved are non-reactive
40
+
41
+ ## Cross-file optimization
42
+
43
+ When the analyzer is enabled, the transform can skip wrapping a prop if the analyzer determined that all callers pass a static value. This reduces runtime overhead.
44
+
45
+ ```tsx
46
+ // Without analyzer: onClick={handleClick} is safe to skip wrapping
47
+ // With analyzer: title="Hello" is also known static, so it is skipped
48
+ <Child title="Hello" onClick={handleClick} />
49
+ ```
50
+
51
+ ## Options
52
+
53
+ ```ts
54
+ transformJSX(code, filename, {
55
+ hoist: true, // template hoisting
56
+ explicitBindings: false, // emit compiler-driven binding descriptors
57
+ analyze: "path/to/reactive-props.json", // production metadata
58
+ analyzeMetadata: cache?.reactiveProps, // dev cache metadata
59
+ });
60
+ ```
61
+
62
+ ## Template hoisting
63
+
64
+ Static JSX subtrees are hoisted into a top-level template factory so they are not recreated on every render.
package/package.json CHANGED
@@ -1,11 +1,53 @@
1
1
  {
2
2
  "name": "sinwan-compiler",
3
- "version": "0.0.0",
4
- "module": "index.ts",
3
+ "version": "0.2.0",
4
+ "description": "Shared compiler core for Sinwan plugins",
5
5
  "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "keywords": [
10
+ "sinwan",
11
+ "compiler",
12
+ "jsx",
13
+ "reactive",
14
+ "signals"
15
+ ],
16
+ "author": "Mohammed Ben Cheikh",
17
+ "license": "MIT",
18
+ "bugs": "https://github.com/sinwanjs/sinwan-compiler/issues",
19
+ "homepage": "https://sinwanjs.com",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/sinwanjs/sinwan-compiler"
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "docs",
27
+ "README.md"
28
+ ],
29
+ "bin": {
30
+ "sinwan": "./dist/cli.js"
31
+ },
32
+ "scripts": {
33
+ "test": "bun test",
34
+ "typecheck": "tsc --noEmit",
35
+ "build": "bun run build.ts",
36
+ "clean": "rm -rf dist",
37
+ "prepublishOnly": "bun run clean && bun run typecheck && bun run test && bun run build"
38
+ },
6
39
  "devDependencies": {
40
+ "@types/babel__generator": "^7.27.0",
41
+ "@types/babel__traverse": "^7.28.0",
7
42
  "@types/bun": "latest"
8
43
  },
44
+ "dependencies": {
45
+ "@babel/core": "^7.24.0",
46
+ "@babel/generator": "^7.24.0",
47
+ "@babel/parser": "^7.24.0",
48
+ "@babel/traverse": "^7.24.0",
49
+ "@babel/types": "^7.24.0"
50
+ },
9
51
  "peerDependencies": {
10
52
  "typescript": "^5"
11
53
  }