@barefootjs/jsx 0.6.0 → 0.6.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.
- package/dist/adapters/parsed-expr-emitter.d.ts +14 -1
- package/dist/adapters/parsed-expr-emitter.d.ts.map +1 -1
- package/dist/expression-parser.d.ts +137 -0
- package/dist/expression-parser.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +296 -4
- package/dist/ir-to-client-js/collect-elements.d.ts.map +1 -1
- package/dist/ir-to-client-js/plan/build-static-array-child-init.d.ts +4 -0
- package/dist/ir-to-client-js/plan/build-static-array-child-init.d.ts.map +1 -1
- package/dist/ir-to-client-js/plan/static-array-child-init.d.ts +46 -2
- package/dist/ir-to-client-js/plan/static-array-child-init.d.ts.map +1 -1
- package/dist/ir-to-client-js/stringify/static-array-child-init.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/child-components-in-map.test.ts +84 -0
- package/src/__tests__/expression-parser.test.ts +109 -1
- package/src/__tests__/foreach-client-only.test.ts +80 -0
- package/src/__tests__/ir-reduce-op.test.ts +51 -0
- package/src/__tests__/reduce-op.test.ts +201 -0
- package/src/adapters/parsed-expr-emitter.ts +43 -1
- package/src/expression-parser.ts +570 -4
- package/src/index.ts +1 -1
- package/src/ir-to-client-js/collect-elements.ts +9 -3
- package/src/ir-to-client-js/plan/build-static-array-child-init.ts +55 -1
- package/src/ir-to-client-js/plan/static-array-child-init.ts +47 -1
- package/src/ir-to-client-js/stringify/static-array-child-init.ts +69 -0
package/dist/index.js
CHANGED
|
@@ -5339,7 +5339,6 @@ var UNSUPPORTED_METHODS = new Set([
|
|
|
5339
5339
|
"some",
|
|
5340
5340
|
"forEach",
|
|
5341
5341
|
"flatMap",
|
|
5342
|
-
"flat",
|
|
5343
5342
|
"replaceAll",
|
|
5344
5343
|
"charAt",
|
|
5345
5344
|
"charCodeAt",
|
|
@@ -5351,6 +5350,9 @@ var UNSUPPORTED_METHODS = new Set([
|
|
|
5351
5350
|
"matchAll",
|
|
5352
5351
|
"search"
|
|
5353
5352
|
]);
|
|
5353
|
+
var UNSUPPORTED_METHOD_REASONS = {
|
|
5354
|
+
forEach: `'.forEach()' returns undefined and has no template-position meaning. ` + `Use it for side effects inside an event handler or createEffect callback ` + `(client JS), or use '.map(...)' if you meant to render each item.`
|
|
5355
|
+
};
|
|
5354
5356
|
var LOWERED_ARRAY_METHODS = new Set([
|
|
5355
5357
|
"includes",
|
|
5356
5358
|
"indexOf",
|
|
@@ -5438,6 +5440,32 @@ function convertNode(node, raw) {
|
|
|
5438
5440
|
if (callee.property === "reverse" || callee.property === "toReversed") {
|
|
5439
5441
|
return { kind: "array-method", method: callee.property, object: callee.object, args };
|
|
5440
5442
|
}
|
|
5443
|
+
if (callee.property === "flat") {
|
|
5444
|
+
const depthNode = node.arguments[0];
|
|
5445
|
+
let flatDepth;
|
|
5446
|
+
if (depthNode === undefined) {
|
|
5447
|
+
flatDepth = 1;
|
|
5448
|
+
} else if (ts8.isIdentifier(depthNode) && depthNode.text === "Infinity") {
|
|
5449
|
+
flatDepth = "infinity";
|
|
5450
|
+
} else {
|
|
5451
|
+
let n;
|
|
5452
|
+
if (ts8.isNumericLiteral(depthNode)) {
|
|
5453
|
+
n = Number(depthNode.text);
|
|
5454
|
+
} else if (ts8.isPrefixUnaryExpression(depthNode) && depthNode.operator === ts8.SyntaxKind.MinusToken && ts8.isNumericLiteral(depthNode.operand)) {
|
|
5455
|
+
n = -Number(depthNode.operand.text);
|
|
5456
|
+
}
|
|
5457
|
+
if (n === undefined || Number.isNaN(n)) {
|
|
5458
|
+
return {
|
|
5459
|
+
kind: "unsupported",
|
|
5460
|
+
raw,
|
|
5461
|
+
reason: `\`.flat(depth)\` needs a literal integer or \`Infinity\` depth — a computed depth can't be resolved at template time. Use a literal depth, or pre-compute the value before the template.`
|
|
5462
|
+
};
|
|
5463
|
+
}
|
|
5464
|
+
const truncated = Math.trunc(n);
|
|
5465
|
+
flatDepth = truncated < 0 ? 0 : truncated;
|
|
5466
|
+
}
|
|
5467
|
+
return { kind: "array-method", method: "flat", object: callee.object, args: [], flatDepth };
|
|
5468
|
+
}
|
|
5441
5469
|
if (callee.property === "toLowerCase") {
|
|
5442
5470
|
return { kind: "array-method", method: "toLowerCase", object: callee.object, args };
|
|
5443
5471
|
}
|
|
@@ -5521,6 +5549,50 @@ function convertNode(node, raw) {
|
|
|
5521
5549
|
` + `(reverse the operands for descending order). ` + `Wrap the call in /* @client */ to evaluate at hydration.`
|
|
5522
5550
|
};
|
|
5523
5551
|
}
|
|
5552
|
+
if ((callee.property === "reduce" || callee.property === "reduceRight") && node.arguments.length === 2) {
|
|
5553
|
+
const reduceOp = extractReduceOpFromTS(node.arguments[0], node.arguments[1]);
|
|
5554
|
+
if (reduceOp) {
|
|
5555
|
+
return {
|
|
5556
|
+
kind: "array-method",
|
|
5557
|
+
method: callee.property,
|
|
5558
|
+
object: callee.object,
|
|
5559
|
+
args: [],
|
|
5560
|
+
reduceOp
|
|
5561
|
+
};
|
|
5562
|
+
}
|
|
5563
|
+
const m = callee.property;
|
|
5564
|
+
return {
|
|
5565
|
+
kind: "unsupported",
|
|
5566
|
+
raw,
|
|
5567
|
+
reason: `Reduce shape not supported. Accepted (arithmetic fold, explicit init):
|
|
5568
|
+
` + ` arr.${m}((acc, x) => acc + x, 0)
|
|
5569
|
+
` + ` arr.${m}((acc, x) => acc + x.field, 0)
|
|
5570
|
+
` + ` arr.${m}((acc, x) => acc * x.field, 1)
|
|
5571
|
+
` + ` arr.${m}((acc, x) => acc + x.field, '') (string concat)
|
|
5572
|
+
` + `The accumulator must be the left operand and the initial ` + `value a number / string literal. ` + `Wrap the call in /* @client */ to evaluate at hydration.`
|
|
5573
|
+
};
|
|
5574
|
+
}
|
|
5575
|
+
if (callee.property === "flatMap") {
|
|
5576
|
+
const flatMapOp = node.arguments.length === 1 ? extractFlatMapOpFromTS(node.arguments[0]) : null;
|
|
5577
|
+
if (flatMapOp) {
|
|
5578
|
+
return {
|
|
5579
|
+
kind: "array-method",
|
|
5580
|
+
method: "flatMap",
|
|
5581
|
+
object: callee.object,
|
|
5582
|
+
args: [],
|
|
5583
|
+
flatMapOp
|
|
5584
|
+
};
|
|
5585
|
+
}
|
|
5586
|
+
return {
|
|
5587
|
+
kind: "unsupported",
|
|
5588
|
+
raw,
|
|
5589
|
+
reason: `flatMap shape not supported. Accepted (self / field leaves, no thisArg):
|
|
5590
|
+
` + ` arr.flatMap(i => i) (flatten one level)
|
|
5591
|
+
` + ` arr.flatMap(i => i.field) (flatten a per-item array field)
|
|
5592
|
+
` + ` arr.flatMap(i => [i.a, i.b]) (gather per-item fields)
|
|
5593
|
+
` + `Richer callbacks (computed / nested access, arithmetic, calls, ` + `literal elements) and the 2-arg \`flatMap(fn, thisArg)\` form ` + `aren't lowered. Wrap the call in /* @client */ to evaluate at hydration.`
|
|
5594
|
+
};
|
|
5595
|
+
}
|
|
5524
5596
|
}
|
|
5525
5597
|
return { kind: "call", callee, args };
|
|
5526
5598
|
}
|
|
@@ -5839,6 +5911,119 @@ function classifySortOperand(expr, paramA, paramB) {
|
|
|
5839
5911
|
}
|
|
5840
5912
|
return null;
|
|
5841
5913
|
}
|
|
5914
|
+
function extractReduceOpFromTS(reducerNode, initNode) {
|
|
5915
|
+
const init = classifyReduceInit(initNode);
|
|
5916
|
+
if (!init)
|
|
5917
|
+
return null;
|
|
5918
|
+
if (!ts8.isArrowFunction(reducerNode) && !ts8.isFunctionExpression(reducerNode))
|
|
5919
|
+
return null;
|
|
5920
|
+
if (reducerNode.parameters.length !== 2)
|
|
5921
|
+
return null;
|
|
5922
|
+
const pAcc = reducerNode.parameters[0];
|
|
5923
|
+
const pItem = reducerNode.parameters[1];
|
|
5924
|
+
if (!ts8.isIdentifier(pAcc.name) || !ts8.isIdentifier(pItem.name))
|
|
5925
|
+
return null;
|
|
5926
|
+
const paramAcc = pAcc.name.text;
|
|
5927
|
+
const paramItem = pItem.name.text;
|
|
5928
|
+
let body;
|
|
5929
|
+
if (ts8.isArrowFunction(reducerNode) && !ts8.isBlock(reducerNode.body)) {
|
|
5930
|
+
body = reducerNode.body;
|
|
5931
|
+
} else {
|
|
5932
|
+
const block = reducerNode.body;
|
|
5933
|
+
const stmts = block.statements;
|
|
5934
|
+
if (stmts.length !== 1 || !ts8.isReturnStatement(stmts[0]) || !stmts[0].expression)
|
|
5935
|
+
return null;
|
|
5936
|
+
body = stmts[0].expression;
|
|
5937
|
+
}
|
|
5938
|
+
const raw = body.getText();
|
|
5939
|
+
const expr = unwrapParens(body);
|
|
5940
|
+
if (!ts8.isBinaryExpression(expr))
|
|
5941
|
+
return null;
|
|
5942
|
+
let op;
|
|
5943
|
+
if (expr.operatorToken.kind === ts8.SyntaxKind.PlusToken)
|
|
5944
|
+
op = "+";
|
|
5945
|
+
else if (expr.operatorToken.kind === ts8.SyntaxKind.AsteriskToken)
|
|
5946
|
+
op = "*";
|
|
5947
|
+
else
|
|
5948
|
+
return null;
|
|
5949
|
+
const left = unwrapParens(expr.left);
|
|
5950
|
+
if (!ts8.isIdentifier(left) || left.text !== paramAcc)
|
|
5951
|
+
return null;
|
|
5952
|
+
const key = classifyReduceKey(unwrapParens(expr.right), paramItem);
|
|
5953
|
+
if (!key)
|
|
5954
|
+
return null;
|
|
5955
|
+
const type = init.type;
|
|
5956
|
+
if (type === "string" && op !== "+")
|
|
5957
|
+
return null;
|
|
5958
|
+
return { op, key, type, init: init.value, raw, paramAcc, paramItem };
|
|
5959
|
+
}
|
|
5960
|
+
function extractFlatMapOpFromTS(cbNode) {
|
|
5961
|
+
if (!ts8.isArrowFunction(cbNode) && !ts8.isFunctionExpression(cbNode))
|
|
5962
|
+
return null;
|
|
5963
|
+
if (cbNode.parameters.length !== 1)
|
|
5964
|
+
return null;
|
|
5965
|
+
const p = cbNode.parameters[0];
|
|
5966
|
+
if (!ts8.isIdentifier(p.name))
|
|
5967
|
+
return null;
|
|
5968
|
+
const param = p.name.text;
|
|
5969
|
+
let body;
|
|
5970
|
+
if (ts8.isArrowFunction(cbNode) && !ts8.isBlock(cbNode.body)) {
|
|
5971
|
+
body = cbNode.body;
|
|
5972
|
+
} else {
|
|
5973
|
+
const block = cbNode.body;
|
|
5974
|
+
const stmts = block.statements;
|
|
5975
|
+
if (stmts.length !== 1 || !ts8.isReturnStatement(stmts[0]) || !stmts[0].expression)
|
|
5976
|
+
return null;
|
|
5977
|
+
body = stmts[0].expression;
|
|
5978
|
+
}
|
|
5979
|
+
const raw = body.getText();
|
|
5980
|
+
const inner = unwrapParens(body);
|
|
5981
|
+
if (ts8.isArrayLiteralExpression(inner)) {
|
|
5982
|
+
if (inner.elements.length === 0)
|
|
5983
|
+
return null;
|
|
5984
|
+
const elements = [];
|
|
5985
|
+
for (const el of inner.elements) {
|
|
5986
|
+
if (ts8.isSpreadElement(el) || ts8.isOmittedExpression(el))
|
|
5987
|
+
return null;
|
|
5988
|
+
const leaf2 = classifyReduceKey(unwrapParens(el), param);
|
|
5989
|
+
if (!leaf2)
|
|
5990
|
+
return null;
|
|
5991
|
+
elements.push(leaf2);
|
|
5992
|
+
}
|
|
5993
|
+
return { projection: { kind: "tuple", elements }, param, raw };
|
|
5994
|
+
}
|
|
5995
|
+
const leaf = classifyReduceKey(inner, param);
|
|
5996
|
+
if (!leaf)
|
|
5997
|
+
return null;
|
|
5998
|
+
return { projection: leaf, param, raw };
|
|
5999
|
+
}
|
|
6000
|
+
function classifyReduceKey(expr, paramItem) {
|
|
6001
|
+
if (ts8.isIdentifier(expr)) {
|
|
6002
|
+
return expr.text === paramItem ? { kind: "self" } : null;
|
|
6003
|
+
}
|
|
6004
|
+
if (ts8.isPropertyAccessExpression(expr) && ts8.isIdentifier(expr.expression)) {
|
|
6005
|
+
if (expr.expression.text === paramItem)
|
|
6006
|
+
return { kind: "field", field: expr.name.text };
|
|
6007
|
+
}
|
|
6008
|
+
return null;
|
|
6009
|
+
}
|
|
6010
|
+
function classifyReduceInit(node) {
|
|
6011
|
+
let n = unwrapParens(node);
|
|
6012
|
+
if (ts8.isPrefixUnaryExpression(n) && n.operator === ts8.SyntaxKind.MinusToken) {
|
|
6013
|
+
if (ts8.isNumericLiteral(n.operand))
|
|
6014
|
+
return { type: "numeric", value: "-" + n.operand.text };
|
|
6015
|
+
return null;
|
|
6016
|
+
}
|
|
6017
|
+
if (ts8.isNumericLiteral(n))
|
|
6018
|
+
return { type: "numeric", value: n.text };
|
|
6019
|
+
if (ts8.isStringLiteral(n)) {
|
|
6020
|
+
const raw = n.getText();
|
|
6021
|
+
if (raw.length < 2 || raw.slice(1, -1) !== n.text)
|
|
6022
|
+
return null;
|
|
6023
|
+
return { type: "string", value: n.text };
|
|
6024
|
+
}
|
|
6025
|
+
return null;
|
|
6026
|
+
}
|
|
5842
6027
|
function collectDestructureBindings(pattern, pathPrefix, fieldMap, raw, excludedTopKeys) {
|
|
5843
6028
|
let restName;
|
|
5844
6029
|
for (const el of pattern.elements) {
|
|
@@ -6147,6 +6332,15 @@ function substituteDestructuredFields(expr, fieldMap, syntheticParam, restName)
|
|
|
6147
6332
|
if (e.method === "sort" || e.method === "toSorted") {
|
|
6148
6333
|
return { kind: "array-method", method: e.method, object: walk(e.object), args: [], comparator: e.comparator };
|
|
6149
6334
|
}
|
|
6335
|
+
if (e.method === "reduce" || e.method === "reduceRight") {
|
|
6336
|
+
return { kind: "array-method", method: e.method, object: walk(e.object), args: [], reduceOp: e.reduceOp };
|
|
6337
|
+
}
|
|
6338
|
+
if (e.method === "flat") {
|
|
6339
|
+
return { kind: "array-method", method: "flat", object: walk(e.object), args: [], flatDepth: e.flatDepth };
|
|
6340
|
+
}
|
|
6341
|
+
if (e.method === "flatMap") {
|
|
6342
|
+
return { kind: "array-method", method: "flatMap", object: walk(e.object), args: [], flatMapOp: e.flatMapOp };
|
|
6343
|
+
}
|
|
6150
6344
|
return { kind: "array-method", method: e.method, object: walk(e.object), args: e.args.map(walk) };
|
|
6151
6345
|
case "literal":
|
|
6152
6346
|
case "unsupported":
|
|
@@ -6267,7 +6461,8 @@ function checkSupport(expr) {
|
|
|
6267
6461
|
return {
|
|
6268
6462
|
supported: false,
|
|
6269
6463
|
level: "L5_UNSUPPORTED",
|
|
6270
|
-
|
|
6464
|
+
selfContained: true,
|
|
6465
|
+
reason: UNSUPPORTED_METHOD_REASONS[methodName] ?? `'${methodName}()' can't render on the server. Pre-compute the value, or add /* @client */ for client-only (no SSR).`
|
|
6271
6466
|
};
|
|
6272
6467
|
}
|
|
6273
6468
|
}
|
|
@@ -6491,6 +6686,20 @@ function exprToString(expr) {
|
|
|
6491
6686
|
const { paramA, paramB, raw } = expr.comparator;
|
|
6492
6687
|
return `${exprToString(expr.object)}.${expr.method}((${paramA},${paramB}) => ${raw})`;
|
|
6493
6688
|
}
|
|
6689
|
+
if (expr.method === "reduce" || expr.method === "reduceRight") {
|
|
6690
|
+
const { paramAcc, paramItem, raw, type, init } = expr.reduceOp;
|
|
6691
|
+
const initSrc = type === "string" ? JSON.stringify(init) : init;
|
|
6692
|
+
return `${exprToString(expr.object)}.${expr.method}((${paramAcc},${paramItem}) => ${raw}, ${initSrc})`;
|
|
6693
|
+
}
|
|
6694
|
+
if (expr.method === "flat") {
|
|
6695
|
+
const d = expr.flatDepth;
|
|
6696
|
+
const depthSrc = d === "infinity" ? "Infinity" : String(d);
|
|
6697
|
+
return `${exprToString(expr.object)}.flat(${d === 1 ? "" : depthSrc})`;
|
|
6698
|
+
}
|
|
6699
|
+
if (expr.method === "flatMap") {
|
|
6700
|
+
const { param, raw } = expr.flatMapOp;
|
|
6701
|
+
return `${exprToString(expr.object)}.flatMap(${param} => ${raw})`;
|
|
6702
|
+
}
|
|
6494
6703
|
return `${exprToString(expr.object)}.${expr.method}(${expr.args.map(exprToString).join(", ")})`;
|
|
6495
6704
|
case "unsupported":
|
|
6496
6705
|
return `[UNSUPPORTED: ${expr.raw}]`;
|
|
@@ -6536,6 +6745,20 @@ function stringifyParsedExpr(expr) {
|
|
|
6536
6745
|
const { paramA, paramB, raw } = expr.comparator;
|
|
6537
6746
|
return `${stringifyParsedExpr(expr.object)}.${expr.method}((${paramA},${paramB}) => ${raw})`;
|
|
6538
6747
|
}
|
|
6748
|
+
if (expr.method === "reduce" || expr.method === "reduceRight") {
|
|
6749
|
+
const { paramAcc, paramItem, raw, type, init } = expr.reduceOp;
|
|
6750
|
+
const initSrc = type === "string" ? JSON.stringify(init) : init;
|
|
6751
|
+
return `${stringifyParsedExpr(expr.object)}.${expr.method}((${paramAcc},${paramItem}) => ${raw}, ${initSrc})`;
|
|
6752
|
+
}
|
|
6753
|
+
if (expr.method === "flat") {
|
|
6754
|
+
const d = expr.flatDepth;
|
|
6755
|
+
const depthSrc = d === "infinity" ? "Infinity" : String(d);
|
|
6756
|
+
return `${stringifyParsedExpr(expr.object)}.flat(${d === 1 ? "" : depthSrc})`;
|
|
6757
|
+
}
|
|
6758
|
+
if (expr.method === "flatMap") {
|
|
6759
|
+
const { param, raw } = expr.flatMapOp;
|
|
6760
|
+
return `${stringifyParsedExpr(expr.object)}.flatMap(${param} => ${raw})`;
|
|
6761
|
+
}
|
|
6539
6762
|
return `${stringifyParsedExpr(expr.object)}.${expr.method}(${expr.args.map(stringifyParsedExpr).join(", ")})`;
|
|
6540
6763
|
case "unsupported":
|
|
6541
6764
|
return expr.raw;
|
|
@@ -10072,7 +10295,7 @@ function collectInnerLoops(nodes, siblingOffsets, outerLoopParam, ctx, options)
|
|
|
10072
10295
|
}
|
|
10073
10296
|
function decideLoopRendering(loop, siblingOffsets, ctx) {
|
|
10074
10297
|
const hasNestedComps = (loop.nestedComponents?.length ?? 0) > 0;
|
|
10075
|
-
const innerLoops =
|
|
10298
|
+
const innerLoops = collectInnerLoops(loop.children, siblingOffsets, loop.param, ctx);
|
|
10076
10299
|
const hasInnerLoops = (innerLoops?.length ?? 0) > 0;
|
|
10077
10300
|
const useElementReconciliation = !loop.childComponent && !loop.isStaticArray && (hasNestedComps || hasInnerLoops);
|
|
10078
10301
|
return { useElementReconciliation, innerLoops };
|
|
@@ -12888,7 +13111,7 @@ function buildStaticArrayChildInitsPlan(ctx) {
|
|
|
12888
13111
|
const innerComps = elem.nestedComponents.filter((c) => (c.loopDepth ?? 0) === innerLoop.depth && c.innerLoopArray === innerLoop.array);
|
|
12889
13112
|
if (innerComps.length === 0)
|
|
12890
13113
|
continue;
|
|
12891
|
-
plans.push(buildInnerLoopNestedPlan(elem, innerLoop, innerComps));
|
|
13114
|
+
plans.push(elem.childComponent ? buildComponentRootedInnerLoopPlan(elem, innerLoop, innerComps) : buildInnerLoopNestedPlan(elem, innerLoop, innerComps));
|
|
12892
13115
|
}
|
|
12893
13116
|
}
|
|
12894
13117
|
}
|
|
@@ -12949,6 +13172,25 @@ function buildInnerLoopNestedPlan(elem, innerLoop, innerComps) {
|
|
|
12949
13172
|
comps
|
|
12950
13173
|
};
|
|
12951
13174
|
}
|
|
13175
|
+
function buildComponentRootedInnerLoopPlan(elem, innerLoop, innerComps) {
|
|
13176
|
+
const comps = innerComps.map((comp) => ({
|
|
13177
|
+
componentName: comp.name,
|
|
13178
|
+
selector: buildCompSelector(comp),
|
|
13179
|
+
propsExpr: buildStaticPropsExpr(comp.props)
|
|
13180
|
+
}));
|
|
13181
|
+
return {
|
|
13182
|
+
kind: "component-rooted-inner-loop",
|
|
13183
|
+
containerVar: `_${varSlotId(elem.slotId)}`,
|
|
13184
|
+
outerArrayExpr: elem.array,
|
|
13185
|
+
outerParam: elem.param,
|
|
13186
|
+
outerPreludeStatements: elem.mapPreamble ? [elem.mapPreamble] : [],
|
|
13187
|
+
innerArrayExpr: innerLoop.array,
|
|
13188
|
+
innerParam: innerLoop.param,
|
|
13189
|
+
innerPreludeStatements: innerLoop.mapPreamble ? [innerLoop.mapPreamble] : [],
|
|
13190
|
+
depth: innerLoop.depth,
|
|
13191
|
+
comps
|
|
13192
|
+
};
|
|
13193
|
+
}
|
|
12952
13194
|
function buildStaticPropsExpr(props) {
|
|
12953
13195
|
const entries = props.map((p) => {
|
|
12954
13196
|
if (p.isEventHandler) {
|
|
@@ -12988,6 +13230,9 @@ function stringifyOne(lines, plan) {
|
|
|
12988
13230
|
case "inner-loop-nested":
|
|
12989
13231
|
emitInnerLoopNested(lines, plan);
|
|
12990
13232
|
break;
|
|
13233
|
+
case "component-rooted-inner-loop":
|
|
13234
|
+
emitComponentRootedInnerLoop(lines, plan);
|
|
13235
|
+
break;
|
|
12991
13236
|
}
|
|
12992
13237
|
}
|
|
12993
13238
|
function emitSingleComp(lines, plan) {
|
|
@@ -13067,6 +13312,44 @@ function emitInnerLoopNested(lines, plan) {
|
|
|
13067
13312
|
lines.push(` }`);
|
|
13068
13313
|
lines.push("");
|
|
13069
13314
|
}
|
|
13315
|
+
function emitComponentRootedInnerLoop(lines, plan) {
|
|
13316
|
+
const {
|
|
13317
|
+
containerVar,
|
|
13318
|
+
outerArrayExpr,
|
|
13319
|
+
outerParam,
|
|
13320
|
+
outerPreludeStatements,
|
|
13321
|
+
innerArrayExpr,
|
|
13322
|
+
innerParam,
|
|
13323
|
+
innerPreludeStatements,
|
|
13324
|
+
depth,
|
|
13325
|
+
comps
|
|
13326
|
+
} = plan;
|
|
13327
|
+
const scopesVar = (i) => comps.length > 1 ? `__compScopes${i}` : "__compScopes";
|
|
13328
|
+
const cursorVar = (i) => comps.length > 1 ? `__ci${i}` : "__ci";
|
|
13329
|
+
const compElVar = (i) => comps.length > 1 ? `__compEl${i}` : "__compEl";
|
|
13330
|
+
lines.push(` // Initialize component-rooted inner-loop components (depth ${depth})`);
|
|
13331
|
+
lines.push(` if (${containerVar}) {`);
|
|
13332
|
+
comps.forEach((comp, i) => {
|
|
13333
|
+
lines.push(` const ${scopesVar(i)} = qsaChildScopes(${containerVar}, ${comp.selector})`);
|
|
13334
|
+
lines.push(` let ${cursorVar(i)} = 0`);
|
|
13335
|
+
});
|
|
13336
|
+
lines.push(` ${outerArrayExpr}.forEach((${outerParam}) => {`);
|
|
13337
|
+
for (const stmt of outerPreludeStatements) {
|
|
13338
|
+
lines.push(` ${stmt}`);
|
|
13339
|
+
}
|
|
13340
|
+
lines.push(` ${innerArrayExpr}.forEach((${innerParam}) => {`);
|
|
13341
|
+
for (const stmt of innerPreludeStatements) {
|
|
13342
|
+
lines.push(` ${stmt}`);
|
|
13343
|
+
}
|
|
13344
|
+
comps.forEach((comp, i) => {
|
|
13345
|
+
lines.push(` const ${compElVar(i)} = ${scopesVar(i)}[${cursorVar(i)}++]`);
|
|
13346
|
+
lines.push(` if (${compElVar(i)}) initChild('${nameForRegistryRef(comp.componentName)}', ${compElVar(i)}, ${comp.propsExpr})`);
|
|
13347
|
+
});
|
|
13348
|
+
lines.push(` })`);
|
|
13349
|
+
lines.push(` })`);
|
|
13350
|
+
lines.push(` }`);
|
|
13351
|
+
lines.push("");
|
|
13352
|
+
}
|
|
13070
13353
|
|
|
13071
13354
|
// src/ir-to-client-js/phases/static-array-child-inits.ts
|
|
13072
13355
|
function emitStaticArrayChildInits(lines, ctx) {
|
|
@@ -17345,6 +17628,15 @@ function emitParsedExpr(expr, emitter) {
|
|
|
17345
17628
|
if (expr.method === "sort" || expr.method === "toSorted") {
|
|
17346
17629
|
return emitter.sortMethod(expr.method, expr.object, expr.comparator, emit);
|
|
17347
17630
|
}
|
|
17631
|
+
if (expr.method === "reduce" || expr.method === "reduceRight") {
|
|
17632
|
+
return emitter.reduceMethod(expr.method, expr.object, expr.reduceOp, emit);
|
|
17633
|
+
}
|
|
17634
|
+
if (expr.method === "flat") {
|
|
17635
|
+
return emitter.flatMethod(expr.object, expr.flatDepth, emit);
|
|
17636
|
+
}
|
|
17637
|
+
if (expr.method === "flatMap") {
|
|
17638
|
+
return emitter.flatMapMethod(expr.object, expr.flatMapOp, emit);
|
|
17639
|
+
}
|
|
17348
17640
|
return emitter.arrayMethod(expr.method, expr.object, expr.args, emit);
|
|
17349
17641
|
case "unsupported":
|
|
17350
17642
|
return emitter.unsupported(expr.raw, expr.reason);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collect-elements.d.ts","sourceRoot":"","sources":["../../src/ir-to-client-js/collect-elements.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,KAAK,MAAM,EAAoC,KAAK,MAAM,EAAmC,MAAM,UAAU,CAAA;AACtH,OAAO,KAAK,EAAE,eAAe,EAA+H,iBAAiB,EAA0B,oBAAoB,EAAc,UAAU,EAAE,MAAM,SAAS,CAAA;AAwGpQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CA0C7E;AAyBD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,wBAAwB;IACvC;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB;AAED,4DAA4D;AAC5D,eAAO,MAAM,sBAAsB,EAAE,wBAIpC,CAAA;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EAAE,EACf,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EACrC,cAAc,CAAC,EAAE,MAAM,EACvB,GAAG,CAAC,EAAE,eAAe,EACrB,OAAO,CAAC,EAAE,wBAAwB,GACjC,UAAU,EAAE,CAyId;
|
|
1
|
+
{"version":3,"file":"collect-elements.d.ts","sourceRoot":"","sources":["../../src/ir-to-client-js/collect-elements.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,KAAK,MAAM,EAAoC,KAAK,MAAM,EAAmC,MAAM,UAAU,CAAA;AACtH,OAAO,KAAK,EAAE,eAAe,EAA+H,iBAAiB,EAA0B,oBAAoB,EAAc,UAAU,EAAE,MAAM,SAAS,CAAA;AAwGpQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CA0C7E;AAyBD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,wBAAwB;IACvC;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB;AAED,4DAA4D;AAC5D,eAAO,MAAM,sBAAsB,EAAE,wBAIpC,CAAA;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EAAE,EACf,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EACrC,cAAc,CAAC,EAAE,MAAM,EACvB,GAAG,CAAC,EAAE,eAAe,EACrB,OAAO,CAAC,EAAE,wBAAwB,GACjC,UAAU,EAAE,CAyId;AAqKD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,eAAe,EACpB,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EACrC,iBAAiB,UAAQ,GACxB,IAAI,CAsON;AA6VD;;;;;;;;;;GAUG;AAEH;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,GAAG,EAAE,eAAe,EACpB,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EACrC,SAAS,EAAE,MAAM,EACjB,iBAAiB,EAAE,SAAS,OAAO,UAAU,EAAE,gBAAgB,EAAE,GAAG,SAAS,GAC5E,iBAAiB,CAUnB;AAED,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,eAAe,EACpB,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EACrC,SAAS,CAAC,EAAE,MAAM,EAClB,iBAAiB,CAAC,EAAE,SAAS,OAAO,UAAU,EAAE,gBAAgB,EAAE,GACjE,oBAAoB,EAAE,CA4DxB"}
|
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
* 2. `outer-nested` for each depth-0 entry in `elem.nestedComponents`.
|
|
9
9
|
* 3. `inner-loop-nested` for each `elem.innerLoops` entry that has
|
|
10
10
|
* matching depth-N components.
|
|
11
|
+
* 4. `component-rooted-inner-loop` instead of (3) when the outer item is
|
|
12
|
+
* itself a child component (#1725) — the inner `.map()` lives inside
|
|
13
|
+
* the component's JSX children, so it's addressed by a document-order
|
|
14
|
+
* zip rather than element offsets.
|
|
11
15
|
*
|
|
12
16
|
* Selector / propsExpr / offset decisions all resolve here. The
|
|
13
17
|
* stringifier never inspects raw IR.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-static-array-child-init.d.ts","sourceRoot":"","sources":["../../../src/ir-to-client-js/plan/build-static-array-child-init.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"build-static-array-child-init.d.ts","sourceRoot":"","sources":["../../../src/ir-to-client-js/plan/build-static-array-child-init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAO/C,OAAO,KAAK,EAQV,yBAAyB,EAC1B,MAAM,2BAA2B,CAAA;AAElC,wBAAgB,8BAA8B,CAC5C,GAAG,EAAE,eAAe,GACnB,yBAAyB,CAsC3B"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Plan types for `emitStaticArrayChildInits` — the
|
|
2
|
+
* Plan types for `emitStaticArrayChildInits` — the shapes that
|
|
3
3
|
* `static array` loops emit for child component initialisation:
|
|
4
4
|
*
|
|
5
5
|
* - `single-comp` — `loop.childComponent` ケース。一つの child component
|
|
@@ -8,6 +8,13 @@
|
|
|
8
8
|
* `__iterEl.querySelector(...)` 経由で initChild。
|
|
9
9
|
* - `inner-loop-nested` — depth > 0 の `nestedComponents`。outer + inner
|
|
10
10
|
* forEach の二重ループで initChild。
|
|
11
|
+
* - `component-rooted-inner-loop`
|
|
12
|
+
* — outer の loop item root が **child component**
|
|
13
|
+
* (`loop.childComponent`) で、その JSX children に
|
|
14
|
+
* component の nested `.map()` を持つケース (#1725)。
|
|
15
|
+
* element offset では fragment-root passthrough の
|
|
16
|
+
* flatten 済み items に届かないため、document order
|
|
17
|
+
* の zip (`qsaChildScopes` + cursor) で initChild。
|
|
11
18
|
*
|
|
12
19
|
* All decisions (selector, propsExpr, offset expressions) are resolved at
|
|
13
20
|
* build time so the stringifier becomes a deterministic walk.
|
|
@@ -122,6 +129,43 @@ export interface InnerLoopNestedInitPlan {
|
|
|
122
129
|
/** Per-component initialisers emitted inside the inner forEach body. */
|
|
123
130
|
comps: readonly InnerLoopComp[];
|
|
124
131
|
}
|
|
125
|
-
|
|
132
|
+
/**
|
|
133
|
+
* Plan for an inner `.map()` of components living inside a **component-rooted**
|
|
134
|
+
* outer loop item (#1725). The outer loop body is a single child component
|
|
135
|
+
* (`loop.childComponent`, e.g. a `SelectGroup` passthrough) whose JSX
|
|
136
|
+
* `children` contain a nested `.map()` of components (e.g. `SelectItem`).
|
|
137
|
+
*
|
|
138
|
+
* `inner-loop-nested` can't be reused here: it addresses inner components via
|
|
139
|
+
* `containerVar.children[outerOffset]`, which assumes the outer loop item is a
|
|
140
|
+
* DOM **element**. A fragment-rooted passthrough component (`<>{children}</>`)
|
|
141
|
+
* emits no wrapper element, so its rendered items are flattened directly under
|
|
142
|
+
* the parent container with no per-group element to index.
|
|
143
|
+
*
|
|
144
|
+
* Instead this shape zips the inner component scopes — found in document order
|
|
145
|
+
* via `qsaChildScopes(container, <selector>)` — against the flattened
|
|
146
|
+
* `outer.forEach(o => inner.forEach(i => ...))` iteration. Document order is
|
|
147
|
+
* the SSR render order, so position `__ci++` pairs each scope with its data
|
|
148
|
+
* item regardless of whether the outer component root is an element or a
|
|
149
|
+
* fragment.
|
|
150
|
+
*/
|
|
151
|
+
export interface ComponentRootedInnerLoopInitPlan {
|
|
152
|
+
kind: 'component-rooted-inner-loop';
|
|
153
|
+
containerVar: string;
|
|
154
|
+
/** Outer loop's array expression. */
|
|
155
|
+
outerArrayExpr: string;
|
|
156
|
+
outerParam: string;
|
|
157
|
+
/** Outer `.map()` callback preamble locals (#1064). */
|
|
158
|
+
outerPreludeStatements: PreludeStatements;
|
|
159
|
+
/** Inner loop's array expression (references the outer param). */
|
|
160
|
+
innerArrayExpr: string;
|
|
161
|
+
innerParam: string;
|
|
162
|
+
/** Inner `.map()` callback preamble locals (#1064). */
|
|
163
|
+
innerPreludeStatements: PreludeStatements;
|
|
164
|
+
/** Depth used in the leading comment line. */
|
|
165
|
+
depth: number;
|
|
166
|
+
/** Per-component initialisers emitted inside the inner forEach body. */
|
|
167
|
+
comps: readonly InnerLoopComp[];
|
|
168
|
+
}
|
|
169
|
+
export type StaticArrayChildInitPlan = SingleCompInitPlan | OuterNestedInitPlan | InnerLoopNestedInitPlan | ComponentRootedInnerLoopInitPlan;
|
|
126
170
|
export type StaticArrayChildInitsPlan = readonly StaticArrayChildInitPlan[];
|
|
127
171
|
//# sourceMappingURL=static-array-child-init.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"static-array-child-init.d.ts","sourceRoot":"","sources":["../../../src/ir-to-client-js/plan/static-array-child-init.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"static-array-child-init.d.ts","sourceRoot":"","sources":["../../../src/ir-to-client-js/plan/static-array-child-init.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"static-array-child-init.d.ts","sourceRoot":"","sources":["../../../src/ir-to-client-js/stringify/static-array-child-init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAEH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"static-array-child-init.d.ts","sourceRoot":"","sources":["../../../src/ir-to-client-js/stringify/static-array-child-init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAEH,OAAO,KAAK,EAMV,yBAAyB,EAC1B,MAAM,iCAAiC,CAAA;AAGxC,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,MAAM,EAAE,EACf,KAAK,EAAE,yBAAyB,GAC/B,IAAI,CAIN"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/jsx",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "JSX compiler for BarefootJS - transforms JSX to server HTML + client JS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"directory": "packages/jsx"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@barefootjs/shared": "0.6.
|
|
56
|
+
"@barefootjs/shared": "0.6.1"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"@barefootjs/client": ">=0.2.0",
|
|
@@ -1181,4 +1181,88 @@ describe('child components inside .map() (#344)', () => {
|
|
|
1181
1181
|
expect(content).toContain('const __compEl =')
|
|
1182
1182
|
expect(content).not.toContain('__compEl0')
|
|
1183
1183
|
})
|
|
1184
|
+
|
|
1185
|
+
describe('nested .map() inside a component-rooted loop item (#1725)', () => {
|
|
1186
|
+
// The outer loop item root is a *component* (a passthrough wrapper like
|
|
1187
|
+
// `SelectGroup`), not an element. Its JSX children contain a nested
|
|
1188
|
+
// `.map()` of components. The parent init emitted `initChild` for the
|
|
1189
|
+
// outer wrapper but never descended into its children to init the inner
|
|
1190
|
+
// loop's components — they rendered from SSR but never hydrated (silent).
|
|
1191
|
+
test('emits initChild for the inner-loop component (component wrapper)', () => {
|
|
1192
|
+
const source = `
|
|
1193
|
+
'use client'
|
|
1194
|
+
|
|
1195
|
+
export function Repro() {
|
|
1196
|
+
const GROUPS = [
|
|
1197
|
+
{ id: 'a', items: [{ id: 'a1', label: 'A1' }] },
|
|
1198
|
+
{ id: 'b', items: [{ id: 'b1', label: 'B1' }] },
|
|
1199
|
+
]
|
|
1200
|
+
return (
|
|
1201
|
+
<div>
|
|
1202
|
+
{GROUPS.map(group => (
|
|
1203
|
+
<Group key={group.id}>
|
|
1204
|
+
{group.items.map(it => (
|
|
1205
|
+
<Item key={it.id} label={it.label} />
|
|
1206
|
+
))}
|
|
1207
|
+
</Group>
|
|
1208
|
+
))}
|
|
1209
|
+
</div>
|
|
1210
|
+
)
|
|
1211
|
+
}
|
|
1212
|
+
`
|
|
1213
|
+
const result = compileJSX(source, 'Repro.tsx', { adapter })
|
|
1214
|
+
expect(result.errors).toHaveLength(0)
|
|
1215
|
+
|
|
1216
|
+
const content = result.files.find(f => f.type === 'clientJs')!.content
|
|
1217
|
+
// Both the outer wrapper and the inner-loop component must init.
|
|
1218
|
+
expect(content).toContain("initChild('Group'")
|
|
1219
|
+
expect(content).toContain("initChild('Item'")
|
|
1220
|
+
// The inner component's props read the inner loop param.
|
|
1221
|
+
expect(content).toContain('return it.label')
|
|
1222
|
+
// Document-order zip shape — a flat cursor over the queried scopes
|
|
1223
|
+
// pairs each with its data item across the nested forEach. This shape
|
|
1224
|
+
// is fragment-root safe (no per-group wrapper element to index).
|
|
1225
|
+
expect(content).toContain('qsaChildScopes')
|
|
1226
|
+
expect(content).toMatch(/__compScopes\[__ci\+\+\]/)
|
|
1227
|
+
// No element-offset addressing (`__outerEl = ...children[...]`) — that
|
|
1228
|
+
// would break for a fragment-rooted passthrough.
|
|
1229
|
+
expect(content).not.toContain('__outerEl')
|
|
1230
|
+
})
|
|
1231
|
+
|
|
1232
|
+
test('multiple inner components each get their own document-order cursor', () => {
|
|
1233
|
+
const source = `
|
|
1234
|
+
'use client'
|
|
1235
|
+
|
|
1236
|
+
export function Repro() {
|
|
1237
|
+
const GROUPS = [{ id: 'a', items: [{ id: 'a1', label: 'A1' }] }]
|
|
1238
|
+
return (
|
|
1239
|
+
<div>
|
|
1240
|
+
{GROUPS.map(group => (
|
|
1241
|
+
<Group key={group.id}>
|
|
1242
|
+
{group.items.map(it => (
|
|
1243
|
+
<>
|
|
1244
|
+
<Item key={it.id} label={it.label} />
|
|
1245
|
+
<Badge text={it.label} />
|
|
1246
|
+
</>
|
|
1247
|
+
))}
|
|
1248
|
+
</Group>
|
|
1249
|
+
))}
|
|
1250
|
+
</div>
|
|
1251
|
+
)
|
|
1252
|
+
}
|
|
1253
|
+
`
|
|
1254
|
+
const result = compileJSX(source, 'Repro.tsx', { adapter })
|
|
1255
|
+
expect(result.errors).toHaveLength(0)
|
|
1256
|
+
|
|
1257
|
+
const content = result.files.find(f => f.type === 'clientJs')!.content
|
|
1258
|
+
expect(content).toContain("initChild('Item'")
|
|
1259
|
+
expect(content).toContain("initChild('Badge'")
|
|
1260
|
+
// Distinct scope arrays + cursors so each comp consumes its own
|
|
1261
|
+
// document-order stream (no `const __compEl` redeclaration, #1664).
|
|
1262
|
+
expect(content).toContain('__compScopes0')
|
|
1263
|
+
expect(content).toContain('__compScopes1')
|
|
1264
|
+
expect(content).toMatch(/__ci0\+\+/)
|
|
1265
|
+
expect(content).toMatch(/__ci1\+\+/)
|
|
1266
|
+
})
|
|
1267
|
+
})
|
|
1184
1268
|
})
|