@barefootjs/jsx 0.5.3 → 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.
Files changed (40) hide show
  1. package/dist/adapters/parsed-expr-emitter.d.ts +15 -2
  2. package/dist/adapters/parsed-expr-emitter.d.ts.map +1 -1
  3. package/dist/expression-parser.d.ts +138 -1
  4. package/dist/expression-parser.d.ts.map +1 -1
  5. package/dist/index.d.ts +1 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +450 -23
  8. package/dist/ir-to-client-js/collect-elements.d.ts.map +1 -1
  9. package/dist/ir-to-client-js/generate-init.d.ts.map +1 -1
  10. package/dist/ir-to-client-js/html-template.d.ts +30 -1
  11. package/dist/ir-to-client-js/html-template.d.ts.map +1 -1
  12. package/dist/ir-to-client-js/phases/provider-and-child-inits.d.ts.map +1 -1
  13. package/dist/ir-to-client-js/plan/build-static-array-child-init.d.ts +4 -0
  14. package/dist/ir-to-client-js/plan/build-static-array-child-init.d.ts.map +1 -1
  15. package/dist/ir-to-client-js/plan/static-array-child-init.d.ts +46 -2
  16. package/dist/ir-to-client-js/plan/static-array-child-init.d.ts.map +1 -1
  17. package/dist/ir-to-client-js/stringify/static-array-child-init.d.ts.map +1 -1
  18. package/dist/ir-to-client-js/types.d.ts +10 -0
  19. package/dist/ir-to-client-js/types.d.ts.map +1 -1
  20. package/package.json +2 -2
  21. package/src/__tests__/child-components-in-map.test.ts +84 -0
  22. package/src/__tests__/expression-parser.test.ts +276 -14
  23. package/src/__tests__/foreach-client-only.test.ts +80 -0
  24. package/src/__tests__/ir-reduce-op.test.ts +51 -0
  25. package/src/__tests__/ir-to-client-js/reactivity.test.ts +1 -0
  26. package/src/__tests__/reduce-op.test.ts +201 -0
  27. package/src/__tests__/staged-ir/06-multi-stage-soak.test.ts +18 -3
  28. package/src/adapters/parsed-expr-emitter.ts +50 -1
  29. package/src/expression-parser.ts +770 -21
  30. package/src/index.ts +1 -1
  31. package/src/ir-to-client-js/collect-elements.ts +9 -3
  32. package/src/ir-to-client-js/emit-registration.ts +1 -1
  33. package/src/ir-to-client-js/generate-init.ts +16 -1
  34. package/src/ir-to-client-js/html-template.ts +156 -2
  35. package/src/ir-to-client-js/index.ts +1 -0
  36. package/src/ir-to-client-js/phases/provider-and-child-inits.ts +12 -1
  37. package/src/ir-to-client-js/plan/build-static-array-child-init.ts +55 -1
  38. package/src/ir-to-client-js/plan/static-array-child-init.ts +47 -1
  39. package/src/ir-to-client-js/stringify/static-array-child-init.ts +69 -0
  40. package/src/ir-to-client-js/types.ts +10 -0
package/dist/index.js CHANGED
@@ -1710,7 +1710,7 @@ function canGenerateStaticTemplate(node, propNames, inlinableConstants, unsafeLo
1710
1710
  return assertNever(node);
1711
1711
  }
1712
1712
  }
