@pyreon/compiler 0.12.10 → 0.12.11
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/lib/analysis/index.js.html +1 -1
- package/lib/index.js +81 -12
- package/lib/index.js.map +1 -1
- package/lib/types/index.d.ts +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/jsx.ts +139 -20
- package/src/tests/jsx.test.ts +238 -2
|
@@ -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":"
|
|
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
|
|
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(() => ${
|
|
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
|
-
|
|
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)
|
|
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
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
|
|
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
|
-
|
|
489
|
-
|
|
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, "&").replace(/"/g, """);
|
|
815
884
|
}
|
|
816
885
|
function escapeHtmlText(s) {
|
|
817
|
-
return s.replace(
|
|
886
|
+
return s.replace(/&(?!(?:#\d+|#x[\da-fA-F]+|[a-zA-Z]\w*);)/g, "&").replace(/</g, "<");
|
|
818
887
|
}
|
|
819
888
|
function isStaticJSXNode(node) {
|
|
820
889
|
if (ts.isJsxSelfClosingElement(node)) return isStaticAttrs(node.attributes);
|