what-compiler 0.10.0 → 0.11.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.
@@ -573,7 +573,7 @@ function whatBabelPlugin({ types: t }) {
573
573
  t.variableDeclarator(t.identifier(elId), t.callExpression(t.identifier(tmplId), []))
574
574
  ])
575
575
  ];
576
- applyDynamicAttrs(statements, elId, attributes, state);
576
+ applyDynamicAttrs(statements, elId, attributes, state, tagName);
577
577
  applyDynamicChildren(statements, elId, children, node, state);
578
578
  if (!state._pendingSetup) state._pendingSetup = [];
579
579
  state._pendingSetup.push(...statements);
@@ -616,8 +616,33 @@ function whatBabelPlugin({ types: t }) {
616
616
  const propsExpr = props.length > 0 ? t.objectExpression(props) : t.nullLiteral();
617
617
  return t.callExpression(t.identifier("h"), [t.stringLiteral(tagName), propsExpr, ...transformedChildren]);
618
618
  }
619
- function applyDynamicAttrs(statements, elId, attributes, state) {
619
+ const VALUE_PROP_TAGS = /* @__PURE__ */ new Set(["input", "textarea", "select", "option"]);
620
+ function applyDynamicAttrs(statements, elId, attributes, state, tagName) {
620
621
  function buildSetPropCall(propName, valueExpr) {
622
+ if (propName === "class") {
623
+ state.needsSetClass = true;
624
+ return t.callExpression(t.identifier("_$setClass"), [t.identifier(elId), valueExpr]);
625
+ }
626
+ if (propName === "style") {
627
+ state.needsSetStyle = true;
628
+ return t.callExpression(t.identifier("_$setStyle"), [t.identifier(elId), valueExpr]);
629
+ }
630
+ if (propName === "value" && tagName && VALUE_PROP_TAGS.has(tagName)) {
631
+ state.needsSetValue = true;
632
+ return t.callExpression(t.identifier("_$setValue"), [t.identifier(elId), valueExpr]);
633
+ }
634
+ if (propName === "checked" && tagName === "input") {
635
+ state.needsSetChecked = true;
636
+ return t.callExpression(t.identifier("_$setChecked"), [t.identifier(elId), valueExpr]);
637
+ }
638
+ if (propName.startsWith("data-") || propName.startsWith("aria-")) {
639
+ state.needsSetAttr = true;
640
+ return t.callExpression(t.identifier("_$setAttr"), [
641
+ t.identifier(elId),
642
+ t.stringLiteral(propName),
643
+ valueExpr
644
+ ]);
645
+ }
621
646
  state.needsSetProp = true;
622
647
  return t.callExpression(t.identifier("_$setProp"), [
623
648
  t.identifier(elId),
@@ -625,6 +650,14 @@ function whatBabelPlugin({ types: t }) {
625
650
  valueExpr
626
651
  ]);
627
652
  }
653
+ let delegateInitEmitted = false;
654
+ function emitDelegateInit() {
655
+ if (delegateInitEmitted) return;
656
+ delegateInitEmitted = true;
657
+ statements.push(
658
+ t.expressionStatement(t.callExpression(t.identifier("_$delegate$"), []))
659
+ );
660
+ }
628
661
  for (const attr of attributes) {
629
662
  if (t.isJSXSpreadAttribute(attr)) {
630
663
  state.needsSpread = true;
@@ -665,6 +698,7 @@ function whatBabelPlugin({ types: t }) {
665
698
  state.needsDelegation = true;
666
699
  if (!state.delegatedEvents) state.delegatedEvents = /* @__PURE__ */ new Set();
667
700
  state.delegatedEvents.add(event);
701
+ emitDelegateInit();
668
702
  statements.push(
669
703
  t.expressionStatement(
670
704
  t.assignmentExpression(
@@ -808,6 +842,50 @@ function whatBabelPlugin({ types: t }) {
808
842
  }
809
843
  }
810
844
  }
845
+ function buildsDOM(node) {
846
+ if (!node || typeof node !== "object") return false;
847
+ if (Array.isArray(node)) return node.some(buildsDOM);
848
+ if (node.type === "JSXElement" || node.type === "JSXFragment") return true;
849
+ if (node.type === "CallExpression" && node.callee && node.callee.type === "Identifier" && (node.callee.name === "_$mapArray" || node.callee.name === "mapArray")) {
850
+ return true;
851
+ }
852
+ for (const key of Object.keys(node)) {
853
+ if (key === "loc" || key === "start" || key === "end" || key === "leadingComments" || key === "trailingComments" || key === "innerComments") continue;
854
+ const v = node[key];
855
+ if (v && typeof v === "object" && buildsDOM(v)) return true;
856
+ }
857
+ return false;
858
+ }
859
+ function memoizeBranchCondition(expr, statements, state) {
860
+ let testExpr = null;
861
+ let isTernary = false;
862
+ if (t.isConditionalExpression(expr)) {
863
+ testExpr = expr.test;
864
+ isTernary = true;
865
+ } else if (t.isLogicalExpression(expr) && (expr.operator === "&&" || expr.operator === "||")) {
866
+ testExpr = expr.left;
867
+ } else {
868
+ return expr;
869
+ }
870
+ if (!isPotentiallyReactive(testExpr, state.signalNames, state.importedIdentifiers)) return expr;
871
+ const branches = isTernary ? [expr.consequent, expr.alternate] : [expr.right];
872
+ if (!branches.some(buildsDOM)) return expr;
873
+ const condId = state.nextMemoId();
874
+ state.needsMemo = true;
875
+ const memoBody = isTernary ? t.unaryExpression("!", t.unaryExpression("!", testExpr)) : testExpr;
876
+ statements.push(
877
+ t.variableDeclaration("const", [
878
+ t.variableDeclarator(
879
+ t.identifier(condId),
880
+ t.callExpression(t.identifier("_$memo"), [
881
+ t.arrowFunctionExpression([], memoBody)
882
+ ])
883
+ )
884
+ ])
885
+ );
886
+ const condRead = t.callExpression(t.identifier(condId), []);
887
+ return isTernary ? t.conditionalExpression(condRead, expr.consequent, expr.alternate) : t.logicalExpression(expr.operator, condRead, expr.right);
888
+ }
811
889
  function applyDynamicChildren(statements, elId, children, parentNode, state) {
812
890
  const entries = [];
813
891
  let childIndex = 0;
@@ -880,11 +958,16 @@ function whatBabelPlugin({ types: t }) {
880
958
  let expr = entry.child.expression;
881
959
  const marker = getMarker(entry.childIndex);
882
960
  state.needsInsert = true;
883
- const mapResult = tryLowerMapToMapArray(expr, state);
961
+ let mapResult = tryLowerMapToMapArray(expr, state);
884
962
  if (mapResult) {
885
963
  state.needsMapArray = true;
886
964
  const isBareMapArray = t.isCallExpression(mapResult) && t.isIdentifier(mapResult.callee) && (mapResult.callee.name === "_$mapArray" || mapResult.callee.name === "mapArray");
887
965
  const isArrowAlready = t.isArrowFunctionExpression(mapResult);
966
+ if (isArrowAlready && t.isExpression(mapResult.body)) {
967
+ mapResult.body = memoizeBranchCondition(mapResult.body, statements, state);
968
+ } else if (!isBareMapArray && !isArrowAlready) {
969
+ mapResult = memoizeBranchCondition(mapResult, statements, state);
970
+ }
888
971
  const insertArg = isBareMapArray || isArrowAlready ? mapResult : t.arrowFunctionExpression([], mapResult);
889
972
  statements.push(
890
973
  t.expressionStatement(
@@ -913,6 +996,7 @@ function whatBabelPlugin({ types: t }) {
913
996
  continue;
914
997
  }
915
998
  if (isPotentiallyReactive(expr, state.signalNames, state.importedIdentifiers)) {
999
+ expr = memoizeBranchCondition(expr, statements, state);
916
1000
  const insertCall = t.callExpression(t.identifier("_$insert"), [
917
1001
  t.identifier(elId),
918
1002
  t.arrowFunctionExpression([], expr),
@@ -970,7 +1054,7 @@ function whatBabelPlugin({ types: t }) {
970
1054
  ])
971
1055
  );
972
1056
  }
973
- applyDynamicAttrs(statements, childElRef, entry.child.openingElement.attributes, state);
1057
+ applyDynamicAttrs(statements, childElRef, entry.child.openingElement.attributes, state, entry.child.openingElement.name.name);
974
1058
  applyDynamicChildren(statements, childElRef, entry.child.children, entry.child, state);
975
1059
  continue;
976
1060
  }
@@ -978,8 +1062,9 @@ function whatBabelPlugin({ types: t }) {
978
1062
  for (const fChild of entry.child.children) {
979
1063
  if (t.isJSXExpressionContainer(fChild) && !t.isJSXEmptyExpression(fChild.expression)) {
980
1064
  state.needsInsert = true;
981
- const expr = fChild.expression;
1065
+ let expr = fChild.expression;
982
1066
  if (isPotentiallyReactive(expr, state.signalNames, state.importedIdentifiers)) {
1067
+ expr = memoizeBranchCondition(expr, statements, state);
983
1068
  statements.push(
984
1069
  t.expressionStatement(
985
1070
  t.callExpression(t.identifier("_$insert"), [
@@ -1272,8 +1357,26 @@ function whatBabelPlugin({ types: t }) {
1272
1357
  condition = whenExpr;
1273
1358
  }
1274
1359
  const vId = path.scope ? path.scope.generateUidIdentifier("v") : t.identifier("_v");
1275
- const consequent = t.isFunction(contentExpr) ? t.callExpression(contentExpr, [t.cloneNode(vId)]) : contentExpr;
1360
+ const contentIsFn = t.isFunction(contentExpr);
1361
+ const consequent = contentIsFn ? t.callExpression(contentExpr, [t.cloneNode(vId)]) : contentExpr;
1276
1362
  const alternate = fallbackExpr || t.nullLiteral();
1363
+ if (isPotentiallyReactive(condition, state.signalNames, state.importedIdentifiers)) {
1364
+ const condId = state.nextMemoId();
1365
+ state.needsMemo = true;
1366
+ const memoBody = contentIsFn ? condition : t.unaryExpression("!", t.unaryExpression("!", condition));
1367
+ if (!state._pendingSetup) state._pendingSetup = [];
1368
+ state._pendingSetup.push(
1369
+ t.variableDeclaration("const", [
1370
+ t.variableDeclarator(
1371
+ t.identifier(condId),
1372
+ t.callExpression(t.identifier("_$memo"), [
1373
+ t.arrowFunctionExpression([], memoBody)
1374
+ ])
1375
+ )
1376
+ ])
1377
+ );
1378
+ condition = t.callExpression(t.identifier(condId), []);
1379
+ }
1277
1380
  return t.arrowFunctionExpression([], t.blockStatement([
1278
1381
  t.variableDeclaration("const", [
1279
1382
  t.variableDeclarator(vId, condition)
@@ -1283,6 +1386,34 @@ function whatBabelPlugin({ types: t }) {
1283
1386
  )
1284
1387
  ]));
1285
1388
  }
1389
+ function lowerFragmentExprChild(expr, state) {
1390
+ if (!state._pendingSetup) state._pendingSetup = [];
1391
+ const setup = state._pendingSetup;
1392
+ const mapResult = tryLowerMapToMapArray(expr, state);
1393
+ if (mapResult) {
1394
+ state.needsMapArray = true;
1395
+ const isBareMapArray = t.isCallExpression(mapResult) && t.isIdentifier(mapResult.callee) && (mapResult.callee.name === "_$mapArray" || mapResult.callee.name === "mapArray");
1396
+ const isArrowAlready = t.isArrowFunctionExpression(mapResult);
1397
+ if (isArrowAlready && t.isExpression(mapResult.body)) {
1398
+ mapResult.body = memoizeBranchCondition(mapResult.body, setup, state);
1399
+ return mapResult;
1400
+ }
1401
+ if (isBareMapArray) return mapResult;
1402
+ const memoized = memoizeBranchCondition(mapResult, setup, state);
1403
+ return t.arrowFunctionExpression([], memoized);
1404
+ }
1405
+ const isMapArrayCall = t.isCallExpression(expr) && t.isIdentifier(expr.callee) && (expr.callee.name === "mapArray" || expr.callee.name === "_$mapArray");
1406
+ if (isMapArrayCall) {
1407
+ state.needsMapArray = true;
1408
+ if (expr.callee.name === "mapArray") expr.callee.name = "_$mapArray";
1409
+ return expr;
1410
+ }
1411
+ if (isPotentiallyReactive(expr, state.signalNames, state.importedIdentifiers)) {
1412
+ expr = memoizeBranchCondition(expr, setup, state);
1413
+ return t.arrowFunctionExpression([], expr);
1414
+ }
1415
+ return expr;
1416
+ }
1286
1417
  function transformFragmentFineGrained(path, state) {
1287
1418
  const { node } = path;
1288
1419
  const children = node.children;
@@ -1293,7 +1424,7 @@ function whatBabelPlugin({ types: t }) {
1293
1424
  if (text) transformed.push(t.stringLiteral(text));
1294
1425
  } else if (t.isJSXExpressionContainer(child)) {
1295
1426
  if (!t.isJSXEmptyExpression(child.expression)) {
1296
- transformed.push(child.expression);
1427
+ transformed.push(lowerFragmentExprChild(child.expression, state));
1297
1428
  }
1298
1429
  } else if (t.isJSXElement(child)) {
1299
1430
  transformed.push(transformElementFineGrained({ node: child }, state));
@@ -1313,6 +1444,46 @@ function whatBabelPlugin({ types: t }) {
1313
1444
  state.templates.push({ id, html });
1314
1445
  return id;
1315
1446
  }
1447
+ function transformJsxRoot(path, state, transform) {
1448
+ const scope = path.scope;
1449
+ let cache = state._signalNamesCache;
1450
+ if (!cache) cache = state._signalNamesCache = /* @__PURE__ */ new WeakMap();
1451
+ let names = cache.get(scope);
1452
+ if (!names) {
1453
+ names = collectSignalNamesFromScope(path);
1454
+ cache.set(scope, names);
1455
+ }
1456
+ state.signalNames = names;
1457
+ state._pendingSetup = [];
1458
+ const transformed = transform(path, state);
1459
+ const pending = state._pendingSetup;
1460
+ state._pendingSetup = [];
1461
+ if (pending.length > 0) {
1462
+ let stmtPath = path;
1463
+ let crossedFunctionBoundary = false;
1464
+ while (stmtPath && !stmtPath.isStatement()) {
1465
+ if (stmtPath.isArrowFunctionExpression() || stmtPath.isFunctionExpression()) {
1466
+ crossedFunctionBoundary = true;
1467
+ }
1468
+ stmtPath = stmtPath.parentPath;
1469
+ }
1470
+ const inStatementList = stmtPath && stmtPath.isStatement() && (stmtPath.listKey === "body" || stmtPath.listKey === "consequent") && Array.isArray(stmtPath.container);
1471
+ if (inStatementList && !crossedFunctionBoundary) {
1472
+ stmtPath.insertBefore(pending);
1473
+ path.replaceWith(transformed);
1474
+ } else {
1475
+ pending.push(t.returnStatement(transformed));
1476
+ path.replaceWith(
1477
+ t.callExpression(
1478
+ t.arrowFunctionExpression([], t.blockStatement(pending)),
1479
+ []
1480
+ )
1481
+ );
1482
+ }
1483
+ } else {
1484
+ path.replaceWith(transformed);
1485
+ }
1486
+ }
1316
1487
  return {
1317
1488
  name: "what-jsx-transform",
1318
1489
  visitor: {
@@ -1324,6 +1495,12 @@ function whatBabelPlugin({ types: t }) {
1324
1495
  state.needsMapArray = false;
1325
1496
  state.needsSpread = false;
1326
1497
  state.needsSetProp = false;
1498
+ state.needsMemo = false;
1499
+ state.needsSetClass = false;
1500
+ state.needsSetStyle = false;
1501
+ state.needsSetAttr = false;
1502
+ state.needsSetValue = false;
1503
+ state.needsSetChecked = false;
1327
1504
  state.needsH = false;
1328
1505
  state.needsCreateComponent = false;
1329
1506
  state.needsFragment = false;
@@ -1334,8 +1511,10 @@ function whatBabelPlugin({ types: t }) {
1334
1511
  state.templateMap = /* @__PURE__ */ new Map();
1335
1512
  state.templateCount = 0;
1336
1513
  state._varCounter = 0;
1514
+ state._memoCounter = 0;
1337
1515
  state._pendingSetup = [];
1338
1516
  state.nextVarId = () => `_el$${state._varCounter++}`;
1517
+ state.nextMemoId = () => `_c$${state._memoCounter++}`;
1339
1518
  state.signalNames = /* @__PURE__ */ new Set();
1340
1519
  state.importedIdentifiers = /* @__PURE__ */ new Set();
1341
1520
  for (const node of path.node.body) {
@@ -1391,20 +1570,19 @@ function whatBabelPlugin({ types: t }) {
1391
1570
  },
1392
1571
  exit(path, state) {
1393
1572
  for (const tmpl of state.templates.reverse()) {
1573
+ const tmplCall = t.callExpression(t.identifier("_$template"), [t.stringLiteral(tmpl.html)]);
1574
+ t.addComment(tmplCall, "leading", " @__PURE__ ");
1394
1575
  path.unshiftContainer(
1395
1576
  "body",
1396
1577
  t.variableDeclaration("const", [
1397
- t.variableDeclarator(
1398
- t.identifier(tmpl.id),
1399
- t.callExpression(t.identifier("_$template"), [t.stringLiteral(tmpl.html)])
1400
- )
1578
+ t.variableDeclarator(t.identifier(tmpl.id), tmplCall)
1401
1579
  ])
1402
1580
  );
1403
1581
  }
1404
1582
  const fgSpecifiers = [];
1405
1583
  if (state.needsTemplate) {
1406
1584
  fgSpecifiers.push(
1407
- t.importSpecifier(t.identifier("_$template"), t.identifier("template"))
1585
+ t.importSpecifier(t.identifier("_$template"), t.identifier("_$template"))
1408
1586
  );
1409
1587
  }
1410
1588
  if (state.needsInsert) {
@@ -1432,6 +1610,36 @@ function whatBabelPlugin({ types: t }) {
1432
1610
  t.importSpecifier(t.identifier("_$setProp"), t.identifier("setProp"))
1433
1611
  );
1434
1612
  }
1613
+ if (state.needsMemo) {
1614
+ fgSpecifiers.push(
1615
+ t.importSpecifier(t.identifier("_$memo"), t.identifier("memo"))
1616
+ );
1617
+ }
1618
+ if (state.needsSetClass) {
1619
+ fgSpecifiers.push(
1620
+ t.importSpecifier(t.identifier("_$setClass"), t.identifier("setClass"))
1621
+ );
1622
+ }
1623
+ if (state.needsSetStyle) {
1624
+ fgSpecifiers.push(
1625
+ t.importSpecifier(t.identifier("_$setStyle"), t.identifier("setStyle"))
1626
+ );
1627
+ }
1628
+ if (state.needsSetAttr) {
1629
+ fgSpecifiers.push(
1630
+ t.importSpecifier(t.identifier("_$setAttr"), t.identifier("setAttr"))
1631
+ );
1632
+ }
1633
+ if (state.needsSetValue) {
1634
+ fgSpecifiers.push(
1635
+ t.importSpecifier(t.identifier("_$setValue"), t.identifier("setValue"))
1636
+ );
1637
+ }
1638
+ if (state.needsSetChecked) {
1639
+ fgSpecifiers.push(
1640
+ t.importSpecifier(t.identifier("_$setChecked"), t.identifier("setChecked"))
1641
+ );
1642
+ }
1435
1643
  if (state.needsCreateComponent) {
1436
1644
  fgSpecifiers.push(
1437
1645
  t.importSpecifier(t.identifier("_$createComponent"), t.identifier("_$createComponent"))
@@ -1489,58 +1697,36 @@ function whatBabelPlugin({ types: t }) {
1489
1697
  const eventArray = t.arrayExpression(
1490
1698
  [...state.delegatedEvents].map((e) => t.stringLiteral(e))
1491
1699
  );
1492
- path.pushContainer(
1493
- "body",
1494
- t.expressionStatement(
1495
- t.callExpression(t.identifier("_$delegateEvents"), [eventArray])
1496
- )
1700
+ const helperFn = t.functionDeclaration(
1701
+ t.identifier("_$delegate$"),
1702
+ [],
1703
+ t.blockStatement([
1704
+ t.ifStatement(
1705
+ t.identifier("_$delegated$"),
1706
+ t.returnStatement()
1707
+ ),
1708
+ t.expressionStatement(
1709
+ t.assignmentExpression("=", t.identifier("_$delegated$"), t.booleanLiteral(true))
1710
+ ),
1711
+ t.expressionStatement(
1712
+ t.callExpression(t.identifier("_$delegateEvents"), [eventArray])
1713
+ )
1714
+ ])
1497
1715
  );
1716
+ path.unshiftContainer("body", [
1717
+ t.variableDeclaration("let", [
1718
+ t.variableDeclarator(t.identifier("_$delegated$"), t.booleanLiteral(false))
1719
+ ]),
1720
+ helperFn
1721
+ ]);
1498
1722
  }
1499
1723
  }
1500
1724
  },
1501
1725
  JSXElement(path, state) {
1502
- const scope = path.scope;
1503
- let cache = state._signalNamesCache;
1504
- if (!cache) cache = state._signalNamesCache = /* @__PURE__ */ new WeakMap();
1505
- let names = cache.get(scope);
1506
- if (!names) {
1507
- names = collectSignalNamesFromScope(path);
1508
- cache.set(scope, names);
1509
- }
1510
- state.signalNames = names;
1511
- state._pendingSetup = [];
1512
- const transformed = transformElementFineGrained(path, state);
1513
- const pending = state._pendingSetup;
1514
- state._pendingSetup = [];
1515
- if (pending.length > 0) {
1516
- let stmtPath = path;
1517
- let crossedFunctionBoundary = false;
1518
- while (stmtPath && !stmtPath.isStatement()) {
1519
- if (stmtPath.isArrowFunctionExpression() || stmtPath.isFunctionExpression()) {
1520
- crossedFunctionBoundary = true;
1521
- }
1522
- stmtPath = stmtPath.parentPath;
1523
- }
1524
- const inStatementList = stmtPath && stmtPath.isStatement() && (stmtPath.listKey === "body" || stmtPath.listKey === "consequent") && Array.isArray(stmtPath.container);
1525
- if (inStatementList && !crossedFunctionBoundary) {
1526
- stmtPath.insertBefore(pending);
1527
- path.replaceWith(transformed);
1528
- } else {
1529
- pending.push(t.returnStatement(transformed));
1530
- path.replaceWith(
1531
- t.callExpression(
1532
- t.arrowFunctionExpression([], t.blockStatement(pending)),
1533
- []
1534
- )
1535
- );
1536
- }
1537
- } else {
1538
- path.replaceWith(transformed);
1539
- }
1726
+ transformJsxRoot(path, state, transformElementFineGrained);
1540
1727
  },
1541
1728
  JSXFragment(path, state) {
1542
- const transformed = transformFragmentFineGrained(path, state);
1543
- path.replaceWith(transformed);
1729
+ transformJsxRoot(path, state, transformFragmentFineGrained);
1544
1730
  }
1545
1731
  }
1546
1732
  };