babel-plugin-jsx-dom-expressions 0.29.17 → 0.30.5

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 (3) hide show
  1. package/README.md +1 -1
  2. package/index.js +510 -105
  3. package/package.json +4 -3
package/README.md CHANGED
@@ -20,7 +20,7 @@ This library uses a heuristic whether to dynamic wrap expressions based on if th
20
20
  ```jsx
21
21
  const view = ({ item }) => {
22
22
  const itemId = item.id;
23
- <tr class={itemId === selected() ? "danger" : ""}>
23
+ return <tr class={itemId === selected() ? "danger" : ""}>
24
24
  <td class="col-md-1">{itemId}</td>
25
25
  <td class="col-md-4">
26
26
  <a onclick={e => select(item, e)}>{item.label}</a>
package/index.js CHANGED
@@ -3,6 +3,7 @@
3
3
  var SyntaxJSX = require('@babel/plugin-syntax-jsx');
4
4
  var t = require('@babel/types');
5
5
  var helperModuleImports = require('@babel/helper-module-imports');
6
+ var htmlEntities = require('html-entities');
6
7
 
7
8
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
8
9
 
@@ -29,21 +30,6 @@ function _interopNamespace(e) {
29
30
  var SyntaxJSX__default = /*#__PURE__*/_interopDefaultLegacy(SyntaxJSX);
30
31
  var t__namespace = /*#__PURE__*/_interopNamespace(t);
31
32
 
32
- var config = {
33
- moduleName: "dom",
34
- generate: "dom",
35
- hydratable: false,
36
- delegateEvents: true,
37
- delegatedEvents: [],
38
- builtIns: [],
39
- requireImportSource: false,
40
- wrapConditionals: true,
41
- contextToCustomElements: false,
42
- staticMarker: "@once",
43
- effectWrapper: "effect",
44
- memoWrapper: "memo"
45
- };
46
-
47
33
  const booleans = [
48
34
  "allowfullscreen",
49
35
  "async",
@@ -234,12 +220,16 @@ const reservedNameSpaces = new Set([
234
220
  "attr"
235
221
  ]);
236
222
 
223
+ function getConfig(path) {
224
+ return path.hub.file.metadata.config;
225
+ }
226
+
237
227
  function registerImportMethod(path, name) {
238
228
  const imports =
239
229
  path.scope.getProgramParent().data.imports ||
240
230
  (path.scope.getProgramParent().data.imports = new Set());
241
231
  if (!imports.has(name)) {
242
- helperModuleImports.addNamed(path, name, config.moduleName, {
232
+ helperModuleImports.addNamed(path, name, getConfig(path).moduleName, {
243
233
  nameHint: `_$${name}`
244
234
  });
245
235
  imports.add(name);
@@ -281,6 +271,7 @@ function isComponent(tagName) {
281
271
  }
282
272
 
283
273
  function isDynamic(path, { checkMember, checkTags, checkCallExpressions = true, native }) {
274
+ const config = getConfig(path);
284
275
  if (config.generate === "ssr" && native) {
285
276
  checkMember = false;
286
277
  checkCallExpressions = false;
@@ -378,7 +369,31 @@ function toPropertyName(name) {
378
369
  return name.toLowerCase().replace(/-([a-z])/g, (_, w) => w.toUpperCase());
379
370
  }
380
371
 
372
+ function wrappedByText(list, startIndex) {
373
+ let index = startIndex,
374
+ wrapped;
375
+ while (--index >= 0) {
376
+ const node = list[index];
377
+ if (!node) continue;
378
+ if (node.text) {
379
+ wrapped = true;
380
+ break;
381
+ }
382
+ if (node.id) return false;
383
+ }
384
+ if (!wrapped) return false;
385
+ index = startIndex;
386
+ while (++index < list.length) {
387
+ const node = list[index];
388
+ if (!node) continue;
389
+ if (node.text) return true;
390
+ if (node.id) return false;
391
+ }
392
+ return false;
393
+ }
394
+
381
395
  function transformCondition(path, deep) {
396
+ const config = getConfig(path);
382
397
  const expr = path.node;
383
398
  if (config.memoWrapper) {
384
399
  registerImportMethod(path, config.memoWrapper);
@@ -497,8 +512,9 @@ function escapeHTML(s, attr) {
497
512
  return left < s.length ? out + s.substring(left) : out;
498
513
  }
499
514
 
500
- function transformElement$1(path, info) {
515
+ function transformElement$2(path, info) {
501
516
  let tagName = getTagName(path.node),
517
+ config = getConfig(path),
502
518
  wrapSVG = info.topLevel && tagName != "svg" && SVGElements.has(tagName),
503
519
  voidTag = VoidElements.indexOf(tagName) > -1,
504
520
  results = {
@@ -513,13 +529,13 @@ function transformElement$1(path, info) {
513
529
  if (tagName === "html" && config.hydratable) results.skipTemplate = true;
514
530
  if (wrapSVG) results.template = "<svg>" + results.template;
515
531
  if (!info.skipId) results.id = path.scope.generateUidIdentifier("el$");
516
- transformAttributes$1(path, results);
532
+ transformAttributes$2(path, results);
517
533
  if (config.contextToCustomElements && (tagName === "slot" || tagName.indexOf("-") > -1)) {
518
534
  contextToCustomElement(path, results);
519
535
  }
520
536
  results.template += ">";
521
537
  if (!voidTag) {
522
- transformChildren$1(path, results);
538
+ transformChildren$2(path, results, config);
523
539
  results.template += `</${tagName}>`;
524
540
  }
525
541
  if (info.topLevel && config.hydratable && results.hasHydratableEvent) {
@@ -532,7 +548,7 @@ function transformElement$1(path, info) {
532
548
  return results;
533
549
  }
534
550
 
535
- function setAttr$1(path, elem, name, value, { isSVG, dynamic, prevId, isCE }) {
551
+ function setAttr$2(path, elem, name, value, { isSVG, dynamic, prevId, isCE }) {
536
552
  // pull out namespace
537
553
  let parts, namespace;
538
554
  if ((parts = name.split(":")) && parts[1] && reservedNameSpaces.has(parts[0])) {
@@ -628,7 +644,7 @@ function detectResolvableEventHandler(attribute, handler) {
628
644
  return t__namespace.isFunction(handler);
629
645
  }
630
646
 
631
- function transformAttributes$1(path, results) {
647
+ function transformAttributes$2(path, results) {
632
648
  let elem = results.id,
633
649
  hasHydratableEvent = false,
634
650
  children,
@@ -637,7 +653,8 @@ function transformAttributes$1(path, results) {
637
653
  tagName = getTagName(path.node),
638
654
  isSVG = SVGElements.has(tagName),
639
655
  isCE = tagName.includes("-"),
640
- hasChildren = path.node.children.length > 0;
656
+ hasChildren = path.node.children.length > 0,
657
+ config = getConfig(path);
641
658
 
642
659
  // preprocess styles
643
660
  const styleAttribute = attributes.find(
@@ -776,7 +793,9 @@ function transformAttributes$1(path, results) {
776
793
  isDynamic(attribute.get("argument"), {
777
794
  checkMember: true
778
795
  })
779
- ? t__namespace.arrowFunctionExpression([], node.argument)
796
+ ? t__namespace.isCallExpression(node.argument) && !node.argument.arguments.length
797
+ ? node.argument.callee
798
+ : t__namespace.arrowFunctionExpression([], node.argument)
780
799
  : node.argument,
781
800
  t__namespace.booleanLiteral(isSVG),
782
801
  t__namespace.booleanLiteral(hasChildren)
@@ -794,6 +813,16 @@ function transformAttributes$1(path, results) {
794
813
  : node.name.name,
795
814
  reservedNameSpace =
796
815
  t__namespace.isJSXNamespacedName(node.name) && reservedNameSpaces.has(node.name.namespace.name);
816
+ if (t__namespace.isJSXExpressionContainer(value)) {
817
+ const evaluated = attribute.get("value").get("expression").evaluate().value;
818
+ let type;
819
+ if (
820
+ evaluated !== undefined &&
821
+ ((type = typeof evaluated) === "string" || type === "number")
822
+ ) {
823
+ value = t__namespace.stringLiteral(String(evaluated));
824
+ }
825
+ }
797
826
  if (
798
827
  t__namespace.isJSXNamespacedName(node.name) &&
799
828
  reservedNameSpace &&
@@ -808,10 +837,18 @@ function transformAttributes$1(path, results) {
808
837
  ) {
809
838
  if (key === "ref") {
810
839
  // Normalize expressions for non-null and type-as
811
- while (t__namespace.isTSNonNullExpression(value.expression) || t__namespace.isTSAsExpression(value.expression)) {
840
+ while (
841
+ t__namespace.isTSNonNullExpression(value.expression) ||
842
+ t__namespace.isTSAsExpression(value.expression)
843
+ ) {
812
844
  value.expression = value.expression.expression;
813
845
  }
814
- if (t__namespace.isLVal(value.expression)) {
846
+ let binding,
847
+ isFunction =
848
+ t__namespace.isIdentifier(value.expression) &&
849
+ (binding = path.scope.getBinding(value.expression.name)) &&
850
+ binding.kind === "const";
851
+ if (!isFunction && t__namespace.isLVal(value.expression)) {
815
852
  const refIdentifier = path.scope.generateUidIdentifier("_ref$");
816
853
  results.exprs.unshift(
817
854
  t__namespace.variableDeclaration("const", [
@@ -829,7 +866,7 @@ function transformAttributes$1(path, results) {
829
866
  )
830
867
  )
831
868
  );
832
- } else if (t__namespace.isFunction(value.expression)) {
869
+ } else if (isFunction || t__namespace.isFunction(value.expression)) {
833
870
  results.exprs.unshift(
834
871
  t__namespace.expressionStatement(t__namespace.callExpression(value.expression, [elem]))
835
872
  );
@@ -997,7 +1034,7 @@ function transformAttributes$1(path, results) {
997
1034
  results.dynamics.push({ elem: nextElem, key, value: value.expression, isSVG, isCE });
998
1035
  } else {
999
1036
  results.exprs.push(
1000
- t__namespace.expressionStatement(setAttr$1(attribute, elem, key, value.expression, { isSVG, isCE }))
1037
+ t__namespace.expressionStatement(setAttr$2(attribute, elem, key, value.expression, { isSVG, isCE }))
1001
1038
  );
1002
1039
  }
1003
1040
  } else {
@@ -1009,7 +1046,7 @@ function transformAttributes$1(path, results) {
1009
1046
  key = Aliases[key] || key;
1010
1047
  if (value && ChildProperties.has(key)) {
1011
1048
  results.exprs.push(
1012
- t__namespace.expressionStatement(setAttr$1(attribute, elem, key, value, { isSVG, isCE }))
1049
+ t__namespace.expressionStatement(setAttr$2(attribute, elem, key, value, { isSVG, isCE }))
1013
1050
  );
1014
1051
  } else {
1015
1052
  !isSVG && (key = key.toLowerCase());
@@ -1025,31 +1062,7 @@ function transformAttributes$1(path, results) {
1025
1062
  results.hasHydratableEvent = results.hasHydratableEvent || hasHydratableEvent;
1026
1063
  }
1027
1064
 
1028
- function wrappedByText(list, startIndex) {
1029
- let index = startIndex,
1030
- wrapped;
1031
- while (--index >= 0) {
1032
- const node = list[index];
1033
- if (!node) continue;
1034
- if (node.text) {
1035
- wrapped = true;
1036
- break;
1037
- }
1038
- if (node.id) return false;
1039
- }
1040
- if (!wrapped) return false;
1041
- index = startIndex;
1042
- while (++index < list.length) {
1043
- const node = list[index];
1044
- if (!node) continue;
1045
- if (node.text) return true;
1046
- if (node.id) return false;
1047
- }
1048
- return false;
1049
- }
1050
-
1051
- function transformChildren$1(path, results) {
1052
- const { hydratable } = config;
1065
+ function transformChildren$2(path, results, config) {
1053
1066
  let tempPath = results.id && results.id.name,
1054
1067
  tagName = getTagName(path.node),
1055
1068
  nextPlaceholder,
@@ -1059,7 +1072,7 @@ function transformChildren$1(path, results) {
1059
1072
  .map(
1060
1073
  (child, index) =>
1061
1074
  transformNode(child, {
1062
- skipId: !results.id || !detectExpressions(filteredChildren, index)
1075
+ skipId: !results.id || !detectExpressions(filteredChildren, index, config)
1063
1076
  })
1064
1077
  // combine adjacent textNodes
1065
1078
  )
@@ -1076,7 +1089,7 @@ function transformChildren$1(path, results) {
1076
1089
  if (!child) return;
1077
1090
  results.template += child.template;
1078
1091
  if (child.id) {
1079
- if (hydratable && tagName === "html") {
1092
+ if (config.hydratable && tagName === "html") {
1080
1093
  registerImportMethod(path, "getNextMatch");
1081
1094
  }
1082
1095
  const walk = t__namespace.memberExpression(
@@ -1086,7 +1099,7 @@ function transformChildren$1(path, results) {
1086
1099
  results.decl.push(
1087
1100
  t__namespace.variableDeclarator(
1088
1101
  child.id,
1089
- hydratable && tagName === "html"
1102
+ config.hydratable && tagName === "html"
1090
1103
  ? t__namespace.callExpression(t__namespace.identifier("_$getNextMatch"), [
1091
1104
  walk,
1092
1105
  t__namespace.stringLiteral(child.tagName)
@@ -1104,7 +1117,7 @@ function transformChildren$1(path, results) {
1104
1117
  } else if (child.exprs.length) {
1105
1118
  registerImportMethod(path, "insert");
1106
1119
  const multi = checkLength(filteredChildren),
1107
- markers = hydratable && multi;
1120
+ markers = config.hydratable && multi;
1108
1121
  // boxed by textNodes
1109
1122
  if (markers || wrappedByText(childNodes, index)) {
1110
1123
  let exprId, contentId;
@@ -1132,7 +1145,7 @@ function transformChildren$1(path, results) {
1132
1145
  t__namespace.callExpression(t__namespace.identifier("_$insert"), [
1133
1146
  results.id,
1134
1147
  child.exprs[0],
1135
- nextChild(childNodes, index) || t__namespace.nullLiteral()
1148
+ nextChild$1(childNodes, index) || t__namespace.nullLiteral()
1136
1149
  ])
1137
1150
  )
1138
1151
  );
@@ -1141,7 +1154,7 @@ function transformChildren$1(path, results) {
1141
1154
  t__namespace.expressionStatement(
1142
1155
  t__namespace.callExpression(
1143
1156
  t__namespace.identifier("_$insert"),
1144
- hydratable
1157
+ config.hydratable
1145
1158
  ? [
1146
1159
  results.id,
1147
1160
  child.exprs[0],
@@ -1170,7 +1183,8 @@ function transformChildren$1(path, results) {
1170
1183
  }
1171
1184
 
1172
1185
  function createPlaceholder(path, results, tempPath, i, char) {
1173
- const exprId = path.scope.generateUidIdentifier("el$");
1186
+ const exprId = path.scope.generateUidIdentifier("el$"),
1187
+ config = getConfig(path);
1174
1188
  let contentId;
1175
1189
  results.template += `<!${char}>`;
1176
1190
  if (config.hydratable && char === "/") {
@@ -1197,12 +1211,12 @@ function createPlaceholder(path, results, tempPath, i, char) {
1197
1211
  return [exprId, contentId];
1198
1212
  }
1199
1213
 
1200
- function nextChild(children, index) {
1201
- return children[index + 1] && (children[index + 1].id || nextChild(children, index + 1));
1214
+ function nextChild$1(children, index) {
1215
+ return children[index + 1] && (children[index + 1].id || nextChild$1(children, index + 1));
1202
1216
  }
1203
1217
 
1204
1218
  // reduce unnecessary refs
1205
- function detectExpressions(children, index) {
1219
+ function detectExpressions(children, index, config) {
1206
1220
  if (children[index - 1]) {
1207
1221
  const node = children[index - 1].node;
1208
1222
  if (
@@ -1239,7 +1253,7 @@ function detectExpressions(children, index) {
1239
1253
  )
1240
1254
  return true;
1241
1255
  const nextChildren = filterChildren(children[i].get("children"));
1242
- if (nextChildren.length) if (detectExpressions(nextChildren, 0)) return true;
1256
+ if (nextChildren.length) if (detectExpressions(nextChildren, 0, config)) return true;
1243
1257
  }
1244
1258
  }
1245
1259
  }
@@ -1257,7 +1271,8 @@ function contextToCustomElement(path, results) {
1257
1271
  );
1258
1272
  }
1259
1273
 
1260
- function createTemplate$1(path, result, wrap) {
1274
+ function createTemplate$2(path, result, wrap) {
1275
+ const config = getConfig(path);
1261
1276
  if (result.id) {
1262
1277
  registerTemplate(path, result);
1263
1278
  if (
@@ -1272,7 +1287,7 @@ function createTemplate$1(path, result, wrap) {
1272
1287
  t__namespace.blockStatement([
1273
1288
  result.decl,
1274
1289
  ...result.exprs.concat(
1275
- wrapDynamics(path, result.dynamics) || [],
1290
+ wrapDynamics$1(path, result.dynamics) || [],
1276
1291
  result.postExprs || []
1277
1292
  ),
1278
1293
  t__namespace.returnStatement(result.id)
@@ -1311,7 +1326,7 @@ function appendTemplates$1(path, templates) {
1311
1326
  }
1312
1327
 
1313
1328
  function registerTemplate(path, results) {
1314
- const { hydratable } = config;
1329
+ const { hydratable } = getConfig(path);
1315
1330
  let decl;
1316
1331
  if (results.template.length) {
1317
1332
  let templateDef, templateId;
@@ -1345,8 +1360,9 @@ function registerTemplate(path, results) {
1345
1360
  results.decl = t__namespace.variableDeclaration("const", results.decl);
1346
1361
  }
1347
1362
 
1348
- function wrapDynamics(path, dynamics) {
1363
+ function wrapDynamics$1(path, dynamics) {
1349
1364
  if (!dynamics.length) return;
1365
+ const config = getConfig(path);
1350
1366
  registerImportMethod(path, config.effectWrapper);
1351
1367
 
1352
1368
  const effectWrapperId = `_$${config.effectWrapper}`;
@@ -1361,7 +1377,7 @@ function wrapDynamics(path, dynamics) {
1361
1377
  t__namespace.callExpression(t__namespace.identifier(effectWrapperId), [
1362
1378
  t__namespace.arrowFunctionExpression(
1363
1379
  prevValue ? [prevValue] : [],
1364
- setAttr$1(path, dynamics[0].elem, dynamics[0].key, dynamics[0].value, {
1380
+ setAttr$2(path, dynamics[0].elem, dynamics[0].key, dynamics[0].value, {
1365
1381
  isSVG: dynamics[0].isSVG,
1366
1382
  isCE: dynamics[0].isCE,
1367
1383
  dynamic: true,
@@ -1386,7 +1402,7 @@ function wrapDynamics(path, dynamics) {
1386
1402
  t__namespace.assignmentExpression(
1387
1403
  "=",
1388
1404
  prev,
1389
- setAttr$1(path, elem, key, identifier, { isSVG, isCE, dynamic: true, prevId: prev })
1405
+ setAttr$2(path, elem, key, identifier, { isSVG, isCE, dynamic: true, prevId: prev })
1390
1406
  )
1391
1407
  )
1392
1408
  );
@@ -1396,7 +1412,7 @@ function wrapDynamics(path, dynamics) {
1396
1412
  t__namespace.logicalExpression(
1397
1413
  "&&",
1398
1414
  t__namespace.binaryExpression("!==", identifier, t__namespace.memberExpression(prevId, identifier)),
1399
- setAttr$1(
1415
+ setAttr$2(
1400
1416
  path,
1401
1417
  elem,
1402
1418
  key,
@@ -1433,8 +1449,9 @@ function appendToTemplate(template, value) {
1433
1449
  if (array && array.length) template.push.apply(template, array);
1434
1450
  }
1435
1451
 
1436
- function transformElement(path, info) {
1452
+ function transformElement$1(path, info) {
1437
1453
  let tagName = getTagName(path.node),
1454
+ config = getConfig(path),
1438
1455
  voidTag = VoidElements.indexOf(tagName) > -1,
1439
1456
  results = {
1440
1457
  template: [`<${tagName}`],
@@ -1448,10 +1465,10 @@ function transformElement(path, info) {
1448
1465
  results.template.push("");
1449
1466
  results.templateValues.push(t__namespace.callExpression(t__namespace.identifier("_$ssrHydrationKey"), []));
1450
1467
  }
1451
- transformAttributes(path, results);
1468
+ transformAttributes$1(path, results);
1452
1469
  appendToTemplate(results.template, ">");
1453
1470
  if (!voidTag) {
1454
- transformChildren(path, results);
1471
+ transformChildren$1(path, results, config);
1455
1472
  appendToTemplate(results.template, `</${tagName}>`);
1456
1473
  }
1457
1474
  return results;
@@ -1463,7 +1480,7 @@ function toAttribute(key, isSVG) {
1463
1480
  return key;
1464
1481
  }
1465
1482
 
1466
- function setAttr(results, name, value, isSVG) {
1483
+ function setAttr$1(results, name, value, isSVG) {
1467
1484
  // strip out namespaces for now, everything at this point is an attribute
1468
1485
  let parts;
1469
1486
  if ((parts = name.split(":")) && parts[1] && reservedNameSpaces.has(parts[0])) {
@@ -1547,7 +1564,7 @@ function transformToObject(attrName, attributes, selectedAttributes) {
1547
1564
  }
1548
1565
  }
1549
1566
 
1550
- function transformAttributes(path, results) {
1567
+ function transformAttributes$1(path, results) {
1551
1568
  let children;
1552
1569
  const tagName = getTagName(path.node),
1553
1570
  isSVG = SVGElements.has(tagName),
@@ -1619,7 +1636,9 @@ function transformAttributes(path, results) {
1619
1636
  checkMember: true,
1620
1637
  native: true
1621
1638
  })
1622
- ? t__namespace.arrowFunctionExpression([], node.argument)
1639
+ ? t__namespace.isCallExpression(node.argument) && !node.argument.arguments.length
1640
+ ? node.argument.callee
1641
+ : t__namespace.arrowFunctionExpression([], node.argument)
1623
1642
  : node.argument,
1624
1643
  t__namespace.booleanLiteral(isSVG),
1625
1644
  t__namespace.booleanLiteral(hasChildren)
@@ -1736,7 +1755,7 @@ function transformAttributes(path, results) {
1736
1755
  doEscape ? escapeExpression(path, value.expression, true) : value.expression
1737
1756
  );
1738
1757
  else if (doEscape) value.expression = escapeExpression(path, value.expression, true);
1739
- setAttr(results, key, value.expression, isSVG);
1758
+ setAttr$1(results, key, value.expression, isSVG);
1740
1759
  }
1741
1760
  } else {
1742
1761
  if (key === "$ServerOnly") return;
@@ -1782,9 +1801,8 @@ function transformClasslistObject(path, expr, values, quasis) {
1782
1801
  });
1783
1802
  }
1784
1803
 
1785
- function transformChildren(path, results) {
1786
- const { hydratable } = config,
1787
- doNotEscape = path.doNotEscape;
1804
+ function transformChildren$1(path, results, { hydratable }) {
1805
+ const doNotEscape = path.doNotEscape;
1788
1806
  const filteredChildren = filterChildren(path.get("children"));
1789
1807
  filteredChildren.forEach(node => {
1790
1808
  const child = transformNode(node, { doNotEscape });
@@ -1810,7 +1828,7 @@ function transformChildren(path, results) {
1810
1828
  });
1811
1829
  }
1812
1830
 
1813
- function createTemplate(path, result) {
1831
+ function createTemplate$1(path, result) {
1814
1832
  if (!result.template) {
1815
1833
  return result.exprs[0];
1816
1834
  }
@@ -1856,11 +1874,343 @@ function appendTemplates(path, templates) {
1856
1874
  path.node.body.unshift(t__namespace.variableDeclaration("const", declarators));
1857
1875
  }
1858
1876
 
1877
+ function transformElement(path, info) {
1878
+ let tagName = getTagName(path.node),
1879
+ results = {
1880
+ id: path.scope.generateUidIdentifier("el$"),
1881
+ decl: [],
1882
+ exprs: [],
1883
+ dynamics: [],
1884
+ postExprs: [],
1885
+ tagName
1886
+ };
1887
+
1888
+ registerImportMethod(path, "createElement");
1889
+ results.decl.push(
1890
+ t__namespace.variableDeclarator(
1891
+ results.id,
1892
+ t__namespace.callExpression(t__namespace.identifier("_$createElement"), [t__namespace.stringLiteral(tagName)])
1893
+ )
1894
+ );
1895
+
1896
+ transformAttributes(path, results);
1897
+ transformChildren(path, results);
1898
+
1899
+ return results;
1900
+ }
1901
+
1902
+ function transformAttributes(path, results) {
1903
+ let children;
1904
+ const elem = results.id,
1905
+ spread = t__namespace.identifier("_$spread"),
1906
+ hasChildren = path.node.children.length > 0,
1907
+ config = getConfig(path);
1908
+
1909
+ path
1910
+ .get("openingElement")
1911
+ .get("attributes")
1912
+ .forEach(attribute => {
1913
+ const node = attribute.node;
1914
+ if (t__namespace.isJSXSpreadAttribute(node)) {
1915
+ registerImportMethod(attribute, "spread");
1916
+ results.exprs.push(
1917
+ t__namespace.expressionStatement(
1918
+ t__namespace.callExpression(spread, [
1919
+ elem,
1920
+ isDynamic(attribute.get("argument"), {
1921
+ checkMember: true
1922
+ })
1923
+ ? t__namespace.isCallExpression(node.argument) && !node.argument.arguments.length
1924
+ ? node.argument.callee
1925
+ : t__namespace.arrowFunctionExpression([], node.argument)
1926
+ : node.argument,
1927
+ t__namespace.booleanLiteral(hasChildren)
1928
+ ])
1929
+ )
1930
+ );
1931
+ return;
1932
+ }
1933
+
1934
+ let value = node.value,
1935
+ key = t__namespace.isJSXNamespacedName(node.name)
1936
+ ? `${node.name.namespace.name}:${node.name.name.name}`
1937
+ : node.name.name,
1938
+ reservedNameSpace = t__namespace.isJSXNamespacedName(node.name) && node.name.namespace.name === "use";
1939
+ if (
1940
+ t__namespace.isJSXNamespacedName(node.name) &&
1941
+ reservedNameSpace &&
1942
+ !t__namespace.isJSXExpressionContainer(value)
1943
+ ) {
1944
+ node.value = value = t__namespace.JSXExpressionContainer(value || t__namespace.JSXEmptyExpression());
1945
+ }
1946
+ if (t__namespace.isJSXExpressionContainer(value)) {
1947
+ if (key === "ref") {
1948
+ // Normalize expressions for non-null and type-as
1949
+ while (
1950
+ t__namespace.isTSNonNullExpression(value.expression) ||
1951
+ t__namespace.isTSAsExpression(value.expression)
1952
+ ) {
1953
+ value.expression = value.expression.expression;
1954
+ }
1955
+ if (t__namespace.isLVal(value.expression)) {
1956
+ const refIdentifier = path.scope.generateUidIdentifier("_ref$");
1957
+ results.exprs.unshift(
1958
+ t__namespace.variableDeclaration("const", [
1959
+ t__namespace.variableDeclarator(refIdentifier, value.expression)
1960
+ ]),
1961
+ t__namespace.expressionStatement(
1962
+ t__namespace.conditionalExpression(
1963
+ t__namespace.binaryExpression(
1964
+ "===",
1965
+ t__namespace.unaryExpression("typeof", refIdentifier),
1966
+ t__namespace.stringLiteral("function")
1967
+ ),
1968
+ t__namespace.callExpression(refIdentifier, [elem]),
1969
+ t__namespace.assignmentExpression("=", value.expression, elem)
1970
+ )
1971
+ )
1972
+ );
1973
+ } else if (t__namespace.isFunction(value.expression)) {
1974
+ results.exprs.unshift(
1975
+ t__namespace.expressionStatement(t__namespace.callExpression(value.expression, [elem]))
1976
+ );
1977
+ } else if (t__namespace.isCallExpression(value.expression)) {
1978
+ const refIdentifier = path.scope.generateUidIdentifier("_ref$");
1979
+ results.exprs.unshift(
1980
+ t__namespace.variableDeclaration("const", [
1981
+ t__namespace.variableDeclarator(refIdentifier, value.expression)
1982
+ ]),
1983
+ t__namespace.expressionStatement(
1984
+ t__namespace.logicalExpression(
1985
+ "&&",
1986
+ t__namespace.binaryExpression(
1987
+ "===",
1988
+ t__namespace.unaryExpression("typeof", refIdentifier),
1989
+ t__namespace.stringLiteral("function")
1990
+ ),
1991
+ t__namespace.callExpression(refIdentifier, [elem])
1992
+ )
1993
+ )
1994
+ );
1995
+ }
1996
+ } else if (key.startsWith("use:")) {
1997
+ results.exprs.unshift(
1998
+ t__namespace.expressionStatement(
1999
+ t__namespace.callExpression(t__namespace.identifier(node.name.name.name), [
2000
+ elem,
2001
+ t__namespace.arrowFunctionExpression(
2002
+ [],
2003
+ t__namespace.isJSXEmptyExpression(value.expression)
2004
+ ? t__namespace.booleanLiteral(true)
2005
+ : value.expression
2006
+ )
2007
+ ])
2008
+ )
2009
+ );
2010
+ } else if (key === "children") {
2011
+ children = value;
2012
+ } else if (
2013
+ config.effectWrapper &&
2014
+ isDynamic(attribute.get("value").get("expression"), {
2015
+ checkMember: true
2016
+ })
2017
+ ) {
2018
+ results.dynamics.push({ elem, key, value: value.expression });
2019
+ } else {
2020
+ results.exprs.push(
2021
+ t__namespace.expressionStatement(setAttr(attribute, elem, key, value.expression))
2022
+ );
2023
+ }
2024
+ } else {
2025
+ results.exprs.push(t__namespace.expressionStatement(setAttr(attribute, elem, key, value)));
2026
+ }
2027
+ });
2028
+ if (!hasChildren && children) {
2029
+ path.node.children.push(children);
2030
+ }
2031
+ }
2032
+
2033
+ function setAttr(path, elem, name, value, { prevId } = {}) {
2034
+ registerImportMethod(path, "setProp");
2035
+ if (!value) value = t__namespace.booleanLiteral(true);
2036
+ return t__namespace.callExpression(
2037
+ t__namespace.identifier("_$setProp"),
2038
+ prevId ? [elem, t__namespace.stringLiteral(name), value, prevId] : [elem, t__namespace.stringLiteral(name), value]
2039
+ );
2040
+ }
2041
+
2042
+ function transformChildren(path, results) {
2043
+ const filteredChildren = filterChildren(path.get("children")),
2044
+ multi = checkLength(filteredChildren),
2045
+ childNodes = filteredChildren.map(transformNode).reduce((memo, child) => {
2046
+ if (!child) return memo;
2047
+ const i = memo.length;
2048
+ if (child.text && i && memo[i - 1].text) {
2049
+ memo[i - 1].template += child.template;
2050
+ } else memo.push(child);
2051
+ return memo;
2052
+ }, []);
2053
+
2054
+ const appends = [];
2055
+ childNodes.forEach((child, index) => {
2056
+ if (!child) return;
2057
+ if (child.id) {
2058
+ registerImportMethod(path, "insertNode");
2059
+ let insert = child.id;
2060
+ if (child.text) {
2061
+ registerImportMethod(path, "createTextNode");
2062
+ if (multi) {
2063
+ results.decl.push(
2064
+ t__namespace.variableDeclarator(
2065
+ child.id,
2066
+ t__namespace.callExpression(t__namespace.identifier("_$createTextNode"), [
2067
+ t__namespace.stringLiteral(htmlEntities.decode(child.template))
2068
+ ])
2069
+ )
2070
+ );
2071
+ } else
2072
+ insert = t__namespace.callExpression(t__namespace.identifier("_$createTextNode"), [
2073
+ t__namespace.stringLiteral(htmlEntities.decode(child.template))
2074
+ ]);
2075
+ }
2076
+ appends.push(
2077
+ t__namespace.expressionStatement(t__namespace.callExpression(t__namespace.identifier("_$insertNode"), [results.id, insert]))
2078
+ );
2079
+ results.decl.push(...child.decl);
2080
+ results.exprs.push(...child.exprs);
2081
+ results.dynamics.push(...child.dynamics);
2082
+ } else if (child.exprs.length) {
2083
+ registerImportMethod(path, "insert");
2084
+ if (multi) {
2085
+ results.exprs.push(
2086
+ t__namespace.expressionStatement(
2087
+ t__namespace.callExpression(t__namespace.identifier("_$insert"), [
2088
+ results.id,
2089
+ child.exprs[0],
2090
+ nextChild(childNodes, index) || t__namespace.nullLiteral()
2091
+ ])
2092
+ )
2093
+ );
2094
+ } else {
2095
+ results.exprs.push(
2096
+ t__namespace.expressionStatement(
2097
+ t__namespace.callExpression(t__namespace.identifier("_$insert"), [results.id, child.exprs[0]])
2098
+ )
2099
+ );
2100
+ }
2101
+ }
2102
+ });
2103
+ results.exprs.unshift(...appends);
2104
+ }
2105
+
2106
+ function nextChild(children, index) {
2107
+ return children[index + 1] && (children[index + 1].id || nextChild(children, index + 1));
2108
+ }
2109
+
2110
+ function createTemplate(path, result, wrap) {
2111
+ const config = getConfig(path);
2112
+ if (result.id) {
2113
+ result.decl = t__namespace.variableDeclaration("const", result.decl);
2114
+ if (
2115
+ !(result.exprs.length || result.dynamics.length || result.postExprs.length) &&
2116
+ result.decl.declarations.length === 1
2117
+ ) {
2118
+ return result.decl.declarations[0].init;
2119
+ } else {
2120
+ return t__namespace.callExpression(
2121
+ t__namespace.arrowFunctionExpression(
2122
+ [],
2123
+ t__namespace.blockStatement([
2124
+ result.decl,
2125
+ ...result.exprs.concat(
2126
+ wrapDynamics(path, result.dynamics) || [],
2127
+ result.postExprs || []
2128
+ ),
2129
+ t__namespace.returnStatement(result.id)
2130
+ ])
2131
+ ),
2132
+ []
2133
+ );
2134
+ }
2135
+ }
2136
+ if (wrap && result.dynamic && config.memoWrapper) {
2137
+ registerImportMethod(path, config.memoWrapper);
2138
+ return t__namespace.callExpression(t__namespace.identifier(`_$${config.memoWrapper}`), [result.exprs[0]]);
2139
+ }
2140
+ return result.exprs[0];
2141
+ }
2142
+
2143
+ function wrapDynamics(path, dynamics) {
2144
+ if (!dynamics.length) return;
2145
+ const config = getConfig(path);
2146
+ registerImportMethod(path, config.effectWrapper);
2147
+
2148
+ const effectWrapperId = `_$${config.effectWrapper}`;
2149
+
2150
+ if (dynamics.length === 1) {
2151
+ const prevValue = t__namespace.identifier("_$p");
2152
+
2153
+ return t__namespace.expressionStatement(
2154
+ t__namespace.callExpression(t__namespace.identifier(effectWrapperId), [
2155
+ t__namespace.arrowFunctionExpression(
2156
+ [prevValue],
2157
+ setAttr(path, dynamics[0].elem, dynamics[0].key, dynamics[0].value, {
2158
+ dynamic: true,
2159
+ prevId: prevValue
2160
+ })
2161
+ )
2162
+ ])
2163
+ );
2164
+ }
2165
+ const decls = [],
2166
+ statements = [],
2167
+ identifiers = [],
2168
+ prevId = t__namespace.identifier("_p$");
2169
+ dynamics.forEach(({ elem, key, value }) => {
2170
+ const identifier = path.scope.generateUidIdentifier("v$");
2171
+ identifiers.push(identifier);
2172
+ decls.push(t__namespace.variableDeclarator(identifier, value));
2173
+ const prev = t__namespace.memberExpression(prevId, identifier);
2174
+ statements.push(
2175
+ t__namespace.expressionStatement(
2176
+ t__namespace.logicalExpression(
2177
+ "&&",
2178
+ t__namespace.binaryExpression("!==", identifier, t__namespace.memberExpression(prevId, identifier)),
2179
+ t__namespace.assignmentExpression("=", t__namespace.memberExpression(prevId, identifier), setAttr(
2180
+ path,
2181
+ elem,
2182
+ key,
2183
+ identifier,
2184
+ { dynamic: true, prevId: prev }
2185
+ ))
2186
+ )
2187
+ )
2188
+ );
2189
+ });
2190
+
2191
+ return t__namespace.expressionStatement(
2192
+ t__namespace.callExpression(t__namespace.identifier(effectWrapperId), [
2193
+ t__namespace.arrowFunctionExpression(
2194
+ [prevId],
2195
+ t__namespace.blockStatement([
2196
+ t__namespace.variableDeclaration("const", decls),
2197
+ ...statements,
2198
+ t__namespace.returnStatement(prevId)
2199
+ ])
2200
+ ),
2201
+ t__namespace.objectExpression(identifiers.map(id => t__namespace.objectProperty(id, t__namespace.identifier("undefined"))))
2202
+ ])
2203
+ );
2204
+ }
2205
+
1859
2206
  function transformComponent(path) {
1860
- let props = [],
2207
+ let exprs = [],
2208
+ config = getConfig(path),
2209
+ tagName = getTagName(path.node),
2210
+ props = [],
1861
2211
  runningObject = [],
1862
- exprs = [],
1863
- tagName = getTagName(path.node);
2212
+ dynamicSpread = false,
2213
+ hasChildren = path.node.children.length > 0;
1864
2214
 
1865
2215
  if (config.builtIns.indexOf(tagName) > -1 && !path.scope.hasBinding(tagName)) {
1866
2216
  registerImportMethod(path, tagName);
@@ -1877,21 +2227,38 @@ function transformComponent(path) {
1877
2227
  props.push(t__namespace.objectExpression(runningObject));
1878
2228
  runningObject = [];
1879
2229
  }
1880
- props.push(node.argument);
2230
+ props.push(
2231
+ isDynamic(attribute.get("argument"), {
2232
+ checkMember: true
2233
+ }) && (dynamicSpread = true)
2234
+ ? t__namespace.isCallExpression(node.argument) && !node.argument.arguments.length
2235
+ ? node.argument.callee
2236
+ : t__namespace.arrowFunctionExpression([], node.argument)
2237
+ : node.argument
2238
+ );
1881
2239
  } else {
1882
2240
  const value = node.value || t__namespace.booleanLiteral(true),
1883
2241
  key = t__namespace.isJSXNamespacedName(node.name)
1884
2242
  ? `${node.name.namespace.name}:${node.name.name.name}`
1885
2243
  : node.name.name,
1886
2244
  wrapName = t__namespace.isValidIdentifier(key) ? t__namespace.identifier : t__namespace.stringLiteral;
2245
+ if (hasChildren && key === "children") return;
1887
2246
  if (t__namespace.isJSXExpressionContainer(value))
1888
2247
  if (key === "ref") {
1889
2248
  if (config.generate === "ssr") return;
1890
2249
  // Normalize expressions for non-null and type-as
1891
- while (t__namespace.isTSNonNullExpression(value.expression) || t__namespace.isTSAsExpression(value.expression)) {
2250
+ while (
2251
+ t__namespace.isTSNonNullExpression(value.expression) ||
2252
+ t__namespace.isTSAsExpression(value.expression)
2253
+ ) {
1892
2254
  value.expression = value.expression.expression;
1893
2255
  }
1894
- if (t__namespace.isLVal(value.expression)) {
2256
+ let binding,
2257
+ isFunction =
2258
+ t__namespace.isIdentifier(value.expression) &&
2259
+ (binding = path.scope.getBinding(value.expression.name)) &&
2260
+ binding.kind === "const";
2261
+ if (!isFunction && t__namespace.isLVal(value.expression)) {
1895
2262
  const refIdentifier = path.scope.generateUidIdentifier("_ref$");
1896
2263
  runningObject.push(
1897
2264
  t__namespace.objectMethod(
@@ -1916,7 +2283,7 @@ function transformComponent(path) {
1916
2283
  ])
1917
2284
  )
1918
2285
  );
1919
- } else if (t__namespace.isFunction(value.expression)) {
2286
+ } else if (isFunction || t__namespace.isFunction(value.expression)) {
1920
2287
  runningObject.push(t__namespace.objectProperty(t__namespace.identifier("ref"), value.expression));
1921
2288
  } else if (t__namespace.isCallExpression(value.expression)) {
1922
2289
  const refIdentifier = path.scope.generateUidIdentifier("_ref$");
@@ -1975,7 +2342,7 @@ function transformComponent(path) {
1975
2342
  }
1976
2343
  });
1977
2344
 
1978
- const childResult = transformComponentChildren(path.get("children"));
2345
+ const childResult = transformComponentChildren(path.get("children"), config);
1979
2346
  if (childResult && childResult[0]) {
1980
2347
  if (childResult[1]) {
1981
2348
  const body =
@@ -1994,7 +2361,7 @@ function transformComponent(path) {
1994
2361
  }
1995
2362
  if (runningObject.length || !props.length) props.push(t__namespace.objectExpression(runningObject));
1996
2363
 
1997
- if (props.length > 1) {
2364
+ if (props.length > 1 || dynamicSpread) {
1998
2365
  registerImportMethod(path, "mergeProps");
1999
2366
  props = [t__namespace.callExpression(t__namespace.identifier("_$mergeProps"), props)];
2000
2367
  }
@@ -2015,15 +2382,20 @@ function transformComponent(path) {
2015
2382
  return { exprs, template: "", component: true };
2016
2383
  }
2017
2384
 
2018
- function transformComponentChildren(children) {
2019
- const createTemplate$2 = config.generate === "ssr" ? createTemplate : createTemplate$1,
2385
+ function transformComponentChildren(children, config) {
2386
+ const createTemplate$3 =
2387
+ config.generate === "universal"
2388
+ ? createTemplate
2389
+ : config.generate === "ssr"
2390
+ ? createTemplate$1
2391
+ : createTemplate$2,
2020
2392
  filteredChildren = filterChildren(children);
2021
2393
  if (!filteredChildren.length) return;
2022
2394
  let dynamic = false;
2023
2395
 
2024
2396
  let transformedChildren = filteredChildren.reduce((memo, path) => {
2025
2397
  if (t__namespace.isJSXText(path.node)) {
2026
- const v = trimWhitespace(path.node.extra.rawValue);
2398
+ const v = htmlEntities.decode(trimWhitespace(path.node.extra.raw));
2027
2399
  if (v.length) memo.push(t__namespace.stringLiteral(v));
2028
2400
  } else {
2029
2401
  const child = transformNode(path, {
@@ -2031,7 +2403,7 @@ function transformComponentChildren(children) {
2031
2403
  componentChild: true
2032
2404
  });
2033
2405
  dynamic = dynamic || child.dynamic;
2034
- memo.push(createTemplate$2(path, child, filteredChildren.length > 1));
2406
+ memo.push(createTemplate$3(path, child, filteredChildren.length > 1));
2035
2407
  }
2036
2408
  return memo;
2037
2409
  }, []);
@@ -2058,25 +2430,30 @@ function transformComponentChildren(children) {
2058
2430
  return [transformedChildren, dynamic];
2059
2431
  }
2060
2432
 
2061
- function transformFragmentChildren(children, results) {
2062
- const createTemplate$2 = config.generate === "ssr" ? createTemplate : createTemplate$1,
2433
+ function transformFragmentChildren(children, results, config) {
2434
+ const createTemplate$3 =
2435
+ config.generate === "universal"
2436
+ ? createTemplate
2437
+ : config.generate === "ssr"
2438
+ ? createTemplate$1
2439
+ : createTemplate$2,
2063
2440
  filteredChildren = filterChildren(children),
2064
2441
  singleChild = filteredChildren.length === 1,
2065
2442
  childNodes = filteredChildren.reduce((memo, path) => {
2066
2443
  if (t__namespace.isJSXText(path.node)) {
2067
- const v = trimWhitespace(path.node.extra.raw);
2444
+ const v = htmlEntities.decode(trimWhitespace(path.node.extra.raw));
2068
2445
  if (v.length) memo.push(t__namespace.stringLiteral(v));
2069
2446
  } else {
2070
2447
  const child = transformNode(path, { topLevel: true, fragmentChild: true });
2071
- memo.push(createTemplate$2(path, child, !singleChild));
2448
+ memo.push(createTemplate$3(path, child, !singleChild));
2072
2449
  }
2073
2450
  return memo;
2074
2451
  }, []);
2075
2452
  results.exprs.push(singleChild ? childNodes[0] : t__namespace.arrayExpression(childNodes));
2076
2453
  }
2077
2454
 
2078
- function transformJSX(path, { opts }) {
2079
- Object.assign(config, opts);
2455
+ function transformJSX(path) {
2456
+ const config = getConfig(path);
2080
2457
  const replace = transformThis(path);
2081
2458
  const result = transformNode(
2082
2459
  path,
@@ -2086,7 +2463,12 @@ function transformJSX(path, { opts }) {
2086
2463
  topLevel: true
2087
2464
  }
2088
2465
  );
2089
- const template = config.generate === "ssr" ? createTemplate : createTemplate$1;
2466
+ const template =
2467
+ config.generate === "universal"
2468
+ ? createTemplate
2469
+ : config.generate === "ssr"
2470
+ ? createTemplate$1
2471
+ : createTemplate$2;
2090
2472
  path.replaceWith(replace(template(path, result, false)));
2091
2473
  }
2092
2474
 
@@ -2114,16 +2496,22 @@ function transformThis(path) {
2114
2496
  }
2115
2497
 
2116
2498
  function transformNode(path, info = {}) {
2499
+ const config = getConfig(path);
2117
2500
  const node = path.node;
2118
2501
  let staticValue;
2119
2502
  if (t__namespace.isJSXElement(node)) {
2120
2503
  let tagName = getTagName(node);
2121
2504
  if (isComponent(tagName)) return transformComponent(path);
2122
- const element = config.generate === "ssr" ? transformElement : transformElement$1;
2505
+ const element =
2506
+ config.generate === "universal"
2507
+ ? transformElement
2508
+ : config.generate === "ssr"
2509
+ ? transformElement$1
2510
+ : transformElement$2;
2123
2511
  return element(path, info);
2124
2512
  } else if (t__namespace.isJSXFragment(node)) {
2125
2513
  let results = { template: "", decl: [], exprs: [], dynamics: [] };
2126
- transformFragmentChildren(path.get("children"), results);
2514
+ transformFragmentChildren(path.get("children"), results, config);
2127
2515
  return results;
2128
2516
  } else if (t__namespace.isJSXText(node) || (staticValue = getStaticExpression(path))) {
2129
2517
  const text =
@@ -2202,6 +2590,7 @@ function transformNode(path, info = {}) {
2202
2590
 
2203
2591
  // add to the top/bottom of the module.
2204
2592
  var postprocess = path => {
2593
+ const config = getConfig(path);
2205
2594
  if (path.scope.data.events) {
2206
2595
  registerImportMethod(path, "delegateEvents");
2207
2596
  path.node.body.push(
@@ -2218,7 +2607,23 @@ var postprocess = path => {
2218
2607
  }
2219
2608
  };
2220
2609
 
2221
- var preprocess = path => {
2610
+ var config = {
2611
+ moduleName: "dom",
2612
+ generate: "dom",
2613
+ hydratable: false,
2614
+ delegateEvents: true,
2615
+ delegatedEvents: [],
2616
+ builtIns: [],
2617
+ requireImportSource: false,
2618
+ wrapConditionals: true,
2619
+ contextToCustomElements: false,
2620
+ staticMarker: "@once",
2621
+ effectWrapper: "effect",
2622
+ memoWrapper: "memo"
2623
+ };
2624
+
2625
+ var preprocess = (path, { opts }) => {
2626
+ path.hub.file.metadata.config = Object.assign({}, config, opts);
2222
2627
  const lib = config.requireImportSource;
2223
2628
  if (lib) {
2224
2629
  const comments = path.hub.file.ast.comments;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "babel-plugin-jsx-dom-expressions",
3
3
  "description": "A JSX to DOM plugin that wraps expressions for fine grained change detection",
4
- "version": "0.29.17",
4
+ "version": "0.30.5",
5
5
  "author": "Ryan Carniato",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -20,7 +20,8 @@
20
20
  "dependencies": {
21
21
  "@babel/helper-module-imports": "^7.10.4",
22
22
  "@babel/plugin-syntax-jsx": "^7.10.4",
23
- "@babel/types": "^7.11.5"
23
+ "@babel/types": "^7.11.5",
24
+ "html-entities": "2.3.2"
24
25
  },
25
- "gitHead": "bdec422181ff938226a7a21b92ad4862b799f687"
26
+ "gitHead": "a8b08272aaaffe70374ad9bd2703f4b730b34706"
26
27
  }