what-compiler 0.6.0 → 0.6.1
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/babel-plugin.js +104 -35
- package/dist/babel-plugin.js.map +2 -2
- package/dist/babel-plugin.min.js +1 -1
- package/dist/babel-plugin.min.js.map +3 -3
- package/dist/index.js +104 -35
- package/dist/index.js.map +2 -2
- package/dist/index.min.js +4 -4
- package/dist/index.min.js.map +3 -3
- package/dist/vite-plugin.js +104 -35
- package/dist/vite-plugin.js.map +2 -2
- package/dist/vite-plugin.min.js +4 -4
- package/dist/vite-plugin.min.js.map +3 -3
- package/package.json +1 -1
- package/src/babel-plugin.js +127 -41
package/package.json
CHANGED
package/src/babel-plugin.js
CHANGED
|
@@ -642,6 +642,28 @@ export default function whatBabelPlugin({ types: t }) {
|
|
|
642
642
|
|
|
643
643
|
const attrName = getAttrName(attr);
|
|
644
644
|
|
|
645
|
+
// Ref handling — assign element to ref object/callback
|
|
646
|
+
if (attrName === 'ref') {
|
|
647
|
+
const refExpr = getAttributeValue(attr.value);
|
|
648
|
+
// Generate: typeof ref === 'function' ? ref(el) : ref.current = el
|
|
649
|
+
statements.push(
|
|
650
|
+
t.expressionStatement(
|
|
651
|
+
t.conditionalExpression(
|
|
652
|
+
t.binaryExpression('===',
|
|
653
|
+
t.unaryExpression('typeof', refExpr),
|
|
654
|
+
t.stringLiteral('function')
|
|
655
|
+
),
|
|
656
|
+
t.callExpression(t.cloneNode(refExpr), [t.identifier(elId)]),
|
|
657
|
+
t.assignmentExpression('=',
|
|
658
|
+
t.memberExpression(t.cloneNode(refExpr), t.identifier('current')),
|
|
659
|
+
t.identifier(elId)
|
|
660
|
+
)
|
|
661
|
+
)
|
|
662
|
+
)
|
|
663
|
+
);
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
|
|
645
667
|
// Event handlers
|
|
646
668
|
if (attrName.startsWith('on') && !attrName.includes('|')) {
|
|
647
669
|
const event = attrName.slice(2).toLowerCase();
|
|
@@ -805,6 +827,12 @@ export default function whatBabelPlugin({ types: t }) {
|
|
|
805
827
|
}
|
|
806
828
|
|
|
807
829
|
function applyDynamicChildren(statements, elId, children, parentNode, state) {
|
|
830
|
+
// Two-pass approach: first collect all children needing DOM references,
|
|
831
|
+
// then pre-capture markers before any _$insert() calls shift indices.
|
|
832
|
+
// This fixes issue #1: childNodes index shifting with multiple dynamic children.
|
|
833
|
+
|
|
834
|
+
// --- Pass 1: Scan children and collect entries ---
|
|
835
|
+
const entries = [];
|
|
808
836
|
let childIndex = 0;
|
|
809
837
|
|
|
810
838
|
for (const child of children) {
|
|
@@ -816,9 +844,73 @@ export default function whatBabelPlugin({ types: t }) {
|
|
|
816
844
|
|
|
817
845
|
if (t.isJSXExpressionContainer(child)) {
|
|
818
846
|
if (t.isJSXEmptyExpression(child.expression)) continue;
|
|
847
|
+
entries.push({ type: 'expression', child, childIndex });
|
|
848
|
+
childIndex++;
|
|
849
|
+
continue;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
if (t.isJSXElement(child)) {
|
|
853
|
+
const childTag = child.openingElement.name.name;
|
|
854
|
+
if (isComponent(childTag) || childTag === 'For' || childTag === 'Show') {
|
|
855
|
+
entries.push({ type: 'component', child, childIndex });
|
|
856
|
+
childIndex++;
|
|
857
|
+
} else {
|
|
858
|
+
const hasAnythingDynamic = child.openingElement.attributes.some(isDynamicAttr) ||
|
|
859
|
+
child.openingElement.attributes.some(a => !t.isJSXSpreadAttribute(a) && getAttrName(a)?.startsWith('on')) ||
|
|
860
|
+
!child.children.every(isStaticChild);
|
|
861
|
+
|
|
862
|
+
entries.push({ type: 'static', child, childIndex, hasAnythingDynamic });
|
|
863
|
+
childIndex++;
|
|
864
|
+
}
|
|
865
|
+
continue;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
if (t.isJSXFragment(child)) {
|
|
869
|
+
entries.push({ type: 'fragment', child });
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// --- Pre-capture marker references if needed ---
|
|
874
|
+
// When there are multiple entries needing DOM refs and at least one _$insert(),
|
|
875
|
+
// capture all markers upfront to avoid index shifting after DOM mutations.
|
|
876
|
+
const entriesNeedingRef = entries.filter(e =>
|
|
877
|
+
e.type === 'expression' || e.type === 'component' ||
|
|
878
|
+
(e.type === 'static' && e.hasAnythingDynamic)
|
|
879
|
+
);
|
|
880
|
+
const hasDynamicInsert = entries.some(e => e.type === 'expression' || e.type === 'component');
|
|
881
|
+
const needsPreCapture = entriesNeedingRef.length >= 2 && hasDynamicInsert;
|
|
882
|
+
|
|
883
|
+
const markerVars = new Map(); // childIndex → variable name
|
|
884
|
+
if (needsPreCapture) {
|
|
885
|
+
for (const entry of entriesNeedingRef) {
|
|
886
|
+
const varName = `_m$${entry.childIndex}`;
|
|
887
|
+
// Use a unique name to avoid collisions with element vars
|
|
888
|
+
const markerVar = state.nextVarId();
|
|
889
|
+
markerVars.set(entry.childIndex, markerVar);
|
|
890
|
+
statements.push(
|
|
891
|
+
t.variableDeclaration('const', [
|
|
892
|
+
t.variableDeclarator(
|
|
893
|
+
t.identifier(markerVar),
|
|
894
|
+
buildChildAccess(elId, entry.childIndex)
|
|
895
|
+
)
|
|
896
|
+
])
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
819
900
|
|
|
820
|
-
|
|
821
|
-
|
|
901
|
+
// Helper: get a marker reference (pre-captured var or inline access)
|
|
902
|
+
function getMarker(idx) {
|
|
903
|
+
if (markerVars.has(idx)) {
|
|
904
|
+
return t.identifier(markerVars.get(idx));
|
|
905
|
+
}
|
|
906
|
+
return buildChildAccess(elId, idx);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// --- Pass 2: Generate code using stable references ---
|
|
910
|
+
for (const entry of entries) {
|
|
911
|
+
if (entry.type === 'expression') {
|
|
912
|
+
const expr = entry.child.expression;
|
|
913
|
+
const marker = getMarker(entry.childIndex);
|
|
822
914
|
state.needsInsert = true;
|
|
823
915
|
|
|
824
916
|
if (isPotentiallyReactive(expr, state.signalNames, state.importedIdentifiers)) {
|
|
@@ -827,7 +919,6 @@ export default function whatBabelPlugin({ types: t }) {
|
|
|
827
919
|
t.arrowFunctionExpression([], expr),
|
|
828
920
|
marker
|
|
829
921
|
]);
|
|
830
|
-
// In dev mode, add a leading comment when the reactive wrapping is uncertain
|
|
831
922
|
if (isUncertainReactive(expr, state.signalNames, state.importedIdentifiers)) {
|
|
832
923
|
t.addComment(insertCall, 'leading',
|
|
833
924
|
' @what-dev: reactive wrapping may be unnecessary — expression contains a non-signal function call with reactive args ',
|
|
@@ -846,53 +937,48 @@ export default function whatBabelPlugin({ types: t }) {
|
|
|
846
937
|
)
|
|
847
938
|
);
|
|
848
939
|
}
|
|
849
|
-
childIndex++;
|
|
850
940
|
continue;
|
|
851
941
|
}
|
|
852
942
|
|
|
853
|
-
if (
|
|
854
|
-
const
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
943
|
+
if (entry.type === 'component') {
|
|
944
|
+
const transformed = transformElementFineGrained({ node: entry.child }, state);
|
|
945
|
+
const marker = getMarker(entry.childIndex);
|
|
946
|
+
state.needsInsert = true;
|
|
947
|
+
statements.push(
|
|
948
|
+
t.expressionStatement(
|
|
949
|
+
t.callExpression(t.identifier('_$insert'), [
|
|
950
|
+
t.identifier(elId),
|
|
951
|
+
transformed,
|
|
952
|
+
marker
|
|
953
|
+
])
|
|
954
|
+
)
|
|
955
|
+
);
|
|
956
|
+
continue;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
if (entry.type === 'static' && entry.hasAnythingDynamic) {
|
|
960
|
+
// Static child with dynamic content — get element reference
|
|
961
|
+
let childElRef;
|
|
962
|
+
if (markerVars.has(entry.childIndex)) {
|
|
963
|
+
childElRef = markerVars.get(entry.childIndex);
|
|
964
|
+
} else {
|
|
965
|
+
childElRef = state.nextVarId();
|
|
859
966
|
statements.push(
|
|
860
|
-
t.
|
|
861
|
-
t.
|
|
862
|
-
t.identifier(
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
)
|
|
967
|
+
t.variableDeclaration('const', [
|
|
968
|
+
t.variableDeclarator(
|
|
969
|
+
t.identifier(childElRef),
|
|
970
|
+
buildChildAccess(elId, entry.childIndex)
|
|
971
|
+
)
|
|
972
|
+
])
|
|
867
973
|
);
|
|
868
|
-
childIndex++;
|
|
869
|
-
} else {
|
|
870
|
-
// Static child element — already in template
|
|
871
|
-
// But check if it has dynamic children/attrs that need effects
|
|
872
|
-
const hasAnythingDynamic = child.openingElement.attributes.some(isDynamicAttr) ||
|
|
873
|
-
child.openingElement.attributes.some(a => !t.isJSXSpreadAttribute(a) && getAttrName(a)?.startsWith('on')) ||
|
|
874
|
-
!child.children.every(isStaticChild);
|
|
875
|
-
|
|
876
|
-
if (hasAnythingDynamic) {
|
|
877
|
-
const childElId = state.nextVarId();
|
|
878
|
-
statements.push(
|
|
879
|
-
t.variableDeclaration('const', [
|
|
880
|
-
t.variableDeclarator(
|
|
881
|
-
t.identifier(childElId),
|
|
882
|
-
buildChildAccess(elId, childIndex)
|
|
883
|
-
)
|
|
884
|
-
])
|
|
885
|
-
);
|
|
886
|
-
applyDynamicAttrs(statements, childElId, child.openingElement.attributes, state);
|
|
887
|
-
applyDynamicChildren(statements, childElId, child.children, child, state);
|
|
888
|
-
}
|
|
889
|
-
childIndex++;
|
|
890
974
|
}
|
|
975
|
+
applyDynamicAttrs(statements, childElRef, entry.child.openingElement.attributes, state);
|
|
976
|
+
applyDynamicChildren(statements, childElRef, entry.child.children, entry.child, state);
|
|
891
977
|
continue;
|
|
892
978
|
}
|
|
893
979
|
|
|
894
|
-
if (
|
|
895
|
-
for (const fChild of child.children) {
|
|
980
|
+
if (entry.type === 'fragment') {
|
|
981
|
+
for (const fChild of entry.child.children) {
|
|
896
982
|
if (t.isJSXExpressionContainer(fChild) && !t.isJSXEmptyExpression(fChild.expression)) {
|
|
897
983
|
state.needsInsert = true;
|
|
898
984
|
const expr = fChild.expression;
|