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