sinwan-compiler 0.2.7 → 0.2.9
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/dist/{chunk-edjqytm9.js → chunk-6dnsvgyf.js} +83 -22
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/transform.d.ts +3 -3
- package/docs/README.md +2 -0
- package/docs/plugins.md +1 -0
- package/docs/transform.md +9 -2
- package/package.json +1 -1
|
@@ -43,8 +43,17 @@ function isBuiltinReactiveProp(componentName, attrName) {
|
|
|
43
43
|
return false;
|
|
44
44
|
return entry.reactiveProps.has(attrName);
|
|
45
45
|
}
|
|
46
|
-
function
|
|
47
|
-
|
|
46
|
+
function isReactiveContainerPassThrough(expr, scope) {
|
|
47
|
+
let node = expr;
|
|
48
|
+
while (t.isTSNonNullExpression(node)) {
|
|
49
|
+
node = node.expression;
|
|
50
|
+
}
|
|
51
|
+
if (!t.isIdentifier(node))
|
|
52
|
+
return false;
|
|
53
|
+
const binding = scope.bindings.get(node.name);
|
|
54
|
+
if (!binding)
|
|
55
|
+
return false;
|
|
56
|
+
return binding.kind === "mutable" || binding.kind === "prop";
|
|
48
57
|
}
|
|
49
58
|
function trackReactiveImports2(ast) {
|
|
50
59
|
const names = {
|
|
@@ -291,6 +300,35 @@ function analyzeObjectLiteralSpread(spread, fullScope, calleeReactive) {
|
|
|
291
300
|
function isMemberLike(node) {
|
|
292
301
|
return t.isMemberExpression(node) || t.isOptionalMemberExpression(node);
|
|
293
302
|
}
|
|
303
|
+
function isFunctionLike(node) {
|
|
304
|
+
return t.isArrowFunctionExpression(node) || t.isFunctionExpression(node);
|
|
305
|
+
}
|
|
306
|
+
function isCallLike(node) {
|
|
307
|
+
return t.isCallExpression(node) || t.isOptionalCallExpression(node);
|
|
308
|
+
}
|
|
309
|
+
var COMPILER_BINDING_CALLEES = new Set([
|
|
310
|
+
"_$bindText",
|
|
311
|
+
"_$bindAttr",
|
|
312
|
+
"_$bindStyle",
|
|
313
|
+
"_$bindClass",
|
|
314
|
+
"_$unwrap"
|
|
315
|
+
]);
|
|
316
|
+
function isCompilerBindingCall(node) {
|
|
317
|
+
const callee = node.callee;
|
|
318
|
+
return t.isIdentifier(callee) && COMPILER_BINDING_CALLEES.has(callee.name);
|
|
319
|
+
}
|
|
320
|
+
function visitCallCalleeAndArgs(node, visitExpr) {
|
|
321
|
+
if (isCompilerBindingCall(node))
|
|
322
|
+
return;
|
|
323
|
+
visitExpr(node.callee, true);
|
|
324
|
+
for (const arg of node.arguments) {
|
|
325
|
+
if (t.isSpreadElement(arg)) {
|
|
326
|
+
visitExpr(arg.argument, true);
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
visitExpr(arg, true);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
294
332
|
function getMemberExpressionRootAndPath(node) {
|
|
295
333
|
const path = [];
|
|
296
334
|
let current = node;
|
|
@@ -428,11 +466,23 @@ function containsReactiveValue2(expr, scope) {
|
|
|
428
466
|
}
|
|
429
467
|
function containsReactiveRead(expr, scope) {
|
|
430
468
|
let found = false;
|
|
431
|
-
function visit(node) {
|
|
469
|
+
function visit(node, enterFunction) {
|
|
432
470
|
if (found)
|
|
433
471
|
return;
|
|
434
472
|
if (!node || typeof node !== "object")
|
|
435
473
|
return;
|
|
474
|
+
if (isFunctionLike(node)) {
|
|
475
|
+
if (!enterFunction)
|
|
476
|
+
return;
|
|
477
|
+
for (const param of node.params) {
|
|
478
|
+
visit(param, false);
|
|
479
|
+
}
|
|
480
|
+
visit(node.body, false);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
if (t.isJSXElement(node) || t.isJSXFragment(node)) {
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
436
486
|
if (isMemberLike(node) || t.isTSNonNullExpression(node) || t.isIdentifier(node)) {
|
|
437
487
|
if (isReactiveRead(node, scope)) {
|
|
438
488
|
found = true;
|
|
@@ -443,7 +493,8 @@ function containsReactiveRead(expr, scope) {
|
|
|
443
493
|
found = true;
|
|
444
494
|
return;
|
|
445
495
|
}
|
|
446
|
-
if (
|
|
496
|
+
if (isCallLike(node)) {
|
|
497
|
+
visitCallCalleeAndArgs(node, visit);
|
|
447
498
|
return;
|
|
448
499
|
}
|
|
449
500
|
for (const key of Object.keys(node)) {
|
|
@@ -453,14 +504,14 @@ function containsReactiveRead(expr, scope) {
|
|
|
453
504
|
if (Array.isArray(value)) {
|
|
454
505
|
for (const item of value) {
|
|
455
506
|
if (item && typeof item === "object")
|
|
456
|
-
visit(item);
|
|
507
|
+
visit(item, false);
|
|
457
508
|
}
|
|
458
509
|
} else if (value && typeof value === "object") {
|
|
459
|
-
visit(value);
|
|
510
|
+
visit(value, false);
|
|
460
511
|
}
|
|
461
512
|
}
|
|
462
513
|
}
|
|
463
|
-
visit(expr);
|
|
514
|
+
visit(expr, false);
|
|
464
515
|
return found;
|
|
465
516
|
}
|
|
466
517
|
function getComponentExpressionInfo(exprPath) {
|
|
@@ -552,11 +603,21 @@ function transformPropMemberAccess(expr, scope) {
|
|
|
552
603
|
parent.object = resolveRoot(rootName);
|
|
553
604
|
}
|
|
554
605
|
}
|
|
555
|
-
function visit(node) {
|
|
606
|
+
function visit(node, enterFunction) {
|
|
556
607
|
if (!node || typeof node !== "object")
|
|
557
608
|
return;
|
|
558
|
-
if (
|
|
609
|
+
if (isFunctionLike(node)) {
|
|
610
|
+
if (!enterFunction)
|
|
611
|
+
return;
|
|
612
|
+
for (const param of node.params) {
|
|
613
|
+
visit(param, false);
|
|
614
|
+
}
|
|
615
|
+
visit(node.body, false);
|
|
559
616
|
return;
|
|
617
|
+
}
|
|
618
|
+
if (t.isJSXElement(node) || t.isJSXFragment(node)) {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
560
621
|
if (isMemberLike(node)) {
|
|
561
622
|
const rootPath = getMemberExpressionRootAndPath(node);
|
|
562
623
|
if (rootPath) {
|
|
@@ -569,6 +630,10 @@ function transformPropMemberAccess(expr, scope) {
|
|
|
569
630
|
}
|
|
570
631
|
return;
|
|
571
632
|
}
|
|
633
|
+
if (isCallLike(node)) {
|
|
634
|
+
visitCallCalleeAndArgs(node, visit);
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
572
637
|
for (const key of Object.keys(node)) {
|
|
573
638
|
if (key === "loc" || key === "start" || key === "end" || key === "leadingComments" || key === "trailingComments")
|
|
574
639
|
continue;
|
|
@@ -576,14 +641,14 @@ function transformPropMemberAccess(expr, scope) {
|
|
|
576
641
|
if (Array.isArray(value)) {
|
|
577
642
|
for (const item of value) {
|
|
578
643
|
if (item && typeof item === "object")
|
|
579
|
-
visit(item);
|
|
644
|
+
visit(item, false);
|
|
580
645
|
}
|
|
581
646
|
} else if (value && typeof value === "object") {
|
|
582
|
-
visit(value);
|
|
647
|
+
visit(value, false);
|
|
583
648
|
}
|
|
584
649
|
}
|
|
585
650
|
}
|
|
586
|
-
visit(expr);
|
|
651
|
+
visit(expr, false);
|
|
587
652
|
return changed;
|
|
588
653
|
}
|
|
589
654
|
function wrapExpression(expr) {
|
|
@@ -611,7 +676,8 @@ function createBindingDescriptor(expr, exprPath, options) {
|
|
|
611
676
|
wrapExpression(expr)
|
|
612
677
|
]);
|
|
613
678
|
}
|
|
614
|
-
|
|
679
|
+
const compInfo = getComponentExpressionInfo(exprPath);
|
|
680
|
+
if (t.isIdentifier(expr, { name: "children" }) || compInfo.isComponent && compInfo.attributeName === null) {
|
|
615
681
|
return wrapExpression(expr);
|
|
616
682
|
}
|
|
617
683
|
return t.callExpression(t.identifier("_$bindText"), [wrapExpression(expr)]);
|
|
@@ -1050,15 +1116,10 @@ function wrapReactiveExpressions2(ast, options = {}) {
|
|
|
1050
1116
|
JSXExpressionContainer(exprPath) {
|
|
1051
1117
|
const expr = exprPath.node.expression;
|
|
1052
1118
|
const compInfo = getComponentExpressionInfo(exprPath);
|
|
1053
|
-
if (compInfo.isComponent) {
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
}
|
|
1058
|
-
} else {
|
|
1059
|
-
if (!isBuiltinControlFlowComponent(compInfo.componentName)) {
|
|
1060
|
-
return;
|
|
1061
|
-
}
|
|
1119
|
+
if (compInfo.isComponent && compInfo.attributeName !== null) {
|
|
1120
|
+
const knownProp = !!compInfo.componentName && isReactiveComponentProp(compInfo.componentName, compInfo.attributeName, componentNames, reactiveProps, importedReactiveProps);
|
|
1121
|
+
if (!knownProp && (!shouldWrap(expr, scope) || isReactiveContainerPassThrough(expr, scope))) {
|
|
1122
|
+
return;
|
|
1062
1123
|
}
|
|
1063
1124
|
}
|
|
1064
1125
|
if (shouldWrap(expr, scope)) {
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
package/dist/transform.d.ts
CHANGED
|
@@ -19,9 +19,9 @@ export interface TransformOptions {
|
|
|
19
19
|
/**
|
|
20
20
|
* Resolves an import source (e.g. "./Child") to an absolute file path,
|
|
21
21
|
* relative to the file being transformed. Used to look up reactive-prop
|
|
22
|
-
* metadata for components imported from other modules
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
* metadata for components imported from other modules. Derived reads
|
|
23
|
+
* (`title={n.value}`) wrap even without metadata; bare signal/store
|
|
24
|
+
* identifiers stay unwrapped.
|
|
25
25
|
*/
|
|
26
26
|
resolveImport?: (source: string, fromFile: string) => string | null;
|
|
27
27
|
}
|
package/docs/README.md
CHANGED
|
@@ -19,6 +19,8 @@ autoWrapComponents() plain `export function App()` → cc(App)
|
|
|
19
19
|
│
|
|
20
20
|
▼
|
|
21
21
|
wrapReactiveExpressions() {count.value} → {() => count.value}
|
|
22
|
+
{arr.map(x => n.value)} → {() => arr.map(...)}
|
|
23
|
+
also wraps those reads as children/attrs of <Label>
|
|
22
24
|
│
|
|
23
25
|
▼
|
|
24
26
|
hoist templates <div class="card">…</div> → _$tmpl_N
|
package/docs/plugins.md
CHANGED
|
@@ -51,6 +51,7 @@ The Vite plugin sets `resolve.dedupe: ["sinwan"]` for the same duplicate-runtime
|
|
|
51
51
|
| Option | Default | Role |
|
|
52
52
|
| ------ | ------- | ---- |
|
|
53
53
|
| `hoist` | `true` | Template hoisting |
|
|
54
|
+
| `dev` | Bun: `NODE_ENV !== "production"` at `sinwan()`; Vite: `config.mode !== "production"` | Warn when hoisting is skipped. Pass `false` for library/`Bun.build` production bundles. |
|
|
54
55
|
| `explicitBindings` | `false` | `_$bind*` helpers instead of bare `() =>` |
|
|
55
56
|
| `analyze` | unset | Path to CLI JSON (production) |
|
|
56
57
|
| `cache` | `true` | Incremental analyzer in dev. `false` turns it off. An object sets `root`, `tsConfigPath`, `bunfigPath`, `workspaces`, `cachePath` |
|
package/docs/transform.md
CHANGED
|
@@ -22,6 +22,8 @@ The compiler turns the child into a lazy getter:
|
|
|
22
22
|
<p>{() => count.value}</p>
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
+
The same wrap applies to **user-component children**. `<Label>{checked.value ? "On" : "Off"}</Label>` becomes `{() => checked.value ? "On" : "Off"}` so a one-shot `cc()` parent does not snapshot the string.
|
|
26
|
+
|
|
25
27
|
The runtime runs that function inside an effect and updates the text when `count` changes.
|
|
26
28
|
|
|
27
29
|
## Automatic `cc()` wrapping
|
|
@@ -70,12 +72,14 @@ Reads the compiler wraps:
|
|
|
70
72
|
- `useState` getters: `count()`
|
|
71
73
|
- `useFetch` signal values: `f.data.value` (not the shell `f` or `f.data`)
|
|
72
74
|
- Calls to a **local** function whose body reads one of the above
|
|
75
|
+
- Reads **inside call callbacks** that run while the JSX expression is evaluated: `.map` / `.filter` / `Array.from` callbacks, IIFEs, and default parameters of those callbacks. `<For each={keys.map((k) => groups[query.value])}>` becomes `each={() => keys.map(...)}`. Nested JSX inside those callbacks is a separate wrap site (`items.map((i) => <span>{count.value}</span>)` wraps the inner text, not the whole map).
|
|
73
76
|
|
|
74
77
|
Not wrapped:
|
|
75
78
|
|
|
76
79
|
- Literals and plain identifiers
|
|
77
80
|
- Event-handler functions (`onClick={fn}` or `onClick={() => …}`)
|
|
78
81
|
- Nested function *values* passed as props (`title={() => s.value}` stays a render prop)
|
|
82
|
+
- Functions stored as object/array values (`{ onClick: () => s.value }`) or returned from a factory (`const make = () => () => s.value`)
|
|
79
83
|
- `useFetch` methods such as `abort` / `execute`
|
|
80
84
|
- Static values the [analyzer](analyzer.md) proved never change
|
|
81
85
|
|
|
@@ -83,9 +87,12 @@ Not wrapped:
|
|
|
83
87
|
|
|
84
88
|
**Native elements** (`div`, `p`, `button`, …): wrap reactive children and attributes.
|
|
85
89
|
|
|
86
|
-
**User components** (`<Child title={…} />`):
|
|
90
|
+
**User components** (`<Label>`, `<Child title={…} />`):
|
|
91
|
+
|
|
92
|
+
- **Children:** wrap reactive reads the same way as DOM text (`{count.value}`, `{checked.value ? "On" : "Off"}`) so `cc()` parents do not snapshot the value. Use a getter (`() => …`), never `_$bindText`, because the child may render nodes as well as strings. Render-prop functions (`{(v) => …}`) stay untouched.
|
|
93
|
+
- **Props:** wrap a prop if it is known-reactive for that component (built-in registry, local call graph, analyzer metadata). For *unknown* imported components, still wrap **derived reads** (`htmlFor={id.value}`, `disabled={off.value}`) and leave **container pass-through** alone (`checked={checked}`, `user={user}`) so the child receives the signal or store proxy.
|
|
87
94
|
|
|
88
|
-
**Built-in control-flow** components wrap specific props, and they
|
|
95
|
+
**Built-in control-flow** components wrap specific props, and they wrap reactive expression children (those children render directly):
|
|
89
96
|
|
|
90
97
|
| Component | Reactive props |
|
|
91
98
|
| --------- | -------------- |
|