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.
Files changed (2) hide show
  1. package/package.json +2 -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.0",
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.0"
26
+ "what-core": "^0.5.1"
27
27
  },
28
28
  "files": [
29
29
  "src"
@@ -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 leave a placeholder
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
- // Void elements
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
- // Dynamic child — placeholder will be handled by insert()
519
- // Skip entirely from template
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
- // Component — skip from template
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
- let domName = attrName;
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
- if (domName === 'class') {
729
- statements.push(
730
- t.expressionStatement(
731
- t.callExpression(t.identifier('_$effect'), [
732
- t.arrowFunctionExpression([], t.assignmentExpression('=',
733
- t.memberExpression(t.identifier(elId), t.identifier('className')),
734
- t.logicalExpression('||', expr, t.stringLiteral(''))
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
- if (domName === 'class') {
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
- // Build _el$.children[index] or _el$.firstChild / .firstChild.nextSibling chain
911
- // Use children[n] for simplicity and readability
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('children')),
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 = [];