what-compiler 0.8.4 → 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.
@@ -65,9 +65,56 @@ var SIGNAL_CREATORS = /* @__PURE__ */ new Set([
65
65
  "useQuery",
66
66
  "useInfiniteQuery"
67
67
  ]);
68
+ function normalizeJsxText(value) {
69
+ if (!/[\r\n]/.test(value)) {
70
+ return value.replace(/\t/g, " ");
71
+ }
72
+ const lines = value.split(/\r\n|\n|\r/);
73
+ let lastNonEmpty = -1;
74
+ for (let i = 0; i < lines.length; i++) {
75
+ if (/[^ \t]/.test(lines[i])) lastNonEmpty = i;
76
+ }
77
+ if (lastNonEmpty === -1) return "";
78
+ let out = "";
79
+ for (let i = 0; i < lines.length; i++) {
80
+ let line = lines[i].replace(/\t/g, " ");
81
+ const isFirst = i === 0;
82
+ const isLast = i === lines.length - 1;
83
+ if (!isFirst) line = line.replace(/^ +/, "");
84
+ if (!isLast) line = line.replace(/ +$/, "");
85
+ if (!line) continue;
86
+ if (i !== lastNonEmpty) line += " ";
87
+ out += line;
88
+ }
89
+ return out;
90
+ }
68
91
  function whatBabelPlugin({ types: t }) {
92
+ const _unknownModifierWarned = /* @__PURE__ */ new Set();
93
+ const _forInfoWarned = /* @__PURE__ */ new Set();
94
+ function hasEventModifiers(name, state) {
95
+ if (!name.includes("__")) return false;
96
+ if (!name.startsWith("on")) return false;
97
+ const parts = name.split("__");
98
+ const tail = parts.slice(1).filter((s) => s !== "");
99
+ if (tail.length === 0) return false;
100
+ if (true) {
101
+ const unknown = tail.filter((m) => !EVENT_MODIFIERS.has(m));
102
+ const filename = state && (state.filename || state.file && state.file.opts && state.file.opts.filename) || "<unknown>";
103
+ for (const m of unknown) {
104
+ const key = `${filename}::${m}`;
105
+ if (!_unknownModifierWarned.has(key)) {
106
+ _unknownModifierWarned.add(key);
107
+ console.warn(
108
+ `[what-compiler] Unknown event modifier "__${m}" in attribute "${name}" (${filename}). Known modifiers: ${[...EVENT_MODIFIERS].join(", ")}. Unknown segments are ignored.`
109
+ );
110
+ }
111
+ }
112
+ }
113
+ return true;
114
+ }
69
115
  function parseEventModifiers(name) {
70
- const parts = name.split("|");
116
+ const delimiter = name.includes("|") ? "|" : "__";
117
+ const parts = name.split(delimiter);
71
118
  const eventName = parts[0];
72
119
  const modifiers = parts.slice(1).filter((m) => EVENT_MODIFIERS.has(m));
73
120
  return { eventName, modifiers };
@@ -197,22 +244,20 @@ function whatBabelPlugin({ types: t }) {
197
244
  }
198
245
  let scope = path3.scope;
199
246
  while (scope) {
200
- for (const [name, binding] of Object.entries(scope.bindings)) {
247
+ for (const binding of Object.values(scope.bindings)) {
201
248
  if (binding.path.isVariableDeclarator()) {
202
249
  extractFromDeclarator(binding.path.node);
203
250
  }
204
- if (binding.path.isIdentifier() || binding.kind === "param") {
205
- const fnPath = binding.scope.path;
206
- if (fnPath && fnPath.node && fnPath.node.params) {
207
- for (const param of fnPath.node.params) {
208
- if (t.isObjectPattern(param)) {
209
- for (const prop of param.properties) {
210
- if (t.isObjectProperty(prop) && t.isIdentifier(prop.value)) {
211
- signalNames.add(prop.value.name);
212
- } else if (t.isRestElement(prop) && t.isIdentifier(prop.argument)) {
213
- signalNames.add(prop.argument.name);
214
- }
215
- }
251
+ }
252
+ const fnNode = scope.path && scope.path.node;
253
+ if (fnNode && fnNode.params) {
254
+ for (const param of fnNode.params) {
255
+ if (t.isObjectPattern(param)) {
256
+ for (const prop of param.properties) {
257
+ if (t.isObjectProperty(prop) && t.isIdentifier(prop.value)) {
258
+ signalNames.add(prop.value.name);
259
+ } else if (t.isRestElement(prop) && t.isIdentifier(prop.argument)) {
260
+ signalNames.add(prop.argument.name);
216
261
  }
217
262
  }
218
263
  }
@@ -280,7 +325,7 @@ function whatBabelPlugin({ types: t }) {
280
325
  return isPotentiallyReactive(expr.callee, signalNames, importedIds) || expr.arguments.some((arg) => isPotentiallyReactive(arg, signalNames, importedIds));
281
326
  }
282
327
  if (t.isIdentifier(expr)) {
283
- return isSignalIdentifier(expr.name, signalNames);
328
+ return isSignalIdentifier(expr.name, signalNames) || importedIds && importedIds.has(expr.name);
284
329
  }
285
330
  if (t.isMemberExpression(expr)) {
286
331
  return isPotentiallyReactive(expr.object, signalNames, importedIds);
@@ -310,6 +355,93 @@ function whatBabelPlugin({ types: t }) {
310
355
  }
311
356
  return false;
312
357
  }
358
+ function tryLowerMapToMapArray(expr, state) {
359
+ let mapCall = expr;
360
+ let wrappedInArrow = false;
361
+ if (t.isArrowFunctionExpression(expr) && expr.params.length === 0) {
362
+ mapCall = expr.body;
363
+ wrappedInArrow = true;
364
+ }
365
+ if (t.isConditionalExpression(mapCall)) {
366
+ const loweredCon = tryLowerMapCall(mapCall.consequent, state);
367
+ const loweredAlt = tryLowerMapCall(mapCall.alternate, state);
368
+ if (loweredCon || loweredAlt) {
369
+ const result = t.conditionalExpression(
370
+ mapCall.test,
371
+ loweredCon || mapCall.consequent,
372
+ loweredAlt || mapCall.alternate
373
+ );
374
+ return wrappedInArrow ? t.arrowFunctionExpression([], result) : result;
375
+ }
376
+ return null;
377
+ }
378
+ if (t.isLogicalExpression(mapCall) && (mapCall.operator === "&&" || mapCall.operator === "||")) {
379
+ const loweredRight = tryLowerMapCall(mapCall.right, state);
380
+ if (loweredRight) {
381
+ const result = t.logicalExpression(mapCall.operator, mapCall.left, loweredRight);
382
+ return wrappedInArrow ? t.arrowFunctionExpression([], result) : result;
383
+ }
384
+ return null;
385
+ }
386
+ const lowered = tryLowerMapCall(mapCall, state);
387
+ return lowered;
388
+ }
389
+ function tryLowerMapCall(mapCall, state) {
390
+ if (!t.isCallExpression(mapCall)) return null;
391
+ if (!t.isMemberExpression(mapCall.callee)) return null;
392
+ if (!t.isIdentifier(mapCall.callee.property, { name: "map" })) return null;
393
+ if (mapCall.arguments.length < 1) return null;
394
+ const mapFn = mapCall.arguments[0];
395
+ if (!t.isArrowFunctionExpression(mapFn) && !t.isFunctionExpression(mapFn)) return null;
396
+ let returnExpr = null;
397
+ if (t.isArrowFunctionExpression(mapFn)) {
398
+ if (t.isExpression(mapFn.body)) {
399
+ returnExpr = mapFn.body;
400
+ } else if (t.isBlockStatement(mapFn.body)) {
401
+ const ret = mapFn.body.body.find((s) => t.isReturnStatement(s));
402
+ if (ret) returnExpr = ret.argument;
403
+ }
404
+ } else if (t.isFunctionExpression(mapFn)) {
405
+ const ret = mapFn.body.body.find((s) => t.isReturnStatement(s));
406
+ if (ret) returnExpr = ret.argument;
407
+ }
408
+ if (!returnExpr) return null;
409
+ if (!t.isJSXElement(returnExpr)) return null;
410
+ const attrs = returnExpr.openingElement.attributes;
411
+ let keyAttr = null;
412
+ for (const attr of attrs) {
413
+ if (t.isJSXAttribute(attr) && getAttrName(attr) === "key") {
414
+ keyAttr = attr;
415
+ break;
416
+ }
417
+ }
418
+ if (!keyAttr) {
419
+ if (true) {
420
+ const loc = returnExpr.loc;
421
+ const fileName = state.filename || state.file?.opts?.filename || "<unknown>";
422
+ const lineInfo = loc ? `:${loc.start.line}:${loc.start.column}` : "";
423
+ console.warn(
424
+ `[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.`
425
+ );
426
+ }
427
+ return null;
428
+ }
429
+ const keyValue = getAttributeValue(keyAttr.value);
430
+ if (!keyValue) return null;
431
+ returnExpr.openingElement.attributes = attrs.filter((a) => a !== keyAttr);
432
+ const sourceObj = mapCall.callee.object;
433
+ const source = t.arrowFunctionExpression([], sourceObj);
434
+ const itemParam = mapFn.params[0] ? t.cloneNode(mapFn.params[0], true) : t.identifier("_item");
435
+ const keyFn = t.arrowFunctionExpression([itemParam], t.cloneNode(keyValue, true));
436
+ return t.callExpression(t.identifier("_$mapArray"), [
437
+ source,
438
+ mapFn,
439
+ t.objectExpression([
440
+ t.objectProperty(t.identifier("key"), keyFn),
441
+ t.objectProperty(t.identifier("raw"), t.booleanLiteral(true))
442
+ ])
443
+ ]);
444
+ }
313
445
  function isStaticChild(child) {
314
446
  if (t.isJSXText(child)) return true;
315
447
  if (t.isJSXExpressionContainer(child)) return false;
@@ -333,7 +465,7 @@ function whatBabelPlugin({ types: t }) {
333
465
  }
334
466
  function extractStaticHTML(node) {
335
467
  if (t.isJSXText(node)) {
336
- const text = node.value.replace(/\n\s+/g, " ").trim();
468
+ const text = normalizeJsxText(node.value);
337
469
  return text ? escapeHTML(text) : "";
338
470
  }
339
471
  if (t.isJSXExpressionContainer(node)) {
@@ -373,7 +505,7 @@ function whatBabelPlugin({ types: t }) {
373
505
  html += ">";
374
506
  for (const child of node.children) {
375
507
  if (t.isJSXText(child)) {
376
- const text = child.value.replace(/\n\s+/g, " ").trim();
508
+ const text = normalizeJsxText(child.value);
377
509
  if (text) html += escapeHTML(text);
378
510
  } else if (t.isJSXExpressionContainer(child)) {
379
511
  if (!t.isJSXEmptyExpression(child.expression)) {
@@ -400,15 +532,15 @@ function whatBabelPlugin({ types: t }) {
400
532
  const { node } = path3;
401
533
  const openingElement = node.openingElement;
402
534
  const tagName = openingElement.name.name;
403
- if (isComponent(tagName)) {
404
- return transformComponentFineGrained(path3, state);
405
- }
406
535
  if (tagName === "For") {
407
536
  return transformForFineGrained(path3, state);
408
537
  }
409
538
  if (tagName === "Show") {
410
539
  return transformShowFineGrained(path3, state);
411
540
  }
541
+ if (isComponent(tagName)) {
542
+ return transformComponentFineGrained(path3, state);
543
+ }
412
544
  const attributes = openingElement.attributes;
413
545
  const children = node.children;
414
546
  const allChildrenStatic = children.every(isStaticChild);
@@ -445,7 +577,7 @@ function whatBabelPlugin({ types: t }) {
445
577
  t.variableDeclarator(t.identifier(elId), t.callExpression(t.identifier(tmplId), []))
446
578
  ])
447
579
  ];
448
- applyDynamicAttrs(statements, elId, attributes, state);
580
+ applyDynamicAttrs(statements, elId, attributes, state, tagName);
449
581
  applyDynamicChildren(statements, elId, children, node, state);
450
582
  if (!state._pendingSetup) state._pendingSetup = [];
451
583
  state._pendingSetup.push(...statements);
@@ -473,7 +605,7 @@ function whatBabelPlugin({ types: t }) {
473
605
  const transformedChildren = [];
474
606
  for (const child of children) {
475
607
  if (t.isJSXText(child)) {
476
- const text = child.value.replace(/\n\s+/g, " ").trim();
608
+ const text = normalizeJsxText(child.value);
477
609
  if (text) transformedChildren.push(t.stringLiteral(text));
478
610
  } else if (t.isJSXExpressionContainer(child)) {
479
611
  if (!t.isJSXEmptyExpression(child.expression)) {
@@ -488,8 +620,33 @@ function whatBabelPlugin({ types: t }) {
488
620
  const propsExpr = props.length > 0 ? t.objectExpression(props) : t.nullLiteral();
489
621
  return t.callExpression(t.identifier("h"), [t.stringLiteral(tagName), propsExpr, ...transformedChildren]);
490
622
  }
491
- function applyDynamicAttrs(statements, elId, attributes, state) {
623
+ const VALUE_PROP_TAGS = /* @__PURE__ */ new Set(["input", "textarea", "select", "option"]);
624
+ function applyDynamicAttrs(statements, elId, attributes, state, tagName) {
492
625
  function buildSetPropCall(propName, valueExpr) {
626
+ if (propName === "class") {
627
+ state.needsSetClass = true;
628
+ return t.callExpression(t.identifier("_$setClass"), [t.identifier(elId), valueExpr]);
629
+ }
630
+ if (propName === "style") {
631
+ state.needsSetStyle = true;
632
+ return t.callExpression(t.identifier("_$setStyle"), [t.identifier(elId), valueExpr]);
633
+ }
634
+ if (propName === "value" && tagName && VALUE_PROP_TAGS.has(tagName)) {
635
+ state.needsSetValue = true;
636
+ return t.callExpression(t.identifier("_$setValue"), [t.identifier(elId), valueExpr]);
637
+ }
638
+ if (propName === "checked" && tagName === "input") {
639
+ state.needsSetChecked = true;
640
+ return t.callExpression(t.identifier("_$setChecked"), [t.identifier(elId), valueExpr]);
641
+ }
642
+ if (propName.startsWith("data-") || propName.startsWith("aria-")) {
643
+ state.needsSetAttr = true;
644
+ return t.callExpression(t.identifier("_$setAttr"), [
645
+ t.identifier(elId),
646
+ t.stringLiteral(propName),
647
+ valueExpr
648
+ ]);
649
+ }
493
650
  state.needsSetProp = true;
494
651
  return t.callExpression(t.identifier("_$setProp"), [
495
652
  t.identifier(elId),
@@ -497,6 +654,14 @@ function whatBabelPlugin({ types: t }) {
497
654
  valueExpr
498
655
  ]);
499
656
  }
657
+ let delegateInitEmitted = false;
658
+ function emitDelegateInit() {
659
+ if (delegateInitEmitted) return;
660
+ delegateInitEmitted = true;
661
+ statements.push(
662
+ t.expressionStatement(t.callExpression(t.identifier("_$delegate$"), []))
663
+ );
664
+ }
500
665
  for (const attr of attributes) {
501
666
  if (t.isJSXSpreadAttribute(attr)) {
502
667
  state.needsSpread = true;
@@ -530,20 +695,21 @@ function whatBabelPlugin({ types: t }) {
530
695
  );
531
696
  continue;
532
697
  }
533
- if (attrName.startsWith("on") && !attrName.includes("|")) {
698
+ if (attrName.startsWith("on") && !attrName.includes("|") && !hasEventModifiers(attrName, state)) {
534
699
  const event = attrName.slice(2).toLowerCase();
535
700
  const handler = getAttributeValue(attr.value);
536
701
  if (DELEGATED_EVENTS.has(event)) {
537
702
  state.needsDelegation = true;
538
703
  if (!state.delegatedEvents) state.delegatedEvents = /* @__PURE__ */ new Set();
539
704
  state.delegatedEvents.add(event);
705
+ emitDelegateInit();
540
706
  statements.push(
541
707
  t.expressionStatement(
542
708
  t.assignmentExpression(
543
709
  "=",
544
710
  t.memberExpression(
545
711
  t.identifier(elId),
546
- t.identifier(`__${event}`)
712
+ t.identifier(`$$${event}`)
547
713
  ),
548
714
  handler
549
715
  )
@@ -561,7 +727,7 @@ function whatBabelPlugin({ types: t }) {
561
727
  }
562
728
  continue;
563
729
  }
564
- if (attrName.startsWith("on") && attrName.includes("|")) {
730
+ if (attrName.startsWith("on") && (attrName.includes("|") || hasEventModifiers(attrName, state))) {
565
731
  const { eventName, modifiers } = parseEventModifiers(attrName);
566
732
  const handler = getAttributeValue(attr.value);
567
733
  const wrappedHandler = createEventHandler(handler, modifiers);
@@ -661,8 +827,9 @@ function whatBabelPlugin({ types: t }) {
661
827
  const domName = normalizeAttrName(attrName);
662
828
  if (isPotentiallyReactive(expr, state.signalNames, state.importedIdentifiers)) {
663
829
  state.needsEffect = true;
830
+ const valueExpr = t.isIdentifier(expr) && (isSignalIdentifier(expr.name, state.signalNames) || state.importedIdentifiers && state.importedIdentifiers.has(expr.name)) ? t.callExpression(expr, []) : expr;
664
831
  const effectCall = t.callExpression(t.identifier("_$effect"), [
665
- t.arrowFunctionExpression([], buildSetPropCall(domName, expr))
832
+ t.arrowFunctionExpression([], buildSetPropCall(domName, valueExpr))
666
833
  ]);
667
834
  if (isUncertainReactive(expr, state.signalNames, state.importedIdentifiers)) {
668
835
  t.addComment(
@@ -679,12 +846,56 @@ function whatBabelPlugin({ types: t }) {
679
846
  }
680
847
  }
681
848
  }
849
+ function buildsDOM(node) {
850
+ if (!node || typeof node !== "object") return false;
851
+ if (Array.isArray(node)) return node.some(buildsDOM);
852
+ if (node.type === "JSXElement" || node.type === "JSXFragment") return true;
853
+ if (node.type === "CallExpression" && node.callee && node.callee.type === "Identifier" && (node.callee.name === "_$mapArray" || node.callee.name === "mapArray")) {
854
+ return true;
855
+ }
856
+ for (const key of Object.keys(node)) {
857
+ if (key === "loc" || key === "start" || key === "end" || key === "leadingComments" || key === "trailingComments" || key === "innerComments") continue;
858
+ const v = node[key];
859
+ if (v && typeof v === "object" && buildsDOM(v)) return true;
860
+ }
861
+ return false;
862
+ }
863
+ function memoizeBranchCondition(expr, statements, state) {
864
+ let testExpr = null;
865
+ let isTernary = false;
866
+ if (t.isConditionalExpression(expr)) {
867
+ testExpr = expr.test;
868
+ isTernary = true;
869
+ } else if (t.isLogicalExpression(expr) && (expr.operator === "&&" || expr.operator === "||")) {
870
+ testExpr = expr.left;
871
+ } else {
872
+ return expr;
873
+ }
874
+ if (!isPotentiallyReactive(testExpr, state.signalNames, state.importedIdentifiers)) return expr;
875
+ const branches = isTernary ? [expr.consequent, expr.alternate] : [expr.right];
876
+ if (!branches.some(buildsDOM)) return expr;
877
+ const condId = state.nextMemoId();
878
+ state.needsMemo = true;
879
+ const memoBody = isTernary ? t.unaryExpression("!", t.unaryExpression("!", testExpr)) : testExpr;
880
+ statements.push(
881
+ t.variableDeclaration("const", [
882
+ t.variableDeclarator(
883
+ t.identifier(condId),
884
+ t.callExpression(t.identifier("_$memo"), [
885
+ t.arrowFunctionExpression([], memoBody)
886
+ ])
887
+ )
888
+ ])
889
+ );
890
+ const condRead = t.callExpression(t.identifier(condId), []);
891
+ return isTernary ? t.conditionalExpression(condRead, expr.consequent, expr.alternate) : t.logicalExpression(expr.operator, condRead, expr.right);
892
+ }
682
893
  function applyDynamicChildren(statements, elId, children, parentNode, state) {
683
894
  const entries = [];
684
895
  let childIndex = 0;
685
896
  for (const child of children) {
686
897
  if (t.isJSXText(child)) {
687
- const text = child.value.replace(/\n\s+/g, " ").trim();
898
+ const text = normalizeJsxText(child.value);
688
899
  if (text) childIndex++;
689
900
  continue;
690
901
  }
@@ -713,22 +924,31 @@ function whatBabelPlugin({ types: t }) {
713
924
  const entriesNeedingRef = entries.filter(
714
925
  (e) => e.type === "expression" || e.type === "component" || e.type === "static" && e.hasAnythingDynamic
715
926
  );
716
- const hasDynamicInsert = entries.some((e) => e.type === "expression" || e.type === "component");
717
- const needsPreCapture = entriesNeedingRef.length >= 2 && hasDynamicInsert;
927
+ const needsPreCapture = entriesNeedingRef.length >= 2;
718
928
  const markerVars = /* @__PURE__ */ new Map();
719
929
  if (needsPreCapture) {
930
+ let prevVar = null;
931
+ let prevIndex = 0;
720
932
  for (const entry of entriesNeedingRef) {
721
- const varName = `_m$${entry.childIndex}`;
933
+ const idx = entry.childIndex;
722
934
  const markerVar = state.nextVarId();
723
- markerVars.set(entry.childIndex, markerVar);
935
+ markerVars.set(idx, markerVar);
936
+ let init;
937
+ if (prevVar === null) {
938
+ init = buildChildAccess(elId, idx);
939
+ } else {
940
+ init = t.identifier(prevVar);
941
+ for (let i = prevIndex; i < idx; i++) {
942
+ init = t.memberExpression(init, t.identifier("nextSibling"));
943
+ }
944
+ }
724
945
  statements.push(
725
946
  t.variableDeclaration("const", [
726
- t.variableDeclarator(
727
- t.identifier(markerVar),
728
- buildChildAccess(elId, entry.childIndex)
729
- )
947
+ t.variableDeclarator(t.identifier(markerVar), init)
730
948
  ])
731
949
  );
950
+ prevVar = markerVar;
951
+ prevIndex = idx;
732
952
  }
733
953
  }
734
954
  function getMarker(idx) {
@@ -739,10 +959,48 @@ function whatBabelPlugin({ types: t }) {
739
959
  }
740
960
  for (const entry of entries) {
741
961
  if (entry.type === "expression") {
742
- const expr = entry.child.expression;
962
+ let expr = entry.child.expression;
743
963
  const marker = getMarker(entry.childIndex);
744
964
  state.needsInsert = true;
965
+ let mapResult = tryLowerMapToMapArray(expr, state);
966
+ if (mapResult) {
967
+ state.needsMapArray = true;
968
+ const isBareMapArray = t.isCallExpression(mapResult) && t.isIdentifier(mapResult.callee) && (mapResult.callee.name === "_$mapArray" || mapResult.callee.name === "mapArray");
969
+ const isArrowAlready = t.isArrowFunctionExpression(mapResult);
970
+ if (isArrowAlready && t.isExpression(mapResult.body)) {
971
+ mapResult.body = memoizeBranchCondition(mapResult.body, statements, state);
972
+ } else if (!isBareMapArray && !isArrowAlready) {
973
+ mapResult = memoizeBranchCondition(mapResult, statements, state);
974
+ }
975
+ const insertArg = isBareMapArray || isArrowAlready ? mapResult : t.arrowFunctionExpression([], mapResult);
976
+ statements.push(
977
+ t.expressionStatement(
978
+ t.callExpression(t.identifier("_$insert"), [
979
+ t.identifier(elId),
980
+ insertArg,
981
+ marker
982
+ ])
983
+ )
984
+ );
985
+ continue;
986
+ }
987
+ const isMapArrayCall = t.isCallExpression(expr) && t.isIdentifier(expr.callee) && (expr.callee.name === "mapArray" || expr.callee.name === "_$mapArray");
988
+ if (isMapArrayCall) {
989
+ state.needsMapArray = true;
990
+ if (expr.callee.name === "mapArray") expr.callee.name = "_$mapArray";
991
+ statements.push(
992
+ t.expressionStatement(
993
+ t.callExpression(t.identifier("_$insert"), [
994
+ t.identifier(elId),
995
+ expr,
996
+ marker
997
+ ])
998
+ )
999
+ );
1000
+ continue;
1001
+ }
745
1002
  if (isPotentiallyReactive(expr, state.signalNames, state.importedIdentifiers)) {
1003
+ expr = memoizeBranchCondition(expr, statements, state);
746
1004
  const insertCall = t.callExpression(t.identifier("_$insert"), [
747
1005
  t.identifier(elId),
748
1006
  t.arrowFunctionExpression([], expr),
@@ -800,7 +1058,7 @@ function whatBabelPlugin({ types: t }) {
800
1058
  ])
801
1059
  );
802
1060
  }
803
- applyDynamicAttrs(statements, childElRef, entry.child.openingElement.attributes, state);
1061
+ applyDynamicAttrs(statements, childElRef, entry.child.openingElement.attributes, state, entry.child.openingElement.name.name);
804
1062
  applyDynamicChildren(statements, childElRef, entry.child.children, entry.child, state);
805
1063
  continue;
806
1064
  }
@@ -808,8 +1066,9 @@ function whatBabelPlugin({ types: t }) {
808
1066
  for (const fChild of entry.child.children) {
809
1067
  if (t.isJSXExpressionContainer(fChild) && !t.isJSXEmptyExpression(fChild.expression)) {
810
1068
  state.needsInsert = true;
811
- const expr = fChild.expression;
1069
+ let expr = fChild.expression;
812
1070
  if (isPotentiallyReactive(expr, state.signalNames, state.importedIdentifiers)) {
1071
+ expr = memoizeBranchCondition(expr, statements, state);
813
1072
  statements.push(
814
1073
  t.expressionStatement(
815
1074
  t.callExpression(t.identifier("_$insert"), [
@@ -950,7 +1209,7 @@ function whatBabelPlugin({ types: t }) {
950
1209
  }
951
1210
  continue;
952
1211
  }
953
- if (attrName.startsWith("on") && attrName.includes("|")) {
1212
+ if (attrName.startsWith("on") && (attrName.includes("|") || hasEventModifiers(attrName, state))) {
954
1213
  const { eventName, modifiers } = parseEventModifiers(attrName);
955
1214
  const handler = getAttributeValue(attr.value);
956
1215
  const wrappedHandler = createEventHandler(handler, modifiers);
@@ -968,7 +1227,7 @@ function whatBabelPlugin({ types: t }) {
968
1227
  const transformedChildren = [];
969
1228
  for (const child of children) {
970
1229
  if (t.isJSXText(child)) {
971
- const text = child.value.replace(/\n\s+/g, " ").trim();
1230
+ const text = normalizeJsxText(child.value);
972
1231
  if (text) transformedChildren.push(t.stringLiteral(text));
973
1232
  } else if (t.isJSXExpressionContainer(child)) {
974
1233
  if (!t.isJSXEmptyExpression(child.expression)) {
@@ -1002,10 +1261,24 @@ function whatBabelPlugin({ types: t }) {
1002
1261
  const { node } = path3;
1003
1262
  const attributes = node.openingElement.attributes;
1004
1263
  const children = node.children;
1264
+ if (true) {
1265
+ const fileName = state.filename || state.file?.opts?.filename || "<unknown>";
1266
+ if (!_forInfoWarned.has(fileName)) {
1267
+ _forInfoWarned.add(fileName);
1268
+ const loc = node.loc;
1269
+ const lineInfo = loc ? `:${loc.start.line}:${loc.start.column}` : "";
1270
+ console.info(
1271
+ `[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).`
1272
+ );
1273
+ }
1274
+ }
1005
1275
  let eachExpr = null;
1276
+ let keyExpr = null;
1006
1277
  for (const attr of attributes) {
1007
- if (t.isJSXAttribute(attr) && getAttrName(attr) === "each") {
1008
- eachExpr = getAttributeValue(attr.value);
1278
+ if (t.isJSXAttribute(attr)) {
1279
+ const name = getAttrName(attr);
1280
+ if (name === "each") eachExpr = getAttributeValue(attr.value);
1281
+ else if (name === "key") keyExpr = getAttributeValue(attr.value);
1009
1282
  }
1010
1283
  }
1011
1284
  if (!eachExpr) {
@@ -1026,11 +1299,124 @@ function whatBabelPlugin({ types: t }) {
1026
1299
  return transformElementAsH(path3, state);
1027
1300
  }
1028
1301
  state.needsMapArray = true;
1029
- return t.callExpression(t.identifier("_$mapArray"), [eachExpr, renderFn]);
1302
+ const args = [eachExpr, renderFn];
1303
+ if (keyExpr) {
1304
+ args.push(t.objectExpression([
1305
+ t.objectProperty(t.identifier("key"), keyExpr)
1306
+ ]));
1307
+ }
1308
+ return t.callExpression(t.identifier("_$mapArray"), args);
1030
1309
  }
1031
1310
  function transformShowFineGrained(path3, state) {
1032
- state.needsCreateComponent = true;
1033
- return transformComponentFineGrained(path3, state);
1311
+ const { node } = path3;
1312
+ const attributes = node.openingElement.attributes;
1313
+ const children = node.children;
1314
+ let whenExpr = null;
1315
+ let fallbackExpr = null;
1316
+ for (const attr of attributes) {
1317
+ if (t.isJSXAttribute(attr)) {
1318
+ const name = getAttrName(attr);
1319
+ if (name === "when") whenExpr = getAttributeValue(attr.value);
1320
+ else if (name === "fallback") fallbackExpr = getAttributeValue(attr.value);
1321
+ }
1322
+ }
1323
+ if (!whenExpr) {
1324
+ throw path3.buildCodeFrameError(
1325
+ '<Show> requires a "when" prop. Example: <Show when={isOpen} fallback={null}>...</Show>'
1326
+ );
1327
+ }
1328
+ let contentExpr = null;
1329
+ for (const child of children) {
1330
+ if (t.isJSXExpressionContainer(child) && !t.isJSXEmptyExpression(child.expression)) {
1331
+ contentExpr = child.expression;
1332
+ break;
1333
+ }
1334
+ }
1335
+ if (!contentExpr) {
1336
+ const transformedChildren = [];
1337
+ for (const child of children) {
1338
+ if (t.isJSXText(child)) {
1339
+ const text = normalizeJsxText(child.value);
1340
+ if (text) transformedChildren.push(t.stringLiteral(text));
1341
+ } else if (t.isJSXElement(child)) {
1342
+ transformedChildren.push(transformElementFineGrained({ node: child }, state));
1343
+ }
1344
+ }
1345
+ if (transformedChildren.length === 1) {
1346
+ contentExpr = transformedChildren[0];
1347
+ } else if (transformedChildren.length > 1) {
1348
+ contentExpr = t.arrayExpression(transformedChildren);
1349
+ } else {
1350
+ contentExpr = t.nullLiteral();
1351
+ }
1352
+ }
1353
+ let condition;
1354
+ if (t.isCallExpression(whenExpr)) {
1355
+ condition = whenExpr;
1356
+ } else if (t.isArrowFunctionExpression(whenExpr) && t.isExpression(whenExpr.body)) {
1357
+ condition = whenExpr.body;
1358
+ } else if (t.isIdentifier(whenExpr) && (state.signalNames && isSignalIdentifier(whenExpr.name, state.signalNames) || state.importedIdentifiers && state.importedIdentifiers.has(whenExpr.name))) {
1359
+ condition = t.callExpression(whenExpr, []);
1360
+ } else {
1361
+ condition = whenExpr;
1362
+ }
1363
+ const vId = path3.scope ? path3.scope.generateUidIdentifier("v") : t.identifier("_v");
1364
+ const contentIsFn = t.isFunction(contentExpr);
1365
+ const consequent = contentIsFn ? t.callExpression(contentExpr, [t.cloneNode(vId)]) : contentExpr;
1366
+ const alternate = fallbackExpr || t.nullLiteral();
1367
+ if (isPotentiallyReactive(condition, state.signalNames, state.importedIdentifiers)) {
1368
+ const condId = state.nextMemoId();
1369
+ state.needsMemo = true;
1370
+ const memoBody = contentIsFn ? condition : t.unaryExpression("!", t.unaryExpression("!", condition));
1371
+ if (!state._pendingSetup) state._pendingSetup = [];
1372
+ state._pendingSetup.push(
1373
+ t.variableDeclaration("const", [
1374
+ t.variableDeclarator(
1375
+ t.identifier(condId),
1376
+ t.callExpression(t.identifier("_$memo"), [
1377
+ t.arrowFunctionExpression([], memoBody)
1378
+ ])
1379
+ )
1380
+ ])
1381
+ );
1382
+ condition = t.callExpression(t.identifier(condId), []);
1383
+ }
1384
+ return t.arrowFunctionExpression([], t.blockStatement([
1385
+ t.variableDeclaration("const", [
1386
+ t.variableDeclarator(vId, condition)
1387
+ ]),
1388
+ t.returnStatement(
1389
+ t.conditionalExpression(t.cloneNode(vId), consequent, alternate)
1390
+ )
1391
+ ]));
1392
+ }
1393
+ function lowerFragmentExprChild(expr, state) {
1394
+ if (!state._pendingSetup) state._pendingSetup = [];
1395
+ const setup = state._pendingSetup;
1396
+ const mapResult = tryLowerMapToMapArray(expr, state);
1397
+ if (mapResult) {
1398
+ state.needsMapArray = true;
1399
+ const isBareMapArray = t.isCallExpression(mapResult) && t.isIdentifier(mapResult.callee) && (mapResult.callee.name === "_$mapArray" || mapResult.callee.name === "mapArray");
1400
+ const isArrowAlready = t.isArrowFunctionExpression(mapResult);
1401
+ if (isArrowAlready && t.isExpression(mapResult.body)) {
1402
+ mapResult.body = memoizeBranchCondition(mapResult.body, setup, state);
1403
+ return mapResult;
1404
+ }
1405
+ if (isBareMapArray) return mapResult;
1406
+ const memoized = memoizeBranchCondition(mapResult, setup, state);
1407
+ return t.arrowFunctionExpression([], memoized);
1408
+ }
1409
+ const isMapArrayCall = t.isCallExpression(expr) && t.isIdentifier(expr.callee) && (expr.callee.name === "mapArray" || expr.callee.name === "_$mapArray");
1410
+ if (isMapArrayCall) {
1411
+ state.needsMapArray = true;
1412
+ if (expr.callee.name === "mapArray") expr.callee.name = "_$mapArray";
1413
+ return expr;
1414
+ }
1415
+ if (isPotentiallyReactive(expr, state.signalNames, state.importedIdentifiers)) {
1416
+ expr = memoizeBranchCondition(expr, setup, state);
1417
+ return t.arrowFunctionExpression([], expr);
1418
+ }
1419
+ return expr;
1034
1420
  }
1035
1421
  function transformFragmentFineGrained(path3, state) {
1036
1422
  const { node } = path3;
@@ -1038,11 +1424,11 @@ function whatBabelPlugin({ types: t }) {
1038
1424
  const transformed = [];
1039
1425
  for (const child of children) {
1040
1426
  if (t.isJSXText(child)) {
1041
- const text = child.value.replace(/\n\s+/g, " ").trim();
1427
+ const text = normalizeJsxText(child.value);
1042
1428
  if (text) transformed.push(t.stringLiteral(text));
1043
1429
  } else if (t.isJSXExpressionContainer(child)) {
1044
1430
  if (!t.isJSXEmptyExpression(child.expression)) {
1045
- transformed.push(child.expression);
1431
+ transformed.push(lowerFragmentExprChild(child.expression, state));
1046
1432
  }
1047
1433
  } else if (t.isJSXElement(child)) {
1048
1434
  transformed.push(transformElementFineGrained({ node: child }, state));
@@ -1062,6 +1448,46 @@ function whatBabelPlugin({ types: t }) {
1062
1448
  state.templates.push({ id, html });
1063
1449
  return id;
1064
1450
  }
1451
+ function transformJsxRoot(path3, state, transform) {
1452
+ const scope = path3.scope;
1453
+ let cache = state._signalNamesCache;
1454
+ if (!cache) cache = state._signalNamesCache = /* @__PURE__ */ new WeakMap();
1455
+ let names = cache.get(scope);
1456
+ if (!names) {
1457
+ names = collectSignalNamesFromScope(path3);
1458
+ cache.set(scope, names);
1459
+ }
1460
+ state.signalNames = names;
1461
+ state._pendingSetup = [];
1462
+ const transformed = transform(path3, state);
1463
+ const pending = state._pendingSetup;
1464
+ state._pendingSetup = [];
1465
+ if (pending.length > 0) {
1466
+ let stmtPath = path3;
1467
+ let crossedFunctionBoundary = false;
1468
+ while (stmtPath && !stmtPath.isStatement()) {
1469
+ if (stmtPath.isArrowFunctionExpression() || stmtPath.isFunctionExpression()) {
1470
+ crossedFunctionBoundary = true;
1471
+ }
1472
+ stmtPath = stmtPath.parentPath;
1473
+ }
1474
+ const inStatementList = stmtPath && stmtPath.isStatement() && (stmtPath.listKey === "body" || stmtPath.listKey === "consequent") && Array.isArray(stmtPath.container);
1475
+ if (inStatementList && !crossedFunctionBoundary) {
1476
+ stmtPath.insertBefore(pending);
1477
+ path3.replaceWith(transformed);
1478
+ } else {
1479
+ pending.push(t.returnStatement(transformed));
1480
+ path3.replaceWith(
1481
+ t.callExpression(
1482
+ t.arrowFunctionExpression([], t.blockStatement(pending)),
1483
+ []
1484
+ )
1485
+ );
1486
+ }
1487
+ } else {
1488
+ path3.replaceWith(transformed);
1489
+ }
1490
+ }
1065
1491
  return {
1066
1492
  name: "what-jsx-transform",
1067
1493
  visitor: {
@@ -1073,6 +1499,12 @@ function whatBabelPlugin({ types: t }) {
1073
1499
  state.needsMapArray = false;
1074
1500
  state.needsSpread = false;
1075
1501
  state.needsSetProp = false;
1502
+ state.needsMemo = false;
1503
+ state.needsSetClass = false;
1504
+ state.needsSetStyle = false;
1505
+ state.needsSetAttr = false;
1506
+ state.needsSetValue = false;
1507
+ state.needsSetChecked = false;
1076
1508
  state.needsH = false;
1077
1509
  state.needsCreateComponent = false;
1078
1510
  state.needsFragment = false;
@@ -1083,8 +1515,10 @@ function whatBabelPlugin({ types: t }) {
1083
1515
  state.templateMap = /* @__PURE__ */ new Map();
1084
1516
  state.templateCount = 0;
1085
1517
  state._varCounter = 0;
1518
+ state._memoCounter = 0;
1086
1519
  state._pendingSetup = [];
1087
1520
  state.nextVarId = () => `_el$${state._varCounter++}`;
1521
+ state.nextMemoId = () => `_c$${state._memoCounter++}`;
1088
1522
  state.signalNames = /* @__PURE__ */ new Set();
1089
1523
  state.importedIdentifiers = /* @__PURE__ */ new Set();
1090
1524
  for (const node of path3.node.body) {
@@ -1140,20 +1574,19 @@ function whatBabelPlugin({ types: t }) {
1140
1574
  },
1141
1575
  exit(path3, state) {
1142
1576
  for (const tmpl of state.templates.reverse()) {
1577
+ const tmplCall = t.callExpression(t.identifier("_$template"), [t.stringLiteral(tmpl.html)]);
1578
+ t.addComment(tmplCall, "leading", " @__PURE__ ");
1143
1579
  path3.unshiftContainer(
1144
1580
  "body",
1145
1581
  t.variableDeclaration("const", [
1146
- t.variableDeclarator(
1147
- t.identifier(tmpl.id),
1148
- t.callExpression(t.identifier("_$template"), [t.stringLiteral(tmpl.html)])
1149
- )
1582
+ t.variableDeclarator(t.identifier(tmpl.id), tmplCall)
1150
1583
  ])
1151
1584
  );
1152
1585
  }
1153
1586
  const fgSpecifiers = [];
1154
1587
  if (state.needsTemplate) {
1155
1588
  fgSpecifiers.push(
1156
- t.importSpecifier(t.identifier("_$template"), t.identifier("template"))
1589
+ t.importSpecifier(t.identifier("_$template"), t.identifier("_$template"))
1157
1590
  );
1158
1591
  }
1159
1592
  if (state.needsInsert) {
@@ -1181,6 +1614,36 @@ function whatBabelPlugin({ types: t }) {
1181
1614
  t.importSpecifier(t.identifier("_$setProp"), t.identifier("setProp"))
1182
1615
  );
1183
1616
  }
1617
+ if (state.needsMemo) {
1618
+ fgSpecifiers.push(
1619
+ t.importSpecifier(t.identifier("_$memo"), t.identifier("memo"))
1620
+ );
1621
+ }
1622
+ if (state.needsSetClass) {
1623
+ fgSpecifiers.push(
1624
+ t.importSpecifier(t.identifier("_$setClass"), t.identifier("setClass"))
1625
+ );
1626
+ }
1627
+ if (state.needsSetStyle) {
1628
+ fgSpecifiers.push(
1629
+ t.importSpecifier(t.identifier("_$setStyle"), t.identifier("setStyle"))
1630
+ );
1631
+ }
1632
+ if (state.needsSetAttr) {
1633
+ fgSpecifiers.push(
1634
+ t.importSpecifier(t.identifier("_$setAttr"), t.identifier("setAttr"))
1635
+ );
1636
+ }
1637
+ if (state.needsSetValue) {
1638
+ fgSpecifiers.push(
1639
+ t.importSpecifier(t.identifier("_$setValue"), t.identifier("setValue"))
1640
+ );
1641
+ }
1642
+ if (state.needsSetChecked) {
1643
+ fgSpecifiers.push(
1644
+ t.importSpecifier(t.identifier("_$setChecked"), t.identifier("setChecked"))
1645
+ );
1646
+ }
1184
1647
  if (state.needsCreateComponent) {
1185
1648
  fgSpecifiers.push(
1186
1649
  t.importSpecifier(t.identifier("_$createComponent"), t.identifier("_$createComponent"))
@@ -1238,47 +1701,36 @@ function whatBabelPlugin({ types: t }) {
1238
1701
  const eventArray = t.arrayExpression(
1239
1702
  [...state.delegatedEvents].map((e) => t.stringLiteral(e))
1240
1703
  );
1241
- path3.pushContainer(
1242
- "body",
1243
- t.expressionStatement(
1244
- t.callExpression(t.identifier("_$delegateEvents"), [eventArray])
1245
- )
1704
+ const helperFn = t.functionDeclaration(
1705
+ t.identifier("_$delegate$"),
1706
+ [],
1707
+ t.blockStatement([
1708
+ t.ifStatement(
1709
+ t.identifier("_$delegated$"),
1710
+ t.returnStatement()
1711
+ ),
1712
+ t.expressionStatement(
1713
+ t.assignmentExpression("=", t.identifier("_$delegated$"), t.booleanLiteral(true))
1714
+ ),
1715
+ t.expressionStatement(
1716
+ t.callExpression(t.identifier("_$delegateEvents"), [eventArray])
1717
+ )
1718
+ ])
1246
1719
  );
1720
+ path3.unshiftContainer("body", [
1721
+ t.variableDeclaration("let", [
1722
+ t.variableDeclarator(t.identifier("_$delegated$"), t.booleanLiteral(false))
1723
+ ]),
1724
+ helperFn
1725
+ ]);
1247
1726
  }
1248
1727
  }
1249
1728
  },
1250
1729
  JSXElement(path3, state) {
1251
- state.signalNames = collectSignalNamesFromScope(path3);
1252
- state._pendingSetup = [];
1253
- const transformed = transformElementFineGrained(path3, state);
1254
- const pending = state._pendingSetup;
1255
- state._pendingSetup = [];
1256
- if (pending.length > 0) {
1257
- let stmtPath = path3;
1258
- while (stmtPath && !stmtPath.isStatement()) {
1259
- stmtPath = stmtPath.parentPath;
1260
- }
1261
- if (stmtPath && stmtPath.isStatement()) {
1262
- for (const stmt of pending) {
1263
- stmtPath.insertBefore(stmt);
1264
- }
1265
- path3.replaceWith(transformed);
1266
- } else {
1267
- pending.push(t.returnStatement(transformed));
1268
- path3.replaceWith(
1269
- t.callExpression(
1270
- t.arrowFunctionExpression([], t.blockStatement(pending)),
1271
- []
1272
- )
1273
- );
1274
- }
1275
- } else {
1276
- path3.replaceWith(transformed);
1277
- }
1730
+ transformJsxRoot(path3, state, transformElementFineGrained);
1278
1731
  },
1279
1732
  JSXFragment(path3, state) {
1280
- const transformed = transformFragmentFineGrained(path3, state);
1281
- path3.replaceWith(transformed);
1733
+ transformJsxRoot(path3, state, transformFragmentFineGrained);
1282
1734
  }
1283
1735
  }
1284
1736
  };
@@ -1417,6 +1869,13 @@ function extractPageConfig(source) {
1417
1869
  return { mode: "client" };
1418
1870
  }
1419
1871
  }
1872
+ function detectPageExports(source) {
1873
+ return {
1874
+ hasLoader: /export\s+(?:async\s+)?(?:const|let|var|function)\s+loader\b/.test(source),
1875
+ hasGetStaticPaths: /export\s+(?:async\s+)?(?:const|let|var|function)\s+getStaticPaths\b/.test(source),
1876
+ hasPageConfig: /export\s+const\s+page\b/.test(source)
1877
+ };
1878
+ }
1420
1879
  function generateRoutesModule(pagesDir, rootDir) {
1421
1880
  const { pages, layouts, apiRoutes } = scanPages(pagesDir);
1422
1881
  const imports = [];
@@ -1433,9 +1892,11 @@ function generateRoutesModule(pagesDir, rootDir) {
1433
1892
  const relPath = toImportPath(page.filePath, rootDir);
1434
1893
  imports.push(`import ${varName} from '${relPath}';`);
1435
1894
  let pageConfig = { mode: "client" };
1895
+ let detected = { hasLoader: false, hasGetStaticPaths: false, hasPageConfig: false };
1436
1896
  try {
1437
1897
  const source = fs.readFileSync(page.filePath, "utf-8");
1438
1898
  pageConfig = extractPageConfig(source);
1899
+ detected = detectPageExports(source);
1439
1900
  } catch {
1440
1901
  }
1441
1902
  const layoutVar = findLayout(page.routePath, layoutMap);
@@ -1443,7 +1904,8 @@ function generateRoutesModule(pagesDir, rootDir) {
1443
1904
  path: page.routePath,
1444
1905
  component: varName,
1445
1906
  mode: pageConfig.mode || "client",
1446
- layout: layoutVar || null
1907
+ layout: layoutVar || null,
1908
+ hasLoader: detected.hasLoader
1447
1909
  };
1448
1910
  routeEntries.push(entry);
1449
1911
  });
@@ -1465,7 +1927,7 @@ function generateRoutesModule(pagesDir, rootDir) {
1465
1927
  "",
1466
1928
  "export const routes = [",
1467
1929
  ...routeEntries.map(
1468
- (r) => ` { path: '${r.path}', component: ${r.component}, mode: '${r.mode}'${r.layout ? `, layout: ${r.layout}` : ""} },`
1930
+ (r) => ` { path: '${r.path}', component: ${r.component}, mode: '${r.mode}'${r.layout ? `, layout: ${r.layout}` : ""}${r.hasLoader ? ", hasLoader: true" : ""} },`
1469
1931
  ),
1470
1932
  "];",
1471
1933
  "",
@@ -1913,7 +2375,12 @@ function whatVitePlugin(options = {}) {
1913
2375
  // Pages directory (relative to project root)
1914
2376
  pages = "src/pages",
1915
2377
  // HMR: enabled by default in dev, disabled in production
1916
- hot = !production
2378
+ hot = !production,
2379
+ // Resolve the `production` exports condition (dist/*.min.js — pre-minified,
2380
+ // dev warnings compiled out) during `vite build`. Set to false to build
2381
+ // against package sources instead — needed e.g. in a monorepo where
2382
+ // workspace-linked dist/ output may be stale or absent. See config() below.
2383
+ prodBundles = true
1917
2384
  } = options;
1918
2385
  let rootDir = "";
1919
2386
  let pagesDir = "";
@@ -1968,6 +2435,13 @@ function whatVitePlugin(options = {}) {
1968
2435
  const result = transformSync(code, {
1969
2436
  filename: id,
1970
2437
  sourceMaps,
2438
+ // Hermetic transform (SPRINT v0.11 C7): never load the project's
2439
+ // babel.config.js/.babelrc. A user's React preset or unrelated
2440
+ // plugins corrupting What's JSX output is a debugging nightmare —
2441
+ // and scanning the disk for config files on every transform is
2442
+ // wasted I/O in dev.
2443
+ configFile: false,
2444
+ babelrc: false,
1971
2445
  plugins: [
1972
2446
  [whatBabelPlugin, { production }]
1973
2447
  ],
@@ -2011,15 +2485,43 @@ function whatVitePlugin(options = {}) {
2011
2485
  return;
2012
2486
  },
2013
2487
  // Configure for development
2014
- config(config, { mode }) {
2488
+ config(config, { mode, command }) {
2489
+ const useProdCondition = command === "build" && mode === "production" && prodBundles;
2015
2490
  return {
2491
+ ...useProdCondition ? { resolve: { conditions: ["production"] } } : {},
2016
2492
  esbuild: {
2017
2493
  // Preserve JSX so our babel plugin handles it -- don't let esbuild transform it
2018
2494
  jsx: "preserve"
2019
2495
  },
2020
2496
  optimizeDeps: {
2021
- // Pre-bundle the framework
2022
- include: ["what-framework"]
2497
+ // Exclude framework packages from Vite's dependency pre-bundling.
2498
+ //
2499
+ // Bug class this prevents — "dual module instance":
2500
+ // The compiler emits `import { ... } from 'what-framework/render'`
2501
+ // (a subpath resolved to the source file). Meanwhile user code
2502
+ // imports `'what-framework'` (the package entry). If Vite
2503
+ // pre-bundles `'what-framework'` into an esbuild chunk under
2504
+ // node_modules/.vite, those two import paths resolve to two
2505
+ // *different* module instances. Module-scoped state — the
2506
+ // `componentStack` used by createComponent, effect ownership,
2507
+ // the signal subscriber registry — is duplicated, so a signal
2508
+ // created in user code never notifies effects created via the
2509
+ // compiler-emitted path, and `getCurrentComponent()` returns
2510
+ // undefined inside components mounted through compiler output.
2511
+ //
2512
+ // Why `exclude` is the right knob:
2513
+ // `include` would force pre-bundling of the package entry, which
2514
+ // does not resolve the subpath import the compiler emits — so the
2515
+ // split persists. Using `exclude` tells Vite to skip the optimizer
2516
+ // for these packages and serve them via the normal module graph,
2517
+ // where both the package entry and the `/render` subpath share
2518
+ // a single ESM module record.
2519
+ //
2520
+ // Regression symptom if this is removed:
2521
+ // Components mount but lifecycle hooks (onMount, onCleanup) and
2522
+ // shared store state silently no-op; effects don't re-run on
2523
+ // signal writes from user code; SSR/CSR hydration mismatches.
2524
+ exclude: ["what-framework", "what-core", "what-compiler", "what-router"]
2023
2525
  }
2024
2526
  };
2025
2527
  }