1713
- function generateCsrTemplate(node, inlinableConstants, ctx, insideLoop, restSpreadNames, propsObjectName, unsafeLocalNames) {
1713
+ function generateCsrTemplate(node, inlinableConstants, ctx, insideLoop, restSpreadNames, propsObjectName, unsafeLocalNames, deferredChildSlots) {
1714
1714
  const base = buildSignalMemoEnv(ctx.signals, ctx.memos, propsObjectName ?? null);
1715
1715
  const csrEnv = { substitutions: new Map(base.substitutions), propsObjectName: base.propsObjectName };
1716
1716
  if (inlinableConstants) {
@@ -1720,7 +1720,83 @@ function generateCsrTemplate(node, inlinableConstants, ctx, insideLoop, restSpre
1720
1720
  }
1721
1721
  }
1722
1722
  }
1723
- return generateCsrTemplateWithOpts(node, { inlinableConstants, restSpreadNames, propsObjectName, csrEnv, insideLoop, unsafeLocalNames, loopDepth: -1 });
1723
+ return generateCsrTemplateWithOpts(node, { inlinableConstants, restSpreadNames, propsObjectName, csrEnv, insideLoop, unsafeLocalNames, deferredChildSlots, loopDepth: -1 });
1724
+ }
1725
+ function buildCsrEnvForCtx(ctx, inlinableConstants, propsObjectName) {
1726
+ const base = buildSignalMemoEnv(ctx.signals, ctx.memos, propsObjectName ?? null);
1727
+ const csrEnv = { substitutions: new Map(base.substitutions), propsObjectName: base.propsObjectName };
1728
+ if (inlinableConstants) {
1729
+ for (const [name, value] of inlinableConstants) {
1730
+ if (!csrEnv.substitutions.has(name)) {
1731
+ csrEnv.substitutions.set(name, { kind: "identifier", replacement: value, freeIdentifiers: new Set });
1732
+ }
1733
+ }
1734
+ }
1735
+ return csrEnv;
1736
+ }
1737
+ function propResolvesUnsafe(prop, env, unsafeLocalNames) {
1738
+ if (unsafeLocalNames.size === 0)
1739
+ return false;
1740
+ let source;
1741
+ switch (prop.value.kind) {
1742
+ case "expression":
1743
+ case "spread":
1744
+ source = prop.value.expr;
1745
+ break;
1746
+ case "template":
1747
+ source = attrValueToString(prop.value, { useTemplate: true }) ?? undefined;
1748
+ break;
1749
+ default:
1750
+ return false;
1751
+ }
1752
+ if (!source)
1753
+ return false;
1754
+ const { freeIdentifiers } = csrSubstitute(source, env);
1755
+ return setIntersects(freeIdentifiers, unsafeLocalNames);
1756
+ }
1757
+ function computeDeferredChildSlots(node, ctx, inlinableConstants, unsafeLocalNames, propsObjectName) {
1758
+ const deferred = new Set;
1759
+ if (!unsafeLocalNames || unsafeLocalNames.size === 0)
1760
+ return deferred;
1761
+ const env = buildCsrEnvForCtx(ctx, inlinableConstants, propsObjectName);
1762
+ const visit = (n) => {
1763
+ switch (n.type) {
1764
+ case "component": {
1765
+ if (n.name === "Portal") {
1766
+ n.children.forEach(visit);
1767
+ return;
1768
+ }
1769
+ if (n.slotId) {
1770
+ const dropped = n.props.some((p) => {
1771
+ if (p.name === "..." || p.name.startsWith("...") || p.name === "key")
1772
+ return false;
1773
+ if (p.name.startsWith("on") && p.name.length > 2 && p.name[2] === p.name[2].toUpperCase())
1774
+ return false;
1775
+ if (p.clientOnly)
1776
+ return false;
1777
+ return propResolvesUnsafe(p, env, unsafeLocalNames);
1778
+ });
1779
+ if (dropped)
1780
+ deferred.add(n.slotId);
1781
+ }
1782
+ return;
1783
+ }
1784
+ case "element":
1785
+ n.children.forEach(visit);
1786
+ return;
1787
+ case "fragment":
1788
+ n.children.forEach(visit);
1789
+ return;
1790
+ case "conditional":
1791
+ return;
1792
+ case "loop":
1793
+ return;
1794
+ default:
1795
+ return;
1796
+ }
1797
+ };
1798
+ visit(node);
1799
+ return deferred;
1724
1800
  }
