what-compiler 0.5.0 → 0.5.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/package.json +2 -2
- package/src/babel-plugin.js +84 -78
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "what-compiler",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "JSX compiler for What Framework - transforms JSX to optimized DOM operations",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"license": "MIT",
|
|
24
24
|
"peerDependencies": {
|
|
25
25
|
"@babel/core": "^7.0.0",
|
|
26
|
-
"what-core": "^0.5.
|
|
26
|
+
"what-core": "^0.5.1"
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
29
|
"src"
|
package/src/babel-plugin.js
CHANGED
|
@@ -20,6 +20,22 @@
|
|
|
20
20
|
|
|
21
21
|
const EVENT_MODIFIERS = new Set(['preventDefault', 'stopPropagation', 'once', 'capture', 'passive', 'self']);
|
|
22
22
|
const EVENT_OPTION_MODIFIERS = new Set(['once', 'capture', 'passive']);
|
|
23
|
+
const VOID_HTML_ELEMENTS = new Set([
|
|
24
|
+
'area',
|
|
25
|
+
'base',
|
|
26
|
+
'br',
|
|
27
|
+
'col',
|
|
28
|
+
'embed',
|
|
29
|
+
'hr',
|
|
30
|
+
'img',
|
|
31
|
+
'input',
|
|
32
|
+
'link',
|
|
33
|
+
'meta',
|
|
34
|
+
'param',
|
|
35
|
+
'source',
|
|
36
|
+
'track',
|
|
37
|
+
'wbr'
|
|
38
|
+
]);
|
|
23
39
|
|
|
24
40
|
export default function whatBabelPlugin({ types: t }) {
|
|
25
41
|
const mode = 'fine-grained'; // Can be overridden via plugin options
|
|
@@ -47,6 +63,10 @@ export default function whatBabelPlugin({ types: t }) {
|
|
|
47
63
|
return /^[A-Z]/.test(name);
|
|
48
64
|
}
|
|
49
65
|
|
|
66
|
+
function isVoidHtmlElement(name) {
|
|
67
|
+
return VOID_HTML_ELEMENTS.has(String(name).toLowerCase());
|
|
68
|
+
}
|
|
69
|
+
|
|
50
70
|
function getAttributeValue(value) {
|
|
51
71
|
if (!value) return t.booleanLiteral(true);
|
|
52
72
|
if (t.isJSXExpressionContainer(value)) return value.expression;
|
|
@@ -54,6 +74,12 @@ export default function whatBabelPlugin({ types: t }) {
|
|
|
54
74
|
return t.stringLiteral(value.value || '');
|
|
55
75
|
}
|
|
56
76
|
|
|
77
|
+
function normalizeAttrName(attrName) {
|
|
78
|
+
if (attrName === 'className') return 'class';
|
|
79
|
+
if (attrName === 'htmlFor') return 'for';
|
|
80
|
+
return attrName;
|
|
81
|
+
}
|
|
82
|
+
|
|
57
83
|
function createEventHandler(handler, modifiers) {
|
|
58
84
|
if (modifiers.length === 0) return handler;
|
|
59
85
|
|
|
@@ -456,6 +482,14 @@ export default function whatBabelPlugin({ types: t }) {
|
|
|
456
482
|
if (t.isTemplateLiteral(expr)) {
|
|
457
483
|
return expr.expressions.some(isPotentiallyReactive);
|
|
458
484
|
}
|
|
485
|
+
if (t.isObjectExpression(expr)) {
|
|
486
|
+
return expr.properties.some(prop =>
|
|
487
|
+
t.isObjectProperty(prop) && isPotentiallyReactive(prop.value)
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
if (t.isArrayExpression(expr)) {
|
|
491
|
+
return expr.elements.some(el => el && isPotentiallyReactive(el));
|
|
492
|
+
}
|
|
459
493
|
return false;
|
|
460
494
|
}
|
|
461
495
|
|
|
@@ -467,8 +501,9 @@ export default function whatBabelPlugin({ types: t }) {
|
|
|
467
501
|
}
|
|
468
502
|
|
|
469
503
|
if (t.isJSXExpressionContainer(node)) {
|
|
470
|
-
// Dynamic
|
|
471
|
-
return '';
|
|
504
|
+
// Dynamic child marker so insert() can preserve source ordering
|
|
505
|
+
if (t.isJSXEmptyExpression(node.expression)) return '';
|
|
506
|
+
return '<!--$-->';
|
|
472
507
|
}
|
|
473
508
|
|
|
474
509
|
if (!t.isJSXElement(node)) return '';
|
|
@@ -501,9 +536,13 @@ export default function whatBabelPlugin({ types: t }) {
|
|
|
501
536
|
}
|
|
502
537
|
|
|
503
538
|
const selfClosing = node.openingElement.selfClosing;
|
|
539
|
+
if (selfClosing && isVoidHtmlElement(tagName)) {
|
|
540
|
+
html += '>';
|
|
541
|
+
return html;
|
|
542
|
+
}
|
|
543
|
+
|
|
504
544
|
if (selfClosing) {
|
|
505
|
-
|
|
506
|
-
html += '/>';
|
|
545
|
+
html += `></${tagName}>`;
|
|
507
546
|
return html;
|
|
508
547
|
}
|
|
509
548
|
|
|
@@ -515,11 +554,12 @@ export default function whatBabelPlugin({ types: t }) {
|
|
|
515
554
|
const text = child.value.replace(/\n\s+/g, ' ').trim();
|
|
516
555
|
if (text) html += escapeHTML(text);
|
|
517
556
|
} else if (t.isJSXExpressionContainer(child)) {
|
|
518
|
-
|
|
519
|
-
|
|
557
|
+
if (!t.isJSXEmptyExpression(child.expression)) {
|
|
558
|
+
html += '<!--$-->';
|
|
559
|
+
}
|
|
520
560
|
} else if (t.isJSXElement(child)) {
|
|
521
561
|
if (isComponent(child.openingElement.name.name)) {
|
|
522
|
-
|
|
562
|
+
html += '<!--$-->';
|
|
523
563
|
} else {
|
|
524
564
|
html += extractStaticHTML(child);
|
|
525
565
|
}
|
|
@@ -615,6 +655,15 @@ export default function whatBabelPlugin({ types: t }) {
|
|
|
615
655
|
}
|
|
616
656
|
|
|
617
657
|
function applyDynamicAttrs(statements, elId, attributes, state) {
|
|
658
|
+
function buildSetPropCall(propName, valueExpr) {
|
|
659
|
+
state.needsSetProp = true;
|
|
660
|
+
return t.callExpression(t.identifier('_$setProp'), [
|
|
661
|
+
t.identifier(elId),
|
|
662
|
+
t.stringLiteral(propName),
|
|
663
|
+
valueExpr
|
|
664
|
+
]);
|
|
665
|
+
}
|
|
666
|
+
|
|
618
667
|
for (const attr of attributes) {
|
|
619
668
|
if (t.isJSXSpreadAttribute(attr)) {
|
|
620
669
|
// spread(el, props) — use runtime spread
|
|
@@ -718,75 +767,21 @@ export default function whatBabelPlugin({ types: t }) {
|
|
|
718
767
|
// Dynamic attribute (expression)
|
|
719
768
|
if (t.isJSXExpressionContainer(attr.value)) {
|
|
720
769
|
const expr = attr.value.expression;
|
|
721
|
-
|
|
722
|
-
if (attrName === 'className') domName = 'class';
|
|
723
|
-
if (attrName === 'htmlFor') domName = 'for';
|
|
770
|
+
const domName = normalizeAttrName(attrName);
|
|
724
771
|
|
|
725
772
|
if (isPotentiallyReactive(expr)) {
|
|
726
773
|
// Reactive attribute — wrap in effect
|
|
727
774
|
state.needsEffect = true;
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
t.
|
|
731
|
-
t.
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
))
|
|
736
|
-
])
|
|
737
|
-
)
|
|
738
|
-
);
|
|
739
|
-
} else if (domName === 'style') {
|
|
740
|
-
statements.push(
|
|
741
|
-
t.expressionStatement(
|
|
742
|
-
t.callExpression(t.identifier('_$effect'), [
|
|
743
|
-
t.arrowFunctionExpression([], t.blockStatement([
|
|
744
|
-
t.expressionStatement(
|
|
745
|
-
t.callExpression(
|
|
746
|
-
t.memberExpression(
|
|
747
|
-
t.memberExpression(t.identifier('Object'), t.identifier('assign')),
|
|
748
|
-
t.identifier('call')
|
|
749
|
-
),
|
|
750
|
-
[t.nullLiteral(), t.memberExpression(t.identifier(elId), t.identifier('style')), expr]
|
|
751
|
-
)
|
|
752
|
-
)
|
|
753
|
-
]))
|
|
754
|
-
])
|
|
755
|
-
)
|
|
756
|
-
);
|
|
757
|
-
} else {
|
|
758
|
-
statements.push(
|
|
759
|
-
t.expressionStatement(
|
|
760
|
-
t.callExpression(t.identifier('_$effect'), [
|
|
761
|
-
t.arrowFunctionExpression([], t.callExpression(
|
|
762
|
-
t.memberExpression(t.identifier(elId), t.identifier('setAttribute')),
|
|
763
|
-
[t.stringLiteral(domName), expr]
|
|
764
|
-
))
|
|
765
|
-
])
|
|
766
|
-
)
|
|
767
|
-
);
|
|
768
|
-
}
|
|
775
|
+
statements.push(
|
|
776
|
+
t.expressionStatement(
|
|
777
|
+
t.callExpression(t.identifier('_$effect'), [
|
|
778
|
+
t.arrowFunctionExpression([], buildSetPropCall(domName, expr))
|
|
779
|
+
])
|
|
780
|
+
)
|
|
781
|
+
);
|
|
769
782
|
} else {
|
|
770
783
|
// Static expression (no signal calls) — set once
|
|
771
|
-
|
|
772
|
-
statements.push(
|
|
773
|
-
t.expressionStatement(
|
|
774
|
-
t.assignmentExpression('=',
|
|
775
|
-
t.memberExpression(t.identifier(elId), t.identifier('className')),
|
|
776
|
-
t.logicalExpression('||', expr, t.stringLiteral(''))
|
|
777
|
-
)
|
|
778
|
-
)
|
|
779
|
-
);
|
|
780
|
-
} else {
|
|
781
|
-
statements.push(
|
|
782
|
-
t.expressionStatement(
|
|
783
|
-
t.callExpression(
|
|
784
|
-
t.memberExpression(t.identifier(elId), t.identifier('setAttribute')),
|
|
785
|
-
[t.stringLiteral(domName), expr]
|
|
786
|
-
)
|
|
787
|
-
)
|
|
788
|
-
);
|
|
789
|
-
}
|
|
784
|
+
statements.push(t.expressionStatement(buildSetPropCall(domName, expr)));
|
|
790
785
|
}
|
|
791
786
|
}
|
|
792
787
|
// Static string/boolean attributes already in template
|
|
@@ -809,16 +804,16 @@ export default function whatBabelPlugin({ types: t }) {
|
|
|
809
804
|
if (t.isJSXEmptyExpression(child.expression)) continue;
|
|
810
805
|
|
|
811
806
|
const expr = child.expression;
|
|
807
|
+
const marker = buildChildAccess(elId, childIndex);
|
|
812
808
|
state.needsInsert = true;
|
|
813
809
|
|
|
814
|
-
// insert(parent, () => expr, marker?)
|
|
815
|
-
// For now use simple insert without marker — appends
|
|
816
810
|
if (isPotentiallyReactive(expr)) {
|
|
817
811
|
statements.push(
|
|
818
812
|
t.expressionStatement(
|
|
819
813
|
t.callExpression(t.identifier('_$insert'), [
|
|
820
814
|
t.identifier(elId),
|
|
821
|
-
t.arrowFunctionExpression([], expr)
|
|
815
|
+
t.arrowFunctionExpression([], expr),
|
|
816
|
+
marker
|
|
822
817
|
])
|
|
823
818
|
)
|
|
824
819
|
);
|
|
@@ -827,11 +822,13 @@ export default function whatBabelPlugin({ types: t }) {
|
|
|
827
822
|
t.expressionStatement(
|
|
828
823
|
t.callExpression(t.identifier('_$insert'), [
|
|
829
824
|
t.identifier(elId),
|
|
830
|
-
expr
|
|
825
|
+
expr,
|
|
826
|
+
marker
|
|
831
827
|
])
|
|
832
828
|
)
|
|
833
829
|
);
|
|
834
830
|
}
|
|
831
|
+
childIndex++;
|
|
835
832
|
continue;
|
|
836
833
|
}
|
|
837
834
|
|
|
@@ -840,15 +837,18 @@ export default function whatBabelPlugin({ types: t }) {
|
|
|
840
837
|
if (isComponent(childTag) || childTag === 'For' || childTag === 'Show') {
|
|
841
838
|
// Component/control-flow — transform and insert
|
|
842
839
|
const transformed = transformElementFineGrained({ node: child }, state);
|
|
840
|
+
const marker = buildChildAccess(elId, childIndex);
|
|
843
841
|
state.needsInsert = true;
|
|
844
842
|
statements.push(
|
|
845
843
|
t.expressionStatement(
|
|
846
844
|
t.callExpression(t.identifier('_$insert'), [
|
|
847
845
|
t.identifier(elId),
|
|
848
|
-
transformed
|
|
846
|
+
transformed,
|
|
847
|
+
marker
|
|
849
848
|
])
|
|
850
849
|
)
|
|
851
850
|
);
|
|
851
|
+
childIndex++;
|
|
852
852
|
} else {
|
|
853
853
|
// Static child element — already in template
|
|
854
854
|
// But check if it has dynamic children/attrs that need effects
|
|
@@ -907,10 +907,10 @@ export default function whatBabelPlugin({ types: t }) {
|
|
|
907
907
|
}
|
|
908
908
|
|
|
909
909
|
function buildChildAccess(elId, index) {
|
|
910
|
-
//
|
|
911
|
-
//
|
|
910
|
+
// Use childNodes[n] (not children[n]) so indices remain stable when text/comment
|
|
911
|
+
// placeholders are present in the static template.
|
|
912
912
|
return t.memberExpression(
|
|
913
|
-
t.memberExpression(t.identifier(elId), t.identifier('
|
|
913
|
+
t.memberExpression(t.identifier(elId), t.identifier('childNodes')),
|
|
914
914
|
t.numericLiteral(index),
|
|
915
915
|
true // computed
|
|
916
916
|
);
|
|
@@ -1022,6 +1022,7 @@ export default function whatBabelPlugin({ types: t }) {
|
|
|
1022
1022
|
state.needsEffect = false;
|
|
1023
1023
|
state.needsMapArray = false;
|
|
1024
1024
|
state.needsSpread = false;
|
|
1025
|
+
state.needsSetProp = false;
|
|
1025
1026
|
state.templates = [];
|
|
1026
1027
|
state.templateCount = 0;
|
|
1027
1028
|
state._varCounter = 0;
|
|
@@ -1069,6 +1070,11 @@ export default function whatBabelPlugin({ types: t }) {
|
|
|
1069
1070
|
t.importSpecifier(t.identifier('_$spread'), t.identifier('spread'))
|
|
1070
1071
|
);
|
|
1071
1072
|
}
|
|
1073
|
+
if (state.needsSetProp) {
|
|
1074
|
+
fgSpecifiers.push(
|
|
1075
|
+
t.importSpecifier(t.identifier('_$setProp'), t.identifier('setProp'))
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1072
1078
|
|
|
1073
1079
|
// Also include h/Fragment/Island if vdom mode used for components
|
|
1074
1080
|
const coreSpecifiers = [];
|