sinwan-compiler 0.2.8 → 0.2.10

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.
@@ -300,6 +300,35 @@ function analyzeObjectLiteralSpread(spread, fullScope, calleeReactive) {
300
300
  function isMemberLike(node) {
301
301
  return t.isMemberExpression(node) || t.isOptionalMemberExpression(node);
302
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
+ }
303
332
  function getMemberExpressionRootAndPath(node) {
304
333
  const path = [];
305
334
  let current = node;
@@ -437,11 +466,23 @@ function containsReactiveValue2(expr, scope) {
437
466
  }
438
467
  function containsReactiveRead(expr, scope) {
439
468
  let found = false;
440
- function visit(node) {
469
+ function visit(node, enterFunction) {
441
470
  if (found)
442
471
  return;
443
472
  if (!node || typeof node !== "object")
444
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
+ }
445
486
  if (isMemberLike(node) || t.isTSNonNullExpression(node) || t.isIdentifier(node)) {
446
487
  if (isReactiveRead(node, scope)) {
447
488
  found = true;
@@ -452,7 +493,8 @@ function containsReactiveRead(expr, scope) {
452
493
  found = true;
453
494
  return;
454
495
  }
455
- if (t.isArrowFunctionExpression(node) || t.isFunctionExpression(node)) {
496
+ if (isCallLike(node)) {
497
+ visitCallCalleeAndArgs(node, visit);
456
498
  return;
457
499
  }
458
500
  for (const key of Object.keys(node)) {
@@ -462,14 +504,14 @@ function containsReactiveRead(expr, scope) {
462
504
  if (Array.isArray(value)) {
463
505
  for (const item of value) {
464
506
  if (item && typeof item === "object")
465
- visit(item);
507
+ visit(item, false);
466
508
  }
467
509
  } else if (value && typeof value === "object") {
468
- visit(value);
510
+ visit(value, false);
469
511
  }
470
512
  }
471
513
  }
472
- visit(expr);
514
+ visit(expr, false);
473
515
  return found;
474
516
  }
475
517
  function getComponentExpressionInfo(exprPath) {
@@ -561,11 +603,21 @@ function transformPropMemberAccess(expr, scope) {
561
603
  parent.object = resolveRoot(rootName);
562
604
  }
563
605
  }
564
- function visit(node) {
606
+ function visit(node, enterFunction) {
565
607
  if (!node || typeof node !== "object")
566
608
  return;
567
- if (t.isArrowFunctionExpression(node) || t.isFunctionExpression(node))
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);
616
+ return;
617
+ }
618
+ if (t.isJSXElement(node) || t.isJSXFragment(node)) {
568
619
  return;
620
+ }
569
621
  if (isMemberLike(node)) {
570
622
  const rootPath = getMemberExpressionRootAndPath(node);
571
623
  if (rootPath) {
@@ -578,6 +630,10 @@ function transformPropMemberAccess(expr, scope) {
578
630
  }
579
631
  return;
580
632
  }
633
+ if (isCallLike(node)) {
634
+ visitCallCalleeAndArgs(node, visit);
635
+ return;
636
+ }
581
637
  for (const key of Object.keys(node)) {
582
638
  if (key === "loc" || key === "start" || key === "end" || key === "leadingComments" || key === "trailingComments")
583
639
  continue;
@@ -585,14 +641,14 @@ function transformPropMemberAccess(expr, scope) {
585
641
  if (Array.isArray(value)) {
586
642
  for (const item of value) {
587
643
  if (item && typeof item === "object")
588
- visit(item);
644
+ visit(item, false);
589
645
  }
590
646
  } else if (value && typeof value === "object") {
591
- visit(value);
647
+ visit(value, false);
592
648
  }
593
649
  }
594
650
  }
595
- visit(expr);
651
+ visit(expr, false);
596
652
  return changed;
597
653
  }
598
654
  function wrapExpression(expr) {
@@ -604,6 +660,10 @@ function createBindingDescriptor(expr, exprPath, options) {
604
660
  }
605
661
  const parent = exprPath.parentPath;
606
662
  if (parent && parent.isJSXAttribute && parent.isJSXAttribute()) {
663
+ const compInfo = getComponentExpressionInfo(exprPath);
664
+ if (compInfo.isComponent) {
665
+ return wrapExpression(expr);
666
+ }
607
667
  const attrName = parent.node.name.name;
608
668
  if (attrName === "style") {
609
669
  return t.callExpression(t.identifier("_$bindStyle"), [
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@
3
3
  import {
4
4
  runAnalyzeCli2,
5
5
  runCliIfMain2
6
- } from "./chunk-v4zrz7j7.js";
6
+ } from "./chunk-qj8m16x2.js";
7
7
  export {
8
8
  runAnalyzeCli2 as runAnalyzeCli,
9
9
  runCliIfMain2 as runCliIfMain
package/dist/index.js CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  propagateReactiveProps2,
18
18
  wrapReactiveExpressions2,
19
19
  runAnalyzeCli2
20
- } from "./chunk-v4zrz7j7.js";
20
+ } from "./chunk-qj8m16x2.js";
21
21
 
22
22
  // src/transform.ts
23
23
  import { parse } from "@babel/parser";
package/docs/README.md CHANGED
@@ -19,6 +19,7 @@ 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(...)}
22
23
  also wraps those reads as children/attrs of <Label>
23
24
  │
24
25
  ▼
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
@@ -72,12 +72,14 @@ Reads the compiler wraps:
72
72
  - `useState` getters: `count()`
73
73
  - `useFetch` signal values: `f.data.value` (not the shell `f` or `f.data`)
74
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).
75
76
 
76
77
  Not wrapped:
77
78
 
78
79
  - Literals and plain identifiers
79
80
  - Event-handler functions (`onClick={fn}` or `onClick={() => …}`)
80
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`)
81
83
  - `useFetch` methods such as `abort` / `execute`
82
84
  - Static values the [analyzer](analyzer.md) proved never change
83
85
 
@@ -88,7 +90,7 @@ Not wrapped:
88
90
  **User components** (`<Label>`, `<Child title={…} />`):
89
91
 
90
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.
91
- - **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.
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. With `explicitBindings`, component props stay **getters** (`each={() => …}`), never `_$bindAttr` — `For` / `Show` / `Label` call `resolve(prop)`, which does not unwrap bind descriptors.
92
94
 
93
95
  **Built-in control-flow** components wrap specific props, and they wrap reactive expression children (those children render directly):
94
96
 
@@ -139,6 +141,6 @@ transformJSX(code, "src/App.tsx", {
139
141
 
140
142
  - **`analyze` / `analyzeMetadata`** — cross-file reactive props. Invalid JSON is ignored (transform still succeeds).
141
143
  - **`resolveImport`** — maps `./Child` to an absolute file so imported components can use analyzer metadata. Without it, imported call sites are not wrapped at the parent.
142
- - **`explicitBindings`** — wrap with `_$bindText` / `_$bindAttr` / `_$bindStyle` / `_$bindClass` from `sinwan/renderer` instead of a bare `() => …`.
144
+ - **`explicitBindings`** — wrap **native** text/attrs with `_$bindText` / `_$bindAttr` / `_$bindStyle` / `_$bindClass`. Component props and component children stay `() => …` so `resolve(prop)` still sees a getter.
143
145
 
144
146
  Returns `{ code, map }` with a source map.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sinwan-compiler",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
4
4
  "description": "Shared compiler core for Sinwan plugins",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",