@barefootjs/vite 0.32.0 → 0.33.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +229 -77
  2. package/package.json +6 -6
package/dist/index.js CHANGED
@@ -183,7 +183,7 @@ function convertNode(node, raw) {
183
183
  }
184
184
  if (n === undefined || Number.isNaN(n)) {
185
185
  const parsedDepth = convertNode(depthNode, raw);
186
- if (checkSupport(parsedDepth).supported) {
186
+ if (checkSupport(parsedDepth, "rendered").supported) {
187
187
  depthExpr = parsedDepth;
188
188
  flatDepth = 1;
189
189
  } else {
@@ -277,10 +277,12 @@ function convertNode(node, raw) {
277
277
  const k = objectLiteralKeyName(prop.name);
278
278
  if (k === null)
279
279
  return { kind: "unsupported", raw, reason: `Unsupported syntax: ${ts.SyntaxKind[node.kind]}` };
280
- properties.push({ key: k.key, keyKind: k.keyKind, shorthand: false, value: convertNode(prop.initializer, raw) });
280
+ properties.push({ kind: "prop", key: k.key, keyKind: k.keyKind, shorthand: false, value: convertNode(prop.initializer, raw) });
281
281
  } else if (ts.isShorthandPropertyAssignment(prop)) {
282
282
  const key = prop.name.text;
283
- properties.push({ key, keyKind: "identifier", shorthand: true, value: { kind: "identifier", name: key } });
283
+ properties.push({ kind: "prop", key, keyKind: "identifier", shorthand: true, value: { kind: "identifier", name: key } });
284
+ } else if (ts.isSpreadAssignment(prop)) {
285
+ properties.push({ kind: "spread", expr: convertNode(prop.expression, raw) });
284
286
  } else {
285
287
  return { kind: "unsupported", raw, reason: `Unsupported syntax: ${ts.SyntaxKind[node.kind]}` };
286
288
  }
@@ -960,14 +962,26 @@ function getUnaryOperatorString(op) {
960
962
  }
961
963
  }
962
964
  function isSupported(expr) {
963
- return checkSupport(expr);
965
+ return checkSupport(expr, "rendered");
964
966
  }
965
- function checkSupport(expr) {
967
+ function isSupportedValue(expr) {
968
+ return checkSupport(expr, "value");
969
+ }
970
+ function checkSupport(expr, pos) {
966
971
  switch (expr.kind) {
967
972
  case "unsupported":
968
973
  return { supported: false, reason: expr.reason };
969
- case "object-literal":
970
- return { supported: false, reason: "Unsupported syntax: ObjectLiteralExpression" };
974
+ case "object-literal": {
975
+ if (pos !== "value") {
976
+ return { supported: false, reason: "Unsupported syntax: ObjectLiteralExpression" };
977
+ }
978
+ for (const prop of expr.properties) {
979
+ const propSupport = checkSupport(prop.kind === "spread" ? prop.expr : prop.value, pos);
980
+ if (!propSupport.supported)
981
+ return propSupport;
982
+ }
983
+ return { supported: true, level: "L2" };
984
+ }
971
985
  case "identifier":
972
986
  return { supported: true, level: "L1" };
973
987
  case "literal":
@@ -977,7 +991,7 @@ function checkSupport(expr) {
977
991
  return { supported: false, reason: "Standalone arrow functions / regex literals are not supported" };
978
992
  case "array-literal": {
979
993
  for (const el of expr.elements) {
980
- const elSupport = checkSupport(el);
994
+ const elSupport = checkSupport(el, pos);
981
995
  if (!elSupport.supported)
982
996
  return elSupport;
983
997
  }
@@ -990,16 +1004,16 @@ function checkSupport(expr) {
990
1004
  reason: `String.prototype.${expr.method} supports only a string pattern + string replacement (the regex form is deferred); use a string pattern or wrap the expression in /* @client */`
991
1005
  };
992
1006
  }
993
- const objSupport = checkSupport(expr.object);
1007
+ const objSupport = checkSupport(expr.object, pos);
994
1008
  if (!objSupport.supported)
995
1009
  return objSupport;
996
1010
  for (const arg of expr.args) {
997
- const argSupport = checkSupport(arg);
1011
+ const argSupport = checkSupport(arg, pos);
998
1012
  if (!argSupport.supported)
999
1013
  return argSupport;
1000
1014
  }
1001
1015
  if (expr.method === "flat" && expr.depthExpr) {
1002
- const depthSupport = checkSupport(expr.depthExpr);
1016
+ const depthSupport = checkSupport(expr.depthExpr, pos);
1003
1017
  if (!depthSupport.supported)
1004
1018
  return depthSupport;
1005
1019
  }
@@ -1008,10 +1022,10 @@ function checkSupport(expr) {
1008
1022
  case "call": {
1009
1023
  const cb = asCallbackMethodCall(expr);
1010
1024
  if (cb) {
1011
- const objSupport = checkSupport(cb.object);
1025
+ const objSupport = checkSupport(cb.object, pos);
1012
1026
  if (!objSupport.supported)
1013
1027
  return objSupport;
1014
- const bodySupport = checkSupport(cb.arrow.body);
1028
+ const bodySupport = checkSupport(cb.arrow.body, pos);
1015
1029
  if (!bodySupport.supported) {
1016
1030
  return {
1017
1031
  supported: false,
@@ -1020,13 +1034,13 @@ function checkSupport(expr) {
1020
1034
  };
1021
1035
  }
1022
1036
  for (const rest of cb.args) {
1023
- const restSupport = checkSupport(rest);
1037
+ const restSupport = checkSupport(rest, pos);
1024
1038
  if (!restSupport.supported)
1025
1039
  return restSupport;
1026
1040
  }
1027
1041
  return { supported: true, level: "L5" };
1028
1042
  }
1029
- const calleeSupport = checkSupport(expr.callee);
1043
+ const calleeSupport = checkSupport(expr.callee, pos);
1030
1044
  if (!calleeSupport.supported) {
1031
1045
  return calleeSupport;
1032
1046
  }
@@ -1045,7 +1059,7 @@ function checkSupport(expr) {
1045
1059
  return { supported: true, level: "L1" };
1046
1060
  }
1047
1061
  for (const arg of expr.args) {
1048
- const argSupport = checkSupport(arg);
1062
+ const argSupport = checkSupport(arg, pos);
1049
1063
  if (!argSupport.supported) {
1050
1064
  return argSupport;
1051
1065
  }
@@ -1053,7 +1067,7 @@ function checkSupport(expr) {
1053
1067
  return { supported: true, level: "L2" };
1054
1068
  }
1055
1069
  case "member": {
1056
- const objSupport = checkSupport(expr.object);
1070
+ const objSupport = checkSupport(expr.object, pos);
1057
1071
  if (!objSupport.supported) {
1058
1072
  return objSupport;
1059
1073
  }
@@ -1063,19 +1077,19 @@ function checkSupport(expr) {
1063
1077
  return { supported: true, level: "L2" };
1064
1078
  }
1065
1079
  case "index-access": {
1066
- const objSupport = checkSupport(expr.object);
1080
+ const objSupport = checkSupport(expr.object, pos);
1067
1081
  if (!objSupport.supported)
1068
1082
  return objSupport;
1069
- const indexSupport = checkSupport(expr.index);
1083
+ const indexSupport = checkSupport(expr.index, pos);
1070
1084
  if (!indexSupport.supported)
1071
1085
  return indexSupport;
1072
1086
  return { supported: true, level: "L2" };
1073
1087
  }
1074
1088
  case "binary": {
1075
- const leftSupport = checkSupport(expr.left);
1089
+ const leftSupport = checkSupport(expr.left, pos);
1076
1090
  if (!leftSupport.supported)
1077
1091
  return leftSupport;
1078
- const rightSupport = checkSupport(expr.right);
1092
+ const rightSupport = checkSupport(expr.right, pos);
1079
1093
  if (!rightSupport.supported)
1080
1094
  return rightSupport;
1081
1095
  if (["===", "==", "!==", "!=", ">", "<", ">=", "<="].includes(expr.op)) {
@@ -1087,7 +1101,7 @@ function checkSupport(expr) {
1087
1101
  return { supported: false, reason: `Unknown operator: ${expr.op}` };
1088
1102
  }
1089
1103
  case "unary": {
1090
- const argSupport = checkSupport(expr.argument);
1104
+ const argSupport = checkSupport(expr.argument, pos);
1091
1105
  if (!argSupport.supported)
1092
1106
  return argSupport;
1093
1107
  if (expr.op === "!") {
@@ -1099,25 +1113,25 @@ function checkSupport(expr) {
1099
1113
  return { supported: false, reason: `Unsupported unary operator: ${expr.op}` };
1100
1114
  }
1101
1115
  case "logical": {
1102
- const leftSupport = checkSupport(expr.left);
1116
+ const leftSupport = checkSupport(expr.left, pos);
1103
1117
  if (!leftSupport.supported)
1104
1118
  return leftSupport;
1105
1119
  if (expr.op === "??" && expr.right.kind === "object-literal" && expr.right.properties.length === 0) {
1106
1120
  return { supported: true, level: "L4" };
1107
1121
  }
1108
- const rightSupport = checkSupport(expr.right);
1122
+ const rightSupport = checkSupport(expr.right, pos);
1109
1123
  if (!rightSupport.supported)
1110
1124
  return rightSupport;
1111
1125
  return { supported: true, level: "L4" };
1112
1126
  }
1113
1127
  case "conditional": {
1114
- const testSupport = checkSupport(expr.test);
1128
+ const testSupport = checkSupport(expr.test, pos);
1115
1129
  if (!testSupport.supported)
1116
1130
  return testSupport;
1117
- const consSupport = checkSupport(expr.consequent);
1131
+ const consSupport = checkSupport(expr.consequent, pos);
1118
1132
  if (!consSupport.supported)
1119
1133
  return consSupport;
1120
- const altSupport = checkSupport(expr.alternate);
1134
+ const altSupport = checkSupport(expr.alternate, pos);
1121
1135
  if (!altSupport.supported)
1122
1136
  return altSupport;
1123
1137
  return { supported: true, level: "L4" };
@@ -1125,7 +1139,7 @@ function checkSupport(expr) {
1125
1139
  case "template-literal": {
1126
1140
  for (const part of expr.parts) {
1127
1141
  if (part.type === "expression") {
1128
- const partSupport = checkSupport(part.expr);
1142
+ const partSupport = checkSupport(part.expr, pos);
1129
1143
  if (!partSupport.supported)
1130
1144
  return partSupport;
1131
1145
  }
@@ -1250,7 +1264,7 @@ function isPureInit(e, pureCallNames) {
1250
1264
  case "array-literal":
1251
1265
  return e.elements.every(pure);
1252
1266
  case "object-literal":
1253
- return e.properties.every((p) => pure(p.value));
1267
+ return e.properties.every((p) => pure(p.kind === "spread" ? p.expr : p.value));
1254
1268
  case "call":
1255
1269
  return e.callee.kind === "identifier" && e.args.length === 0 && pureCallNames !== undefined && pureCallNames.has(e.callee.name);
1256
1270
  case "array-method":
@@ -1304,7 +1318,7 @@ function usesPerPath(name, expr) {
1304
1318
  }
1305
1319
  return add(walk(e.object), sum(e.args));
1306
1320
  case "object-literal":
1307
- return sum(e.properties.map((p) => p.value));
1321
+ return sum(e.properties.map((p) => p.kind === "spread" ? p.expr : p.value));
1308
1322
  case "arrow":
1309
1323
  return walk(e.body).max > 0 ? { min: 0, max: Number.POSITIVE_INFINITY } : { min: 0, max: 0 };
1310
1324
  }
@@ -1370,7 +1384,7 @@ function inlineBinding(expr, name, value) {
1370
1384
  case "object-literal":
1371
1385
  return {
1372
1386
  kind: "object-literal",
1373
- properties: e.properties.map((p) => ({ ...p, value: walk(p.value, enclosing) })),
1387
+ properties: e.properties.map((p) => p.kind === "spread" ? { ...p, expr: walk(p.expr, enclosing) } : { ...p, value: walk(p.value, enclosing) }),
1374
1388
  raw: e.raw
1375
1389
  };
1376
1390
  case "literal":
@@ -1566,7 +1580,7 @@ function freeIdentifiers(expr) {
1566
1580
  return true;
1567
1581
  case "object-literal":
1568
1582
  for (const p of e.properties)
1569
- if (!visit(p.value, bound))
1583
+ if (!visit(p.kind === "spread" ? p.expr : p.value, bound))
1570
1584
  return false;
1571
1585
  return true;
1572
1586
  case "arrow": {
@@ -5043,6 +5057,7 @@ var ErrorCodes = {
5043
5057
  MISSING_KEY_IN_LIST: "BF023",
5044
5058
  MISSING_KEY_IN_NESTED_LIST: "BF024",
5045
5059
  UNSUPPORTED_DESTRUCTURE_REST: "BF025",
5060
+ RETURN_VALUE_NOT_JSX: "BF027",
5046
5061
  PROPS_DESTRUCTURING: "BF043",
5047
5062
  SIGNAL_GETTER_NOT_CALLED: "BF044",
5048
5063
  JSX_IN_LOCAL_FUNCTION: "BF045",
@@ -5074,6 +5089,7 @@ var errorMessages = {
5074
5089
  [ErrorCodes.MISSING_KEY_IN_LIST]: "Missing key attribute in list rendering. Add a key prop for efficient updates",
5075
5090
  [ErrorCodes.MISSING_KEY_IN_NESTED_LIST]: "Nested .map() loop requires key attribute for event delegation. Add a key prop to elements in the inner loop",
5076
5091
  [ErrorCodes.UNSUPPORTED_DESTRUCTURE_REST]: "Computed property key in .map() callback destructure is not supported. Rewrite the callback to destructure explicit bindings (e.g., `({ a, b }) => ...`) so the compiler can rewrite references to per-item signal accessors.",
5092
+ [ErrorCodes.RETURN_VALUE_NOT_JSX]: "Component's return value is not recognized as JSX — return the JSX expression directly instead of binding it to a local variable first.",
5077
5093
  [ErrorCodes.PROPS_DESTRUCTURING]: "Props destructuring in function parameters breaks reactivity. Use props object directly.",
5078
5094
  [ErrorCodes.SIGNAL_GETTER_NOT_CALLED]: "Signal/memo getter passed without calling it. Use getter() to read the value.",
5079
5095
  [ErrorCodes.JSX_IN_LOCAL_FUNCTION]: "Local function returns JSX but cannot be inlined. Extract it as a top-level PascalCase component or use a single return statement.",
@@ -5716,6 +5732,14 @@ function visitComponentBody(node, ctx) {
5716
5732
  }
5717
5733
  }
5718
5734
  if (isTopLevel && (ts9.isTryStatement(node) || ts9.isSwitchStatement(node) || ts9.isForStatement(node) || ts9.isForInStatement(node) || ts9.isForOfStatement(node) || ts9.isWhileStatement(node) || ts9.isDoStatement(node) || ts9.isThrowStatement(node) || ts9.isBlock(node) && node.parent === ctx.componentBodyBlock)) {
5735
+ if (ts9.isBlock(node)) {
5736
+ const returnedLocal = findBlockBodyReturnedJsxLocalName(node);
5737
+ if (returnedLocal) {
5738
+ ctx.errors.push(createError(ErrorCodes.RETURN_VALUE_NOT_JSX, getSourceLocation(node, ctx.sourceFile, ctx.filePath), {
5739
+ message: `Component '${ctx.componentName ?? "(unknown)"}' return value is not recognized ` + `as JSX — return the JSX expression directly instead of binding it to a local ` + `variable first (\`return ${returnedLocal}\` after \`const ${returnedLocal} = ` + `<jsx/>\` is not resolved at return position).`
5740
+ }));
5741
+ }
5742
+ }
5719
5743
  collectInitStatement(node, ctx);
5720
5744
  return;
5721
5745
  }
@@ -5751,6 +5775,31 @@ function unwrapJsxTransparent(expr) {
5751
5775
  }
5752
5776
  return current;
5753
5777
  }
5778
+ function findBlockBodyReturnedJsxLocalName(block) {
5779
+ const stmts = block.statements;
5780
+ const last = stmts[stmts.length - 1];
5781
+ if (!last || !ts9.isReturnStatement(last) || !last.expression)
5782
+ return null;
5783
+ const returned = unwrapJsxTransparent(last.expression);
5784
+ if (!ts9.isIdentifier(returned))
5785
+ return null;
5786
+ const name = returned.text;
5787
+ for (const stmt of stmts) {
5788
+ if (!ts9.isVariableStatement(stmt))
5789
+ continue;
5790
+ for (const decl of stmt.declarationList.declarations) {
5791
+ if (!ts9.isIdentifier(decl.name) || decl.name.text !== name || !decl.initializer)
5792
+ continue;
5793
+ let init = decl.initializer;
5794
+ while (ts9.isParenthesizedExpression(init))
5795
+ init = init.expression;
5796
+ if (ts9.isJsxElement(init) || ts9.isJsxSelfClosingElement(init) || ts9.isJsxFragment(init) || initializerShapeContainsJsx(init) || isMapLikeCallWithJsx(init)) {
5797
+ return name;
5798
+ }
5799
+ }
5800
+ }
5801
+ return null;
5802
+ }
5754
5803
  function extractJsxFromExpression(expr) {
5755
5804
  const inner = unwrapJsxTransparent(expr);
5756
5805
  if (ts9.isJsxElement(inner) || ts9.isJsxFragment(inner) || ts9.isJsxSelfClosingElement(inner)) {
@@ -9518,6 +9567,8 @@ function matchToLocaleDateStringCall(callee, args, metadata) {
9518
9567
  let tz = null;
9519
9568
  const probeOptions = {};
9520
9569
  for (const prop of options.properties) {
9570
+ if (prop.kind === "spread")
9571
+ return null;
9521
9572
  if (prop.value.kind !== "literal" || prop.value.literalType !== "string")
9522
9573
  return null;
9523
9574
  const value = String(prop.value.value);
@@ -10090,8 +10141,14 @@ function buildIRRoot(analyzer) {
10090
10141
  }
10091
10142
  ctx.isRoot = false;
10092
10143
  const ir = transformJsxExpression(jsxReturn, ctx);
10093
- if (ir === null)
10144
+ if (ir === null) {
10145
+ if (ts13.isIdentifier(jsxReturn) && (analyzer.jsxConstants.has(jsxReturn.text) || analyzer.inlineableJsxConsts.has(jsxReturn.text))) {
10146
+ analyzer.errors.push(createError(ErrorCodes.RETURN_VALUE_NOT_JSX, getSourceLocation(jsxReturn, analyzer.sourceFile, analyzer.filePath), {
10147
+ message: `Component '${analyzer.componentName ?? "(unknown)"}' return value is not recognized ` + `as JSX — return the JSX expression directly instead of binding it to a local variable ` + `first (\`return ${jsxReturn.text}\` after \`const ${jsxReturn.text} = <jsx/>\` is not ` + `resolved at return position).`
10148
+ }));
10149
+ }
10094
10150
  return null;
10151
+ }
10095
10152
  return wrapInScopeElement(ir);
10096
10153
  }
10097
10154
  function needsScopeWrapper(ir) {
@@ -11282,7 +11339,10 @@ function resolveCallbackMethodFunctionReferences(expr, analyzer, bound = EMPTY_B
11282
11339
  ...e.method === "flat" && e.depthExpr ? { depthExpr: visit2(e.depthExpr, bound2) } : {}
11283
11340
  };
11284
11341
  case "object-literal":
11285
- return { ...e, properties: e.properties.map((p) => ({ ...p, value: visit2(p.value, bound2) })) };
11342
+ return {
11343
+ ...e,
11344
+ properties: e.properties.map((p) => p.kind === "spread" ? { ...p, expr: visit2(p.expr, bound2) } : { ...p, value: visit2(p.value, bound2) })
11345
+ };
11286
11346
  case "arrow": {
11287
11347
  const inner = e.params.length === 0 ? bound2 : new Set([...bound2, ...e.params]);
11288
11348
  return { ...e, body: visit2(e.body, inner) };
@@ -13203,6 +13263,35 @@ function getStringValue(node) {
13203
13263
  }
13204
13264
  return null;
13205
13265
  }
13266
+ function unwrapTransparentTsWrappers(node) {
13267
+ let n = node;
13268
+ while (ts13.isParenthesizedExpression(n) || ts13.isAsExpression(n) || ts13.isSatisfiesExpression(n) || ts13.isNonNullExpression(n)) {
13269
+ n = n.expression;
13270
+ }
13271
+ return n;
13272
+ }
13273
+ function expressionWrapsJsx(node) {
13274
+ const n = unwrapTransparentTsWrappers(node);
13275
+ if (ts13.isJsxElement(n) || ts13.isJsxSelfClosingElement(n) || ts13.isJsxFragment(n))
13276
+ return true;
13277
+ if (ts13.isConditionalExpression(n)) {
13278
+ return expressionWrapsJsx(n.whenTrue) || expressionWrapsJsx(n.whenFalse);
13279
+ }
13280
+ if (ts13.isArrayLiteralExpression(n)) {
13281
+ return n.elements.some((el) => expressionWrapsJsx(ts13.isSpreadElement(el) ? el.expression : el));
13282
+ }
13283
+ return false;
13284
+ }
13285
+ function reportNakedJsxWrapperProp(ctx, attr, propName, jsxExpr) {
13286
+ const shape = ts13.isConditionalExpression(jsxExpr) ? "a ternary" : "an array literal";
13287
+ ctx.analyzer.errors.push(createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, getSourceLocation(attr, ctx.sourceFile, ctx.filePath), {
13288
+ message: `Prop '${propName}' is ${shape} wrapping JSX (${jsxExpr.getText(ctx.sourceFile)}). ` + `This shape is not compiled — only a JSX element/fragment given DIRECTLY as the prop value is.`,
13289
+ suggestion: {
13290
+ message: `Move the conditional/array out of the prop position: compute it in a local ` + `const and pass it as the component's children instead of a named prop ` + `(e.g. const ${propName} = ${jsxExpr.getText(ctx.sourceFile)}; <Comp>{${propName}}</Comp>). ` + `Wrapping the ternary/array in a fragment at the prop position ` + `(${propName}={<>{${jsxExpr.getText(ctx.sourceFile)}}</>}) is NOT a safe escape here: it compiles, ` + `but the child's own reactive prop getter receives the branch's HTML unbranded and re-escapes it as ` + `text on the child's very next reactive run, corrupting the DOM (a narrower gap #2651's door ` + `inventory left open — tracked separately).`,
13291
+ escape: [{ kind: "rewrite" }]
13292
+ }
13293
+ }));
13294
+ }
13206
13295
  function processComponentProps(attributes, ctx) {
13207
13296
  const props = [];
13208
13297
  for (const attr of attributes.properties) {
@@ -13214,10 +13303,7 @@ function processComponentProps(attributes, ctx) {
13214
13303
  continue;
13215
13304
  const name = attr.name.getText(ctx.sourceFile);
13216
13305
  if (attr.initializer && ts13.isJsxExpression(attr.initializer) && attr.initializer.expression) {
13217
- let jsxExpr = attr.initializer.expression;
13218
- while (ts13.isParenthesizedExpression(jsxExpr)) {
13219
- jsxExpr = jsxExpr.expression;
13220
- }
13306
+ const jsxExpr = unwrapTransparentTsWrappers(attr.initializer.expression);
13221
13307
  if (ts13.isJsxElement(jsxExpr) || ts13.isJsxSelfClosingElement(jsxExpr) || ts13.isJsxFragment(jsxExpr)) {
13222
13308
  const prevInsideComponentChildren = ctx.insideComponentChildren;
13223
13309
  ctx.insideComponentChildren = true;
@@ -13232,6 +13318,10 @@ function processComponentProps(attributes, ctx) {
13232
13318
  continue;
13233
13319
  }
13234
13320
  }
13321
+ if ((ts13.isConditionalExpression(jsxExpr) || ts13.isArrayLiteralExpression(jsxExpr)) && expressionWrapsJsx(jsxExpr)) {
13322
+ reportNakedJsxWrapperProp(ctx, attr, name, jsxExpr);
13323
+ continue;
13324
+ }
13235
13325
  }
13236
13326
  let value = getAttributeValue(attr, ctx);
13237
13327
  if (value.kind === "template") {
@@ -14313,10 +14403,11 @@ function collectInnerLoops(nodes, siblingOffsets, outerLoopParam, ctx, options)
14313
14403
  const innerPreambleNames = preambleNamesOf(n);
14314
14404
  if (ctx) {
14315
14405
  for (const child of n.children) {
14316
- bindings.reactiveTexts.push(...collectLoopChildReactiveTexts(child, ctx, n.param, n.paramBindings, false, innerPreambleNames, n.index));
14317
- bindings.reactiveAttrs.push(...collectLoopChildReactiveAttrs(child, ctx, n.param, n.paramBindings, false, innerPreambleNames, n.index));
14406
+ bindings.reactiveTexts.push(...collectLoopChildReactiveTexts(child, ctx, n.param, n.paramBindings, true, innerPreambleNames, n.index));
14407
+ bindings.reactiveAttrs.push(...collectLoopChildReactiveAttrs(child, ctx, n.param, n.paramBindings, true, innerPreambleNames, n.index));
14318
14408
  bindings.refs.push(...collectLoopChildRefs(child));
14319
14409
  }
14410
+ bindings.conditionals.push(...collectLoopChildConditionals({ type: "fragment", children: n.children, loc: n.loc }, ctx, siblingOffsets, n.param, n.paramBindings, innerPreambleNames, n.index));
14320
14411
  }
14321
14412
  let childComponents;
14322
14413
  if (collectBindings) {
@@ -14340,9 +14431,6 @@ function collectInnerLoops(nodes, siblingOffsets, outerLoopParam, ctx, options)
14340
14431
  for (const child of n.children) {
14341
14432
  bindings.events.push(...collectLoopChildEventsWithNesting(child));
14342
14433
  }
14343
- if (ctx) {
14344
- bindings.conditionals.push(...collectLoopChildConditionals({ type: "fragment", children: n.children, loc: n.loc }, ctx, siblingOffsets, n.param, n.paramBindings, innerPreambleNames, n.index));
14345
- }
14346
14434
  }
14347
14435
  result.push({
14348
14436
  kind: "nested",
@@ -15478,6 +15566,7 @@ var RUNTIME_IMPORT_CANDIDATES = [
15478
15566
  "mapArrayLazy",
15479
15567
  "patchLeaf",
15480
15568
  "createDisposableEffect",
15569
+ "findCondContainer",
15481
15570
  "createComponent",
15482
15571
  "renderChild",
15483
15572
  "registerComponent",
@@ -17335,7 +17424,8 @@ function emitProviderAndChildInits(lines, ctx) {
17335
17424
  lines.push(` upsertChild(__scope, '${registryName}', '${child.slotId}', ${child.propsExpr})`);
17336
17425
  continue;
17337
17426
  }
17338
- const scopeRef = child.slotId ? `_${varSlotId(child.slotId)}` : "__scope";
17427
+ const isCommentRoot = child.slotId !== null && child.slotId === ctx.commentScopeRootSlotId;
17428
+ const scopeRef = !child.slotId || isCommentRoot ? "__scope" : `_${varSlotId(child.slotId)}`;
17339
17429
  lines.push(` initChild('${registryName}', ${scopeRef}, ${child.propsExpr})`);
17340
17430
  }
17341
17431
  }
@@ -17985,6 +18075,7 @@ function buildBranchInnerLoopsPlan(args) {
17985
18075
  const {
17986
18076
  innerLoops,
17987
18077
  scopeVar,
18078
+ condSlotId,
17988
18079
  outerLoopParam,
17989
18080
  outerLoopParamBindings,
17990
18081
  wrapOuter
@@ -17999,7 +18090,7 @@ function buildBranchInnerLoopsPlan(args) {
17999
18090
  const wrapInner = (expr) => wrapLoopParamAsAccessor(expr, inner.param, inner.paramBindings);
18000
18091
  const wrapBoth = (expr) => wrapLoopParamAsAccessor(wrapOuter(expr), inner.param, inner.paramBindings);
18001
18092
  const csl = inner.containerSlotId;
18002
- const containerExpr = csl ? `(${scopeVar}.querySelector('[bf="${csl}"]') ?? ${scopeVar}.querySelector(\`[${BF_HOST}="\${__scopeId}"][${BF_AT}="${csl}"]\`) ?? ${scopeVar})` : scopeVar;
18093
+ const containerExpr = csl ? `(${scopeVar}.querySelector('[bf="${csl}"]') ?? ${scopeVar}.querySelector(\`[${BF_HOST}="\${__scopeId}"][${BF_AT}="${csl}"]\`) ?? ${scopeVar})` : `findCondContainer(${scopeVar}, '${condSlotId}')`;
18003
18094
  const { head: paramHead, unwrap: paramUnwrap } = destructureLoopParam(inner.param, inner.paramBindings);
18004
18095
  const wrappedKey = inner.key ? wrapLoopParamAsAccessor(inner.key, inner.param, inner.paramBindings) : null;
18005
18096
  const wrapIRNode = (node) => {
@@ -18083,13 +18174,15 @@ function buildLoopChildConditionalsPlan(args) {
18083
18174
  branch: cond.whenTrue,
18084
18175
  wrap,
18085
18176
  loopParam,
18086
- loopParamBindings
18177
+ loopParamBindings,
18178
+ condId: cond.slotId
18087
18179
  }),
18088
18180
  whenFalseArm: buildLoopChildArmPlan({
18089
18181
  branch: cond.whenFalse,
18090
18182
  wrap,
18091
18183
  loopParam,
18092
- loopParamBindings
18184
+ loopParamBindings,
18185
+ condId: cond.slotId
18093
18186
  })
18094
18187
  });
18095
18188
  }
@@ -18129,7 +18222,7 @@ function buildArmTextsPlan(texts, wrap) {
18129
18222
  }));
18130
18223
  }
18131
18224
  function buildLoopChildArmPlan(args) {
18132
- const { branch, wrap, loopParam, loopParamBindings } = args;
18225
+ const { branch, wrap, loopParam, loopParamBindings, condId } = args;
18133
18226
  return {
18134
18227
  events: buildBranchEventBindingsPlan({
18135
18228
  events: branch.events,
@@ -18142,6 +18235,7 @@ function buildLoopChildArmPlan(args) {
18142
18235
  innerLoops: buildBranchInnerLoopsPlan({
18143
18236
  innerLoops: branch.innerLoops,
18144
18237
  scopeVar: "__branchScope",
18238
+ condSlotId: condId,
18145
18239
  outerLoopParam: loopParam,
18146
18240
  outerLoopParamBindings: loopParamBindings,
18147
18241
  wrapOuter: wrap
@@ -18195,8 +18289,8 @@ function buildReactiveEffectsPlan(args) {
18195
18289
  wrappedCondition: wrap(cond.condition),
18196
18290
  whenTrueTemplateHtml: addCondAttrToTemplate(wrap(cond.whenTrueHtml), cond.slotId),
18197
18291
  whenFalseTemplateHtml: addCondAttrToTemplate(wrap(cond.whenFalseHtml), cond.slotId),
18198
- whenTrueArm: buildOuterArm(cond.whenTrue, wrap, loopParam, loopParamBindings, profileComponentName),
18199
- whenFalseArm: buildOuterArm(cond.whenFalse, wrap, loopParam, loopParamBindings, profileComponentName),
18292
+ whenTrueArm: buildOuterArm(cond.whenTrue, wrap, loopParam, loopParamBindings, cond.slotId, profileComponentName),
18293
+ whenFalseArm: buildOuterArm(cond.whenFalse, wrap, loopParam, loopParamBindings, cond.slotId, profileComponentName),
18200
18294
  ...cond.readsPreamble && { readsPreamble: true }
18201
18295
  });
18202
18296
  }
@@ -18208,7 +18302,7 @@ function buildReactiveEffectsPlan(args) {
18208
18302
  profileComponentName
18209
18303
  };
18210
18304
  }
18211
- function buildOuterArm(branch, wrap, loopParam, loopParamBindings, profileComponentName) {
18305
+ function buildOuterArm(branch, wrap, loopParam, loopParamBindings, condSlotId, profileComponentName) {
18212
18306
  return {
18213
18307
  events: buildBranchEventBindingsPlan({
18214
18308
  events: branch.events,
@@ -18222,6 +18316,7 @@ function buildOuterArm(branch, wrap, loopParam, loopParamBindings, profileCompon
18222
18316
  innerLoops: buildBranchInnerLoopsPlan({
18223
18317
  innerLoops: branch.innerLoops,
18224
18318
  scopeVar: "__branchScope",
18319
+ condSlotId,
18225
18320
  outerLoopParam: loopParam,
18226
18321
  outerLoopParamBindings: loopParamBindings,
18227
18322
  wrapOuter: wrap
@@ -18323,6 +18418,7 @@ function buildInnerLoopsPlan(args) {
18323
18418
  }
18324
18419
  function buildReactiveEmit(inner, level, wrapOuter, uidSuffix, outerLoopParam, outerLoopParamBindings) {
18325
18420
  const wrapInner = (expr) => wrapLoopParamAsAccessor(expr, inner.param, inner.paramBindings);
18421
+ const wrapBoth = (expr) => wrapLoopParamAsAccessor(wrapOuter(expr), inner.param, inner.paramBindings);
18326
18422
  const { head: paramHead, unwrap: paramUnwrap } = destructureLoopParam(inner.param, inner.paramBindings);
18327
18423
  const wrappedKey = inner.key ? wrapLoopParamAsAccessor(inner.key, inner.param, inner.paramBindings) : null;
18328
18424
  const wrapIRNode = (node) => {
@@ -18384,6 +18480,13 @@ function buildReactiveEmit(inner, level, wrapOuter, uidSuffix, outerLoopParam, o
18384
18480
  }));
18385
18481
  }
18386
18482
  const childRefs = buildChildRefBindings(inner.bindings.refs, inner.param, inner.paramBindings);
18483
+ const conditionals = buildLoopChildConditionalsPlan({
18484
+ conditionals: inner.bindings.conditionals,
18485
+ scopeVar: `__innerEl${uidSuffix}`,
18486
+ wrap: wrapBoth,
18487
+ loopParam: inner.param,
18488
+ loopParamBindings: inner.paramBindings
18489
+ });
18387
18490
  return {
18388
18491
  mode: "reactive",
18389
18492
  keyFn: loopKeyFn(inner),
@@ -18396,6 +18499,7 @@ function buildReactiveEmit(inner, level, wrapOuter, uidSuffix, outerLoopParam, o
18396
18499
  events,
18397
18500
  reactiveTexts,
18398
18501
  reactiveAttrs,
18502
+ conditionals,
18399
18503
  childRefs
18400
18504
  };
18401
18505
  }
@@ -19348,6 +19452,17 @@ function bindingIdArg(ctx, slotId) {
19348
19452
  return "";
19349
19453
  return `, ${JSON.stringify(`${ctx.componentName}#binding:${slotId}`)}`;
19350
19454
  }
19455
+ function emitValueUpdateStatements(target, expression) {
19456
+ return [
19457
+ `const __val = String(${expression})`,
19458
+ `if ('value' in ${target}) { if (${target}.value !== __val) ${target}.value = __val } else { ${target}.setAttribute('value', __val) }`
19459
+ ];
19460
+ }
19461
+ function emitChildValueMirrorStatements(target, expression) {
19462
+ return [
19463
+ `if ('value' in ${target}) { const __val = String(${expression}); if (${target}.value !== __val) ${target}.value = __val }`
19464
+ ];
19465
+ }
19351
19466
  function emitAttrUpdate(target, attrName, expression, meta) {
19352
19467
  const htmlName = toHTMLAttrName(attrName);
19353
19468
  if (attrName === "dangerouslySetInnerHTML" || htmlName === "dangerouslySetInnerHTML") {
@@ -19366,10 +19481,7 @@ function emitAttrUpdate(target, attrName, expression, meta) {
19366
19481
  ];
19367
19482
  }
19368
19483
  if (htmlName === "value") {
19369
- return [
19370
- `const __val = String(${expression})`,
19371
- `if (${target}.value !== __val) ${target}.value = __val`
19372
- ];
19484
+ return emitValueUpdateStatements(target, expression);
19373
19485
  }
19374
19486
  if (isBooleanAttr(htmlName)) {
19375
19487
  return [`${target}.${htmlName} = !!(${expression})`];
@@ -19617,30 +19729,30 @@ function emitReactivePropBindings(lines, ctx) {
19617
19729
  propsBySlot.get(prop.slotId).push(prop);
19618
19730
  }
19619
19731
  for (const [slotId, props] of propsBySlot) {
19620
- const v = varSlotId(slotId);
19621
- lines.push(` if (_${v}) {`);
19732
+ const ref = slotId === ctx.commentScopeRootSlotId ? "__scope" : `_${varSlotId(slotId)}`;
19733
+ lines.push(` if (${ref}) {`);
19622
19734
  for (const prop of props) {
19623
19735
  const value = `${prop.expression}()`;
19624
19736
  if (prop.propName === "selected") {
19625
19737
  if (prop.componentName === "TabsContent") {
19626
- lines.push(` _${v}.setAttribute('data-state', ${value} ? 'active' : 'inactive')`);
19738
+ lines.push(` ${ref}.setAttribute('data-state', ${value} ? 'active' : 'inactive')`);
19627
19739
  lines.push(` if (${value}) {`);
19628
- lines.push(` _${v}.classList.remove('hidden')`);
19740
+ lines.push(` ${ref}.classList.remove('hidden')`);
19629
19741
  lines.push(` } else {`);
19630
- lines.push(` _${v}.classList.add('hidden')`);
19742
+ lines.push(` ${ref}.classList.add('hidden')`);
19631
19743
  lines.push(` }`);
19632
19744
  } else {
19633
- lines.push(` _${v}.setAttribute('aria-selected', String(${value}))`);
19634
- lines.push(` _${v}.setAttribute('data-state', ${value} ? 'active' : 'inactive')`);
19635
- lines.push(` _${v}.setAttribute('tabindex', ${value} ? '0' : '-1')`);
19745
+ lines.push(` ${ref}.setAttribute('aria-selected', String(${value}))`);
19746
+ lines.push(` ${ref}.setAttribute('data-state', ${value} ? 'active' : 'inactive')`);
19747
+ lines.push(` ${ref}.setAttribute('tabindex', ${value} ? '0' : '-1')`);
19636
19748
  }
19637
19749
  } else if (prop.propName === "value") {
19638
- lines.push(` const __val = String(${value})`);
19639
- lines.push(` if (_${v}.value !== __val) _${v}.value = __val`);
19750
+ for (const stmt of emitChildValueMirrorStatements(ref, value))
19751
+ lines.push(` ${stmt}`);
19640
19752
  } else if (isBooleanAttr(prop.propName)) {
19641
- lines.push(` _${v}.${prop.propName} = !!(${value})`);
19753
+ lines.push(` ${ref}.${prop.propName} = !!(${value})`);
19642
19754
  } else {
19643
- lines.push(` _${v}.setAttribute('${prop.propName}', String(${value}))`);
19755
+ lines.push(` ${ref}.setAttribute('${prop.propName}', String(${value}))`);
19644
19756
  }
19645
19757
  }
19646
19758
  lines.push(` }`);
@@ -19663,13 +19775,17 @@ function emitReactiveChildProps(lines, ctx) {
19663
19775
  }
19664
19776
  for (const [, props] of propsByComponent) {
19665
19777
  const first = props[0];
19778
+ const isCommentRoot = first.slotId !== null && first.slotId === ctx.commentScopeRootSlotId;
19666
19779
  const varSuffix = first.slotId ? varSlotId(first.slotId).replace(/-/g, "_") : first.componentName;
19667
- const varName = `__${first.componentName}_${varSuffix}El`;
19668
- const selectorArg = first.slotId ? first.slotId : first.componentName;
19669
- lines.push(` const [${varName}] = $c(__scope, '${selectorArg}')`);
19780
+ const varName = isCommentRoot ? "__scope" : `__${first.componentName}_${varSuffix}El`;
19781
+ if (!isCommentRoot) {
19782
+ const selectorArg = first.slotId ? first.slotId : first.componentName;
19783
+ lines.push(` const [${varName}] = $c(__scope, '${selectorArg}')`);
19784
+ }
19670
19785
  lines.push(` if (${varName}) {`);
19671
19786
  for (const prop of props) {
19672
- for (const stmt of emitAttrUpdate(varName, prop.attrName, prop.expression, prop)) {
19787
+ const stmts = toHTMLAttrName(prop.attrName) === "value" ? emitChildValueMirrorStatements(varName, prop.expression) : emitAttrUpdate(varName, prop.attrName, prop.expression, prop);
19788
+ for (const stmt of stmts) {
19673
19789
  lines.push(` ${stmt}`);
19674
19790
  }
19675
19791
  }
@@ -20230,8 +20346,9 @@ function seedDiffersExpr(target, a) {
20230
20346
  return `${target}.getAttribute('style') !== styleToCss(__x)`;
20231
20347
  if (html === "class")
20232
20348
  return `${target}.getAttribute('class') !== (__x != null ? String(__x) : null)`;
20233
- if (html === "value")
20234
- return `${target}.value !== String(__x)`;
20349
+ if (html === "value") {
20350
+ return `('value' in ${target} ? ${target}.value !== String(__x) : ${target}.getAttribute('value') !== String(__x))`;
20351
+ }
20235
20352
  if (isBooleanAttr(html))
20236
20353
  return `${target}.${html} !== !!(__x)`;
20237
20354
  if (a.meta.presenceOrUndefined) {
@@ -20589,6 +20706,9 @@ function emitReactive(lines, inner, indent, pc) {
20589
20706
  }
20590
20707
  lines.push(`${indent} }${profileBindingId(pc, attr.slotId)}) }`);
20591
20708
  }
20709
+ if (emit.conditionals.length > 0) {
20710
+ stringifyLoopChildConditionals(lines, emit.conditionals, `${indent} `, pc);
20711
+ }
20592
20712
  emitLoopChildRefs(lines, emit.childRefs, {
20593
20713
  indent: `${indent} `,
20594
20714
  elVar: `__innerEl${uid}`,
@@ -21352,6 +21472,9 @@ function generateElementRefs(ctx) {
21352
21472
  for (const slotId of componentSlots) {
21353
21473
  regularSlots.delete(slotId);
21354
21474
  }
21475
+ if (ctx.commentScopeRootSlotId) {
21476
+ componentSlots.delete(ctx.commentScopeRootSlotId);
21477
+ }
21355
21478
  if (regularSlots.size === 0 && componentSlots.size === 0)
21356
21479
  return "";
21357
21480
  const refLines = [];
@@ -21837,6 +21960,7 @@ function createContext(ir, scope, adapterCapabilities, profile) {
21837
21960
  refElements: [],
21838
21961
  childInits: [],
21839
21962
  deferredChildSlots: new Set,
21963
+ commentScopeRootSlotId: ir.root.type === "component" ? ir.root.slotId : null,
21840
21964
  reactiveProps: [],
21841
21965
  reactiveChildProps: [],
21842
21966
  reactiveAttrs: [],
@@ -22819,12 +22943,38 @@ function evalNode(node, ctx) {
22819
22943
  const baseResult = evalNode(node.expression, ctx);
22820
22944
  if (baseResult === undefined)
22821
22945
  return;
22822
- return UNRESOLVED;
22946
+ if (baseResult === UNRESOLVED || baseResult === null || typeof baseResult !== "object") {
22947
+ return UNRESOLVED;
22948
+ }
22949
+ const proto = Object.getPrototypeOf(baseResult);
22950
+ if (proto !== Object.prototype && proto !== null)
22951
+ return UNRESOLVED;
22952
+ const key = node.name.text;
22953
+ return Object.prototype.hasOwnProperty.call(baseResult, key) ? baseResult[key] : undefined;
22823
22954
  }
22824
22955
  if (ts22.isCallExpression(node)) {
22825
22956
  if (node.arguments.length === 0 && ts22.isIdentifier(node.expression) && node.expression.text in ctx.bindings) {
22826
22957
  return ctx.bindings[node.expression.text];
22827
22958
  }
22959
+ if (ts22.isPropertyAccessExpression(node.expression) && node.expression.name.text === "map" && node.arguments.length === 1) {
22960
+ const arrow = node.arguments[0];
22961
+ if (ts22.isArrowFunction(arrow) && arrow.parameters.length === 1 && ts22.isIdentifier(arrow.parameters[0].name) && !ts22.isBlock(arrow.body)) {
22962
+ const recv = evalNode(node.expression.expression, ctx);
22963
+ if (Array.isArray(recv)) {
22964
+ const paramName = arrow.parameters[0].name.text;
22965
+ const mapped = [];
22966
+ for (const item of recv) {
22967
+ const localBindings = { ...ctx.bindings, [paramName]: item };
22968
+ const v = evalNode(arrow.body, { ...ctx, bindings: localBindings });
22969
+ if (v === UNRESOLVED)
22970
+ return UNRESOLVED;
22971
+ mapped.push(v === undefined ? null : v);
22972
+ }
22973
+ return mapped;
22974
+ }
22975
+ }
22976
+ return UNRESOLVED;
22977
+ }
22828
22978
  if (ts22.isPropertyAccessExpression(node.expression) && node.expression.name.text === "join") {
22829
22979
  const recv = evalNode(node.expression.expression, ctx);
22830
22980
  if (Array.isArray(recv)) {
@@ -23011,7 +23161,7 @@ function evalStringArrayJoin(source) {
23011
23161
 
23012
23162
  // ../jsx/src/ssr-seed-plan.ts
23013
23163
  function classify2(name, origin, expr, parsed, available) {
23014
- if (!isSupported(parsed).supported)
23164
+ if (!isSupportedValue(parsed).supported)
23015
23165
  return { kind: "opaque", name, origin };
23016
23166
  const frees = freeIdentifiers(parsed);
23017
23167
  if (frees === null)
@@ -23074,7 +23224,7 @@ function computeSsrSeedPlan(metadata) {
23074
23224
  }
23075
23225
  }
23076
23226
  const expr = signal.initialValue.trim();
23077
- steps.push(expr === "" ? { kind: "opaque", name: signal.getter, origin: "signal" } : classify2(signal.getter, "signal", expr, resolveThroughLocalConsts(parseExpression(expr), localConsts), available));
23227
+ steps.push(expr === "" ? { kind: "opaque", name: signal.getter, origin: "signal" } : classify2(signal.getter, "signal", expr, resolveThroughLocalConsts(signal.parsed ?? parseExpression(`(${expr})`), localConsts), available));
23078
23228
  available.add(signal.getter);
23079
23229
  }
23080
23230
  for (const memo of metadata.memos) {
@@ -23238,7 +23388,7 @@ function checkExpr(expr, loc, meta, bindings, matchers, errors, seen) {
23238
23388
  break;
23239
23389
  case "object-literal":
23240
23390
  for (const prop of expr.properties)
23241
- recurse(prop.value);
23391
+ recurse(prop.kind === "spread" ? prop.expr : prop.value);
23242
23392
  break;
23243
23393
  case "array-method":
23244
23394
  recurse(expr.object);
@@ -24569,6 +24719,8 @@ function matchQueryHrefCall(callee, args, localNames) {
24569
24719
  return null;
24570
24720
  const triples = [];
24571
24721
  for (const p of obj.properties) {
24722
+ if (p.kind === "spread")
24723
+ return null;
24572
24724
  const v = p.value;
24573
24725
  if (v.kind === "conditional" && isOmitBranch(v.alternate)) {
24574
24726
  triples.push({ guard: v.test, key: p.key, value: v.consequent });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@barefootjs/vite",
3
- "version": "0.32.0",
3
+ "version": "0.33.1",
4
4
  "description": "Vite plugin for BarefootJS: Vite/Rollup owns bundling, hashing, chunking, tree-shaking and minification of client assets, BarefootJS keeps only the JSX to (template, client JS) compile",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -38,17 +38,17 @@
38
38
  "directory": "packages/vite"
39
39
  },
40
40
  "dependencies": {
41
- "@barefootjs/shared": "0.32.0"
41
+ "@barefootjs/shared": "0.33.1"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "@barefootjs/jsx": ">=0.2.0",
45
45
  "vite": "^6.0.0"
46
46
  },
47
47
  "devDependencies": {
48
- "@barefootjs/client": "0.32.0",
49
- "@barefootjs/go-template": "0.32.0",
50
- "@barefootjs/hono": "0.32.0",
51
- "@barefootjs/jsx": "0.32.0",
48
+ "@barefootjs/client": "0.33.1",
49
+ "@barefootjs/go-template": "0.33.1",
50
+ "@barefootjs/hono": "0.33.1",
51
+ "@barefootjs/jsx": "0.33.1",
52
52
  "typescript": "^5.0.0",
53
53
  "vite": "^6.0.0"
54
54
  }