sinwan-compiler 0.2.3 → 0.2.5
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/README.md +37 -14
- package/dist/analyze.d.ts +0 -1
- package/dist/{chunk-w580fdrf.js → chunk-m4rk009c.js} +169 -219
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +5 -3
- package/dist/index.js +51 -40
- package/dist/reactive-wrap.d.ts +4 -0
- package/dist/transform.d.ts +3 -3
- package/docs/README.md +38 -19
- package/docs/analyzer.md +63 -52
- package/docs/api.md +77 -26
- package/docs/architecture.md +52 -49
- package/docs/cli.md +24 -17
- package/docs/plugins.md +35 -32
- package/docs/transform.md +109 -34
- package/package.json +2 -2
package/dist/cli.d.ts
CHANGED
|
@@ -8,3 +8,5 @@
|
|
|
8
8
|
* Writes `.sinwan/reactive-props.json` by default.
|
|
9
9
|
*/
|
|
10
10
|
export declare function runAnalyzeCli(args?: string[]): void;
|
|
11
|
+
/** Runs the CLI only when this module is the process entry point. */
|
|
12
|
+
export declare function runCliIfMain(isMain?: boolean, args?: string[]): void;
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
} from "./chunk-
|
|
3
|
+
autoWrapComponents2,
|
|
4
|
+
analyzeModule2,
|
|
5
|
+
analyzeProject2,
|
|
6
|
+
AnalyzerCache2,
|
|
7
|
+
analyze2,
|
|
8
|
+
loadMetadata2,
|
|
9
|
+
trackReactiveImports2,
|
|
10
|
+
getAllPropNames2,
|
|
11
|
+
isReactiveValue2,
|
|
12
|
+
containsReactiveValue2,
|
|
13
|
+
collectComponentFunctions2,
|
|
14
|
+
computeLocalScopes2,
|
|
15
|
+
collectComponentCallGraph2,
|
|
16
|
+
buildFullScope2,
|
|
17
|
+
propagateReactiveProps2,
|
|
18
|
+
wrapReactiveExpressions2,
|
|
19
|
+
runAnalyzeCli2
|
|
20
|
+
} from "./chunk-m4rk009c.js";
|
|
21
21
|
|
|
22
22
|
// src/transform.ts
|
|
23
23
|
import { parse } from "@babel/parser";
|
|
@@ -183,7 +183,18 @@ function elementToHtml(node, slots, path, filename, counter) {
|
|
|
183
183
|
if (attrName === "children")
|
|
184
184
|
continue;
|
|
185
185
|
if (attrName === "ref") {
|
|
186
|
-
|
|
186
|
+
if (attr.value?.type === "JSXExpressionContainer") {
|
|
187
|
+
const expr = attr.value.expression;
|
|
188
|
+
if (expr.type !== "JSXEmptyExpression") {
|
|
189
|
+
slots.push({
|
|
190
|
+
path: [...path],
|
|
191
|
+
type: "ref",
|
|
192
|
+
name: "ref",
|
|
193
|
+
expr
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
continue;
|
|
187
198
|
}
|
|
188
199
|
if (attr.value?.type === "JSXExpressionContainer") {
|
|
189
200
|
const expr = attr.value.expression;
|
|
@@ -281,8 +292,8 @@ function transformJSX(code, filename, options = {}) {
|
|
|
281
292
|
plugins: ["jsx", "typescript"],
|
|
282
293
|
sourceFilename: filename
|
|
283
294
|
});
|
|
284
|
-
|
|
285
|
-
|
|
295
|
+
autoWrapComponents2(ast, filename);
|
|
296
|
+
wrapReactiveExpressions2(ast, {
|
|
286
297
|
explicitBindings: options.explicitBindings,
|
|
287
298
|
analyze: options.analyze,
|
|
288
299
|
analyzeMetadata: options.analyzeMetadata,
|
|
@@ -330,11 +341,11 @@ function transformJSX(code, filename, options = {}) {
|
|
|
330
341
|
], t.stringLiteral("sinwan/renderer"));
|
|
331
342
|
ast.program.body.unshift(bindingImport);
|
|
332
343
|
}
|
|
333
|
-
const
|
|
344
|
+
const result = generate(ast, {
|
|
334
345
|
sourceMaps: true,
|
|
335
346
|
sourceFileName: filename
|
|
336
347
|
});
|
|
337
|
-
return { code:
|
|
348
|
+
return { code: result.code, map: result.map };
|
|
338
349
|
}
|
|
339
350
|
const templateDecls = templates.map(({ id, template }) => t.variableDeclaration("const", [
|
|
340
351
|
t.variableDeclarator(t.identifier(id), t.objectExpression([
|
|
@@ -416,24 +427,24 @@ function collectExportedComponents(code, filename) {
|
|
|
416
427
|
return found;
|
|
417
428
|
}
|
|
418
429
|
export {
|
|
419
|
-
AnalyzerCache,
|
|
430
|
+
AnalyzerCache2 as AnalyzerCache,
|
|
420
431
|
COMPILER_TEMPLATE_SLOT_PROTOCOL,
|
|
421
|
-
analyze,
|
|
422
|
-
analyzeModule,
|
|
423
|
-
analyzeProject,
|
|
424
|
-
autoWrapComponents,
|
|
425
|
-
buildFullScope,
|
|
426
|
-
collectComponentCallGraph,
|
|
427
|
-
collectComponentFunctions,
|
|
432
|
+
analyze2 as analyze,
|
|
433
|
+
analyzeModule2 as analyzeModule,
|
|
434
|
+
analyzeProject2 as analyzeProject,
|
|
435
|
+
autoWrapComponents2 as autoWrapComponents,
|
|
436
|
+
buildFullScope2 as buildFullScope,
|
|
437
|
+
collectComponentCallGraph2 as collectComponentCallGraph,
|
|
438
|
+
collectComponentFunctions2 as collectComponentFunctions,
|
|
428
439
|
collectExportedComponents,
|
|
429
|
-
computeLocalScopes,
|
|
430
|
-
containsReactiveValue,
|
|
431
|
-
getAllPropNames,
|
|
432
|
-
isReactiveValue,
|
|
433
|
-
loadMetadata,
|
|
434
|
-
propagateReactiveProps,
|
|
435
|
-
runAnalyzeCli,
|
|
436
|
-
trackReactiveImports,
|
|
440
|
+
computeLocalScopes2 as computeLocalScopes,
|
|
441
|
+
containsReactiveValue2 as containsReactiveValue,
|
|
442
|
+
getAllPropNames2 as getAllPropNames,
|
|
443
|
+
isReactiveValue2 as isReactiveValue,
|
|
444
|
+
loadMetadata2 as loadMetadata,
|
|
445
|
+
propagateReactiveProps2 as propagateReactiveProps,
|
|
446
|
+
runAnalyzeCli2 as runAnalyzeCli,
|
|
447
|
+
trackReactiveImports2 as trackReactiveImports,
|
|
437
448
|
transformJSX,
|
|
438
|
-
wrapReactiveExpressions
|
|
449
|
+
wrapReactiveExpressions2 as wrapReactiveExpressions
|
|
439
450
|
};
|
package/dist/reactive-wrap.d.ts
CHANGED
|
@@ -59,6 +59,7 @@ export interface ReactiveScope {
|
|
|
59
59
|
bindings: Map<string, Binding>;
|
|
60
60
|
}
|
|
61
61
|
export declare function getAllPropNames(params: t.Node | undefined): Set<string>;
|
|
62
|
+
export declare function resolveLocalObjectLiteral(fn: t.Function, name: string): t.ObjectExpression | null;
|
|
62
63
|
export declare function isReactiveValue(node: t.Node, scope: ReactiveScope): boolean;
|
|
63
64
|
export declare function containsReactiveValue(expr: t.Expression, scope: ReactiveScope): boolean;
|
|
64
65
|
export interface ComponentExpressionInfo {
|
|
@@ -66,6 +67,9 @@ export interface ComponentExpressionInfo {
|
|
|
66
67
|
componentName: string | null;
|
|
67
68
|
attributeName: string | null;
|
|
68
69
|
}
|
|
70
|
+
export declare function getComponentExpressionInfo(exprPath: {
|
|
71
|
+
parentPath?: unknown;
|
|
72
|
+
}): ComponentExpressionInfo;
|
|
69
73
|
export declare function collectComponentFunctions(ast: t.Node, ccNames: Set<string>): {
|
|
70
74
|
functions: Set<t.Function>;
|
|
71
75
|
names: Map<string, t.Function>;
|
package/dist/transform.d.ts
CHANGED
|
@@ -35,9 +35,9 @@ export interface TransformOptions {
|
|
|
35
35
|
* in sync.
|
|
36
36
|
*/
|
|
37
37
|
export declare const COMPILER_TEMPLATE_SLOT_PROTOCOL: {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
slotPrefix: string;
|
|
39
|
+
encodeSlot(index: number): string;
|
|
40
|
+
decodeSlot(data: string): number | null;
|
|
41
41
|
};
|
|
42
42
|
export declare function transformJSX(code: string, filename: string, options?: TransformOptions): {
|
|
43
43
|
code: string;
|
package/docs/README.md
CHANGED
|
@@ -1,26 +1,45 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Compiler overview
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`sinwan-compiler` is the shared engine behind the Bun and Vite plugins. It has three jobs: rewrite JSX, decide which props are reactive, and keep that decision cheap in dev.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
If you are building a Sinwan app, start with [Plugins](plugins.md). If you are changing the compiler, start with [Architecture](architecture.md).
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- **Cross-file analysis**: Determines which component props are reactive across the whole project, allowing the transform to avoid wrapping static props in effects.
|
|
9
|
-
- **Dev cache**: Maintains an incremental, persistent cache for Bun and Vite so dev startup and HMR are fast.
|
|
7
|
+
## Pipeline
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
1. **Auto-`cc`** — exported functions that look like components (`App`, `Card`, …) are wrapped with `cc(...)` so the rest of the pipeline sees the same components the runtime will.
|
|
10
|
+
2. **Reactive wrap** — JSX expressions that read signals, stores, `useState`, or `useFetch` become `() => …` (or explicit binding helpers when that mode is on).
|
|
11
|
+
3. **Template hoist** — static native-element trees become `_$createTemplate(...)` calls with slots for dynamic bits (`child`, `attr`, `event`, `ref`).
|
|
12
|
+
4. **Analyze (optional)** — a project-wide pass marks which *exported* component props are actually reactive, so static strings are not wrapped.
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
```text
|
|
15
|
+
source.tsx
|
|
16
|
+
│
|
|
17
|
+
▼
|
|
18
|
+
autoWrapComponents() plain `export function App()` → cc(App)
|
|
19
|
+
│
|
|
20
|
+
▼
|
|
21
|
+
wrapReactiveExpressions() {count.value} → {() => count.value}
|
|
22
|
+
│
|
|
23
|
+
▼
|
|
24
|
+
hoist templates <div class="card">…</div> → _$tmpl_N
|
|
25
|
+
│
|
|
26
|
+
▼
|
|
27
|
+
generated module
|
|
28
|
+
```
|
|
18
29
|
|
|
19
|
-
##
|
|
30
|
+
## When analysis runs
|
|
20
31
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
| Mode | Who runs it | What the transform receives |
|
|
33
|
+
| ---- | ----------- | --------------------------- |
|
|
34
|
+
| Dev | Plugin `AnalyzerCache` | In-memory `analyzeMetadata` after each file update |
|
|
35
|
+
| Production | CLI / `analyze()` | JSON file via `analyze: "./.sinwan/reactive-props.json"` |
|
|
36
|
+
| Off | Nobody | Conservative wrapping (exported props treated as reactive) |
|
|
37
|
+
|
|
38
|
+
## Guides
|
|
39
|
+
|
|
40
|
+
- [Transform](transform.md) — what gets wrapped, auto-`cc`, hoisting, built-in components
|
|
41
|
+
- [Analyzer](analyzer.md) — call graph, spreads, workspaces, cache
|
|
42
|
+
- [CLI](cli.md) — `sinwan analyze`
|
|
43
|
+
- [Plugins](plugins.md) — wiring Bun and Vite
|
|
44
|
+
- [API](api.md) — exported functions and types
|
|
45
|
+
- [Architecture](architecture.md) — data structures and propagation
|
package/docs/analyzer.md
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
# Cross-file
|
|
1
|
+
# Cross-file analyzer
|
|
2
2
|
|
|
3
|
-
The analyzer
|
|
3
|
+
The analyzer answers: **for each exported component, which props actually change?** The transform uses that map so a static `title="Hello"` does not become an effect.
|
|
4
|
+
|
|
5
|
+
Result shape: `filePath → exportName → Set<propName>`.
|
|
4
6
|
|
|
5
7
|
## How it works
|
|
6
8
|
|
|
7
|
-
1.
|
|
8
|
-
2.
|
|
9
|
-
3.
|
|
10
|
-
4.
|
|
11
|
-
5.
|
|
9
|
+
1. Scan `.tsx` / `.ts` / `.jsx` / `.js` (or the files you pass in).
|
|
10
|
+
2. Auto-wrap the same exported components the transform would wrap.
|
|
11
|
+
3. Track reactive imports and `cc(...)`.
|
|
12
|
+
4. Build a call graph: local `<Child />` and imported `<Child />`.
|
|
13
|
+
5. Propagate reactivity until the sets stop growing.
|
|
12
14
|
|
|
13
|
-
A prop is reactive
|
|
15
|
+
A prop is reactive if **any** caller passes a value that reads a reactive source (including through another component).
|
|
14
16
|
|
|
15
17
|
## Example
|
|
16
18
|
|
|
@@ -23,10 +25,12 @@ export const Child = cc(({ title }) => <h1>{title}</h1>);
|
|
|
23
25
|
import { cc } from "sinwan/component";
|
|
24
26
|
import { signal } from "sinwan/reactivity";
|
|
25
27
|
import { Child } from "./Child";
|
|
26
|
-
const Parent = cc(() => <Child title={signal.value} />);
|
|
27
|
-
```
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
const Parent = cc(() => {
|
|
30
|
+
const title = signal("Hello");
|
|
31
|
+
return <Child title={title.value} />;
|
|
32
|
+
});
|
|
33
|
+
```
|
|
30
34
|
|
|
31
35
|
```json
|
|
32
36
|
{
|
|
@@ -34,64 +38,71 @@ The analyzer produces:
|
|
|
34
38
|
}
|
|
35
39
|
```
|
|
36
40
|
|
|
37
|
-
|
|
41
|
+
If every caller passed `title="Hello"`, `Child` would be `[]` and the transform would not wrap `title` inside `Child`.
|
|
38
42
|
|
|
39
|
-
|
|
43
|
+
Default exports are stored under the name `"default"`.
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
// From a workspace file
|
|
45
|
-
workspaces: "../../package.json",
|
|
46
|
-
// From a pnpm workspace
|
|
47
|
-
workspaces: "../../pnpm-workspace.yaml",
|
|
48
|
-
// Explicit package paths or globs
|
|
49
|
-
workspaces: ["../../packages/sinwan-ui", "../../packages/shared"],
|
|
50
|
-
// Or combine both
|
|
51
|
-
workspaces: {
|
|
52
|
-
file: "../../package.json",
|
|
53
|
-
include: ["../../packages/sinwan-ui"],
|
|
54
|
-
},
|
|
55
|
-
});
|
|
56
|
-
```
|
|
45
|
+
## Spreads
|
|
46
|
+
|
|
47
|
+
The analyzer is precise when it can see the object:
|
|
57
48
|
|
|
58
|
-
|
|
49
|
+
```tsx
|
|
50
|
+
<Child {...{ title: "Hello", count: s.value }} />
|
|
51
|
+
// only `count` is reactive
|
|
59
52
|
|
|
53
|
+
const props = { title: "Hello", count: s.value };
|
|
54
|
+
<Child {...props} />
|
|
55
|
+
// same, if `props` is that object literal in the same function
|
|
56
|
+
```
|
|
60
57
|
|
|
58
|
+
String keys work (`{ "title": s.value }`). If the spread is an unknown value (`{...rest}` from parameters), **every known prop of the callee**, including `children`, is marked reactive.
|
|
61
59
|
|
|
62
60
|
## Import resolution
|
|
63
61
|
|
|
64
|
-
|
|
62
|
+
Lookups run in this order:
|
|
63
|
+
|
|
64
|
+
1. `tsconfig.json` `paths` (when a config is passed or `tsconfig.json` exists next to `root`)
|
|
65
|
+
2. `bunfig.toml` aliases (`[install] alias = { … }` or `[install.alias]`)
|
|
66
|
+
3. Workspace packages (see below)
|
|
67
|
+
4. Relative imports (`./Child`, `./ui` → `ui.tsx` or `ui/index.tsx`)
|
|
68
|
+
|
|
69
|
+
You can replace that with `resolve(source, fromFile)`.
|
|
70
|
+
|
|
71
|
+
`AnalyzerCache` also resolves **in-memory** modules first (HMR can update a file before it hits disk), including `./ui` → `ui/index.tsx` already in the cache.
|
|
65
72
|
|
|
66
|
-
|
|
67
|
-
2. `bunfig.toml` aliases (if configured or auto-detected).
|
|
68
|
-
3. Workspace packages (if configured).
|
|
69
|
-
4. Relative imports with the configured extensions.
|
|
73
|
+
## Workspaces
|
|
70
74
|
|
|
71
|
-
|
|
75
|
+
Only packages that the app actually imports are pulled in.
|
|
72
76
|
|
|
73
77
|
```ts
|
|
74
78
|
analyzeProject({
|
|
75
|
-
root: "/
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
root: "./apps/web",
|
|
80
|
+
workspaces: "../../package.json", // npm/bun `workspaces`
|
|
81
|
+
// or "../../pnpm-workspace.yaml"
|
|
82
|
+
// or ["../../packages/ui"]
|
|
83
|
+
// or { file: "../../package.json", include: ["../../packages/ui"] }
|
|
79
84
|
});
|
|
80
85
|
```
|
|
81
86
|
|
|
82
|
-
|
|
87
|
+
`@scope/ui/Button` maps to `packages/ui/src/Button.tsx` (or the directory of `package.json` `"source"`).
|
|
88
|
+
|
|
89
|
+
## Cache (`AnalyzerCache`)
|
|
90
|
+
|
|
91
|
+
Dev plugins call `update(file, code)` on each transform. The cache:
|
|
92
|
+
|
|
93
|
+
- Re-analyzes that file
|
|
94
|
+
- Walks importers **and** imported modules
|
|
95
|
+
- Writes `cachePath` when you set one
|
|
96
|
+
|
|
97
|
+
`remove(file)` is for deletes / HMR unlinks. It drops the module and **recomputes both**:
|
|
83
98
|
|
|
84
|
-
|
|
99
|
+
- files that imported it (parents)
|
|
100
|
+
- files it imported (children — their props may become static again)
|
|
85
101
|
|
|
86
|
-
|
|
87
|
-
- `<Child {...props} />` is also resolved precisely if `props` is a local variable initialized with an object literal in the same function:
|
|
88
|
-
```tsx
|
|
89
|
-
const props = { title: "Hello", count: signal.value };
|
|
90
|
-
return <Child {...props} />;
|
|
91
|
-
```
|
|
92
|
-
- Spreads of function parameters or other unknown runtime objects fall back to the conservative rule: all known props of the callee are marked reactive.
|
|
102
|
+
If `cachePath` exists, the constructor restores it. Function identities are start/end offsets. If a file changed so those spans no longer match, that module is skipped (no throw); the next `update` rebuilds it.
|
|
93
103
|
|
|
94
|
-
##
|
|
104
|
+
## Conservative rules
|
|
95
105
|
|
|
96
|
-
- Unknown
|
|
97
|
-
-
|
|
106
|
+
- Unknown spreads are conservative (all callee props).
|
|
107
|
+
- `children` is a normal prop.
|
|
108
|
+
- Without metadata, the transform treats **exported** component props as reactive so a parent in another file cannot silently go stale.
|
package/docs/api.md
CHANGED
|
@@ -1,25 +1,41 @@
|
|
|
1
1
|
# API reference
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Import from `sinwan-compiler`. Types below match the package exports.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Transform
|
|
6
|
+
|
|
7
|
+
### `transformJSX(code, filename, options?)`
|
|
8
|
+
|
|
9
|
+
Rewrite one module. Returns `{ code: string; map?: unknown }`.
|
|
6
10
|
|
|
7
11
|
```ts
|
|
8
12
|
import { transformJSX } from "sinwan-compiler";
|
|
9
13
|
|
|
10
|
-
const
|
|
14
|
+
const { code, map } = transformJSX(source, "src/App.tsx", {
|
|
11
15
|
hoist: true,
|
|
16
|
+
dev: false,
|
|
12
17
|
explicitBindings: false,
|
|
13
18
|
analyze: "./.sinwan/reactive-props.json",
|
|
14
|
-
analyzeMetadata:
|
|
19
|
+
analyzeMetadata: cache.reactiveProps,
|
|
20
|
+
resolveImport: (source, fromFile) => /* absolute path or null */,
|
|
15
21
|
});
|
|
16
22
|
```
|
|
17
23
|
|
|
18
|
-
|
|
24
|
+
See [Transform](transform.md) for wrapping rules and options.
|
|
25
|
+
|
|
26
|
+
### `COMPILER_TEMPLATE_SLOT_PROTOCOL`
|
|
27
|
+
|
|
28
|
+
`{ slotPrefix, encodeSlot(index), decodeSlot(data) }`. Markers look like `s:0`. Keep this aligned with the Sinwan runtime protocol.
|
|
29
|
+
|
|
30
|
+
### `autoWrapComponents(ast, filename?)`
|
|
19
31
|
|
|
20
|
-
|
|
32
|
+
Mutates a Babel AST: wrap exported component-like functions with `cc(...)`. Returns `true` if anything changed. `transformJSX` already runs this.
|
|
21
33
|
|
|
22
|
-
|
|
34
|
+
## Analysis
|
|
35
|
+
|
|
36
|
+
### `analyze(options)`
|
|
37
|
+
|
|
38
|
+
Scan a project and write JSON to `outFile`.
|
|
23
39
|
|
|
24
40
|
```ts
|
|
25
41
|
import { analyze } from "sinwan-compiler";
|
|
@@ -27,58 +43,93 @@ import { analyze } from "sinwan-compiler";
|
|
|
27
43
|
analyze({
|
|
28
44
|
root: "./src",
|
|
29
45
|
outFile: "./.sinwan/reactive-props.json",
|
|
46
|
+
extensions: [".tsx", ".ts", ".jsx", ".js"], // default
|
|
30
47
|
tsConfigPath: "./tsconfig.json",
|
|
31
48
|
bunfigPath: "./bunfig.toml",
|
|
32
49
|
workspaces: "../../package.json",
|
|
50
|
+
resolve: (source, fromFile) => null,
|
|
33
51
|
});
|
|
34
52
|
```
|
|
35
53
|
|
|
36
|
-
|
|
54
|
+
Missing `tsconfig.json` / `bunfig.toml` next to `root` are skipped unless you pass the paths explicitly.
|
|
55
|
+
|
|
56
|
+
### `analyzeProject(options)`
|
|
37
57
|
|
|
38
|
-
|
|
58
|
+
Same scan, no disk write. Returns `{ modules, reactiveProps }`.
|
|
39
59
|
|
|
40
60
|
```ts
|
|
41
61
|
import { analyzeProject } from "sinwan-compiler";
|
|
42
62
|
|
|
43
63
|
const project = analyzeProject({
|
|
44
64
|
root: "./src",
|
|
45
|
-
files: { "/
|
|
65
|
+
files: { "/tmp/A.tsx": "export const A = () => <div/>" }, // optional in-memory map
|
|
46
66
|
tsConfigPath: "./tsconfig.json",
|
|
67
|
+
workspaces: { file: "../../package.json", include: ["../../packages/ui"] },
|
|
47
68
|
});
|
|
48
69
|
|
|
49
|
-
project.reactiveProps.get("/
|
|
70
|
+
project.reactiveProps.get("/tmp/A.tsx")?.get("A"); // Set<string> | undefined
|
|
50
71
|
```
|
|
51
72
|
|
|
52
|
-
|
|
73
|
+
### `analyzeModule(code, filePath)`
|
|
74
|
+
|
|
75
|
+
Analyze one file. Used by the cache and tests.
|
|
76
|
+
|
|
77
|
+
### `loadMetadata(filePath)`
|
|
53
78
|
|
|
54
|
-
|
|
79
|
+
Read analyzer JSON into `Map<file, Map<exportName, Set<prop>>>`. Missing file → empty map.
|
|
80
|
+
|
|
81
|
+
### `loadWorkspacePackages(workspaces, root)`
|
|
82
|
+
|
|
83
|
+
Resolve workspace directories from a file, a list of globs/paths, or `{ file, include }`.
|
|
84
|
+
|
|
85
|
+
### `AnalyzerCache`
|
|
86
|
+
|
|
87
|
+
Incremental graph for plugins.
|
|
55
88
|
|
|
56
89
|
```ts
|
|
57
90
|
import { AnalyzerCache } from "sinwan-compiler";
|
|
58
91
|
|
|
59
92
|
const cache = new AnalyzerCache({
|
|
60
|
-
root:
|
|
93
|
+
root: process.cwd(),
|
|
61
94
|
tsConfigPath: "./tsconfig.json",
|
|
62
95
|
bunfigPath: "./bunfig.toml",
|
|
63
96
|
workspaces: "../../package.json",
|
|
64
97
|
cachePath: "./.sinwan/cache.json",
|
|
98
|
+
resolve: (source, fromFile) => null, // optional
|
|
65
99
|
});
|
|
66
100
|
|
|
67
|
-
cache.update(
|
|
68
|
-
|
|
101
|
+
cache.update(absPath, source);
|
|
102
|
+
cache.remove(absPath); // also recomputes modules this file imported
|
|
103
|
+
cache.save(); // no-op without cachePath
|
|
104
|
+
|
|
105
|
+
cache.reactiveProps; // Map<file, Map<export, Set<prop>>>
|
|
106
|
+
cache.modules;
|
|
69
107
|
```
|
|
70
108
|
|
|
71
|
-
|
|
109
|
+
`restore` runs in the constructor when `cachePath` exists. Stale function spans skip that module instead of throwing.
|
|
110
|
+
|
|
111
|
+
## CLI helpers
|
|
112
|
+
|
|
113
|
+
### `runAnalyzeCli(args?)`
|
|
114
|
+
|
|
115
|
+
Parse `analyze [root] [outFile] [--tsconfig] [--bunfig] [--workspaces]` and call `analyze()`. Defaults to `process.argv.slice(2)`. Unknown commands write to stderr and `process.exit(1)`.
|
|
116
|
+
|
|
117
|
+
## Other exports
|
|
72
118
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
119
|
+
| Name | Role |
|
|
120
|
+
| ---- | ---- |
|
|
121
|
+
| `collectExportedComponents(code)` | File-local exported component names (no `cc` required) |
|
|
122
|
+
| `wrapReactiveExpressions(ast, options?)` | Reactive wrap without hoisting |
|
|
123
|
+
| `trackReactiveImports(ast)` | Import-name sets for signals, `cc`, `useFetch`, … |
|
|
124
|
+
| `collectComponentFunctions(ast, ccNames)` | `cc(...)` functions, names, and exports |
|
|
125
|
+
| `computeLocalScopes(ast, names)` | Per-function reactive bindings |
|
|
126
|
+
| `collectComponentCallGraph(ast, names)` | Local JSX call sites |
|
|
127
|
+
| `propagateReactiveProps(scopes, graph, fns)` | Fixed-point prop sets |
|
|
128
|
+
| `buildFullScope(scopes, fn, reactiveProps)` | Local bindings plus reactive props |
|
|
129
|
+
| `containsReactiveValue(expr, scope)` | Whether an expression reads a reactive value |
|
|
130
|
+
| `isReactiveValue(node, scope)` | Single-node check |
|
|
131
|
+
| `getAllPropNames(params)` | Prop names from a component parameter (`*` for a bare identifier) |
|
|
77
132
|
|
|
78
133
|
## Types
|
|
79
134
|
|
|
80
|
-
|
|
81
|
-
- `ImportedCallSite` — cross-file component call site.
|
|
82
|
-
- `ModuleAnalysis` — analysis of a single module.
|
|
83
|
-
- `ProjectAnalysis` — analysis of the whole project.
|
|
84
|
-
- `WorkspacesConfig` — workspace file path, explicit package paths/globs, or both. Used to enable cross-package analysis in monorepos.
|
|
135
|
+
`AnalyzeOptions`, `WorkspacesConfig`, `ModuleAnalysis`, `ProjectAnalysis`, `ImportInfo`, `ImportedCallSite`, `CallSite`, `ReactiveScope`, `Binding`, `ImportNames`, `ComponentExpressionInfo`, `ExportedComponent`, `TransformOptions`.
|