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.
- package/LICENSE +21 -0
- package/README.md +27 -0
- package/dist/analyze.d.ts +103 -0
- package/dist/chunk-ba0546ea.js +1848 -0
- package/dist/cli.d.ts +10 -0
- package/dist/cli.js +8 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +329 -0
- package/dist/reactive-wrap.d.ts +80 -0
- package/dist/transform.d.ts +23 -0
- package/docs/README.md +26 -0
- package/docs/analyzer.md +97 -0
- package/docs/api.md +84 -0
- package/docs/architecture.md +75 -0
- package/docs/cli.md +46 -0
- package/docs/plugins.md +60 -0
- package/docs/transform.md +64 -0
- package/package.json +44 -2
|
@@ -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
|
+
```
|
package/docs/plugins.md
ADDED
|
@@ -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.
|
|
4
|
-
"
|
|
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
|
}
|