what-compiler 0.8.4 → 0.10.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.
@@ -61,9 +61,56 @@ var SIGNAL_CREATORS = /* @__PURE__ */ new Set([
61
61
  "useQuery",
62
62
  "useInfiniteQuery"
63
63
  ]);
64
+ function normalizeJsxText(value) {
65
+ if (!/[\r\n]/.test(value)) {
66
+ return value.replace(/\t/g, " ");
67
+ }
68
+ const lines = value.split(/\r\n|\n|\r/);
69
+ let lastNonEmpty = -1;
70
+ for (let i = 0; i < lines.length; i++) {
71
+ if (/[^ \t]/.test(lines[i])) lastNonEmpty = i;
72
+ }
73
+ if (lastNonEmpty === -1) return "";
74
+ let out = "";
75
+ for (let i = 0; i < lines.length; i++) {
76
+ let line = lines[i].replace(/\t/g, " ");
77
+ const isFirst = i === 0;
78
+ const isLast = i === lines.length - 1;
79
+ if (!isFirst) line = line.replace(/^ +/, "");
80
+ if (!isLast) line = line.replace(/ +$/, "");
81
+ if (!line) continue;
82
+ if (i !== lastNonEmpty) line += " ";
83
+ out += line;
84
+ }
85
+ return out;
86
+ }
64
87
  function whatBabelPlugin({ types: t }) {
88
+ const _unknownModifierWarned = /* @__PURE__ */ new Set();
89
+ const _forInfoWarned = /* @__PURE__ */ new Set();
90
+ function hasEventModifiers(name, state) {
91
+ if (!name.includes("__")) return false;
92
+ if (!name.startsWith("on")) return false;
93
+ const parts = name.split("__");
94
+ const tail = parts.slice(1).filter((s) => s !== "");
95
+ if (tail.length === 0) return false;
96
+ if (true) {
97
+ const unknown = tail.filter((m) => !EVENT_MODIFIERS.has(m));
98
+ const filename = state && (state.filename || state.file && state.file.opts && state.file.opts.filename) || "<unknown>";
99
+ for (const m of unknown) {
100
+ const key = `${filename}::${m}`;
101
+ if (!_unknownModifierWarned.has(key)) {
102
+ _unknownModifierWarned.add(key);
103
+ console.warn(
104
+ `[what-compiler] Unknown event modifier "__${m}" in attribute "${name}" (${filename}). Known modifiers: ${[...EVENT_MODIFIERS].join(", ")}. Unknown segments are ignored.`
105
+ );
106
+ }
107
+ }
108
+ }
109
+ return true;
110
+ }
65
111
  function parseEventModifiers(name) {
66
- const parts = name.split("|");
112
+ const delimiter = name.includes("|") ? "|" : "__";
113
+ const parts = name.split(delimiter);
67
114
  const eventName = parts[0];
68
115
  const modifiers = parts.slice(1).filter((m) => EVENT_MODIFIERS.has(m));
69
116
  return { eventName, modifiers };
@@ -193,22 +240,20 @@ function whatBabelPlugin({ types: t }) {
193
240
  }
194
241
  let scope = path.scope;
195
242
  while (scope) {
196
- for (const [name, binding] of Object.entries(scope.bindings)) {
243
+ for (const binding of Object.values(scope.bindings)) {
197
244
  if (binding.path.isVariableDeclarator()) {
198
245
  extractFromDeclarator(binding.path.node);
199
246
  }
200
- if (binding.path.isIdentifier() || binding.kind === "param") {
201
- const fnPath = binding.scope.path;
202
- if (fnPath && fnPath.node && fnPath.node.params) {
203
- for (const param of fnPath.node.params) {
204
- if (t.isObjectPattern(param)) {
205
- for (const prop of param.properties) {
206
- if (t.isObjectProperty(prop) && t.isIdentifier(prop.value)) {
207
- signalNames.add(prop.value.name);
208
- } else if (t.isRestElement(prop) && t.isIdentifier(prop.argument)) {
209
- signalNames.add(prop.argument.name);
210
- }
211
- }
247
+ }
248
+ const fnNode = scope.path && scope.path.node;
249
+ if (fnNode && fnNode.params) {
250
+ for (const param of fnNode.params) {
251
+ if (t.isObjectPattern(param)) {
252
+ for (const prop of param.properties) {
253
+ if (t.isObjectProperty(prop) && t.isIdentifier(prop.value)) {
254
+ signalNames.add(prop.value.name);
255
+ } else if (t.isRestElement(prop) && t.isIdentifier(prop.argument)) {
256
+ signalNames.add(prop.argument.name);
212
257
  }
213
258
  }
214
259
  }
@@ -276,7 +321,7 @@ function whatBabelPlugin({ types: t }) {
276
321
  return isPotentiallyReactive(expr.callee, signalNames, importedIds) || expr.arguments.some((arg) => isPotentiallyReactive(arg, signalNames, importedIds));
277
322
  }
278
323
  if (t.isIdentifier(expr)) {
279
- return isSignalIdentifier(expr.name, signalNames);
324
+ return isSignalIdentifier(expr.name, signalNames) || importedIds && importedIds.has(expr.name);
280
325
  }
281
326
  if (t.isMemberExpression(expr)) {
282
327
  return isPotentiallyReactive(expr.object, signalNames, importedIds);
@@ -306,6 +351,93 @@ function whatBabelPlugin({ types: t }) {
306
351
  }
307
352
  return false;
308
353
  }
354
+ function tryLowerMapToMapArray(expr, state) {
355
+ let mapCall = expr;
356
+ let wrappedInArrow = false;
357
+ if (t.isArrowFunctionExpression(expr) && expr.params.length === 0) {
358
+ mapCall = expr.body;
359
+ wrappedInArrow = true;
360
+ }
361
+ if (t.isConditionalExpression(mapCall)) {
362
+ const loweredCon = tryLowerMapCall(mapCall.consequent, state);
363
+ const loweredAlt = tryLowerMapCall(mapCall.alternate, state);
364
+ if (loweredCon || loweredAlt) {
365
+ const result = t.conditionalExpression(
366
+ mapCall.test,
367
+ loweredCon || mapCall.consequent,
368
+ loweredAlt || mapCall.alternate
369
+ );
370
+ return wrappedInArrow ? t.arrowFunctionExpression([], result) : result;
371
+ }
372
+ return null;
373
+ }
374
+ if (t.isLogicalExpression(mapCall) && (mapCall.operator === "&&" || mapCall.operator === "||")) {
375
+ const loweredRight = tryLowerMapCall(mapCall.right, state);
376
+ if (loweredRight) {
377
+ const result = t.logicalExpression(mapCall.operator, mapCall.left, loweredRight);
378
+ return wrappedInArrow ? t.arrowFunctionExpression([], result) : result;
379
+ }
380
+ return null;
381
+ }
382
+ const lowered = tryLowerMapCall(mapCall, state);
383
+ return lowered;
384
+ }
385
+ function tryLowerMapCall(mapCall, state) {
386
+ if (!t.isCallExpression(mapCall)) return null;
387
+ if (!t.isMemberExpression(mapCall.callee)) return null;
388
+ if (!t.isIdentifier(mapCall.callee.property, { name: "map" })) return null;
389
+ if (mapCall.arguments.length < 1) return null;
390
+ const mapFn = mapCall.arguments[0];
391
+ if (!t.isArrowFunctionExpression(mapFn) && !t.isFunctionExpression(mapFn)) return null;
392
+ let returnExpr = null;
393
+ if (t.isArrowFunctionExpression(mapFn)) {
394
+ if (t.isExpression(mapFn.body)) {
395
+ returnExpr = mapFn.body;
396
+ } else if (t.isBlockStatement(mapFn.body)) {
397
+ const ret = mapFn.body.body.find((s) => t.isReturnStatement(s));
398
+ if (ret) returnExpr = ret.argument;
399
+ }
400
+ } else if (t.isFunctionExpression(mapFn)) {
401
+ const ret = mapFn.body.body.find((s) => t.isReturnStatement(s));
402
+ if (ret) returnExpr = ret.argument;
403
+ }
404
+ if (!returnExpr) return null;
405
+ if (!t.isJSXElement(returnExpr)) return null;
406
+ const attrs = returnExpr.openingElement.attributes;
407
+ let keyAttr = null;
408
+ for (const attr of attrs) {
409
+ if (t.isJSXAttribute(attr) && getAttrName(attr) === "key") {
410
+ keyAttr = attr;
411
+ break;
412
+ }
413
+ }
414
+ if (!keyAttr) {
415
+ if (true) {
416
+ const loc = returnExpr.loc;
417
+ const fileName = state.filename || state.file?.opts?.filename || "<unknown>";
418
+ const lineInfo = loc ? `:${loc.start.line}:${loc.start.column}` : "";
419
+ console.warn(
420
+ `[what-compiler] .map() returning JSX without a \`key\` prop at ${fileName}${lineInfo}. Without a key, the list cannot use keyed reconciliation \u2014 items are re-created on every update. Add key={...} to enable efficient updates.`
421
+ );
422
+ }
423
+ return null;
424
+ }
425
+ const keyValue = getAttributeValue(keyAttr.value);
426
+ if (!keyValue) return null;
427
+ returnExpr.openingElement.attributes = attrs.filter((a) => a !== keyAttr);
428
+ const sourceObj = mapCall.callee.object;
429
+ const source = t.arrowFunctionExpression([], sourceObj);
430
+ const itemParam = mapFn.params[0] ? t.cloneNode(mapFn.params[0], true) : t.identifier("_item");
431
+ const keyFn = t.arrowFunctionExpression([itemParam], t.cloneNode(keyValue, true));
432
+ return t.callExpression(t.identifier("_$mapArray"), [
433
+ source,
434
+ mapFn,
435
+ t.objectExpression([
436
+ t.objectProperty(t.identifier("key"), keyFn),
437
+ t.objectProperty(t.identifier("raw"), t.booleanLiteral(true))
438
+ ])
439
+ ]);
440
+ }
309
441
  function isStaticChild(child) {
310
442
  if (t.isJSXText(child)) return true;
311
443
  if (t.isJSXExpressionContainer(child)) return false;
@@ -329,7 +461,7 @@ function whatBabelPlugin({ types: t }) {
329
461
  }
330
462
  function extractStaticHTML(node) {
331
463
  if (t.isJSXText(node)) {
332
- const text = node.value.replace(/\n\s+/g, " ").trim();
464
+ const text = normalizeJsxText(node.value);
333
465
  return text ? escapeHTML(text) : "";
334
466
  }
335
467
  if (t.isJSXExpressionContainer(node)) {
@@ -369,7 +501,7 @@ function whatBabelPlugin({ types: t }) {
369
501
  html += ">";
370
502
  for (const child of node.children) {
371
503
  if (t.isJSXText(child)) {
372
- const text = child.value.replace(/\n\s+/g, " ").trim();
504
+ const text = normalizeJsxText(child.value);
373
505
  if (text) html += escapeHTML(text);
374
506
  } else if (t.isJSXExpressionContainer(child)) {
375
507
  if (!t.isJSXEmptyExpression(child.expression)) {
@@ -396,15 +528,15 @@ function whatBabelPlugin({ types: t }) {
396
528
  const { node } = path;
397
529
  const openingElement = node.openingElement;
398
530
  const tagName = openingElement.name.name;
399
- if (isComponent(tagName)) {
400
- return transformComponentFineGrained(path, state);
401
- }
402
531
  if (tagName === "For") {
403
532
  return transformForFineGrained(path, state);
404
533
  }
405
534
  if (tagName === "Show") {
406
535
  return transformShowFineGrained(path, state);
407
536
  }
537
+ if (isComponent(tagName)) {
538
+ return transformComponentFineGrained(path, state);
539
+ }
408
540
  const attributes = openingElement.attributes;
409
541
  const children = node.children;
410
542
  const allChildrenStatic = children.every(isStaticChild);
@@ -469,7 +601,7 @@ function whatBabelPlugin({ types: t }) {
469
601
  const transformedChildren = [];
470
602
  for (const child of children) {
471
603
  if (t.isJSXText(child)) {
472
- const text = child.value.replace(/\n\s+/g, " ").trim();
604
+ const text = normalizeJsxText(child.value);
473
605
  if (text) transformedChildren.push(t.stringLiteral(text));
474
606
  } else if (t.isJSXExpressionContainer(child)) {
475
607
  if (!t.isJSXEmptyExpression(child.expression)) {
@@ -526,7 +658,7 @@ function whatBabelPlugin({ types: t }) {
526
658
  );
527
659
  continue;
528
660
  }
529
- if (attrName.startsWith("on") && !attrName.includes("|")) {
661
+ if (attrName.startsWith("on") && !attrName.includes("|") && !hasEventModifiers(attrName, state)) {
530
662
  const event = attrName.slice(2).toLowerCase();
531
663
  const handler = getAttributeValue(attr.value);
532
664
  if (DELEGATED_EVENTS.has(event)) {
@@ -539,7 +671,7 @@ function whatBabelPlugin({ types: t }) {
539
671
  "=",
540
672
  t.memberExpression(
541
673
  t.identifier(elId),
542
- t.identifier(`__${event}`)
674
+ t.identifier(`$$${event}`)
543
675
  ),
544
676
  handler
545
677
  )
@@ -557,7 +689,7 @@ function whatBabelPlugin({ types: t }) {
557
689
  }
558
690
  continue;
559
691
  }
560
- if (attrName.startsWith("on") && attrName.includes("|")) {
692
+ if (attrName.startsWith("on") && (attrName.includes("|") || hasEventModifiers(attrName, state))) {
561
693
  const { eventName, modifiers } = parseEventModifiers(attrName);
562
694
  const handler = getAttributeValue(attr.value);
563
695
  const wrappedHandler = createEventHandler(handler, modifiers);
@@ -657,8 +789,9 @@ function whatBabelPlugin({ types: t }) {
657
789
  const domName = normalizeAttrName(attrName);
658
790
  if (isPotentiallyReactive(expr, state.signalNames, state.importedIdentifiers)) {
659
791
  state.needsEffect = true;
792
+ const valueExpr = t.isIdentifier(expr) && (isSignalIdentifier(expr.name, state.signalNames) || state.importedIdentifiers && state.importedIdentifiers.has(expr.name)) ? t.callExpression(expr, []) : expr;
660
793
  const effectCall = t.callExpression(t.identifier("_$effect"), [
661
- t.arrowFunctionExpression([], buildSetPropCall(domName, expr))
794
+ t.arrowFunctionExpression([], buildSetPropCall(domName, valueExpr))
662
795
  ]);
663
796
  if (isUncertainReactive(expr, state.signalNames, state.importedIdentifiers)) {
664
797
  t.addComment(
@@ -680,7 +813,7 @@ function whatBabelPlugin({ types: t }) {
680
813
  let childIndex = 0;
681
814
  for (const child of children) {
682
815
  if (t.isJSXText(child)) {
683
- const text = child.value.replace(/\n\s+/g, " ").trim();
816
+ const text = normalizeJsxText(child.value);
684
817
  if (text) childIndex++;
685
818
  continue;
686
819
  }
@@ -709,22 +842,31 @@ function whatBabelPlugin({ types: t }) {
709
842
  const entriesNeedingRef = entries.filter(
710
843
  (e) => e.type === "expression" || e.type === "component" || e.type === "static" && e.hasAnythingDynamic
711
844
  );
712
- const hasDynamicInsert = entries.some((e) => e.type === "expression" || e.type === "component");
713
- const needsPreCapture = entriesNeedingRef.length >= 2 && hasDynamicInsert;
845
+ const needsPreCapture = entriesNeedingRef.length >= 2;
714
846
  const markerVars = /* @__PURE__ */ new Map();
715
847
  if (needsPreCapture) {
848
+ let prevVar = null;
849
+ let prevIndex = 0;
716
850
  for (const entry of entriesNeedingRef) {
717
- const varName = `_m$${entry.childIndex}`;
851
+ const idx = entry.childIndex;
718
852
  const markerVar = state.nextVarId();
719
- markerVars.set(entry.childIndex, markerVar);
853
+ markerVars.set(idx, markerVar);
854
+ let init;
855
+ if (prevVar === null) {
856
+ init = buildChildAccess(elId, idx);
857
+ } else {
858
+ init = t.identifier(prevVar);
859
+ for (let i = prevIndex; i < idx; i++) {
860
+ init = t.memberExpression(init, t.identifier("nextSibling"));
861
+ }
862
+ }
720
863
  statements.push(
721
864
  t.variableDeclaration("const", [
722
- t.variableDeclarator(
723
- t.identifier(markerVar),
724
- buildChildAccess(elId, entry.childIndex)
725
- )
865
+ t.variableDeclarator(t.identifier(markerVar), init)
726
866
  ])
727
867
  );
868
+ prevVar = markerVar;
869
+ prevIndex = idx;
728
870
  }
729
871
  }
730
872
  function getMarker(idx) {
@@ -735,9 +877,41 @@ function whatBabelPlugin({ types: t }) {
735
877
  }
736
878
  for (const entry of entries) {
737
879
  if (entry.type === "expression") {
738
- const expr = entry.child.expression;
880
+ let expr = entry.child.expression;
739
881
  const marker = getMarker(entry.childIndex);
740
882
  state.needsInsert = true;
883
+ const mapResult = tryLowerMapToMapArray(expr, state);
884
+ if (mapResult) {
885
+ state.needsMapArray = true;
886
+ const isBareMapArray = t.isCallExpression(mapResult) && t.isIdentifier(mapResult.callee) && (mapResult.callee.name === "_$mapArray" || mapResult.callee.name === "mapArray");
887
+ const isArrowAlready = t.isArrowFunctionExpression(mapResult);
888
+ const insertArg = isBareMapArray || isArrowAlready ? mapResult : t.arrowFunctionExpression([], mapResult);
889
+ statements.push(
890
+ t.expressionStatement(
891
+ t.callExpression(t.identifier("_$insert"), [
892
+ t.identifier(elId),
893
+ insertArg,
894
+ marker
895
+ ])
896
+ )
897
+ );
898
+ continue;
899
+ }
900
+ const isMapArrayCall = t.isCallExpression(expr) && t.isIdentifier(expr.callee) && (expr.callee.name === "mapArray" || expr.callee.name === "_$mapArray");
901
+ if (isMapArrayCall) {
902
+ state.needsMapArray = true;
903
+ if (expr.callee.name === "mapArray") expr.callee.name = "_$mapArray";
904
+ statements.push(
905
+ t.expressionStatement(
906
+ t.callExpression(t.identifier("_$insert"), [
907
+ t.identifier(elId),
908
+ expr,
909
+ marker
910
+ ])
911
+ )
912
+ );
913
+ continue;
914
+ }
741
915
  if (isPotentiallyReactive(expr, state.signalNames, state.importedIdentifiers)) {
742
916
  const insertCall = t.callExpression(t.identifier("_$insert"), [
743
917
  t.identifier(elId),
@@ -946,7 +1120,7 @@ function whatBabelPlugin({ types: t }) {
946
1120
  }
947
1121
  continue;
948
1122
  }
949
- if (attrName.startsWith("on") && attrName.includes("|")) {
1123
+ if (attrName.startsWith("on") && (attrName.includes("|") || hasEventModifiers(attrName, state))) {
950
1124
  const { eventName, modifiers } = parseEventModifiers(attrName);
951
1125
  const handler = getAttributeValue(attr.value);
952
1126
  const wrappedHandler = createEventHandler(handler, modifiers);
@@ -964,7 +1138,7 @@ function whatBabelPlugin({ types: t }) {
964
1138
  const transformedChildren = [];
965
1139
  for (const child of children) {
966
1140
  if (t.isJSXText(child)) {
967
- const text = child.value.replace(/\n\s+/g, " ").trim();
1141
+ const text = normalizeJsxText(child.value);
968
1142
  if (text) transformedChildren.push(t.stringLiteral(text));
969
1143
  } else if (t.isJSXExpressionContainer(child)) {
970
1144
  if (!t.isJSXEmptyExpression(child.expression)) {
@@ -998,10 +1172,24 @@ function whatBabelPlugin({ types: t }) {
998
1172
  const { node } = path;
999
1173
  const attributes = node.openingElement.attributes;
1000
1174
  const children = node.children;
1175
+ if (true) {
1176
+ const fileName = state.filename || state.file?.opts?.filename || "<unknown>";
1177
+ if (!_forInfoWarned.has(fileName)) {
1178
+ _forInfoWarned.add(fileName);
1179
+ const loc = node.loc;
1180
+ const lineInfo = loc ? `:${loc.start.line}:${loc.start.column}` : "";
1181
+ console.info(
1182
+ `[what-compiler] <For> at ${fileName}${lineInfo}: consider using .map() with a key prop instead. The compiler auto-lowers .map() to efficient keyed reconciliation. <For> is only needed for signal-wrapped item accessors (advanced).`
1183
+ );
1184
+ }
1185
+ }
1001
1186
  let eachExpr = null;
1187
+ let keyExpr = null;
1002
1188
  for (const attr of attributes) {
1003
- if (t.isJSXAttribute(attr) && getAttrName(attr) === "each") {
1004
- eachExpr = getAttributeValue(attr.value);
1189
+ if (t.isJSXAttribute(attr)) {
1190
+ const name = getAttrName(attr);
1191
+ if (name === "each") eachExpr = getAttributeValue(attr.value);
1192
+ else if (name === "key") keyExpr = getAttributeValue(attr.value);
1005
1193
  }
1006
1194
  }
1007
1195
  if (!eachExpr) {
@@ -1022,11 +1210,78 @@ function whatBabelPlugin({ types: t }) {
1022
1210
  return transformElementAsH(path, state);
1023
1211
  }
1024
1212
  state.needsMapArray = true;
1025
- return t.callExpression(t.identifier("_$mapArray"), [eachExpr, renderFn]);
1213
+ const args = [eachExpr, renderFn];
1214
+ if (keyExpr) {
1215
+ args.push(t.objectExpression([
1216
+ t.objectProperty(t.identifier("key"), keyExpr)
1217
+ ]));
1218
+ }
1219
+ return t.callExpression(t.identifier("_$mapArray"), args);
1026
1220
  }
1027
1221
  function transformShowFineGrained(path, state) {
1028
- state.needsCreateComponent = true;
1029
- return transformComponentFineGrained(path, state);
1222
+ const { node } = path;
1223
+ const attributes = node.openingElement.attributes;
1224
+ const children = node.children;
1225
+ let whenExpr = null;
1226
+ let fallbackExpr = null;
1227
+ for (const attr of attributes) {
1228
+ if (t.isJSXAttribute(attr)) {
1229
+ const name = getAttrName(attr);
1230
+ if (name === "when") whenExpr = getAttributeValue(attr.value);
1231
+ else if (name === "fallback") fallbackExpr = getAttributeValue(attr.value);
1232
+ }
1233
+ }
1234
+ if (!whenExpr) {
1235
+ throw path.buildCodeFrameError(
1236
+ '<Show> requires a "when" prop. Example: <Show when={isOpen} fallback={null}>...</Show>'
1237
+ );
1238
+ }
1239
+ let contentExpr = null;
1240
+ for (const child of children) {
1241
+ if (t.isJSXExpressionContainer(child) && !t.isJSXEmptyExpression(child.expression)) {
1242
+ contentExpr = child.expression;
1243
+ break;
1244
+ }
1245
+ }
1246
+ if (!contentExpr) {
1247
+ const transformedChildren = [];
1248
+ for (const child of children) {
1249
+ if (t.isJSXText(child)) {
1250
+ const text = normalizeJsxText(child.value);
1251
+ if (text) transformedChildren.push(t.stringLiteral(text));
1252
+ } else if (t.isJSXElement(child)) {
1253
+ transformedChildren.push(transformElementFineGrained({ node: child }, state));
1254
+ }
1255
+ }
1256
+ if (transformedChildren.length === 1) {
1257
+ contentExpr = transformedChildren[0];
1258
+ } else if (transformedChildren.length > 1) {
1259
+ contentExpr = t.arrayExpression(transformedChildren);
1260
+ } else {
1261
+ contentExpr = t.nullLiteral();
1262
+ }
1263
+ }
1264
+ let condition;
1265
+ if (t.isCallExpression(whenExpr)) {
1266
+ condition = whenExpr;
1267
+ } else if (t.isArrowFunctionExpression(whenExpr) && t.isExpression(whenExpr.body)) {
1268
+ condition = whenExpr.body;
1269
+ } else if (t.isIdentifier(whenExpr) && (state.signalNames && isSignalIdentifier(whenExpr.name, state.signalNames) || state.importedIdentifiers && state.importedIdentifiers.has(whenExpr.name))) {
1270
+ condition = t.callExpression(whenExpr, []);
1271
+ } else {
1272
+ condition = whenExpr;
1273
+ }
1274
+ const vId = path.scope ? path.scope.generateUidIdentifier("v") : t.identifier("_v");
1275
+ const consequent = t.isFunction(contentExpr) ? t.callExpression(contentExpr, [t.cloneNode(vId)]) : contentExpr;
1276
+ const alternate = fallbackExpr || t.nullLiteral();
1277
+ return t.arrowFunctionExpression([], t.blockStatement([
1278
+ t.variableDeclaration("const", [
1279
+ t.variableDeclarator(vId, condition)
1280
+ ]),
1281
+ t.returnStatement(
1282
+ t.conditionalExpression(t.cloneNode(vId), consequent, alternate)
1283
+ )
1284
+ ]));
1030
1285
  }
1031
1286
  function transformFragmentFineGrained(path, state) {
1032
1287
  const { node } = path;
@@ -1034,7 +1289,7 @@ function whatBabelPlugin({ types: t }) {
1034
1289
  const transformed = [];
1035
1290
  for (const child of children) {
1036
1291
  if (t.isJSXText(child)) {
1037
- const text = child.value.replace(/\n\s+/g, " ").trim();
1292
+ const text = normalizeJsxText(child.value);
1038
1293
  if (text) transformed.push(t.stringLiteral(text));
1039
1294
  } else if (t.isJSXExpressionContainer(child)) {
1040
1295
  if (!t.isJSXEmptyExpression(child.expression)) {
@@ -1244,20 +1499,31 @@ function whatBabelPlugin({ types: t }) {
1244
1499
  }
1245
1500
  },
1246
1501
  JSXElement(path, state) {
1247
- state.signalNames = collectSignalNamesFromScope(path);
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;
1248
1511
  state._pendingSetup = [];
1249
1512
  const transformed = transformElementFineGrained(path, state);
1250
1513
  const pending = state._pendingSetup;
1251
1514
  state._pendingSetup = [];
1252
1515
  if (pending.length > 0) {
1253
1516
  let stmtPath = path;
1517
+ let crossedFunctionBoundary = false;
1254
1518
  while (stmtPath && !stmtPath.isStatement()) {
1519
+ if (stmtPath.isArrowFunctionExpression() || stmtPath.isFunctionExpression()) {
1520
+ crossedFunctionBoundary = true;
1521
+ }
1255
1522
  stmtPath = stmtPath.parentPath;
1256
1523
  }
1257
- if (stmtPath && stmtPath.isStatement()) {
1258
- for (const stmt of pending) {
1259
- stmtPath.insertBefore(stmt);
1260
- }
1524
+ const inStatementList = stmtPath && stmtPath.isStatement() && (stmtPath.listKey === "body" || stmtPath.listKey === "consequent") && Array.isArray(stmtPath.container);
1525
+ if (inStatementList && !crossedFunctionBoundary) {
1526
+ stmtPath.insertBefore(pending);
1261
1527
  path.replaceWith(transformed);
1262
1528
  } else {
1263
1529
  pending.push(t.returnStatement(transformed));