@pyreon/compiler 0.19.0 → 0.21.0
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 +418 -18
- package/lib/types/index.d.ts +92 -1
- package/package.json +13 -12
- package/src/index.ts +2 -1
- package/src/jsx.ts +669 -17
- package/src/tests/backend-parity-r7-r9.test.ts +91 -0
- package/src/tests/backend-prop-derived-callback-divergence.test.ts +74 -0
- package/src/tests/collapse-bail-census.test.ts +245 -0
- package/src/tests/collapse-key-source-hygiene.test.ts +88 -0
- package/src/tests/element-valued-const-child.test.ts +61 -0
- package/src/tests/falsy-child-characterization.test.ts +48 -0
- package/src/tests/malformed-input-resilience.test.ts +50 -0
- package/src/tests/partial-collapse-detector.test.ts +121 -0
- package/src/tests/partial-collapse-emit.test.ts +104 -0
- package/src/tests/partial-collapse-robustness.test.ts +53 -0
- package/src/tests/prop-derived-shadow.test.ts +96 -0
- package/src/tests/pure-call-reactive-args.test.ts +50 -0
- package/src/tests/r13-callback-stmt-equivalence.test.ts +58 -0
- package/src/tests/r14-ssr-mode-parity.test.ts +51 -0
- package/src/tests/r15-elemconst-propderived.test.ts +47 -0
- package/src/tests/r19-defer-inline-robust.test.ts +54 -0
- package/src/tests/r20-backend-equivalence-sweep.test.ts +50 -0
- package/src/tests/rocketstyle-collapse.test.ts +208 -0
- package/src/tests/signal-autocall-shadow.test.ts +86 -0
- package/src/tests/sourcemap-fidelity.test.ts +77 -0
- package/src/tests/static-text-baking.test.ts +64 -0
- package/src/tests/transform-state-isolation.test.ts +49 -0
|
@@ -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":"5a08f146-1","name":"defer-inline.ts"},{"uid":"5a08f146-3","name":"event-names.ts"},{"uid":"5a08f146-5","name":"load-native.ts"},{"uid":"5a08f146-7","name":"jsx.ts"},{"uid":"5a08f146-9","name":"pyreon-intercept.ts"},{"uid":"5a08f146-11","name":"reactivity-lens.ts"},{"uid":"5a08f146-13","name":"project-scanner.ts"},{"uid":"5a08f146-15","name":"react-intercept.ts"},{"uid":"5a08f146-17","name":"test-audit.ts"},{"uid":"5a08f146-19","name":"island-audit.ts"},{"uid":"5a08f146-21","name":"ssg-audit.ts"},{"uid":"5a08f146-23","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"5a08f146-1":{"renderedLength":15789,"gzipLength":5000,"brotliLength":0,"metaUid":"5a08f146-0"},"5a08f146-3":{"renderedLength":2941,"gzipLength":1335,"brotliLength":0,"metaUid":"5a08f146-2"},"5a08f146-5":{"renderedLength":3959,"gzipLength":1744,"brotliLength":0,"metaUid":"5a08f146-4"},"5a08f146-7":{"renderedLength":63671,"gzipLength":15676,"brotliLength":0,"metaUid":"5a08f146-6"},"5a08f146-9":{"renderedLength":29316,"gzipLength":9273,"brotliLength":0,"metaUid":"5a08f146-8"},"5a08f146-11":{"renderedLength":4373,"gzipLength":2130,"brotliLength":0,"metaUid":"5a08f146-10"},"5a08f146-13":{"renderedLength":4762,"gzipLength":1730,"brotliLength":0,"metaUid":"5a08f146-12"},"5a08f146-15":{"renderedLength":28896,"gzipLength":7322,"brotliLength":0,"metaUid":"5a08f146-14"},"5a08f146-17":{"renderedLength":13167,"gzipLength":5060,"brotliLength":0,"metaUid":"5a08f146-16"},"5a08f146-19":{"renderedLength":18208,"gzipLength":6051,"brotliLength":0,"metaUid":"5a08f146-18"},"5a08f146-21":{"renderedLength":12753,"gzipLength":4173,"brotliLength":0,"metaUid":"5a08f146-20"},"5a08f146-23":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"5a08f146-22"}},"nodeMetas":{"5a08f146-0":{"id":"/src/defer-inline.ts","moduleParts":{"index.js":"5a08f146-1"},"imported":[{"uid":"5a08f146-24"}],"importedBy":[{"uid":"5a08f146-22"}]},"5a08f146-2":{"id":"/src/event-names.ts","moduleParts":{"index.js":"5a08f146-3"},"imported":[],"importedBy":[{"uid":"5a08f146-6"}]},"5a08f146-4":{"id":"/src/load-native.ts","moduleParts":{"index.js":"5a08f146-5"},"imported":[{"uid":"5a08f146-29"},{"uid":"5a08f146-30"},{"uid":"5a08f146-27"}],"importedBy":[{"uid":"5a08f146-6"}]},"5a08f146-6":{"id":"/src/jsx.ts","moduleParts":{"index.js":"5a08f146-7"},"imported":[{"uid":"5a08f146-25"},{"uid":"5a08f146-24"},{"uid":"5a08f146-2"},{"uid":"5a08f146-4"}],"importedBy":[{"uid":"5a08f146-22"},{"uid":"5a08f146-10"}]},"5a08f146-8":{"id":"/src/pyreon-intercept.ts","moduleParts":{"index.js":"5a08f146-9"},"imported":[{"uid":"5a08f146-28"}],"importedBy":[{"uid":"5a08f146-22"},{"uid":"5a08f146-10"}]},"5a08f146-10":{"id":"/src/reactivity-lens.ts","moduleParts":{"index.js":"5a08f146-11"},"imported":[{"uid":"5a08f146-6"},{"uid":"5a08f146-8"}],"importedBy":[{"uid":"5a08f146-22"}]},"5a08f146-12":{"id":"/src/project-scanner.ts","moduleParts":{"index.js":"5a08f146-13"},"imported":[{"uid":"5a08f146-26"},{"uid":"5a08f146-27"}],"importedBy":[{"uid":"5a08f146-22"}]},"5a08f146-14":{"id":"/src/react-intercept.ts","moduleParts":{"index.js":"5a08f146-15"},"imported":[{"uid":"5a08f146-28"}],"importedBy":[{"uid":"5a08f146-22"}]},"5a08f146-16":{"id":"/src/test-audit.ts","moduleParts":{"index.js":"5a08f146-17"},"imported":[{"uid":"5a08f146-26"},{"uid":"5a08f146-27"}],"importedBy":[{"uid":"5a08f146-22"}]},"5a08f146-18":{"id":"/src/island-audit.ts","moduleParts":{"index.js":"5a08f146-19"},"imported":[{"uid":"5a08f146-26"},{"uid":"5a08f146-27"},{"uid":"5a08f146-28"}],"importedBy":[{"uid":"5a08f146-22"}]},"5a08f146-20":{"id":"/src/ssg-audit.ts","moduleParts":{"index.js":"5a08f146-21"},"imported":[{"uid":"5a08f146-26"},{"uid":"5a08f146-27"},{"uid":"5a08f146-28"}],"importedBy":[{"uid":"5a08f146-22"}]},"5a08f146-22":{"id":"/src/index.ts","moduleParts":{"index.js":"5a08f146-23"},"imported":[{"uid":"5a08f146-0"},{"uid":"5a08f146-6"},{"uid":"5a08f146-10"},{"uid":"5a08f146-12"},{"uid":"5a08f146-14"},{"uid":"5a08f146-8"},{"uid":"5a08f146-16"},{"uid":"5a08f146-18"},{"uid":"5a08f146-20"}],"importedBy":[],"isEntry":true},"5a08f146-24":{"id":"oxc-parser","moduleParts":{},"imported":[],"importedBy":[{"uid":"5a08f146-0"},{"uid":"5a08f146-6"}]},"5a08f146-25":{"id":"magic-string","moduleParts":{},"imported":[],"importedBy":[{"uid":"5a08f146-6"}]},"5a08f146-26":{"id":"node:fs","moduleParts":{},"imported":[],"importedBy":[{"uid":"5a08f146-12"},{"uid":"5a08f146-16"},{"uid":"5a08f146-18"},{"uid":"5a08f146-20"}]},"5a08f146-27":{"id":"node:path","moduleParts":{},"imported":[],"importedBy":[{"uid":"5a08f146-12"},{"uid":"5a08f146-16"},{"uid":"5a08f146-18"},{"uid":"5a08f146-20"},{"uid":"5a08f146-4"}]},"5a08f146-28":{"id":"typescript","moduleParts":{},"imported":[],"importedBy":[{"uid":"5a08f146-14"},{"uid":"5a08f146-8"},{"uid":"5a08f146-18"},{"uid":"5a08f146-20"}]},"5a08f146-29":{"id":"node:module","moduleParts":{},"imported":[],"importedBy":[{"uid":"5a08f146-4"}]},"5a08f146-30":{"id":"node:url","moduleParts":{},"imported":[],"importedBy":[{"uid":"5a08f146-4"}]}},"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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { parseSync } from "oxc-parser";
|
|
2
|
+
import MagicString from "magic-string";
|
|
2
3
|
import { createRequire } from "node:module";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
4
5
|
import * as path from "node:path";
|
|
@@ -657,6 +658,21 @@ const DELEGATED_EVENTS = new Set([
|
|
|
657
658
|
"touchmove",
|
|
658
659
|
"submit"
|
|
659
660
|
]);
|
|
661
|
+
/**
|
|
662
|
+
* Canonical key for a collapsible rocketstyle call site. The Vite plugin
|
|
663
|
+
* computes this when it resolves a site; the compiler recomputes the
|
|
664
|
+
* IDENTICAL key from the JSX node to look the resolution up. Stable
|
|
665
|
+
* ordering of props so attribute order in source doesn't change the key.
|
|
666
|
+
*/
|
|
667
|
+
function rocketstyleCollapseKey(componentName, props, childrenText) {
|
|
668
|
+
const src = `${componentName}\u0000${Object.keys(props).sort().map((k) => `${k}=${props[k]}`).join("")}\u0000${childrenText}`;
|
|
669
|
+
let h = 2166136261;
|
|
670
|
+
for (let i = 0; i < src.length; i++) {
|
|
671
|
+
h ^= src.charCodeAt(i);
|
|
672
|
+
h = Math.imul(h, 16777619);
|
|
673
|
+
}
|
|
674
|
+
return (h >>> 0).toString(36);
|
|
675
|
+
}
|
|
660
676
|
function getLang(filename) {
|
|
661
677
|
if (filename.endsWith(".jsx")) return "jsx";
|
|
662
678
|
return "tsx";
|
|
@@ -709,7 +725,163 @@ function jsxAttrs(node) {
|
|
|
709
725
|
function jsxChildren(node) {
|
|
710
726
|
return node.children ?? [];
|
|
711
727
|
}
|
|
728
|
+
/**
|
|
729
|
+
* Build a `localName → { imported, source }` table from a module's
|
|
730
|
+
* import declarations. Only named imports (`import { X as Y }`) are
|
|
731
|
+
* relevant — the collapsible components are always named exports.
|
|
732
|
+
*/
|
|
733
|
+
function collectImportTable(program) {
|
|
734
|
+
const table = /* @__PURE__ */ new Map();
|
|
735
|
+
for (const stmt of program.body ?? []) {
|
|
736
|
+
if (stmt.type !== "ImportDeclaration") continue;
|
|
737
|
+
const source = stmt.source?.value;
|
|
738
|
+
if (typeof source !== "string") continue;
|
|
739
|
+
for (const spec of stmt.specifiers ?? []) {
|
|
740
|
+
if (spec.type !== "ImportSpecifier") continue;
|
|
741
|
+
const local = spec.local?.name;
|
|
742
|
+
const imported = spec.imported?.name ?? local;
|
|
743
|
+
if (typeof local === "string") table.set(local, {
|
|
744
|
+
imported,
|
|
745
|
+
source
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
return table;
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Pure detector — finds every collapsible rocketstyle call site in a
|
|
753
|
+
* module. Used by `@pyreon/vite-plugin` to know which (component, props,
|
|
754
|
+
* text) tuples to SSR-resolve. The bail catalogue here MUST stay
|
|
755
|
+
* byte-identical to `tryRocketstyleCollapse`'s (RFC decision 3): a
|
|
756
|
+
* candidate PascalCase tag whose import source is in `collapsibleSources`,
|
|
757
|
+
* every attr a plain string literal (no spread, no `{expr}`, no boolean
|
|
758
|
+
* attr), children empty or static text only. A consistency test asserts
|
|
759
|
+
* the keys this produces equal the keys the compiler looks up.
|
|
760
|
+
*/
|
|
761
|
+
function scanCollapsibleSites(code, filename, collapsibleSources) {
|
|
762
|
+
let program;
|
|
763
|
+
try {
|
|
764
|
+
program = parseSync(filename, code, {
|
|
765
|
+
sourceType: "module",
|
|
766
|
+
lang: getLang(filename)
|
|
767
|
+
}).program;
|
|
768
|
+
} catch {
|
|
769
|
+
return [];
|
|
770
|
+
}
|
|
771
|
+
const imports = collectImportTable(program);
|
|
772
|
+
const out = [];
|
|
773
|
+
const visit = (node) => {
|
|
774
|
+
if (!node || typeof node !== "object") return;
|
|
775
|
+
if (node.type === "JSXElement") {
|
|
776
|
+
const tag = jsxTagName(node);
|
|
777
|
+
const imp = tag ? imports.get(tag) : void 0;
|
|
778
|
+
if (tag && tag.charAt(0) !== tag.charAt(0).toLowerCase() && imp && collapsibleSources.has(imp.source)) {
|
|
779
|
+
const site = detectCollapsibleShape(node, tag);
|
|
780
|
+
if (site) out.push({
|
|
781
|
+
componentName: tag,
|
|
782
|
+
source: imp.source,
|
|
783
|
+
importedName: imp.imported,
|
|
784
|
+
props: site.props,
|
|
785
|
+
childrenText: site.childrenText,
|
|
786
|
+
key: rocketstyleCollapseKey(tag, site.props, site.childrenText)
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
for (const k in node) {
|
|
791
|
+
const v = node[k];
|
|
792
|
+
if (Array.isArray(v)) for (const c of v) visit(c);
|
|
793
|
+
else if (v && typeof v === "object" && typeof v.type === "string") visit(v);
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
visit(program);
|
|
797
|
+
return out;
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* The shared bail catalogue — every attr a string literal (no spread, no
|
|
801
|
+
* `{expr}`, no boolean attr), children empty or static text. Returns the
|
|
802
|
+
* extracted {props, childrenText} or null (bail). `tryRocketstyleCollapse`
|
|
803
|
+
* inlines the identical checks; a consistency test locks them together.
|
|
804
|
+
*/
|
|
805
|
+
function detectCollapsibleShape(node, _tag) {
|
|
806
|
+
const props = {};
|
|
807
|
+
for (const attr of jsxAttrs(node)) {
|
|
808
|
+
if (attr.type !== "JSXAttribute") return null;
|
|
809
|
+
const nm = attr.name?.type === "JSXIdentifier" ? attr.name.name : null;
|
|
810
|
+
if (!nm) return null;
|
|
811
|
+
const v = attr.value;
|
|
812
|
+
if (!v) return null;
|
|
813
|
+
if (!(v.type === "StringLiteral" || v.type === "Literal" && typeof v.value === "string")) return null;
|
|
814
|
+
props[nm] = String(v.value);
|
|
815
|
+
}
|
|
816
|
+
let childrenText = "";
|
|
817
|
+
for (const c of jsxChildren(node)) if (c.type === "JSXText") childrenText += c.value ?? "";
|
|
818
|
+
else return null;
|
|
819
|
+
return {
|
|
820
|
+
props,
|
|
821
|
+
childrenText: childrenText.trim()
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Partial-collapse detector — PR 1 of the partial-collapse spec
|
|
826
|
+
* (`.claude/plans/open-work-2026-q3.md` → #1). The `on*`-handler-only
|
|
827
|
+
* subset the bail-reason census measured at 7.8% of all
|
|
828
|
+
* `@pyreon/ui-components` call sites (`collapse-bail-census.test.ts`).
|
|
829
|
+
*
|
|
830
|
+
* It is the EXACT `detectCollapsibleShape` bail catalogue with ONE
|
|
831
|
+
* relaxation: a `{expr}`-valued attribute whose name matches `on[A-Z]…`
|
|
832
|
+
* (an event handler) does NOT bail — it is peeled into `handlers[]`
|
|
833
|
+
* instead. Handlers are orthogonal to the SSR-resolved styler class (an
|
|
834
|
+
* event binding never changes rendered CSS), so the literal-prop subset
|
|
835
|
+
* still feeds the UNCHANGED `rocketstyleCollapseKey` and the resolver's
|
|
836
|
+
* pre-resolved `templateHtml` / `lightClass` / `darkClass` are
|
|
837
|
+
* byte-identical to a full-collapse site's. The collapsed runtime node
|
|
838
|
+
* just re-attaches the residual handlers (PR 2 — `_rsCollapseH`).
|
|
839
|
+
*
|
|
840
|
+
* Every OTHER non-literal shape still bails (spread, non-handler
|
|
841
|
+
* `{expr}` prop, boolean attr, element/expression child) — conservative
|
|
842
|
+
* by construction, exactly like the full detector. Returns `null` when
|
|
843
|
+
* there are ZERO handlers so the full-collapse path stays byte-unchanged
|
|
844
|
+
* and the two detectors never both claim the same site (full-collapse
|
|
845
|
+
* sites have no handlers; partial sites have ≥1). A consistency test
|
|
846
|
+
* will lock this catalogue against the plugin scan in PR 3, mirroring
|
|
847
|
+
* the existing `detectCollapsibleShape` ↔ `scanCollapsibleSites`
|
|
848
|
+
* invariant — keys cannot drift.
|
|
849
|
+
*/
|
|
850
|
+
function detectPartialCollapsibleShape(node, _tag) {
|
|
851
|
+
const props = {};
|
|
852
|
+
const handlers = [];
|
|
853
|
+
for (const attr of jsxAttrs(node)) {
|
|
854
|
+
if (attr.type !== "JSXAttribute") return null;
|
|
855
|
+
const nm = attr.name?.type === "JSXIdentifier" ? attr.name.name : null;
|
|
856
|
+
if (!nm) return null;
|
|
857
|
+
const v = attr.value;
|
|
858
|
+
if (!v) return null;
|
|
859
|
+
if (v.type === "StringLiteral" || v.type === "Literal" && typeof v.value === "string") {
|
|
860
|
+
props[nm] = String(v.value);
|
|
861
|
+
continue;
|
|
862
|
+
}
|
|
863
|
+
if (/^on[A-Z]/.test(nm) && v.type === "JSXExpressionContainer" && v.expression && typeof v.expression.start === "number" && typeof v.expression.end === "number") {
|
|
864
|
+
handlers.push({
|
|
865
|
+
name: nm,
|
|
866
|
+
exprStart: v.expression.start,
|
|
867
|
+
exprEnd: v.expression.end
|
|
868
|
+
});
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
871
|
+
return null;
|
|
872
|
+
}
|
|
873
|
+
let childrenText = "";
|
|
874
|
+
for (const c of jsxChildren(node)) if (c.type === "JSXText") childrenText += c.value ?? "";
|
|
875
|
+
else return null;
|
|
876
|
+
if (handlers.length === 0) return null;
|
|
877
|
+
return {
|
|
878
|
+
props,
|
|
879
|
+
childrenText: childrenText.trim(),
|
|
880
|
+
handlers
|
|
881
|
+
};
|
|
882
|
+
}
|
|
712
883
|
function transformJSX(code, filename = "input.tsx", options = {}) {
|
|
884
|
+
if (options.collapseRocketstyle) return transformJSX_JS(code, filename, options);
|
|
713
885
|
if (nativeTransformJsx) try {
|
|
714
886
|
return nativeTransformJsx(code, filename, options.ssr === true, options.knownSignals ?? null, options.reactivityLens === true);
|
|
715
887
|
} catch {}
|
|
@@ -801,6 +973,102 @@ function transformJSX_JS(code, filename = "input.tsx", options = {}) {
|
|
|
801
973
|
let needsBindImportGlobal = false;
|
|
802
974
|
let needsApplyPropsImportGlobal = false;
|
|
803
975
|
let needsMountSlotImportGlobal = false;
|
|
976
|
+
let needsCollapse = false;
|
|
977
|
+
let needsCollapseH = false;
|
|
978
|
+
const collapseRuleKeys = /* @__PURE__ */ new Set();
|
|
979
|
+
const collapseRules = [];
|
|
980
|
+
/**
|
|
981
|
+
* Detect + collapse a literal-prop rocketstyle call site. Conservative
|
|
982
|
+
* bail catalogue (RFC decision 3): PascalCase candidate, every attr a
|
|
983
|
+
* StringLiteral (no spread, no `{expr}`, no boolean attr), children
|
|
984
|
+
* empty or a single static JSXText. The plugin must already have
|
|
985
|
+
* SSR-resolved this exact (component, props, text) tuple — an absent
|
|
986
|
+
* `sites` entry is a hard bail (covers resolver-bailed shapes,
|
|
987
|
+
* cross-package-without-data, anything uncertain). Emits ONE
|
|
988
|
+
* `_rsCollapse(tpl, light, dark, () => mode()==='dark')` (dual-emit)
|
|
989
|
+
* plus a once-per-module idempotent `injectRules`. A false negative is
|
|
990
|
+
* correct-but-slow; a false positive is wrong output — so every
|
|
991
|
+
* uncertain signal returns false.
|
|
992
|
+
*/
|
|
993
|
+
function tryRocketstyleCollapse(node) {
|
|
994
|
+
const cfg = options.collapseRocketstyle;
|
|
995
|
+
if (!cfg) return false;
|
|
996
|
+
const tag = jsxTagName(node);
|
|
997
|
+
if (!tag || tag.charAt(0) === tag.charAt(0).toLowerCase()) return false;
|
|
998
|
+
if (!cfg.candidates.has(tag)) return false;
|
|
999
|
+
const shape = detectCollapsibleShape(node, tag);
|
|
1000
|
+
if (!shape) return tryPartialCollapse(node, tag);
|
|
1001
|
+
const { props, childrenText } = shape;
|
|
1002
|
+
const key = rocketstyleCollapseKey(tag, props, childrenText);
|
|
1003
|
+
const site = cfg.sites.get(key);
|
|
1004
|
+
if (!site) return false;
|
|
1005
|
+
const call = `__rsCollapse(${JSON.stringify(site.templateHtml)}, ${JSON.stringify(site.lightClass)}, ${JSON.stringify(site.darkClass)}, () => __pyrMode() === "dark")`;
|
|
1006
|
+
const start = node.start;
|
|
1007
|
+
const end = node.end;
|
|
1008
|
+
const parent = findParent(node);
|
|
1009
|
+
const needsBraces = parent && (parent.type === "JSXElement" || parent.type === "JSXFragment");
|
|
1010
|
+
replacements.push({
|
|
1011
|
+
start,
|
|
1012
|
+
end,
|
|
1013
|
+
text: needsBraces ? `{${call}}` : call
|
|
1014
|
+
});
|
|
1015
|
+
needsCollapse = true;
|
|
1016
|
+
if (!collapseRuleKeys.has(site.ruleKey)) {
|
|
1017
|
+
collapseRuleKeys.add(site.ruleKey);
|
|
1018
|
+
collapseRules.push({
|
|
1019
|
+
ruleKey: site.ruleKey,
|
|
1020
|
+
rules: site.rules
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
return true;
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* PR 3 of the partial-collapse build (open-work #1). The `on*`-handler-
|
|
1027
|
+
* only fallback `tryRocketstyleCollapse` defers to when the FULL
|
|
1028
|
+
* `detectCollapsibleShape` bails. Identical site-resolution contract as
|
|
1029
|
+
* the full path — handlers are orthogonal to the SSR-resolved styler
|
|
1030
|
+
* class, so the literal-prop subset feeds the UNCHANGED
|
|
1031
|
+
* `rocketstyleCollapseKey` and the resolver's pre-resolved
|
|
1032
|
+
* `templateHtml`/`lightClass`/`darkClass` are byte-identical to a
|
|
1033
|
+
* full-collapse site's. The ONLY difference vs the full emit is
|
|
1034
|
+
* `__rsCollapseH(...)` with a handlers object literal built from the
|
|
1035
|
+
* sliced source spans `detectPartialCollapsibleShape` (PR 1) returned;
|
|
1036
|
+
* the runtime helper (`_rsCollapseH`, PR 2 / #681) re-attaches them
|
|
1037
|
+
* through the canonical event path. Same conservative discipline: an
|
|
1038
|
+
* unresolved key, the option absent, or any non-handler non-literal
|
|
1039
|
+
* shape ⇒ keep the normal mount (return false).
|
|
1040
|
+
*/
|
|
1041
|
+
function tryPartialCollapse(node, tag) {
|
|
1042
|
+
const cfg = options.collapseRocketstyle;
|
|
1043
|
+
if (!cfg) return false;
|
|
1044
|
+
const partial = detectPartialCollapsibleShape(node, tag);
|
|
1045
|
+
if (!partial) return false;
|
|
1046
|
+
const { props, childrenText, handlers } = partial;
|
|
1047
|
+
const key = rocketstyleCollapseKey(tag, props, childrenText);
|
|
1048
|
+
const site = cfg.sites.get(key);
|
|
1049
|
+
if (!site) return false;
|
|
1050
|
+
const handlerObj = `{ ${handlers.map((h) => `${JSON.stringify(h.name)}: (${code.slice(h.exprStart, h.exprEnd)})`).join(", ")} }`;
|
|
1051
|
+
const call = `__rsCollapseH(${JSON.stringify(site.templateHtml)}, ${JSON.stringify(site.lightClass)}, ${JSON.stringify(site.darkClass)}, () => __pyrMode() === "dark", ${handlerObj})`;
|
|
1052
|
+
const start = node.start;
|
|
1053
|
+
const end = node.end;
|
|
1054
|
+
const parent = findParent(node);
|
|
1055
|
+
const needsBraces = parent && (parent.type === "JSXElement" || parent.type === "JSXFragment");
|
|
1056
|
+
replacements.push({
|
|
1057
|
+
start,
|
|
1058
|
+
end,
|
|
1059
|
+
text: needsBraces ? `{${call}}` : call
|
|
1060
|
+
});
|
|
1061
|
+
needsCollapse = true;
|
|
1062
|
+
needsCollapseH = true;
|
|
1063
|
+
if (!collapseRuleKeys.has(site.ruleKey)) {
|
|
1064
|
+
collapseRuleKeys.add(site.ruleKey);
|
|
1065
|
+
collapseRules.push({
|
|
1066
|
+
ruleKey: site.ruleKey,
|
|
1067
|
+
rules: site.rules
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
return true;
|
|
1071
|
+
}
|
|
804
1072
|
function maybeHoist(node) {
|
|
805
1073
|
if ((node.type === "JSXElement" || node.type === "JSXFragment") && isStaticJSXNode(node)) {
|
|
806
1074
|
const name = `_$h${hoistIdx++}`;
|
|
@@ -945,6 +1213,7 @@ function transformJSX_JS(code, filename = "input.tsx", options = {}) {
|
|
|
945
1213
|
}
|
|
946
1214
|
const propsNames = /* @__PURE__ */ new Set();
|
|
947
1215
|
const propDerivedVars = /* @__PURE__ */ new Map();
|
|
1216
|
+
const elementVars = /* @__PURE__ */ new Set();
|
|
948
1217
|
const signalVars = new Set(options.knownSignals);
|
|
949
1218
|
const shadowedSignals = /* @__PURE__ */ new Set();
|
|
950
1219
|
/** Check if an identifier name is an active (non-shadowed) signal variable. */
|
|
@@ -1010,6 +1279,11 @@ function transformJSX_JS(code, filename = "input.tsx", options = {}) {
|
|
|
1010
1279
|
for (const el of decl.id.elements ?? []) if (el?.type === "Identifier") propsNames.add(el.name);
|
|
1011
1280
|
}
|
|
1012
1281
|
}
|
|
1282
|
+
if ((node.kind === "const" || node.kind === "let") && decl.id?.type === "Identifier" && decl.init) {
|
|
1283
|
+
let initNode = decl.init;
|
|
1284
|
+
while (initNode?.type === "ParenthesizedExpression") initNode = initNode.expression;
|
|
1285
|
+
if (initNode?.type === "JSXElement" || initNode?.type === "JSXFragment") elementVars.add(decl.id.name);
|
|
1286
|
+
}
|
|
1013
1287
|
if (node.kind !== "const") continue;
|
|
1014
1288
|
if (callbackDepth > 0) continue;
|
|
1015
1289
|
if (decl.id?.type === "Identifier" && decl.init) {
|
|
@@ -1070,11 +1344,68 @@ function transformJSX_JS(code, filename = "input.tsx", options = {}) {
|
|
|
1070
1344
|
function resolveIdentifiersInText(text, baseOffset, sourceNode) {
|
|
1071
1345
|
const endOffset = baseOffset + text.length;
|
|
1072
1346
|
const idents = [];
|
|
1347
|
+
const shadowed = /* @__PURE__ */ new Set();
|
|
1348
|
+
/** Collect identifier names bound by a pattern (params / declarators). */
|
|
1349
|
+
function patternBindingNames(p, out) {
|
|
1350
|
+
if (!p) return;
|
|
1351
|
+
switch (p.type) {
|
|
1352
|
+
case "Identifier":
|
|
1353
|
+
out.push(p.name);
|
|
1354
|
+
break;
|
|
1355
|
+
case "ObjectPattern":
|
|
1356
|
+
for (const pr of p.properties ?? []) if (pr.type === "RestElement") patternBindingNames(pr.argument, out);
|
|
1357
|
+
else patternBindingNames(pr.value ?? pr.key, out);
|
|
1358
|
+
break;
|
|
1359
|
+
case "ArrayPattern":
|
|
1360
|
+
for (const el of p.elements ?? []) patternBindingNames(el, out);
|
|
1361
|
+
break;
|
|
1362
|
+
case "AssignmentPattern":
|
|
1363
|
+
patternBindingNames(p.left, out);
|
|
1364
|
+
break;
|
|
1365
|
+
case "RestElement":
|
|
1366
|
+
patternBindingNames(p.argument, out);
|
|
1367
|
+
break;
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
/**
|
|
1371
|
+
* Prop-derived names bound by `node` FOR ITS OWN SUBTREE (block-accurate
|
|
1372
|
+
* lexical scoping). Excludes the prop-derived const's own defining
|
|
1373
|
+
* declaration (matched by init span) so the binding we inline FROM is
|
|
1374
|
+
* never mistaken for a shadow of itself.
|
|
1375
|
+
*/
|
|
1376
|
+
function scopeBoundPropDerived(node) {
|
|
1377
|
+
const out = [];
|
|
1378
|
+
const t = node.type;
|
|
1379
|
+
const declNames = (declNode) => {
|
|
1380
|
+
for (const d of declNode.declarations ?? []) {
|
|
1381
|
+
if (d.id?.type === "Identifier" && propDerivedVars.has(d.id.name)) {
|
|
1382
|
+
const span = propDerivedVars.get(d.id.name);
|
|
1383
|
+
if (d.init && d.init.start === span.start) continue;
|
|
1384
|
+
}
|
|
1385
|
+
patternBindingNames(d.id, out);
|
|
1386
|
+
}
|
|
1387
|
+
};
|
|
1388
|
+
if (t === "ArrowFunctionExpression" || t === "FunctionExpression" || t === "FunctionDeclaration") for (const p of node.params ?? []) patternBindingNames(p, out);
|
|
1389
|
+
else if (t === "CatchClause") patternBindingNames(node.param, out);
|
|
1390
|
+
else if (t === "ForStatement") {
|
|
1391
|
+
if (node.init?.type === "VariableDeclaration") declNames(node.init);
|
|
1392
|
+
} else if (t === "ForInStatement" || t === "ForOfStatement") {
|
|
1393
|
+
if (node.left?.type === "VariableDeclaration") declNames(node.left);
|
|
1394
|
+
} else if (t === "BlockStatement" || t === "Program" || t === "StaticBlock") {
|
|
1395
|
+
const stmts = node.body ?? node.statements;
|
|
1396
|
+
if (Array.isArray(stmts)) {
|
|
1397
|
+
for (const s of stmts) if (s.type === "VariableDeclaration") declNames(s);
|
|
1398
|
+
else if (s.type === "FunctionDeclaration" && s.id?.type === "Identifier") out.push(s.id.name);
|
|
1399
|
+
else if (s.type === "ClassDeclaration" && s.id?.type === "Identifier") out.push(s.id.name);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
return out.filter((n) => propDerivedVars.has(n));
|
|
1403
|
+
}
|
|
1073
1404
|
function findIdents(node, parent) {
|
|
1074
1405
|
const nodeStart = node.start;
|
|
1075
1406
|
const nodeEnd = node.end;
|
|
1076
1407
|
if (nodeStart >= endOffset || nodeEnd <= baseOffset) return;
|
|
1077
|
-
if (node.type === "Identifier" && propDerivedVars.has(node.name)) {
|
|
1408
|
+
if (node.type === "Identifier" && propDerivedVars.has(node.name) && !shadowed.has(node.name)) {
|
|
1078
1409
|
if (parent) {
|
|
1079
1410
|
if (parent.type === "MemberExpression" && parent.property === node && !parent.computed) {} else if (parent.type === "VariableDeclarator" && parent.id === node) {} else if (parent.type === "Property" && parent.key === node && !parent.computed) {} else if (parent.type === "Property" && parent.shorthand) {} else if (nodeStart >= baseOffset && nodeEnd <= endOffset) idents.push({
|
|
1080
1411
|
start: nodeStart,
|
|
@@ -1087,7 +1418,10 @@ function transformJSX_JS(code, filename = "input.tsx", options = {}) {
|
|
|
1087
1418
|
name: node.name
|
|
1088
1419
|
});
|
|
1089
1420
|
}
|
|
1421
|
+
const introduced = scopeBoundPropDerived(node).filter((n) => !shadowed.has(n));
|
|
1422
|
+
for (const n of introduced) shadowed.add(n);
|
|
1090
1423
|
forEachChildFast(node, (child) => findIdents(child, node));
|
|
1424
|
+
for (const n of introduced) shadowed.delete(n);
|
|
1091
1425
|
}
|
|
1092
1426
|
findIdents(program, null);
|
|
1093
1427
|
if (idents.length === 0) return text;
|
|
@@ -1175,6 +1509,7 @@ function transformJSX_JS(code, filename = "input.tsx", options = {}) {
|
|
|
1175
1509
|
}
|
|
1176
1510
|
if (node.type === "VariableDeclaration") collectPropDerivedFromDecl(node, _callbackDepth);
|
|
1177
1511
|
if (node.type === "JSXElement") {
|
|
1512
|
+
if (tryRocketstyleCollapse(node)) return;
|
|
1178
1513
|
if (!isSelfClosing(node) && tryTemplateEmit(node)) return;
|
|
1179
1514
|
checkForWarnings(node);
|
|
1180
1515
|
for (const attr of jsxAttrs(node)) if (attr.type === "JSXAttribute") handleJsxAttribute(attr, node);
|
|
@@ -1204,16 +1539,11 @@ function transformJSX_JS(code, filename = "input.tsx", options = {}) {
|
|
|
1204
1539
|
warnings
|
|
1205
1540
|
};
|
|
1206
1541
|
replacements.sort((a, b) => a.start - b.start);
|
|
1207
|
-
const
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
outPos = r.end;
|
|
1213
|
-
}
|
|
1214
|
-
outParts.push(code.slice(outPos));
|
|
1215
|
-
let output = outParts.join("");
|
|
1216
|
-
if (hoists.length > 0) output = hoists.map((h) => `const ${h.name} = /*@__PURE__*/ ${h.text}\n`).join("") + output;
|
|
1542
|
+
const s = new MagicString(code);
|
|
1543
|
+
for (const r of replacements) if (r.start === r.end) s.appendLeft(r.start, r.text);
|
|
1544
|
+
else s.update(r.start, r.end, r.text);
|
|
1545
|
+
let preamble = "";
|
|
1546
|
+
if (hoists.length > 0) preamble = hoists.map((h) => `const ${h.name} = /*@__PURE__*/ ${h.text}\n`).join("") + preamble;
|
|
1217
1547
|
if (needsTplImport) {
|
|
1218
1548
|
const runtimeDomImports = ["_tpl"];
|
|
1219
1549
|
if (needsBindDirectImportGlobal) runtimeDomImports.push("_bindDirect");
|
|
@@ -1221,23 +1551,39 @@ function transformJSX_JS(code, filename = "input.tsx", options = {}) {
|
|
|
1221
1551
|
if (needsApplyPropsImportGlobal) runtimeDomImports.push("_applyProps");
|
|
1222
1552
|
if (needsMountSlotImportGlobal) runtimeDomImports.push("_mountSlot");
|
|
1223
1553
|
const reactivityImports = needsBindImportGlobal ? `\nimport { _bind } from "@pyreon/reactivity";` : "";
|
|
1224
|
-
|
|
1554
|
+
preamble = `import { ${runtimeDomImports.join(", ")} } from "@pyreon/runtime-dom";${reactivityImports}\n` + preamble;
|
|
1225
1555
|
}
|
|
1226
1556
|
if (needsRpImport || needsWrapSpreadImport) {
|
|
1227
1557
|
const coreImports = [];
|
|
1228
1558
|
if (needsRpImport) coreImports.push("_rp");
|
|
1229
1559
|
if (needsWrapSpreadImport) coreImports.push("_wrapSpread");
|
|
1230
|
-
|
|
1231
|
-
}
|
|
1560
|
+
preamble = `import { ${coreImports.join(", ")} } from "@pyreon/core";\n` + preamble;
|
|
1561
|
+
}
|
|
1562
|
+
if (needsCollapse) {
|
|
1563
|
+
const cfg = options.collapseRocketstyle;
|
|
1564
|
+
const rd = cfg.runtimeDomSource ?? "@pyreon/runtime-dom";
|
|
1565
|
+
const st = cfg.stylerSource ?? "@pyreon/styler";
|
|
1566
|
+
const inj = collapseRules.map((r) => `__rsSheet.injectRules(${JSON.stringify(r.rules)},${JSON.stringify(r.ruleKey)});`).join("");
|
|
1567
|
+
preamble = `import { _rsCollapse as __rsCollapse${needsCollapseH ? ", _rsCollapseH as __rsCollapseH" : ""} } from "${rd}";\nimport { sheet as __rsSheet } from "${st}";\nimport { ${cfg.mode.name} as __pyrMode } from "${cfg.mode.source}";\n${inj}\n` + preamble;
|
|
1568
|
+
}
|
|
1569
|
+
if (preamble) s.prepend(preamble);
|
|
1570
|
+
const output = s.toString();
|
|
1571
|
+
const map = s.generateMap({
|
|
1572
|
+
source: filename,
|
|
1573
|
+
includeContent: true,
|
|
1574
|
+
hires: true
|
|
1575
|
+
});
|
|
1232
1576
|
return collectLens ? {
|
|
1233
1577
|
code: output,
|
|
1234
1578
|
usesTemplates: needsTplImport,
|
|
1235
1579
|
warnings,
|
|
1580
|
+
map,
|
|
1236
1581
|
reactivityLens
|
|
1237
1582
|
} : {
|
|
1238
1583
|
code: output,
|
|
1239
1584
|
usesTemplates: needsTplImport,
|
|
1240
|
-
warnings
|
|
1585
|
+
warnings,
|
|
1586
|
+
map
|
|
1241
1587
|
};
|
|
1242
1588
|
function hasBailAttr(node, isRoot = false) {
|
|
1243
1589
|
for (const attr of jsxAttrs(node)) {
|
|
@@ -1525,7 +1871,8 @@ function transformJSX_JS(code, filename = "input.tsx", options = {}) {
|
|
|
1525
1871
|
}
|
|
1526
1872
|
const needsPlaceholder = useMixed || useMultiExpr;
|
|
1527
1873
|
const { expr, isReactive } = unwrapAccessor(child.expression);
|
|
1528
|
-
|
|
1874
|
+
const isElementValuedIdent = child.expression?.type === "Identifier" && elementVars.has(child.expression.name) || !isReactive && /^[A-Za-z_$][\w$]*$/.test(expr) && elementVars.has(expr);
|
|
1875
|
+
if (isChildrenExpression(child.expression, expr) || isElementValuedIdent) {
|
|
1529
1876
|
needsMountSlotImport = true;
|
|
1530
1877
|
const placeholder = `${parentRef}.childNodes[${childNodeIdx}]`;
|
|
1531
1878
|
const d = nextDisp();
|
|
@@ -1618,12 +1965,64 @@ function transformJSX_JS(code, filename = "input.tsx", options = {}) {
|
|
|
1618
1965
|
}
|
|
1619
1966
|
/** Auto-insert () after signal variable references in the expression source.
|
|
1620
1967
|
* Uses the AST to find exact Identifier positions — never scans raw text. */
|
|
1968
|
+
function sigPatternNames(p, out) {
|
|
1969
|
+
if (!p) return;
|
|
1970
|
+
switch (p.type) {
|
|
1971
|
+
case "Identifier":
|
|
1972
|
+
out.push(p.name);
|
|
1973
|
+
break;
|
|
1974
|
+
case "ObjectPattern":
|
|
1975
|
+
for (const pr of p.properties ?? []) if (pr.type === "RestElement") sigPatternNames(pr.argument, out);
|
|
1976
|
+
else sigPatternNames(pr.value ?? pr.key, out);
|
|
1977
|
+
break;
|
|
1978
|
+
case "ArrayPattern":
|
|
1979
|
+
for (const el of p.elements ?? []) sigPatternNames(el, out);
|
|
1980
|
+
break;
|
|
1981
|
+
case "AssignmentPattern":
|
|
1982
|
+
sigPatternNames(p.left, out);
|
|
1983
|
+
break;
|
|
1984
|
+
case "RestElement":
|
|
1985
|
+
sigPatternNames(p.argument, out);
|
|
1986
|
+
break;
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
function scopeBoundSignals(node) {
|
|
1990
|
+
const out = [];
|
|
1991
|
+
const t = node.type;
|
|
1992
|
+
const declNames = (declNode) => {
|
|
1993
|
+
for (const d of declNode.declarations ?? []) {
|
|
1994
|
+
if (d.id?.type === "Identifier" && d.init && isSignalCall(d.init)) continue;
|
|
1995
|
+
sigPatternNames(d.id, out);
|
|
1996
|
+
}
|
|
1997
|
+
};
|
|
1998
|
+
if (t === "ArrowFunctionExpression" || t === "FunctionExpression" || t === "FunctionDeclaration") for (const p of node.params ?? []) sigPatternNames(p, out);
|
|
1999
|
+
else if (t === "CatchClause") sigPatternNames(node.param, out);
|
|
2000
|
+
else if (t === "ForStatement") {
|
|
2001
|
+
if (node.init?.type === "VariableDeclaration") declNames(node.init);
|
|
2002
|
+
} else if (t === "ForInStatement" || t === "ForOfStatement") {
|
|
2003
|
+
if (node.left?.type === "VariableDeclaration") declNames(node.left);
|
|
2004
|
+
} else if (t === "BlockStatement" || t === "StaticBlock") {
|
|
2005
|
+
const stmts = node.body ?? node.statements;
|
|
2006
|
+
if (Array.isArray(stmts)) {
|
|
2007
|
+
for (const s of stmts) if (s.type === "VariableDeclaration") declNames(s);
|
|
2008
|
+
else if (s.type === "FunctionDeclaration" && s.id?.type === "Identifier") out.push(s.id.name);
|
|
2009
|
+
else if (s.type === "ClassDeclaration" && s.id?.type === "Identifier") out.push(s.id.name);
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
return out.filter((n) => signalVars.has(n));
|
|
2013
|
+
}
|
|
1621
2014
|
function autoCallSignals(text, expr) {
|
|
1622
2015
|
const start = expr.start;
|
|
1623
2016
|
const idents = [];
|
|
2017
|
+
const shadowed = /* @__PURE__ */ new Set();
|
|
1624
2018
|
function findSignalIdents(node) {
|
|
1625
2019
|
if (node.start >= start + text.length || node.end <= start) return;
|
|
1626
|
-
|
|
2020
|
+
const introduced = [];
|
|
2021
|
+
for (const n of scopeBoundSignals(node)) if (!shadowed.has(n)) {
|
|
2022
|
+
shadowed.add(n);
|
|
2023
|
+
introduced.push(n);
|
|
2024
|
+
}
|
|
2025
|
+
if (node.type === "Identifier" && isActiveSignal(node.name) && !shadowed.has(node.name)) {
|
|
1627
2026
|
const parent = findParent(node);
|
|
1628
2027
|
if (parent && parent.type === "MemberExpression" && parent.property === node && !parent.computed) return;
|
|
1629
2028
|
if (parent && parent.type === "MemberExpression" && parent.object === node) {
|
|
@@ -1642,6 +2041,7 @@ function transformJSX_JS(code, filename = "input.tsx", options = {}) {
|
|
|
1642
2041
|
});
|
|
1643
2042
|
}
|
|
1644
2043
|
forEachChildFast(node, findSignalIdents);
|
|
2044
|
+
for (const n of introduced) shadowed.delete(n);
|
|
1645
2045
|
}
|
|
1646
2046
|
findSignalIdents(expr);
|
|
1647
2047
|
if (idents.length === 0) return text;
|
|
@@ -4438,5 +4838,5 @@ function formatSsgAudit(result, _options = {}) {
|
|
|
4438
4838
|
}
|
|
4439
4839
|
|
|
4440
4840
|
//#endregion
|
|
4441
|
-
export { analyzeReactivity, auditIslands, auditSsg, auditTestEnvironment, detectPyreonPatterns, detectReactPatterns, diagnoseError, formatIslandAudit, formatReactivityLens, formatSsgAudit, formatTestAudit, generateContext, hasPyreonPatterns, hasReactPatterns, migrateReactCode, transformDeferInline, transformJSX, transformJSX_JS };
|
|
4841
|
+
export { analyzeReactivity, auditIslands, auditSsg, auditTestEnvironment, detectPyreonPatterns, detectReactPatterns, diagnoseError, formatIslandAudit, formatReactivityLens, formatSsgAudit, formatTestAudit, generateContext, hasPyreonPatterns, hasReactPatterns, migrateReactCode, rocketstyleCollapseKey, scanCollapsibleSites, transformDeferInline, transformJSX, transformJSX_JS };
|
|
4442
4842
|
//# sourceMappingURL=index.js.map
|