1725
1801
  function generateCsrTemplateWithOpts(node, opts) {
1726
1802
  const { restSpreadNames, propsObjectName, csrEnv, insideLoop, unsafeLocalNames, loopDepth = 0 } = opts;
@@ -1826,6 +1902,9 @@ function generateCsrTemplateWithOpts(node, opts) {
1826
1902
  if (node.name === "Portal") {
1827
1903
  return node.children.map(recurse).join("");
1828
1904
  }
1905
+ if (node.slotId && opts.deferredChildSlots?.has(node.slotId)) {
1906
+ return `<div ${DATA_BF_PH}="${node.slotId}"></div>`;
1907
+ }
1829
1908
  const propsEntries = node.props.filter((p) => p.name !== "..." && !p.name.startsWith("...") && p.name !== "key").filter((p) => !(p.name.startsWith("on") && p.name.length > 2 && p.name[2] === p.name[2].toUpperCase())).map((p) => {
1830
1909
  if (p.clientOnly)
1831
1910
  return null;
@@ -5260,15 +5339,7 @@ var UNSUPPORTED_METHODS = new Set([
5260
5339
  "some",
5261
5340
  "forEach",
5262
5341
  "flatMap",
5263
- "flat",
5264
- "split",
5265
- "startsWith",
5266
- "endsWith",
5267
- "replace",
5268
5342
  "replaceAll",
5269
- "repeat",
5270
- "padStart",
5271
- "padEnd",
5272
5343
  "charAt",
5273
5344
  "charCodeAt",
5274
5345
  "codePointAt",
@@ -5279,6 +5350,15 @@ var UNSUPPORTED_METHODS = new Set([
5279
5350
  "matchAll",
5280
5351
  "search"
5281
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
+ };
5356
+ var LOWERED_ARRAY_METHODS = new Set([
5357
+ "includes",
5358
+ "indexOf",
5359
+ "lastIndexOf",
5360
+ "concat"
5361
+ ]);
5282
5362
  function parseExpression(expr) {
5283
5363
  const trimmed = expr.trim();
5284
5364
  if (!trimmed) {
@@ -5339,7 +5419,7 @@ function convertNode(node, raw) {
5339
5419
  }
5340
5420
  }
5341
5421
  if (callee.kind === "member" && !callee.computed) {
5342
- if (callee.property === "join" && args.length === 1) {
5422
+ if (callee.property === "join") {
5343
5423
  return { kind: "array-method", method: "join", object: callee.object, args };
5344
5424
  }
5345
5425
  if (callee.property === "includes" && args.length === 1) {
@@ -5348,27 +5428,103 @@ function convertNode(node, raw) {
5348
5428
  if ((callee.property === "indexOf" || callee.property === "lastIndexOf") && args.length === 1) {
5349
5429
  return { kind: "array-method", method: callee.property, object: callee.object, args };
5350
5430
  }
5351
- if (callee.property === "at" && args.length === 1) {
5431
+ if (callee.property === "at") {
5352
5432
  return { kind: "array-method", method: "at", object: callee.object, args };
5353
5433
  }
5354
- if (callee.property === "concat" && args.length === 1) {
5434
+ if (callee.property === "concat" && args.length <= 1) {
5355
5435
  return { kind: "array-method", method: "concat", object: callee.object, args };
5356
5436
  }
5357
- if (callee.property === "slice" && (args.length === 1 || args.length === 2)) {
5437
+ if (callee.property === "slice") {
5358
5438
  return { kind: "array-method", method: "slice", object: callee.object, args };
5359
5439
  }
5360
- if ((callee.property === "reverse" || callee.property === "toReversed") && args.length === 0) {
5440
+ if (callee.property === "reverse" || callee.property === "toReversed") {
5361
5441
  return { kind: "array-method", method: callee.property, object: callee.object, args };
5362
5442
  }
5363
- if (callee.property === "toLowerCase" && args.length === 0) {
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
+ }
5469
+ if (callee.property === "toLowerCase") {
5364
5470
  return { kind: "array-method", method: "toLowerCase", object: callee.object, args };
5365
5471
  }
5366
- if (callee.property === "toUpperCase" && args.length === 0) {
5472
+ if (callee.property === "toUpperCase") {
5367
5473
  return { kind: "array-method", method: "toUpperCase", object: callee.object, args };
5368
5474
  }
5369
- if (callee.property === "trim" && args.length === 0) {
5475
+ if (callee.property === "trim") {
5370
5476
  return { kind: "array-method", method: "trim", object: callee.object, args };
5371
5477
  }
5478
+ if (callee.property === "split") {
5479
+ return { kind: "array-method", method: "split", object: callee.object, args };
5480
+ }
5481
+ if (LOWERED_ARRAY_METHODS.has(callee.property)) {
5482
+ const argName = callee.property === "concat" ? "other" : "x";
5483
+ const detail = callee.property === "concat" ? "the variadic `.concat(a, b, …)` form" : `\`.${callee.property}(…)\` with ${args.length} argument(s)`;
5484
+ return {
5485
+ kind: "unsupported",
5486
+ raw,
5487
+ reason: `${detail} is not yet lowered to the Go/Mojo template adapters. Use the single-argument \`.${callee.property}(${argName})\` form, or pre-compute the value before the template.`
5488
+ };
5489
+ }
5490
+ if (callee.property === "startsWith" || callee.property === "endsWith") {
5491
+ if (args.length === 0) {
5492
+ return {
5493
+ kind: "unsupported",
5494
+ raw,
5495
+ reason: `\`.${callee.property}()\` with no search string is not lowered — JS coerces the missing argument to the string "undefined", a degenerate result. Pass an explicit search string, or pre-compute the value before the template.`
5496
+ };
5497
+ }
5498
+ return { kind: "array-method", method: callee.property, object: callee.object, args };
5499
+ }
5500
+ if (callee.property === "replace") {
5501
+ if (args.length < 2) {
5502
+ return {
5503
+ kind: "unsupported",
5504
+ raw,
5505
+ reason: `\`.replace(${args.length === 0 ? "" : "pattern"})\` needs both a pattern and a replacement — JS coerces the missing argument to the string "undefined", a degenerate result. Pass both arguments, or pre-compute the value before the template.`
5506
+ };
5507
+ }
5508
+ const patternNode = node.arguments[0];
5509
+ if (patternNode && ts8.isRegularExpressionLiteral(patternNode)) {
5510
+ return {
5511
+ kind: "unsupported",
5512
+ raw,
5513
+ reason: "String.prototype.replace supports only a string pattern + string replacement (the regex form is deferred); use a string pattern or wrap the expression in /* @client */"
5514
+ };
5515
+ }
5516
+ const badArg = args[0].kind === "unsupported" ? args[0] : args[1].kind === "unsupported" ? args[1] : undefined;
5517
+ if (badArg && badArg.kind === "unsupported") {
5518
+ return { kind: "unsupported", raw, reason: badArg.reason };
5519
+ }
5520
+ return { kind: "array-method", method: "replace", object: callee.object, args };
5521
+ }
5522
+ if (callee.property === "repeat") {
5523
+ return { kind: "array-method", method: "repeat", object: callee.object, args };
5524
+ }
5525
+ if (callee.property === "padStart" || callee.property === "padEnd") {
5526
+ return { kind: "array-method", method: callee.property, object: callee.object, args };
5527
+ }
5372
5528
  if ((callee.property === "sort" || callee.property === "toSorted") && node.arguments.length === 1) {
5373
5529
  const comparator = extractSortComparatorFromTS(node.arguments[0], callee.property);
5374
5530
  if (comparator) {
@@ -5393,6 +5549,50 @@ function convertNode(node, raw) {
5393
5549
  ` + `(reverse the operands for descending order). ` + `Wrap the call in /* @client */ to evaluate at hydration.`
5394
5550
  };
5395
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
+ }
5396
5596
  }
5397
5597
  return { kind: "call", callee, args };
5398
5598
  }
@@ -5711,6 +5911,119 @@ function classifySortOperand(expr, paramA, paramB) {
5711
5911
  }
5712
5912
  return null;
5713
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
+ }
5714
6027
  function collectDestructureBindings(pattern, pathPrefix, fieldMap, raw, excludedTopKeys) {
5715
6028
  let restName;
5716
6029
  for (const el of pattern.elements) {
@@ -6019,6 +6332,15 @@ function substituteDestructuredFields(expr, fieldMap, syntheticParam, restName)
6019
6332
  if (e.method === "sort" || e.method === "toSorted") {
6020
6333
  return { kind: "array-method", method: e.method, object: walk(e.object), args: [], comparator: e.comparator };
6021
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
+ }
6022
6344
  return { kind: "array-method", method: e.method, object: walk(e.object), args: e.args.map(walk) };
6023
6345
  case "literal":
6024
6346
  case "unsupported":
@@ -6139,7 +6461,8 @@ function checkSupport(expr) {
6139
6461
  return {
6140
6462
  supported: false,
6141
6463
  level: "L5_UNSUPPORTED",
6142
- reason: `Method '${methodName}()' has no template lowering and requires client-side evaluation. Wrap the expression in /* @client */ to defer it to hydration, or pre-compute the value before rendering.`
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).`
6143
6466
  };
6144
6467
  }
6145
6468
  }
@@ -6363,6 +6686,20 @@ function exprToString(expr) {
6363
6686
  const { paramA, paramB, raw } = expr.comparator;
6364
6687
  return `${exprToString(expr.object)}.${expr.method}((${paramA},${paramB}) => ${raw})`;
6365
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
+ }
6366
6703
  return `${exprToString(expr.object)}.${expr.method}(${expr.args.map(exprToString).join(", ")})`;
6367
6704
  case "unsupported":
6368
6705
  return `[UNSUPPORTED: ${expr.raw}]`;
@@ -6408,6 +6745,20 @@ function stringifyParsedExpr(expr) {
6408
6745
  const { paramA, paramB, raw } = expr.comparator;
6409
6746
  return `${stringifyParsedExpr(expr.object)}.${expr.method}((${paramA},${paramB}) => ${raw})`;
6410
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
+ }
6411
6762
  return `${stringifyParsedExpr(expr.object)}.${expr.method}(${expr.args.map(stringifyParsedExpr).join(", ")})`;
6412
6763
  case "unsupported":
6413
6764
  return expr.raw;
@@ -9944,7 +10295,7 @@ function collectInnerLoops(nodes, siblingOffsets, outerLoopParam, ctx, options)
9944
10295
  }
9945
10296
  function decideLoopRendering(loop, siblingOffsets, ctx) {
9946
10297
  const hasNestedComps = (loop.nestedComponents?.length ?? 0) > 0;
9947
- const innerLoops = !loop.childComponent ? collectInnerLoops(loop.children, siblingOffsets, loop.param, ctx) : undefined;
10298
+ const innerLoops = collectInnerLoops(loop.children, siblingOffsets, loop.param, ctx);
9948
10299
  const hasInnerLoops = (innerLoops?.length ?? 0) > 0;
9949
10300
  const useElementReconciliation = !loop.childComponent && !loop.isStaticArray && (hasNestedComps || hasInnerLoops);
9950
10301
  return { useElementReconciliation, innerLoops };
@@ -11814,7 +12165,7 @@ function emitRegistrationAndHydration(lines, ctx, _ir, graph, inlinability) {
11814
12165
  }
11815
12166
  } else {
11816
12167
  const csrInlinableConstants = csrInlinableConstantsFromCtx(ctx);
11817
- const templateHtml = generateCsrTemplate(_ir.root, csrInlinableConstants, ctx, undefined, restSpreadNames, ctx.propsObjectName, unsafeLocalNames);
12168
+ const templateHtml = generateCsrTemplate(_ir.root, csrInlinableConstants, ctx, undefined, restSpreadNames, ctx.propsObjectName, unsafeLocalNames, ctx.deferredChildSlots);
11818
12169
  if (templateHtml) {
11819
12170
  defParts.push(`template: (${PROPS_PARAM}) => \`${templateHtml}\``);
11820
12171
  }
@@ -12505,8 +12856,13 @@ function emitProviderAndChildInits(lines, ctx) {
12505
12856
  lines.push("");
12506
12857
  lines.push(` // Initialize child components with props`);
12507
12858
  for (const child of ctx.childInits) {
12859
+ const registryName = nameForRegistryRef(child.name);
12860
+ if (child.slotId && ctx.deferredChildSlots.has(child.slotId)) {
12861
+ lines.push(` upsertChild(__scope, '${registryName}', '${child.slotId}', ${child.propsExpr})`);
12862
+ continue;
12863
+ }
12508
12864
  const scopeRef = child.slotId ? `_${varSlotId(child.slotId)}` : "__scope";
12509
- lines.push(` initChild('${nameForRegistryRef(child.name)}', ${scopeRef}, ${child.propsExpr})`);
12865
+ lines.push(` initChild('${registryName}', ${scopeRef}, ${child.propsExpr})`);
12510
12866
  }
12511
12867
  }
12512
12868
  }
@@ -12755,7 +13111,7 @@ function buildStaticArrayChildInitsPlan(ctx) {
12755
13111
  const innerComps = elem.nestedComponents.filter((c) => (c.loopDepth ?? 0) === innerLoop.depth && c.innerLoopArray === innerLoop.array);
12756
13112
  if (innerComps.length === 0)
12757
13113
  continue;
12758
- plans.push(buildInnerLoopNestedPlan(elem, innerLoop, innerComps));
13114
+ plans.push(elem.childComponent ? buildComponentRootedInnerLoopPlan(elem, innerLoop, innerComps) : buildInnerLoopNestedPlan(elem, innerLoop, innerComps));
12759
13115
  }
12760
13116
  }
12761
13117
  }
@@ -12816,6 +13172,25 @@ function buildInnerLoopNestedPlan(elem, innerLoop, innerComps) {
12816
13172
  comps
12817
13173
  };
12818
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
+ }
12819
13194
  function buildStaticPropsExpr(props) {
12820
13195
  const entries = props.map((p) => {
12821
13196
  if (p.isEventHandler) {
@@ -12855,6 +13230,9 @@ function stringifyOne(lines, plan) {
12855
13230
  case "inner-loop-nested":
12856
13231
  emitInnerLoopNested(lines, plan);
12857
13232
  break;
13233
+ case "component-rooted-inner-loop":
13234
+ emitComponentRootedInnerLoop(lines, plan);
13235
+ break;
12858
13236
  }
12859
13237
  }
12860
13238
  function emitSingleComp(lines, plan) {
@@ -12934,6 +13312,44 @@ function emitInnerLoopNested(lines, plan) {
12934
13312
  lines.push(` }`);
12935
13313
  lines.push("");
12936
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
+ }
12937
13353
 
12938
13354
  // src/ir-to-client-js/phases/static-array-child-inits.ts
12939
13355
  function emitStaticArrayChildInits(lines, ctx) {
@@ -15306,6 +15722,7 @@ function generateInitFunction(ir, ctx, siblingComponents, localImportPrefixes) {
15306
15722
  const classification = classifyLocalDeclarations(ctx, graph);
15307
15723
  const propUsage = computePropUsage(ctx, classification.neededConstants);
15308
15724
  const inlinability = buildInlinableConstants(ctx, graph, ir.root);
15725
+ ctx.deferredChildSlots = computeDeferredChildSlots(ir.root, ctx, csrInlinableConstantsFromCtx(ctx), inlinability.unsafeLocalNames, ctx.propsObjectName);
15309
15726
  const phaseCtx = buildPhaseCtx({
15310
15727
  ctx,
15311
15728
  ir,
@@ -15574,6 +15991,7 @@ function createContext(ir, scope, adapterCapabilities) {
15574
15991
  loopElements: [],
15575
15992
  refElements: [],
15576
15993
  childInits: [],
15994
+ deferredChildSlots: new Set,
15577
15995
  reactiveProps: [],
15578
15996
  reactiveChildProps: [],
15579
15997
  reactiveAttrs: [],
@@ -17210,6 +17628,15 @@ function emitParsedExpr(expr, emitter) {
17210
17628
  if (expr.method === "sort" || expr.method === "toSorted") {
17211
17629
  return emitter.sortMethod(expr.method, expr.object, expr.comparator, emit);
17212
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
+ }
17213
17640
  return emitter.arrayMethod(expr.method, expr.object, expr.args, emit);
17214
17641
  case "unsupported":
17215
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;AA+JD;;;;;;;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"}
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"}
@@ -1 +1 @@
1
- {"version":3,"file":"generate-init.d.ts","sourceRoot":"","sources":["../../src/ir-to-client-js/generate-init.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAC3C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAc9C,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,WAAW,EACf,GAAG,EAAE,eAAe,EACpB,iBAAiB,CAAC,EAAE,MAAM,EAAE,EAC5B,mBAAmB,CAAC,EAAE,MAAM,EAAE,GAC7B,MAAM,CAmER"}
1
+ {"version":3,"file":"generate-init.d.ts","sourceRoot":"","sources":["../../src/ir-to-client-js/generate-init.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAC3C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAe9C,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,WAAW,EACf,GAAG,EAAE,eAAe,EACpB,iBAAiB,CAAC,EAAE,MAAM,EAAE,EAC5B,mBAAmB,CAAC,EAAE,MAAM,EAAE,GAC7B,MAAM,CAiFR"}