@pyreon/compiler 0.12.10 → 0.12.12

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.
@@ -5386,7 +5386,7 @@ var drawChart = (function (exports) {
5386
5386
  </script>
5387
5387
  <script>
5388
5388
  /*<!--*/
5389
- const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"1afeff4f-1","name":"jsx.ts"},{"uid":"1afeff4f-3","name":"project-scanner.ts"},{"uid":"1afeff4f-5","name":"react-intercept.ts"},{"uid":"1afeff4f-7","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"1afeff4f-1":{"renderedLength":34308,"gzipLength":9245,"brotliLength":0,"metaUid":"1afeff4f-0"},"1afeff4f-3":{"renderedLength":4762,"gzipLength":1730,"brotliLength":0,"metaUid":"1afeff4f-2"},"1afeff4f-5":{"renderedLength":27692,"gzipLength":6920,"brotliLength":0,"metaUid":"1afeff4f-4"},"1afeff4f-7":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"1afeff4f-6"}},"nodeMetas":{"1afeff4f-0":{"id":"/src/jsx.ts","moduleParts":{"index.js":"1afeff4f-1"},"imported":[{"uid":"1afeff4f-8"}],"importedBy":[{"uid":"1afeff4f-6"}]},"1afeff4f-2":{"id":"/src/project-scanner.ts","moduleParts":{"index.js":"1afeff4f-3"},"imported":[{"uid":"1afeff4f-9"},{"uid":"1afeff4f-10"}],"importedBy":[{"uid":"1afeff4f-6"}]},"1afeff4f-4":{"id":"/src/react-intercept.ts","moduleParts":{"index.js":"1afeff4f-5"},"imported":[{"uid":"1afeff4f-8"}],"importedBy":[{"uid":"1afeff4f-6"}]},"1afeff4f-6":{"id":"/src/index.ts","moduleParts":{"index.js":"1afeff4f-7"},"imported":[{"uid":"1afeff4f-0"},{"uid":"1afeff4f-2"},{"uid":"1afeff4f-4"}],"importedBy":[],"isEntry":true},"1afeff4f-8":{"id":"typescript","moduleParts":{},"imported":[],"importedBy":[{"uid":"1afeff4f-0"},{"uid":"1afeff4f-4"}]},"1afeff4f-9":{"id":"node:fs","moduleParts":{},"imported":[],"importedBy":[{"uid":"1afeff4f-2"}]},"1afeff4f-10":{"id":"node:path","moduleParts":{},"imported":[],"importedBy":[{"uid":"1afeff4f-2"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5389
+ const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"f7639ad9-1","name":"jsx.ts"},{"uid":"f7639ad9-3","name":"project-scanner.ts"},{"uid":"f7639ad9-5","name":"react-intercept.ts"},{"uid":"f7639ad9-7","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"f7639ad9-1":{"renderedLength":37147,"gzipLength":10065,"brotliLength":0,"metaUid":"f7639ad9-0"},"f7639ad9-3":{"renderedLength":4762,"gzipLength":1730,"brotliLength":0,"metaUid":"f7639ad9-2"},"f7639ad9-5":{"renderedLength":27692,"gzipLength":6920,"brotliLength":0,"metaUid":"f7639ad9-4"},"f7639ad9-7":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"f7639ad9-6"}},"nodeMetas":{"f7639ad9-0":{"id":"/src/jsx.ts","moduleParts":{"index.js":"f7639ad9-1"},"imported":[{"uid":"f7639ad9-8"}],"importedBy":[{"uid":"f7639ad9-6"}]},"f7639ad9-2":{"id":"/src/project-scanner.ts","moduleParts":{"index.js":"f7639ad9-3"},"imported":[{"uid":"f7639ad9-9"},{"uid":"f7639ad9-10"}],"importedBy":[{"uid":"f7639ad9-6"}]},"f7639ad9-4":{"id":"/src/react-intercept.ts","moduleParts":{"index.js":"f7639ad9-5"},"imported":[{"uid":"f7639ad9-8"}],"importedBy":[{"uid":"f7639ad9-6"}]},"f7639ad9-6":{"id":"/src/index.ts","moduleParts":{"index.js":"f7639ad9-7"},"imported":[{"uid":"f7639ad9-0"},{"uid":"f7639ad9-2"},{"uid":"f7639ad9-4"}],"importedBy":[],"isEntry":true},"f7639ad9-8":{"id":"typescript","moduleParts":{},"imported":[],"importedBy":[{"uid":"f7639ad9-0"},{"uid":"f7639ad9-4"}]},"f7639ad9-9":{"id":"node:fs","moduleParts":{},"imported":[],"importedBy":[{"uid":"f7639ad9-2"}]},"f7639ad9-10":{"id":"node:path","moduleParts":{},"imported":[],"importedBy":[{"uid":"f7639ad9-2"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
5390
5390
 
5391
5391
  const run = () => {
5392
5392
  const width = window.innerWidth;
package/lib/index.js CHANGED
@@ -86,6 +86,7 @@ function transformJSX(code, filename = "input.tsx") {
86
86
  let needsBindDirectImportGlobal = false;
87
87
  let needsBindImportGlobal = false;
88
88
  let needsApplyPropsImportGlobal = false;
89
+ let needsMountSlotImportGlobal = false;
89
90
  /**
90
91
  * If `node` is a fully-static JSX element/fragment, register a module-scope
91
92
  * hoist for it and return the generated variable name. Otherwise return null.
@@ -105,10 +106,12 @@ function transformJSX(code, filename = "input.tsx") {
105
106
  function wrap(expr) {
106
107
  const start = expr.getStart(sf);
107
108
  const end = expr.getEnd();
109
+ const sliced = sliceExpr(expr);
110
+ const text = ts.isObjectLiteralExpression(expr) ? `() => (${sliced})` : `() => ${sliced}`;
108
111
  replacements.push({
109
112
  start,
110
113
  end,
111
- text: `() => ${sliceExpr(expr)}`
114
+ text
112
115
  });
113
116
  }
114
117
  /** Try to hoist or wrap an expression, pushing a replacement if needed. */
@@ -176,10 +179,12 @@ function transformJSX(code, filename = "input.tsx") {
176
179
  else if (shouldWrap(expr)) {
177
180
  const start = expr.getStart(sf);
178
181
  const end = expr.getEnd();
182
+ const sliced = sliceExpr(expr);
183
+ const inner = ts.isObjectLiteralExpression(expr) ? `(${sliced})` : sliced;
179
184
  replacements.push({
180
185
  start,
181
186
  end,
182
- text: `_rp(() => ${sliceExpr(expr)})`
187
+ text: `_rp(() => ${inner})`
183
188
  });
184
189
  needsRpImport = true;
185
190
  }
@@ -264,6 +269,7 @@ function transformJSX(code, filename = "input.tsx") {
264
269
  if (!(node.declarationList.flags & ts.NodeFlags.Const)) continue;
265
270
  if (_callbackDepth > 0) continue;
266
271
  if (ts.isIdentifier(decl.name) && decl.initializer) {
272
+ if (isStatefulCall(decl.initializer)) continue;
267
273
  if (readsFromProps(decl.initializer)) propDerivedVars.set(decl.name.text, decl.initializer);
268
274
  }
269
275
  }
@@ -300,15 +306,28 @@ function transformJSX(code, filename = "input.tsx") {
300
306
  }
301
307
  });
302
308
  }
303
- function resolveExprTransitive(node, excludeVar) {
309
+ const warnedCycles = /* @__PURE__ */ new Set();
310
+ function resolveExprTransitive(node, visited = /* @__PURE__ */ new Set(), sourceNode) {
304
311
  return ts.visitNode(node, function visit(n) {
305
- if (ts.isIdentifier(n) && propDerivedVars.has(n.text) && n.text !== excludeVar) {
312
+ if (ts.isIdentifier(n) && propDerivedVars.has(n.text)) {
313
+ if (visited.has(n.text)) {
314
+ const cycleKey = [...visited, n.text].sort().join(",");
315
+ if (!warnedCycles.has(cycleKey)) {
316
+ warnedCycles.add(cycleKey);
317
+ const chain = [...visited, n.text].join(" → ");
318
+ warn(sourceNode ?? n, `[Pyreon] Circular prop-derived const reference: ${chain}. The cyclic identifier \`${n.text}\` will use its captured value instead of being reactively inlined. Break the cycle by reading from \`props.*\` directly or restructuring the derivation chain.`, "circular-prop-derived");
319
+ }
320
+ return n;
321
+ }
306
322
  const parent = n.parent;
307
- if (parent && ts.isPropertyAccessExpression(parent) && parent.name === n) return n;
308
- if (parent && ts.isJsxAttribute(parent) && parent.name === n) return n;
309
- if (parent && ts.isShorthandPropertyAssignment(parent)) return n;
323
+ if (parent) {
324
+ if ("name" in parent && parent.name === n) return n;
325
+ if (ts.isShorthandPropertyAssignment(parent)) return n;
326
+ }
310
327
  const resolved = propDerivedVars.get(n.text);
311
- return ts.factory.createParenthesizedExpression(resolveExprTransitive(resolved, n.text));
328
+ const nextVisited = new Set(visited);
329
+ nextVisited.add(n.text);
330
+ return ts.factory.createParenthesizedExpression(resolveExprTransitive(resolved, nextVisited, sourceNode));
312
331
  }
313
332
  return ts.visitEachChild(n, visit, void 0);
314
333
  });
@@ -382,6 +401,7 @@ function transformJSX(code, filename = "input.tsx") {
382
401
  if (needsBindDirectImportGlobal) runtimeDomImports.push("_bindDirect");
383
402
  if (needsBindTextImportGlobal) runtimeDomImports.push("_bindText");
384
403
  if (needsApplyPropsImportGlobal) runtimeDomImports.push("_applyProps");
404
+ if (needsMountSlotImportGlobal) runtimeDomImports.push("_mountSlot");
385
405
  const reactivityImports = needsBindImportGlobal ? `\nimport { _bind } from "@pyreon/reactivity";` : "";
386
406
  result = `import { ${runtimeDomImports.join(", ")} } from "@pyreon/runtime-dom";${reactivityImports}\n` + result;
387
407
  }
@@ -461,6 +481,7 @@ function transformJSX(code, filename = "input.tsx") {
461
481
  let needsBindTextImport = false;
462
482
  let needsBindDirectImport = false;
463
483
  let needsApplyPropsImport = false;
484
+ let needsMountSlotImport = false;
464
485
  function nextVar() {
465
486
  return `__e${varIdx++}`;
466
487
  }
@@ -485,8 +506,10 @@ function transformJSX(code, filename = "input.tsx") {
485
506
  /** Emit bind line for a ref attribute. */
486
507
  function emitRef(attr, varName) {
487
508
  if (!attr.initializer || !ts.isJsxExpression(attr.initializer)) return;
488
- if (!attr.initializer.expression) return;
489
- bindLines.push(`${sliceExpr(attr.initializer.expression)}.current = ${varName}`);
509
+ const expr = attr.initializer.expression;
510
+ if (!expr) return;
511
+ if (ts.isArrowFunction(expr) || ts.isFunctionExpression(expr)) bindLines.push(`(${sliceExpr(expr)})(${varName})`);
512
+ else bindLines.push(`{ const __r = ${sliceExpr(expr)}; if (typeof __r === "function") __r(${varName}); else if (__r) __r.current = ${varName} }`);
490
513
  }
491
514
  /** Emit event handler bind line — delegated (expando) or addEventListener. */
492
515
  function emitEventListener(attr, attrName, varName) {
@@ -646,6 +669,13 @@ function transformJSX(code, filename = "input.tsx") {
646
669
  }
647
670
  const needsPlaceholder = useMixed || useMultiExpr;
648
671
  const { expr, isReactive } = unwrapAccessor(child.expression);
672
+ if (isChildrenExpression(child.expression, expr)) {
673
+ needsMountSlotImport = true;
674
+ const placeholder = `${parentRef}.childNodes[${childNodeIdx}]`;
675
+ const d = nextDisp();
676
+ bindLines.push(`const ${d} = _mountSlot(${expr}, ${parentRef}, ${placeholder})`);
677
+ return "<!>";
678
+ }
649
679
  if (isReactive) return emitReactiveTextChild(expr, child.expression, varName, parentRef, childNodeIdx, needsPlaceholder);
650
680
  return emitStaticTextChild(expr, varName, parentRef, childNodeIdx, needsPlaceholder);
651
681
  }
@@ -683,6 +713,7 @@ function transformJSX(code, filename = "input.tsx") {
683
713
  if (needsBindTextImport) needsBindTextImportGlobal = true;
684
714
  if (needsBindDirectImport) needsBindDirectImportGlobal = true;
685
715
  if (needsApplyPropsImport) needsApplyPropsImportGlobal = true;
716
+ if (needsMountSlotImport) needsMountSlotImportGlobal = true;
686
717
  const escaped = html.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
687
718
  if (reactiveBindExprs.length > 0) {
688
719
  needsBindImportGlobal = true;
@@ -767,7 +798,7 @@ function transformJSX(code, filename = "input.tsx") {
767
798
  * via AST transformation — handles template literals, ternaries, etc. */
768
799
  function sliceExpr(expr) {
769
800
  if (propDerivedVars.size > 0 && accessesProps(expr)) {
770
- const resolved = resolveExprTransitive(expr);
801
+ const resolved = resolveExprTransitive(expr, /* @__PURE__ */ new Set(), expr);
771
802
  return printer.printNode(ts.EmitHint.Expression, resolved, sf);
772
803
  }
773
804
  return code.slice(expr.getStart(sf), expr.getEnd());
@@ -802,6 +833,44 @@ const JSX_TO_HTML_ATTR = {
802
833
  className: "class",
803
834
  htmlFor: "for"
804
835
  };
836
+ /**
837
+ * Detect if an expression is a stateful call that must NOT be inlined.
838
+ * signal(), computed(), effect() etc. create state — inlining them would
839
+ * create new instances at each use site instead of referencing the original.
840
+ */
841
+ const STATEFUL_CALLS = new Set([
842
+ "signal",
843
+ "computed",
844
+ "effect",
845
+ "batch",
846
+ "createContext",
847
+ "createReactiveContext",
848
+ "useContext",
849
+ "useRef",
850
+ "createRef",
851
+ "useForm",
852
+ "useQuery",
853
+ "useMutation",
854
+ "defineStore",
855
+ "useStore"
856
+ ]);
857
+ function isStatefulCall(node) {
858
+ if (!ts.isCallExpression(node)) return false;
859
+ const callee = node.expression;
860
+ if (ts.isIdentifier(callee)) return STATEFUL_CALLS.has(callee.text);
861
+ return false;
862
+ }
863
+ /**
864
+ * Detect if an expression accesses `.children` — these can contain VNodes
865
+ * and must use _mountSlot instead of text node binding in templates.
866
+ * Matches: props.children, own.children, x.children, or bare `children` identifier.
867
+ */
868
+ function isChildrenExpression(node, expr) {
869
+ if (ts.isPropertyAccessExpression(node) && node.name.text === "children") return true;
870
+ if (ts.isIdentifier(node) && node.text === "children") return true;
871
+ if (expr.endsWith(".children") || expr === "children") return true;
872
+ return false;
873
+ }
805
874
  function isLowerCase(s) {
806
875
  return s.length > 0 && s[0] === s[0]?.toLowerCase();
807
876
  }
@@ -814,7 +883,7 @@ function escapeHtmlAttr(s) {
814
883
  return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
815
884
  }
816
885
  function escapeHtmlText(s) {
817
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;");
886
+ return s.replace(/&(?!(?:#\d+|#x[\da-fA-F]+|[a-zA-Z]\w*);)/g, "&amp;").replace(/</g, "&lt;");
818
887
  }
819
888
  function isStaticJSXNode(node) {
820
889
  if (ts.isJsxSelfClosingElement(node)) return isStaticAttrs(node.attributes);