@barefootjs/jsx 0.17.1 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/adapters/interface.d.ts +20 -4
  2. package/dist/adapters/interface.d.ts.map +1 -1
  3. package/dist/adapters/parsed-expr-emitter.d.ts +3 -1
  4. package/dist/adapters/parsed-expr-emitter.d.ts.map +1 -1
  5. package/dist/analyzer-context.d.ts.map +1 -1
  6. package/dist/analyzer.d.ts.map +1 -1
  7. package/dist/augment-inherited-props.d.ts +19 -0
  8. package/dist/augment-inherited-props.d.ts.map +1 -1
  9. package/dist/compiler.d.ts.map +1 -1
  10. package/dist/expression-parser.d.ts +1 -0
  11. package/dist/expression-parser.d.ts.map +1 -1
  12. package/dist/index.d.ts +19 -3
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +815 -251
  15. package/dist/ir-to-client-js/collect-elements.d.ts.map +1 -1
  16. package/dist/ir-to-client-js/compute-inlinability.d.ts.map +1 -1
  17. package/dist/ir-to-client-js/control-flow/plan/build-loop.d.ts.map +1 -1
  18. package/dist/ir-to-client-js/control-flow/plan/loop.d.ts +9 -0
  19. package/dist/ir-to-client-js/control-flow/plan/loop.d.ts.map +1 -1
  20. package/dist/ir-to-client-js/control-flow/stringify/loop.d.ts.map +1 -1
  21. package/dist/ir-to-client-js/control-flow/stringify/template-parse.d.ts +19 -0
  22. package/dist/ir-to-client-js/control-flow/stringify/template-parse.d.ts.map +1 -1
  23. package/dist/ir-to-client-js/emit-registration.d.ts +4 -2
  24. package/dist/ir-to-client-js/emit-registration.d.ts.map +1 -1
  25. package/dist/ir-to-client-js/html-template.d.ts +41 -0
  26. package/dist/ir-to-client-js/html-template.d.ts.map +1 -1
  27. package/dist/ir-to-client-js/types.d.ts +22 -1
  28. package/dist/ir-to-client-js/types.d.ts.map +1 -1
  29. package/dist/jsx-to-ir.d.ts.map +1 -1
  30. package/dist/loop-destructure.d.ts +55 -18
  31. package/dist/loop-destructure.d.ts.map +1 -1
  32. package/dist/lowering-registry.d.ts +13 -0
  33. package/dist/lowering-registry.d.ts.map +1 -1
  34. package/dist/relocate.d.ts +28 -0
  35. package/dist/relocate.d.ts.map +1 -1
  36. package/dist/ssr-defaults.d.ts.map +1 -1
  37. package/dist/types.d.ts +68 -0
  38. package/dist/types.d.ts.map +1 -1
  39. package/package.json +2 -2
  40. package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +284 -12
  41. package/src/__tests__/augment-inherited-props.test.ts +96 -0
  42. package/src/__tests__/compiler-runtime-contract.test.ts +11 -1
  43. package/src/__tests__/compiler-stress-1244.test.ts +13 -4
  44. package/src/__tests__/csr-substitution-safety-divergence.test.ts +137 -0
  45. package/src/__tests__/destructured-map-params.test.ts +11 -1
  46. package/src/__tests__/expression-parser.test.ts +63 -2
  47. package/src/__tests__/ir-sort-comparator.test.ts +261 -0
  48. package/src/__tests__/loop-destructure.test.ts +313 -0
  49. package/src/__tests__/loop-hoisted-template.test.ts +235 -0
  50. package/src/__tests__/props-destructuring.test.ts +110 -0
  51. package/src/__tests__/serialize-parsed-expr.test.ts +70 -3
  52. package/src/__tests__/ssr-defaults.test.ts +20 -0
  53. package/src/__tests__/staged-ir/11-template-primitive-registry.test.ts +231 -1
  54. package/src/__tests__/tagged-template-interleave.test.ts +268 -0
  55. package/src/__tests__/unsupported-expression.test.ts +194 -7
  56. package/src/adapters/interface.ts +20 -4
  57. package/src/adapters/parsed-expr-emitter.ts +19 -2
  58. package/src/analyzer-context.ts +20 -0
  59. package/src/analyzer.ts +74 -1
  60. package/src/augment-inherited-props.ts +139 -9
  61. package/src/compiler.ts +4 -0
  62. package/src/expression-parser.ts +237 -56
  63. package/src/index.ts +23 -1
  64. package/src/ir-to-client-js/collect-elements.ts +15 -1
  65. package/src/ir-to-client-js/compute-inlinability.ts +6 -1
  66. package/src/ir-to-client-js/control-flow/plan/build-loop.ts +1 -0
  67. package/src/ir-to-client-js/control-flow/plan/loop.ts +9 -0
  68. package/src/ir-to-client-js/control-flow/stringify/loop.ts +30 -8
  69. package/src/ir-to-client-js/control-flow/stringify/template-parse.ts +30 -0
  70. package/src/ir-to-client-js/emit-registration.ts +4 -2
  71. package/src/ir-to-client-js/html-template.ts +198 -1
  72. package/src/ir-to-client-js/index.ts +1 -0
  73. package/src/ir-to-client-js/types.ts +22 -0
  74. package/src/jsx-to-ir.ts +409 -24
  75. package/src/loop-destructure.ts +89 -36
  76. package/src/lowering-registry.ts +16 -0
  77. package/src/relocate.ts +201 -14
  78. package/src/ssr-defaults.ts +34 -32
  79. package/src/types.ts +65 -0
package/dist/index.js CHANGED
@@ -441,6 +441,7 @@ function convertNode(node, raw) {
441
441
  if (callee.property === "flat") {
442
442
  const depthNode = node.arguments[0];
443
443
  let flatDepth;
444
+ let depthExpr;
444
445
  if (depthNode === undefined) {
445
446
  flatDepth = 1;
446
447
  } else if (ts2.isIdentifier(depthNode) && depthNode.text === "Infinity") {
@@ -453,16 +454,23 @@ function convertNode(node, raw) {
453
454
  n = -Number(depthNode.operand.text);
454
455
  }
455
456
  if (n === undefined || Number.isNaN(n)) {
456
- return {
457
- kind: "unsupported",
458
- raw,
459
- 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.`
460
- };
457
+ const parsedDepth = convertNode(depthNode, raw);
458
+ if (checkSupport(parsedDepth).supported) {
459
+ depthExpr = parsedDepth;
460
+ flatDepth = 1;
461
+ } else {
462
+ return {
463
+ kind: "unsupported",
464
+ raw,
465
+ reason: `\`.flat(depth)\` needs a literal integer, \`Infinity\`, or a supported dynamic depth expression — this depth can't be resolved. Use a literal depth, a supported expression (prop/signal/arithmetic), or pre-compute the value before the template.`
466
+ };
467
+ }
468
+ } else {
469
+ const truncated = Math.trunc(n);
470
+ flatDepth = truncated < 0 ? 0 : truncated;
461
471
  }
462
- const truncated = Math.trunc(n);
463
- flatDepth = truncated < 0 ? 0 : truncated;
464
472
  }
465
- return { kind: "array-method", method: "flat", object: callee.object, args: [], flatDepth };
473
+ return depthExpr !== undefined ? { kind: "array-method", method: "flat", object: callee.object, args: [], flatDepth, depthExpr } : { kind: "array-method", method: "flat", object: callee.object, args: [], flatDepth };
466
474
  }
467
475
  if (callee.property === "toLowerCase") {
468
476
  return { kind: "array-method", method: "toLowerCase", object: callee.object, args };
@@ -1007,6 +1015,8 @@ function validateRestUsage(expr, restName, excludedTopKeys) {
1007
1015
  walk(e.object);
1008
1016
  for (const a of e.args)
1009
1017
  walk(a);
1018
+ if (e.method === "flat" && e.depthExpr)
1019
+ walk(e.depthExpr);
1010
1020
  return;
1011
1021
  case "literal":
1012
1022
  case "unsupported":
@@ -1092,6 +1102,8 @@ function collectIdentifiers(expr, out) {
1092
1102
  case "array-method":
1093
1103
  collectIdentifiers(expr.object, out);
1094
1104
  expr.args.forEach((e) => collectIdentifiers(e, out));
1105
+ if (expr.method === "flat" && expr.depthExpr)
1106
+ collectIdentifiers(expr.depthExpr, out);
1095
1107
  return;
1096
1108
  case "literal":
1097
1109
  case "regex":
@@ -1149,7 +1161,14 @@ function substituteDestructuredFields(expr, fieldMap, syntheticParam, restName)
1149
1161
  return { kind: "array-literal", elements: e.elements.map(walk) };
1150
1162
  case "array-method":
1151
1163
  if (e.method === "flat") {
1152
- return { kind: "array-method", method: "flat", object: walk(e.object), args: [], flatDepth: e.flatDepth };
1164
+ return {
1165
+ kind: "array-method",
1166
+ method: "flat",
1167
+ object: walk(e.object),
1168
+ args: [],
1169
+ flatDepth: e.flatDepth,
1170
+ ...e.depthExpr ? { depthExpr: walk(e.depthExpr) } : {}
1171
+ };
1153
1172
  }
1154
1173
  return { kind: "array-method", method: e.method, object: walk(e.object), args: e.args.map(walk) };
1155
1174
  case "literal":
@@ -1246,6 +1265,11 @@ function checkSupport(expr) {
1246
1265
  if (!argSupport.supported)
1247
1266
  return argSupport;
1248
1267
  }
1268
+ if (expr.method === "flat" && expr.depthExpr) {
1269
+ const depthSupport = checkSupport(expr.depthExpr);
1270
+ if (!depthSupport.supported)
1271
+ return depthSupport;
1272
+ }
1249
1273
  return { supported: true, level: "L2" };
1250
1274
  }
1251
1275
  case "call": {
@@ -1345,6 +1369,9 @@ function checkSupport(expr) {
1345
1369
  const leftSupport = checkSupport(expr.left);
1346
1370
  if (!leftSupport.supported)
1347
1371
  return leftSupport;
1372
+ if (expr.op === "??" && expr.right.kind === "object-literal" && expr.right.properties.length === 0) {
1373
+ return { supported: true, level: "L4" };
1374
+ }
1348
1375
  const rightSupport = checkSupport(expr.right);
1349
1376
  if (!rightSupport.supported)
1350
1377
  return rightSupport;
@@ -1399,7 +1426,7 @@ function containsHigherOrder(expr) {
1399
1426
  case "array-literal":
1400
1427
  return expr.elements.some(containsHigherOrder);
1401
1428
  case "array-method":
1402
- return containsHigherOrder(expr.object) || expr.args.some(containsHigherOrder);
1429
+ return containsHigherOrder(expr.object) || expr.args.some(containsHigherOrder) || expr.method === "flat" && expr.depthExpr !== undefined && containsHigherOrder(expr.depthExpr);
1403
1430
  default:
1404
1431
  return false;
1405
1432
  }
@@ -1567,7 +1594,10 @@ function usesPerPath(name, expr) {
1567
1594
  case "array-literal":
1568
1595
  return sum(e.elements);
1569
1596
  case "array-method":
1570
- return add(walk(e.object), e.method === "flat" ? { min: 0, max: 0 } : sum(e.args));
1597
+ if (e.method === "flat") {
1598
+ return add(walk(e.object), e.depthExpr ? walk(e.depthExpr) : { min: 0, max: 0 });
1599
+ }
1600
+ return add(walk(e.object), sum(e.args));
1571
1601
  case "object-literal":
1572
1602
  return sum(e.properties.map((p) => p.value));
1573
1603
  case "arrow":
@@ -1622,7 +1652,14 @@ function inlineBinding(expr, name, value) {
1622
1652
  return { kind: "array-literal", elements: e.elements.map((el) => walk(el, enclosing)) };
1623
1653
  case "array-method":
1624
1654
  if (e.method === "flat") {
1625
- return { kind: "array-method", method: "flat", object: walk(e.object, enclosing), args: [], flatDepth: e.flatDepth };
1655
+ return {
1656
+ kind: "array-method",
1657
+ method: "flat",
1658
+ object: walk(e.object, enclosing),
1659
+ args: [],
1660
+ flatDepth: e.flatDepth,
1661
+ ...e.depthExpr ? { depthExpr: walk(e.depthExpr, enclosing) } : {}
1662
+ };
1626
1663
  }
1627
1664
  return { kind: "array-method", method: e.method, object: walk(e.object, enclosing), args: e.args.map((a) => walk(a, enclosing)) };
1628
1665
  case "object-literal":
@@ -1753,6 +1790,9 @@ function exprToString(expr) {
1753
1790
  return `[${expr.elements.map(exprToString).join(", ")}]`;
1754
1791
  case "array-method":
1755
1792
  if (expr.method === "flat") {
1793
+ if (expr.depthExpr) {
1794
+ return `${exprToString(expr.object)}.flat(${exprToString(expr.depthExpr)})`;
1795
+ }
1756
1796
  const d = expr.flatDepth;
1757
1797
  const depthSrc = d === "infinity" ? "Infinity" : String(d);
1758
1798
  return `${exprToString(expr.object)}.flat(${d === 1 ? "" : depthSrc})`;
@@ -1804,6 +1844,9 @@ function stringifyParsedExpr(expr) {
1804
1844
  return `[${expr.elements.map(stringifyParsedExpr).join(", ")}]`;
1805
1845
  case "array-method":
1806
1846
  if (expr.method === "flat") {
1847
+ if (expr.depthExpr) {
1848
+ return `${stringifyParsedExpr(expr.object)}.flat(${stringifyParsedExpr(expr.depthExpr)})`;
1849
+ }
1807
1850
  const d = expr.flatDepth;
1808
1851
  const depthSrc = d === "infinity" ? "Infinity" : String(d);
1809
1852
  return `${stringifyParsedExpr(expr.object)}.flat(${d === 1 ? "" : depthSrc})`;
@@ -1847,8 +1890,9 @@ function materializeGetterCalls(expr, names) {
1847
1890
  case "array-literal":
1848
1891
  return { kind: "array-literal", elements: expr.elements.map(rw) };
1849
1892
  case "array-method":
1850
- if (expr.method === "flat")
1851
- return { ...expr, object: rw(expr.object) };
1893
+ if (expr.method === "flat") {
1894
+ return { ...expr, object: rw(expr.object), ...expr.depthExpr ? { depthExpr: rw(expr.depthExpr) } : {} };
1895
+ }
1852
1896
  return { ...expr, object: rw(expr.object), args: expr.args.map(rw) };
1853
1897
  case "object-literal":
1854
1898
  return {
@@ -1871,63 +1915,67 @@ function serializeParsedExpr(expr) {
1871
1915
  }
1872
1916
  function freeVarsInBody(body, params) {
1873
1917
  const found = new Set;
1874
- const visit = (e) => {
1918
+ const visit = (e, bound) => {
1875
1919
  switch (e.kind) {
1876
1920
  case "identifier":
1877
- if (!params.has(e.name))
1921
+ if (!bound.has(e.name))
1878
1922
  found.add(e.name);
1879
1923
  return;
1880
1924
  case "binary":
1881
1925
  case "logical":
1882
- visit(e.left);
1883
- visit(e.right);
1926
+ visit(e.left, bound);
1927
+ visit(e.right, bound);
1884
1928
  return;
1885
1929
  case "unary":
1886
- visit(e.argument);
1930
+ visit(e.argument, bound);
1887
1931
  return;
1888
1932
  case "conditional":
1889
- visit(e.test);
1890
- visit(e.consequent);
1891
- visit(e.alternate);
1933
+ visit(e.test, bound);
1934
+ visit(e.consequent, bound);
1935
+ visit(e.alternate, bound);
1892
1936
  return;
1893
1937
  case "member":
1894
- visit(e.object);
1938
+ visit(e.object, bound);
1895
1939
  return;
1896
1940
  case "index-access":
1897
- visit(e.object);
1898
- visit(e.index);
1941
+ visit(e.object, bound);
1942
+ visit(e.index, bound);
1899
1943
  return;
1900
1944
  case "call":
1901
1945
  if (evalBuiltinCalleeName(e.callee) === null)
1902
- visit(e.callee);
1903
- e.args.forEach(visit);
1946
+ visit(e.callee, bound);
1947
+ e.args.forEach((a) => visit(a, bound));
1904
1948
  return;
1905
1949
  case "template-literal":
1906
1950
  for (const p of e.parts)
1907
1951
  if (p.type === "expression")
1908
- visit(p.expr);
1952
+ visit(p.expr, bound);
1909
1953
  return;
1910
1954
  case "array-literal":
1911
- e.elements.forEach(visit);
1955
+ e.elements.forEach((el) => visit(el, bound));
1912
1956
  return;
1913
1957
  case "object-literal":
1914
1958
  for (const p of e.properties)
1915
- visit(p.value);
1959
+ visit(p.value, bound);
1916
1960
  return;
1917
1961
  case "array-method":
1918
- if (e.method === "includes") {
1919
- visit(e.object);
1920
- e.args.forEach(visit);
1962
+ if (e.method === "includes" || e.method === "join") {
1963
+ visit(e.object, bound);
1964
+ e.args.forEach((a) => visit(a, bound));
1921
1965
  }
1922
1966
  return;
1967
+ case "arrow": {
1968
+ const inner = e.params.length === 0 ? bound : new Set([...bound, ...e.params]);
1969
+ visit(e.body, inner);
1970
+ return;
1971
+ }
1923
1972
  case "literal":
1924
- case "arrow":
1925
1973
  case "regex":
1926
1974
  case "unsupported":
1927
1975
  return;
1928
1976
  }
1929
1977
  };
1930
- visit(body);
1978
+ visit(body, params);
1931
1979
  return [...found].sort();
1932
1980
  }
1933
1981
  function freeIdentifiers(expr) {
@@ -1978,6 +2026,8 @@ function freeIdentifiers(expr) {
1978
2026
  for (const a of e.args)
1979
2027
  if (!visit(a, bound))
1980
2028
  return false;
2029
+ if (e.method === "flat" && e.depthExpr && !visit(e.depthExpr, bound))
2030
+ return false;
1981
2031
  return true;
1982
2032
  case "object-literal":
1983
2033
  for (const p of e.properties)
@@ -2073,6 +2123,20 @@ function toEvalNode(e) {
2073
2123
  return object && index ? { kind: "index-access", object, index } : null;
2074
2124
  }
2075
2125
  case "call": {
2126
+ const cb = asCallbackMethodCall(e);
2127
+ if (cb && (cb.method === "map" || cb.method === "filter")) {
2128
+ const object = toEvalNode(cb.object);
2129
+ if (!object)
2130
+ return null;
2131
+ const body = toEvalNode(cb.arrow.body);
2132
+ if (!body)
2133
+ return null;
2134
+ return {
2135
+ kind: "call",
2136
+ callee: { kind: "member", object, property: cb.method, computed: false },
2137
+ args: [{ kind: "arrow", params: cb.arrow.params, body }]
2138
+ };
2139
+ }
2076
2140
  if (evalBuiltinCalleeName(e.callee) === null)
2077
2141
  return null;
2078
2142
  const callee = toEvalNode(e.callee);
@@ -2127,6 +2191,15 @@ function toEvalNode(e) {
2127
2191
  const arg = toEvalNode(e.args[0]);
2128
2192
  return object && arg ? { kind: "array-method", method: "includes", object, args: [arg] } : null;
2129
2193
  }
2194
+ if (e.method === "join" && e.args.length <= 1) {
2195
+ const object = toEvalNode(e.object);
2196
+ if (!object)
2197
+ return null;
2198
+ if (e.args.length === 0)
2199
+ return { kind: "array-method", method: "join", object, args: [] };
2200
+ const sep = toEvalNode(e.args[0]);
2201
+ return sep ? { kind: "array-method", method: "join", object, args: [sep] } : null;
2202
+ }
2130
2203
  return null;
2131
2204
  }
2132
2205
  case "arrow":
@@ -3282,6 +3355,89 @@ function irToHtmlTemplate(node, restSpreadNames, loopDepth = 0, loopParams, bran
3282
3355
  return assertNever(node);
3283
3356
  }
3284
3357
  }
3358
+ function buildLoopSkeletonTemplate(node, safe) {
3359
+ switch (node.type) {
3360
+ case "element": {
3361
+ const attrParts = [];
3362
+ for (const a of node.attrs) {
3363
+ if (a.name === "...")
3364
+ return null;
3365
+ if (a.name === "dangerouslySetInnerHTML")
3366
+ return null;
3367
+ if (a.name === "key") {
3368
+ attrParts.push(`${keyAttrName(0)}=""`);
3369
+ continue;
3370
+ }
3371
+ const v = a.value;
3372
+ switch (v.kind) {
3373
+ case "literal":
3374
+ attrParts.push(`${toHtmlAttrName(a.name)}="${v.value}"`);
3375
+ break;
3376
+ case "boolean-attr":
3377
+ attrParts.push(toHtmlAttrName(a.name));
3378
+ break;
3379
+ case "boolean-shorthand":
3380
+ case "jsx-children":
3381
+ break;
3382
+ case "expression":
3383
+ case "template": {
3384
+ const attrKey = node.slotId ? `${node.slotId}::${a.name}` : null;
3385
+ if (!attrKey || !safe.reactiveAttrKeys.has(attrKey))
3386
+ return null;
3387
+ break;
3388
+ }
3389
+ case "spread":
3390
+ return null;
3391
+ }
3392
+ }
3393
+ if (node.slotId)
3394
+ attrParts.push(`bf="${node.slotId}"`);
3395
+ const attrs = attrParts.join(" ");
3396
+ let children = "";
3397
+ for (const child of node.children) {
3398
+ const rendered = buildLoopSkeletonTemplate(child, safe);
3399
+ if (rendered === null)
3400
+ return null;
3401
+ children += rendered;
3402
+ }
3403
+ if (children || !VOID_ELEMENTS.has(node.tag)) {
3404
+ return `<${node.tag}${attrs ? " " + attrs : ""}>${children}</${node.tag}>`;
3405
+ }
3406
+ return `<${node.tag}${attrs ? " " + attrs : ""} />`;
3407
+ }
3408
+ case "text":
3409
+ return node.value;
3410
+ case "expression":
3411
+ if (node.expr === "null" || node.expr === "undefined")
3412
+ return "";
3413
+ if (!node.slotId) {
3414
+ return null;
3415
+ }
3416
+ if (!safe.reactiveTextSlotIds.has(node.slotId))
3417
+ return null;
3418
+ return `<!--bf:${node.slotId}--><!--/-->`;
3419
+ case "fragment": {
3420
+ let out = "";
3421
+ for (const child of node.children) {
3422
+ const rendered = buildLoopSkeletonTemplate(child, safe);
3423
+ if (rendered === null)
3424
+ return null;
3425
+ out += rendered;
3426
+ }
3427
+ return out;
3428
+ }
3429
+ case "conditional":
3430
+ case "component":
3431
+ case "loop":
3432
+ case "if-statement":
3433
+ case "provider":
3434
+ case "async":
3435
+ case "slot":
3436
+ return null;
3437
+ default:
3438
+ return assertNever(node);
3439
+ }
3440
+ }
3285
3441
  function irToPlaceholderTemplate(node, restSpreadNames, loopDepth = 0, loopParams) {
3286
3442
  const recurse = (n) => irToPlaceholderTemplate(n, restSpreadNames, loopDepth, loopParams);
3287
3443
  const wrapExpr = (expr) => wrapExprWithLoopParams(expr, loopParams);
@@ -3681,7 +3837,23 @@ function generateCsrTemplate(node, inlinableConstants, ctx, insideLoop, restSpre
3681
3837
  }
3682
3838
  }
3683
3839
  }
3684
- return generateCsrTemplateWithOpts(node, { inlinableConstants, restSpreadNames, propsObjectName, csrEnv, insideLoop, unsafeLocalNames, deferredChildSlots, loopDepth: -1 });
3840
+ const effectiveUnsafeLocalNames = mergeCsrNullUnsafe(ctx, unsafeLocalNames);
3841
+ return generateCsrTemplateWithOpts(node, { inlinableConstants, restSpreadNames, propsObjectName, csrEnv, insideLoop, unsafeLocalNames: effectiveUnsafeLocalNames, deferredChildSlots, loopDepth: -1 });
3842
+ }
3843
+ function mergeCsrNullUnsafe(ctx, unsafeLocalNames) {
3844
+ let merged = null;
3845
+ let exemptNames = null;
3846
+ for (const [name, entry] of ctx.csrInlinable) {
3847
+ if (entry !== null || unsafeLocalNames?.has(name))
3848
+ continue;
3849
+ exemptNames ??= new Set(ctx.localConstants.filter((c) => c.isJsx || c.systemConstructKind).map((c) => c.name));
3850
+ if (exemptNames.has(name))
3851
+ continue;
3852
+ if (!merged)
3853
+ merged = new Set(unsafeLocalNames ?? []);
3854
+ merged.add(name);
3855
+ }
3856
+ return merged ?? unsafeLocalNames;
3685
3857
  }
3686
3858
  function buildCsrEnvForCtx(ctx, inlinableConstants, propsObjectName) {
3687
3859
  const base = buildSignalMemoEnv(ctx.signals, ctx.memos, propsObjectName ?? null);
@@ -4269,6 +4441,15 @@ function createAnalyzerContext(sourceFile, filePath) {
4269
4441
  checker: null,
4270
4442
  componentBodyBlock: null,
4271
4443
  getJS(node) {
4444
+ let ownSourceFile;
4445
+ try {
4446
+ ownSourceFile = node.getSourceFile();
4447
+ } catch {
4448
+ ownSourceFile = undefined;
4449
+ }
4450
+ if (ownSourceFile && ownSourceFile !== sourceFile) {
4451
+ return node.getText(ownSourceFile);
4452
+ }
4272
4453
  return reconstructWithoutTypes(node, sourceFile, this.typeExcludeRanges);
4273
4454
  }
4274
4455
  };
@@ -6622,6 +6803,7 @@ function extractProps(param, ctx) {
6622
6803
  loc: getSourceLocation(param, ctx.sourceFile, ctx.filePath),
6623
6804
  hasIgnoreDirective: ignored
6624
6805
  };
6806
+ const memberTypes = param.type ? collectMemberTypes(param.type, ctx) : null;
6625
6807
  for (const element of param.name.elements) {
6626
6808
  if (ts8.isBindingElement(element) && ts8.isIdentifier(element.name)) {
6627
6809
  const localName = element.name.text;
@@ -6630,10 +6812,12 @@ function extractProps(param, ctx) {
6630
6812
  ctx.restPropsName = localName;
6631
6813
  continue;
6632
6814
  }
6815
+ const sourcePropName = element.propertyName && ts8.isIdentifier(element.propertyName) ? element.propertyName.text : localName;
6816
+ const resolvedType = memberTypes?.get(sourcePropName) ?? { kind: "unknown", raw: "unknown" };
6633
6817
  const defaultContainsArrow = element.initializer ? nodeContainsArrow(element.initializer) : false;
6634
6818
  ctx.propsParams.push({
6635
6819
  name: localName,
6636
- type: { kind: "unknown", raw: "unknown" },
6820
+ type: resolvedType,
6637
6821
  optional: !!element.initializer,
6638
6822
  defaultValue,
6639
6823
  defaultContainsArrow: defaultContainsArrow || undefined
@@ -6694,6 +6878,37 @@ function collectKeysFromMembers(members, ctx) {
6694
6878
  }
6695
6879
  return keys;
6696
6880
  }
6881
+ function collectMemberTypes(typeNode, ctx) {
6882
+ const isResolvablePrimitive = (info) => info.kind === "primitive" && (info.primitive === "string" || info.primitive === "number" || info.primitive === "boolean");
6883
+ const fromMembers = (members) => {
6884
+ const map = new Map;
6885
+ for (const member of members) {
6886
+ if (ts8.isPropertySignature(member) && member.name && member.type && !member.questionToken) {
6887
+ const info = typeNodeToTypeInfo(member.type, ctx.sourceFile);
6888
+ if (info && isResolvablePrimitive(info)) {
6889
+ map.set(member.name.getText(ctx.sourceFile), info);
6890
+ }
6891
+ }
6892
+ }
6893
+ return map;
6894
+ };
6895
+ if (ts8.isTypeLiteralNode(typeNode)) {
6896
+ return fromMembers(typeNode.members);
6897
+ }
6898
+ if (ts8.isTypeReferenceNode(typeNode)) {
6899
+ const typeName = typeNode.typeName.getText(ctx.sourceFile);
6900
+ const typeDecl = findTypeDeclaration(typeName, ctx.sourceFile);
6901
+ if (!typeDecl)
6902
+ return null;
6903
+ if (ts8.isInterfaceDeclaration(typeDecl)) {
6904
+ return fromMembers(typeDecl.members);
6905
+ }
6906
+ if (ts8.isTypeAliasDeclaration(typeDecl) && ts8.isTypeLiteralNode(typeDecl.type)) {
6907
+ return fromMembers(typeDecl.type.members);
6908
+ }
6909
+ }
6910
+ return null;
6911
+ }
6697
6912
  function extractPropsFromType(typeNode, ctx) {
6698
6913
  if (ts8.isTypeLiteralNode(typeNode)) {
6699
6914
  extractPropsFromTypeMembers(typeNode.members, ctx);
@@ -7388,6 +7603,119 @@ function pickAttrMetaFromIR(src) {
7388
7603
  };
7389
7604
  }
7390
7605
 
7606
+ // src/module-exports.ts
7607
+ function generateModuleExports(ir, extraInlineExported = new Set, rewriteRelativeImport) {
7608
+ const lines = [];
7609
+ for (const constant of ir.metadata.localConstants) {
7610
+ if (!constant.isExported)
7611
+ continue;
7612
+ const keyword = constant.declarationKind ?? "const";
7613
+ if (!constant.value) {
7614
+ lines.push(`export ${keyword} ${constant.name}`);
7615
+ continue;
7616
+ }
7617
+ const value = constant.value.trim();
7618
+ if (/^createContext\b/.test(value) || /^new WeakMap\b/.test(value))
7619
+ continue;
7620
+ lines.push(`export ${keyword} ${constant.name} = ${constant.value}`);
7621
+ }
7622
+ for (const func of ir.metadata.localFunctions) {
7623
+ if (!func.isExported)
7624
+ continue;
7625
+ const params = func.typedParams !== undefined ? func.typedParams : func.params.map(formatParamWithType).join(", ");
7626
+ const returnAnnotation = func.typedReturnType ? `: ${func.typedReturnType}` : "";
7627
+ const body = func.typedBody ?? func.body;
7628
+ const asyncKw = func.isAsync ? "async " : "";
7629
+ lines.push(`export ${asyncKw}function ${func.name}(${params})${returnAnnotation} ${body}`);
7630
+ }
7631
+ const inlineExported = collectInlineExportedNames(ir);
7632
+ for (const name of extraInlineExported)
7633
+ inlineExported.add(name);
7634
+ for (const block of ir.metadata.namedExports) {
7635
+ const isReexportFrom = block.source !== null;
7636
+ const survivingSpecs = block.specifiers.filter((spec) => {
7637
+ if (isReexportFrom)
7638
+ return true;
7639
+ return !(inlineExported.has(spec.name) && spec.alias == null);
7640
+ });
7641
+ if (survivingSpecs.length === 0)
7642
+ continue;
7643
+ const specText = survivingSpecs.map((s) => {
7644
+ const prefix = s.isTypeOnly ? "type " : "";
7645
+ return s.alias ? `${prefix}${s.name} as ${s.alias}` : `${prefix}${s.name}`;
7646
+ }).join(", ");
7647
+ const typeKw = block.isTypeOnly ? "type " : "";
7648
+ if (isReexportFrom) {
7649
+ const source = rewriteRelativeImport && block.source.startsWith(".") ? rewriteRelativeImport(block.source) : block.source;
7650
+ lines.push(`export ${typeKw}{ ${specText} } from '${source}'`);
7651
+ } else {
7652
+ lines.push(`export ${typeKw}{ ${specText} }`);
7653
+ }
7654
+ }
7655
+ return lines.length > 0 ? lines.join(`
7656
+ `) : null;
7657
+ }
7658
+ function collectInlineExportedNames(ir) {
7659
+ const names = new Set;
7660
+ for (const c of ir.metadata.localConstants) {
7661
+ if (c.isExported)
7662
+ names.add(c.name);
7663
+ }
7664
+ for (const f of ir.metadata.localFunctions) {
7665
+ if (f.isExported)
7666
+ names.add(f.name);
7667
+ }
7668
+ if (ir.metadata.isExported && ir.metadata.componentName) {
7669
+ names.add(ir.metadata.componentName);
7670
+ }
7671
+ return names;
7672
+ }
7673
+ function formatParamWithType(p) {
7674
+ const rest = p.isRest ? "..." : "";
7675
+ const optional = p.optional ? "?" : "";
7676
+ const typeAnnotation = p.type?.raw && p.type.raw !== "unknown" ? `: ${p.type.raw}` : "";
7677
+ const defaultPart = p.defaultValue !== undefined ? ` = ${p.defaultValue}` : "";
7678
+ return `${rest}${p.name}${optional}${typeAnnotation}${defaultPart}`;
7679
+ }
7680
+ function findReachableNames(primaryRefs, declarations) {
7681
+ const allNames = new Set(declarations.map((d) => d.name));
7682
+ const bodyMap = new Map(declarations.map((d) => [d.name, d.body]));
7683
+ const reachable = new Set;
7684
+ const queue = [];
7685
+ for (const name of allNames) {
7686
+ if (new RegExp(`\\b${name}\\b`).test(primaryRefs)) {
7687
+ reachable.add(name);
7688
+ queue.push(name);
7689
+ }
7690
+ }
7691
+ while (queue.length > 0) {
7692
+ const current = queue.shift();
7693
+ const body = bodyMap.get(current) || "";
7694
+ for (const name of allNames) {
7695
+ if (!reachable.has(name) && new RegExp(`\\b${name}\\b`).test(body)) {
7696
+ reachable.add(name);
7697
+ queue.push(name);
7698
+ }
7699
+ }
7700
+ }
7701
+ return reachable;
7702
+ }
7703
+ function extractFunctionParams(value) {
7704
+ const arrowMatch = value.match(/^(?:async\s*)?\(([^)]*)\)\s*(?::\s*[^=]+)?\s*=>/);
7705
+ if (arrowMatch) {
7706
+ return arrowMatch[1].split(",").map((p) => p.trim().split(":")[0].split("=")[0].trim()).filter(Boolean).join(", ");
7707
+ }
7708
+ const singleMatch = value.match(/^(?:async\s*)?(\w+)\s*=>/);
7709
+ if (singleMatch) {
7710
+ return singleMatch[1];
7711
+ }
7712
+ const funcMatch = value.match(/^(?:async\s*)?function\s*\w*\s*\(([^)]*)\)/);
7713
+ if (funcMatch) {
7714
+ return funcMatch[1].split(",").map((p) => p.trim().split(":")[0].split("=")[0].trim()).filter(Boolean).join(", ");
7715
+ }
7716
+ return "";
7717
+ }
7718
+
7391
7719
  // src/builtins.ts
7392
7720
  var CLIENT_BUILTIN_SOURCE = "@barefootjs/client";
7393
7721
  function isClientBuiltinName(name) {
@@ -8509,6 +8837,7 @@ function transformExpression(node, ctx) {
8509
8837
  return transformExpressionInner(expr, ctx, node, isClientOnly);
8510
8838
  }
8511
8839
  function transformExpressionInner(expr, ctx, node, isClientOnly) {
8840
+ expr = tryDesugarInterleaveTaggedTemplate(expr, ctx);
8512
8841
  checkBareSignalOrMemoIdentifier(expr, ctx);
8513
8842
  if (ts11.isIdentifier(expr)) {
8514
8843
  const jsxNode = ctx.analyzer.jsxConstants.get(expr.text);
@@ -9050,11 +9379,10 @@ function isIteratorShapeCall(node) {
9050
9379
  return { array: node.expression.expression, shape: name };
9051
9380
  }
9052
9381
  function extractSortComparator(callback, _method, ctx) {
9053
- const unsupported = () => {
9054
- const raw = ctx.getJS(callback);
9055
- return {
9056
- result: null,
9057
- unsupportedReason: `Sort comparator '${raw}' is not a supported shape. Accepted:
9382
+ const outerRaw = ctx.getJS(callback);
9383
+ const unsupported = () => ({
9384
+ result: null,
9385
+ unsupportedReason: `Sort comparator '${outerRaw}' is not a supported shape. Accepted:
9058
9386
  ` + ` (a, b) => a - b
9059
9387
  ` + ` (a, b) => a.field - b.field
9060
9388
  ` + ` (a, b) => a.localeCompare(b)
@@ -9062,15 +9390,25 @@ function extractSortComparator(callback, _method, ctx) {
9062
9390
  ` + ` (a, b) => a.field > b.field ? 1 : a.field < b.field ? -1 : 0
9063
9391
  ` + ` any of the above '||'-chained for multi-key tie-breaks
9064
9392
  ` + `(reverse the operands for descending order).`
9065
- };
9066
- };
9067
- if (!ts11.isArrowFunction(callback) && !ts11.isFunctionExpression(callback)) {
9393
+ });
9394
+ let resolvedNode = callback;
9395
+ if (ts11.isIdentifier(callback)) {
9396
+ const resolved = resolveSortComparatorIdentifier(callback.text, ctx);
9397
+ if (!resolved) {
9398
+ return {
9399
+ result: null,
9400
+ unsupportedReason: `Sort comparator '${outerRaw}' could not be resolved to a local function — ` + `declare it in the same file or inline it.`
9401
+ };
9402
+ }
9403
+ resolvedNode = resolved;
9404
+ }
9405
+ if (!ts11.isArrowFunction(resolvedNode) && !ts11.isFunctionExpression(resolvedNode)) {
9068
9406
  return {
9069
9407
  result: null,
9070
9408
  unsupportedReason: "Sort comparator must be an arrow function or function expression"
9071
9409
  };
9072
9410
  }
9073
- const arrow = tsNodeToParsedExpr(callback);
9411
+ const arrow = tsNodeToParsedExpr(resolvedNode);
9074
9412
  if (arrow.kind !== "arrow" || arrow.params.length !== 2)
9075
9413
  return unsupported();
9076
9414
  if (sortComparatorFromArrow(arrow) === null)
@@ -9084,6 +9422,21 @@ function extractSortComparator(callback, _method, ctx) {
9084
9422
  }
9085
9423
  };
9086
9424
  }
9425
+ function resolveSortComparatorIdentifier(name, ctx) {
9426
+ const constInfo = findLocalConst(name, ctx);
9427
+ const fnInfo = findLocalFunction(name, ctx);
9428
+ if (constInfo && fnInfo)
9429
+ return null;
9430
+ if (constInfo) {
9431
+ const ast = parseConstInitializer(constInfo);
9432
+ return ast && (ts11.isArrowFunction(ast) || ts11.isFunctionExpression(ast)) ? ast : null;
9433
+ }
9434
+ if (fnInfo) {
9435
+ const ast = parseFunctionInfoAsExpr(fnInfo);
9436
+ return ast && (ts11.isArrowFunction(ast) || ts11.isFunctionExpression(ast)) ? ast : null;
9437
+ }
9438
+ return null;
9439
+ }
9087
9440
  function extractFilterPredicate(callback, ctx) {
9088
9441
  if (!ts11.isArrowFunction(callback))
9089
9442
  return { result: null };
@@ -9156,7 +9509,7 @@ function extractLoopParamBindings(pattern) {
9156
9509
  const appendDotAccess = (prefix, key) => {
9157
9510
  return isIdent(key) ? `${prefix}.${key}` : `${prefix}[${JSON.stringify(key)}]`;
9158
9511
  };
9159
- const walk = (p, prefix) => {
9512
+ const walk = (p, prefix, segments) => {
9160
9513
  if (unsupported)
9161
9514
  return;
9162
9515
  if (ts11.isArrayBindingPattern(p)) {
@@ -9173,15 +9526,17 @@ function extractLoopParamBindings(pattern) {
9173
9526
  bindings.push({
9174
9527
  name: el.name.text,
9175
9528
  path: prefix,
9176
- rest: { kind: "array", from: index }
9529
+ rest: { kind: "array", from: index },
9530
+ segments
9177
9531
  });
9178
9532
  return;
9179
9533
  }
9180
9534
  const path = `${prefix}[${index}]`;
9535
+ const nextSegments = [...segments, { kind: "index", index }];
9181
9536
  if (ts11.isIdentifier(el.name)) {
9182
- bindings.push({ name: el.name.text, path });
9537
+ bindings.push({ name: el.name.text, path, segments: nextSegments });
9183
9538
  } else {
9184
- walk(el.name, path);
9539
+ walk(el.name, path, nextSegments);
9185
9540
  }
9186
9541
  }
9187
9542
  return;
@@ -9198,7 +9553,8 @@ function extractLoopParamBindings(pattern) {
9198
9553
  bindings.push({
9199
9554
  name: el.name.text,
9200
9555
  path: prefix,
9201
- rest: { kind: "object", exclude: collectedKeys }
9556
+ rest: { kind: "object", exclude: collectedKeys },
9557
+ segments
9202
9558
  });
9203
9559
  return;
9204
9560
  }
@@ -9221,17 +9577,19 @@ function extractLoopParamBindings(pattern) {
9221
9577
  unsupported = true;
9222
9578
  return;
9223
9579
  }
9224
- collectedKeys.push({ key: keyText, isIdent: isIdent(keyText) });
9580
+ const keyIsIdent = isIdent(keyText);
9581
+ collectedKeys.push({ key: keyText, isIdent: keyIsIdent });
9225
9582
  const path = appendDotAccess(prefix, keyText);
9583
+ const nextSegments = [...segments, { kind: "field", key: keyText, isIdent: keyIsIdent }];
9226
9584
  if (ts11.isIdentifier(el.name)) {
9227
- bindings.push({ name: el.name.text, path });
9585
+ bindings.push({ name: el.name.text, path, segments: nextSegments });
9228
9586
  } else {
9229
- walk(el.name, path);
9587
+ walk(el.name, path, nextSegments);
9230
9588
  }
9231
9589
  }
9232
9590
  };
9233
9591
  if (ts11.isArrayBindingPattern(pattern) || ts11.isObjectBindingPattern(pattern)) {
9234
- walk(pattern, "");
9592
+ walk(pattern, "", []);
9235
9593
  if (unsupported)
9236
9594
  return { unsupported: true };
9237
9595
  return bindings;
@@ -10000,6 +10358,7 @@ function getAttributeValue(attr, ctx) {
10000
10358
  expr = branchInit;
10001
10359
  }
10002
10360
  }
10361
+ expr = tryDesugarInterleaveTaggedTemplate(expr, ctx);
10003
10362
  if (ts11.isAwaitExpression(expr)) {
10004
10363
  ctx.analyzer.errors.push(createError(ErrorCodes.STAGE_AWAIT_IN_TEMPLATE, getSourceLocation(expr, ctx.sourceFile, ctx.filePath)));
10005
10364
  return AttrValueOf.expression("undefined");
@@ -10135,6 +10494,14 @@ function findLocalConst(name, ctx) {
10135
10494
  const pool = fnScoped.length > 0 ? fnScoped : matches;
10136
10495
  return pool[pool.length - 1];
10137
10496
  }
10497
+ function findLocalFunction(name, ctx) {
10498
+ const matches = ctx.analyzer.localFunctions.filter((f) => f.name === name);
10499
+ if (matches.length === 0)
10500
+ return;
10501
+ const fnScoped = matches.filter((f) => !f.isModule);
10502
+ const pool = fnScoped.length > 0 ? fnScoped : matches;
10503
+ return pool[pool.length - 1];
10504
+ }
10138
10505
  function isDynamicTagLocal(name, ctx) {
10139
10506
  if (!hasDynamicTagBinding(name, ctx.sourceFile))
10140
10507
  return false;
@@ -10229,10 +10596,151 @@ function parseConstInitializerImpl(c) {
10229
10596
  function astText(node) {
10230
10597
  return node.getText(node.getSourceFile());
10231
10598
  }
10232
- function parseTernary(expr, ctx) {
10233
- const whenTrueValue = getStringValue(expr.whenTrue);
10234
- const whenFalseValue = getStringValue(expr.whenFalse);
10235
- if (whenTrueValue !== null && whenFalseValue !== null) {
10599
+ var functionInfoExprCache = new WeakMap;
10600
+ function parseFunctionInfoAsExpr(fn) {
10601
+ const cached = functionInfoExprCache.get(fn);
10602
+ if (cached !== undefined)
10603
+ return cached;
10604
+ const result = parseFunctionInfoAsExprImpl(fn);
10605
+ functionInfoExprCache.set(fn, result);
10606
+ return result;
10607
+ }
10608
+ function parseFunctionInfoAsExprImpl(fn) {
10609
+ if (!fn.body)
10610
+ return null;
10611
+ const params = fn.typedParams !== undefined ? fn.typedParams : fn.params.map(formatParamWithType).join(", ");
10612
+ const body = fn.typedBody ?? fn.body;
10613
+ const wrapped = `const __bf_resolve_fn__ = function(${params}) ${body}`;
10614
+ const sf = ts11.createSourceFile("__bf_resolve_fn.ts", wrapped, ts11.ScriptTarget.Latest, true, ts11.ScriptKind.TS);
10615
+ const stmt = sf.statements[0];
10616
+ if (!stmt || !ts11.isVariableStatement(stmt))
10617
+ return null;
10618
+ const decl = stmt.declarationList.declarations[0];
10619
+ if (!decl?.initializer)
10620
+ return null;
10621
+ return decl.initializer;
10622
+ }
10623
+ function tryDesugarInterleaveTaggedTemplate(expr, ctx) {
10624
+ if (!ts11.isTaggedTemplateExpression(expr))
10625
+ return expr;
10626
+ if (!ts11.isIdentifier(expr.tag))
10627
+ return expr;
10628
+ const resolvedTag = resolveInterleaveTagIdentifier(expr.tag.text, ctx);
10629
+ if (!resolvedTag)
10630
+ return expr;
10631
+ if (!isInterleaveTagFunction(resolvedTag))
10632
+ return expr;
10633
+ const rewritten = buildUntaggedTemplateLiteral(expr, ctx);
10634
+ return rewritten ?? expr;
10635
+ }
10636
+ function resolveInterleaveTagIdentifier(name, ctx) {
10637
+ const constInfo = findLocalConst(name, ctx);
10638
+ const fnInfo = findLocalFunction(name, ctx);
10639
+ if (constInfo && fnInfo)
10640
+ return null;
10641
+ if (constInfo) {
10642
+ const ast = parseConstInitializer(constInfo);
10643
+ return ast && (ts11.isArrowFunction(ast) || ts11.isFunctionExpression(ast)) ? ast : null;
10644
+ }
10645
+ if (fnInfo) {
10646
+ const ast = parseFunctionInfoAsExpr(fnInfo);
10647
+ return ast && (ts11.isArrowFunction(ast) || ts11.isFunctionExpression(ast)) ? ast : null;
10648
+ }
10649
+ return null;
10650
+ }
10651
+ function isInterleaveTagFunction(fn) {
10652
+ if (!ts11.isArrowFunction(fn) && !ts11.isFunctionExpression(fn))
10653
+ return false;
10654
+ if (fn.parameters.length !== 2)
10655
+ return false;
10656
+ const [partsParam, argsParam] = fn.parameters;
10657
+ if (!ts11.isIdentifier(partsParam.name) || partsParam.dotDotDotToken)
10658
+ return false;
10659
+ if (!ts11.isIdentifier(argsParam.name) || !argsParam.dotDotDotToken)
10660
+ return false;
10661
+ const parsed = tsNodeToParsedExpr(fn);
10662
+ if (parsed.kind !== "arrow")
10663
+ return false;
10664
+ return isInterleaveReduceCall(parsed.body, partsParam.name.text, argsParam.name.text);
10665
+ }
10666
+ function isInterleaveReduceCall(body, partsName, argsName) {
10667
+ if (body.kind !== "call" || body.args.length !== 2)
10668
+ return false;
10669
+ const { callee, args } = body;
10670
+ if (callee.kind !== "member" || callee.computed || callee.property !== "reduce")
10671
+ return false;
10672
+ if (callee.object.kind !== "identifier" || callee.object.name !== partsName)
10673
+ return false;
10674
+ const [callback, init] = args;
10675
+ if (init.kind !== "literal" || init.literalType !== "string" || init.value !== "")
10676
+ return false;
10677
+ if (callback.kind !== "arrow" || callback.params.length !== 3)
10678
+ return false;
10679
+ const [acc, p, i] = callback.params;
10680
+ return isInterleaveReduceCallbackBody(callback.body, acc, p, i, argsName);
10681
+ }
10682
+ function isInterleaveReduceCallbackBody(body, acc, p, i, argsName) {
10683
+ if (body.kind !== "binary" || body.op !== "+")
10684
+ return false;
10685
+ const { left, right } = body;
10686
+ if (left.kind !== "binary" || left.op !== "+")
10687
+ return false;
10688
+ if (left.left.kind !== "identifier" || left.left.name !== acc)
10689
+ return false;
10690
+ if (left.right.kind !== "identifier" || left.right.name !== p)
10691
+ return false;
10692
+ return isInterleaveSpanExpr(right, i, argsName);
10693
+ }
10694
+ function isInterleaveSpanExpr(expr, i, argsName) {
10695
+ let inner = expr;
10696
+ if (inner.kind === "call" && inner.args.length === 1 && inner.callee.kind === "identifier" && inner.callee.name === "String") {
10697
+ inner = inner.args[0];
10698
+ }
10699
+ if (inner.kind !== "logical" || inner.op !== "??")
10700
+ return false;
10701
+ if (inner.right.kind !== "literal" || inner.right.literalType !== "string" || inner.right.value !== "") {
10702
+ return false;
10703
+ }
10704
+ const idx = inner.left;
10705
+ if (idx.kind !== "index-access")
10706
+ return false;
10707
+ if (idx.object.kind !== "identifier" || idx.object.name !== argsName)
10708
+ return false;
10709
+ if (idx.index.kind !== "identifier" || idx.index.name !== i)
10710
+ return false;
10711
+ return true;
10712
+ }
10713
+ function buildUntaggedTemplateLiteral(node, ctx) {
10714
+ const template = node.template;
10715
+ let text;
10716
+ if (ts11.isNoSubstitutionTemplateLiteral(template)) {
10717
+ text = "`" + (template.rawText ?? template.text) + "`";
10718
+ } else {
10719
+ let body = template.head.rawText ?? template.head.text;
10720
+ for (const span of template.templateSpans) {
10721
+ const spanText = ctx.getJS(span.expression);
10722
+ body += "${(" + spanText + ") ?? ''}";
10723
+ body += span.literal.rawText ?? span.literal.text;
10724
+ }
10725
+ text = "`" + body + "`";
10726
+ }
10727
+ const wrapped = `const __bf_resolve_tagged__ = (${text})`;
10728
+ const sf = ts11.createSourceFile("__bf_resolve_tagged.tsx", wrapped, ts11.ScriptTarget.Latest, true, ts11.ScriptKind.TSX);
10729
+ const stmt = sf.statements[0];
10730
+ if (!stmt || !ts11.isVariableStatement(stmt))
10731
+ return null;
10732
+ const decl = stmt.declarationList.declarations[0];
10733
+ if (!decl?.initializer)
10734
+ return null;
10735
+ const result = ts11.isParenthesizedExpression(decl.initializer) ? decl.initializer.expression : decl.initializer;
10736
+ if (!ts11.isTemplateExpression(result) && !ts11.isNoSubstitutionTemplateLiteral(result))
10737
+ return null;
10738
+ return result;
10739
+ }
10740
+ function parseTernary(expr, ctx) {
10741
+ const whenTrueValue = getStringValue(expr.whenTrue);
10742
+ const whenFalseValue = getStringValue(expr.whenFalse);
10743
+ if (whenTrueValue !== null && whenFalseValue !== null) {
10236
10744
  const condition = ctx.getJS(expr.condition);
10237
10745
  return {
10238
10746
  type: "ternary",
@@ -11344,6 +11852,7 @@ function collectElements(node, ctx, siblingOffsets, insideConditional = false) {
11344
11852
  const { useElementReconciliation, innerLoops } = decideLoopRendering(l, siblingOffsets, ctx);
11345
11853
  let template = "";
11346
11854
  let staticItemTemplate;
11855
+ let skeletonTemplate;
11347
11856
  if (l.childComponent) {
11348
11857
  template = "";
11349
11858
  if (l.isStaticArray && l.children[0]) {
@@ -11354,6 +11863,11 @@ function collectElements(node, ctx, siblingOffsets, insideConditional = false) {
11354
11863
  template = useElementReconciliation ? irToPlaceholderTemplate(l.children[0], buildRestSpreadNames(ctx), 0, loopParamSpec) : irToHtmlTemplate(l.children[0], buildRestSpreadNames(ctx), 0, loopParamSpec);
11355
11864
  if (l.isStaticArray) {
11356
11865
  staticItemTemplate = useElementReconciliation ? irToPlaceholderTemplate(l.children[0], buildRestSpreadNames(ctx), 0) : irToHtmlTemplate(l.children[0], buildRestSpreadNames(ctx), 0);
11866
+ } else if (!useElementReconciliation && !l.bodyIsMultiRoot && !l.bodyIsItemConditional) {
11867
+ skeletonTemplate = buildLoopSkeletonTemplate(l.children[0], {
11868
+ reactiveAttrKeys: new Set(bindings.reactiveAttrs.map((a) => `${a.childSlotId}::${a.attrName}`)),
11869
+ reactiveTextSlotIds: new Set(bindings.reactiveTexts.map((t) => t.slotId))
11870
+ }) ?? undefined;
11357
11871
  }
11358
11872
  }
11359
11873
  ctx.loopElements.push({
@@ -11371,6 +11885,7 @@ function collectElements(node, ctx, siblingOffsets, insideConditional = false) {
11371
11885
  iterationShape: l.iterationShape,
11372
11886
  template,
11373
11887
  staticItemTemplate,
11888
+ skeletonTemplate,
11374
11889
  childEventHandlers: childHandlers,
11375
11890
  bindings,
11376
11891
  childComponent: l.childComponent,
@@ -12259,6 +12774,45 @@ function collectComponentNames(node) {
12259
12774
 
12260
12775
  // src/relocate.ts
12261
12776
  import ts13 from "typescript";
12777
+
12778
+ // src/lowering-registry.ts
12779
+ var plugins = [];
12780
+ function registerLoweringPlugin(plugin) {
12781
+ const existing = plugins.findIndex((p) => p.name === plugin.name);
12782
+ if (existing >= 0)
12783
+ plugins[existing] = plugin;
12784
+ else
12785
+ plugins.push(plugin);
12786
+ }
12787
+ function getLoweringPlugins() {
12788
+ return [...plugins];
12789
+ }
12790
+ function prepareLoweringMatchers(metadata) {
12791
+ const matchers = [];
12792
+ for (const plugin of plugins) {
12793
+ const matcher = plugin.prepare(metadata);
12794
+ if (matcher)
12795
+ matchers.push(matcher);
12796
+ }
12797
+ return matchers;
12798
+ }
12799
+ function matchLoweringCall(callee, args, metadata) {
12800
+ for (const matcher of prepareLoweringMatchers(metadata)) {
12801
+ const node = matcher(callee, args);
12802
+ if (node)
12803
+ return node;
12804
+ }
12805
+ return null;
12806
+ }
12807
+ function isValidHelperId(helper) {
12808
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(helper);
12809
+ }
12810
+ function __resetLoweringPluginsForTest(next = []) {
12811
+ plugins.length = 0;
12812
+ plugins.push(...next);
12813
+ }
12814
+
12815
+ // src/relocate.ts
12262
12816
  function classify(name, env) {
12263
12817
  return env.bindings.get(name) ?? "global";
12264
12818
  }
@@ -12293,6 +12847,14 @@ function decideAction(kind, toScope, env, name) {
12293
12847
  return { action: "lift-to-prop", rewrittenAs: `${PROPS_PARAM}.${name}` };
12294
12848
  }
12295
12849
  if ((kind === "init-local" || kind === "sub-init-local") && toScope === "template") {
12850
+ const aliasTarget = env.aliasTargets?.get(name);
12851
+ if (aliasTarget !== undefined) {
12852
+ const targetLeftmost = aliasTarget.includes(".") ? aliasTarget.split(".")[0] : aliasTarget;
12853
+ const targetKind = classify(targetLeftmost, env);
12854
+ if (isVisibleIn(toScope, targetKind)) {
12855
+ return { action: "inline", rewrittenAs: aliasTarget };
12856
+ }
12857
+ }
12296
12858
  const inlineForm = env.inlinable.get(name);
12297
12859
  if (inlineForm !== undefined) {
12298
12860
  return { action: "inline", rewrittenAs: inlineForm };
@@ -12372,6 +12934,8 @@ function isInlinableInTemplate(value, env) {
12372
12934
  return { ok: true, rewrittenValue: r.text, decisions: r.decisions };
12373
12935
  }
12374
12936
  function getCalleeIdentifierPath(callee) {
12937
+ if (ts13.isParenthesizedExpression(callee))
12938
+ return getCalleeIdentifierPath(callee.expression);
12375
12939
  if (ts13.isIdentifier(callee))
12376
12940
  return callee.text;
12377
12941
  if (ts13.isPropertyAccessExpression(callee)) {
@@ -12383,6 +12947,8 @@ function getCalleeIdentifierPath(callee) {
12383
12947
  return null;
12384
12948
  }
12385
12949
  function getCalleeLeftmostIdentifier(callee) {
12950
+ if (ts13.isParenthesizedExpression(callee))
12951
+ return getCalleeLeftmostIdentifier(callee.expression);
12386
12952
  if (ts13.isIdentifier(callee))
12387
12953
  return callee.text;
12388
12954
  if (ts13.isPropertyAccessExpression(callee)) {
@@ -12396,20 +12962,39 @@ var REGISTRY_SAFE_BINDING_KINDS = new Set([
12396
12962
  "module-local"
12397
12963
  ]);
12398
12964
  function isCallAcceptedByAdapter(call, env) {
12399
- const name = getCalleeIdentifierPath(call.expression);
12400
- if (name === null)
12965
+ const originalPath = getCalleeIdentifierPath(call.expression);
12966
+ if (originalPath === null)
12401
12967
  return false;
12402
12968
  const leftmost = getCalleeLeftmostIdentifier(call.expression);
12969
+ let resolvedPath = originalPath;
12970
+ let resolvedLeftmost = leftmost;
12403
12971
  if (leftmost !== null) {
12404
- const kind = env.bindings.get(leftmost);
12972
+ const aliasTarget = env.aliasTargets?.get(leftmost);
12973
+ if (aliasTarget !== undefined) {
12974
+ resolvedPath = originalPath === leftmost ? aliasTarget : `${aliasTarget}${originalPath.slice(leftmost.length)}`;
12975
+ resolvedLeftmost = aliasTarget.includes(".") ? aliasTarget.split(".")[0] : aliasTarget;
12976
+ }
12977
+ }
12978
+ if (resolvedLeftmost !== null) {
12979
+ const kind = env.bindings.get(resolvedLeftmost);
12405
12980
  if (kind !== undefined && !REGISTRY_SAFE_BINDING_KINDS.has(kind)) {
12406
12981
  return false;
12407
12982
  }
12408
12983
  }
12409
- if (env.templatePrimitives && env.templatePrimitives[name])
12984
+ if (env.templatePrimitives && env.templatePrimitives[resolvedPath])
12410
12985
  return true;
12411
- if (env.acceptsTemplateCall && env.acceptsTemplateCall(name))
12986
+ if (env.acceptsTemplateCall && env.acceptsTemplateCall(resolvedPath))
12412
12987
  return true;
12988
+ if (env.loweringMatchers && env.loweringMatchers.length > 0) {
12989
+ const parsed = tsNodeToParsedExpr(call);
12990
+ if (parsed.kind === "call") {
12991
+ const calleeForMatch = resolvedPath !== originalPath && !resolvedPath.includes(".") && parsed.callee.kind === "identifier" ? { kind: "identifier", name: resolvedPath } : parsed.callee;
12992
+ for (const matcher of env.loweringMatchers) {
12993
+ if (matcher(calleeForMatch, parsed.args))
12994
+ return true;
12995
+ }
12996
+ }
12997
+ }
12413
12998
  return false;
12414
12999
  }
12415
13000
  function parseExpressionNode(text) {
@@ -12555,6 +13140,7 @@ function buildRelocateEnvFromIR(metadata, options) {
12555
13140
  env.templatePrimitives = options.templatePrimitives;
12556
13141
  if (options?.acceptsTemplateCall)
12557
13142
  env.acceptsTemplateCall = options.acceptsTemplateCall;
13143
+ env.loweringMatchers = prepareLoweringMatchers(metadata);
12558
13144
  return env;
12559
13145
  }
12560
13146
  function buildRelocateEnvFromFields(src) {
@@ -12603,14 +13189,35 @@ function buildRelocateEnvFromFields(src) {
12603
13189
  if (kind === "prop")
12604
13190
  propsForLift.add(name);
12605
13191
  }
13192
+ const aliasTargets = new Map;
13193
+ for (const c of src.localConstants) {
13194
+ const kind = bindings.get(c.name);
13195
+ if (kind !== "init-local" && kind !== "module-local")
13196
+ continue;
13197
+ const target = identifierPathFromParsed(c.parsed);
13198
+ if (target !== null)
13199
+ aliasTargets.set(c.name, target);
13200
+ }
12606
13201
  return {
12607
13202
  bindings,
12608
13203
  inlinable: new Map,
12609
13204
  propsForLift,
12610
13205
  propsObjectName,
12611
- allowFallback: true
13206
+ allowFallback: true,
13207
+ aliasTargets
12612
13208
  };
12613
13209
  }
13210
+ function identifierPathFromParsed(expr) {
13211
+ if (!expr)
13212
+ return null;
13213
+ if (expr.kind === "identifier")
13214
+ return expr.name;
13215
+ if (expr.kind === "member" && !expr.computed) {
13216
+ const object = identifierPathFromParsed(expr.object);
13217
+ return object === null ? null : `${object}.${expr.property}`;
13218
+ }
13219
+ return null;
13220
+ }
12614
13221
 
12615
13222
  // src/ir-to-client-js/compute-inlinability.ts
12616
13223
  function buildEnvFromCtx(ctx) {
@@ -12630,7 +13237,7 @@ function buildEnvFromCtx(ctx) {
12630
13237
  effects: ctx.effects,
12631
13238
  onMounts: ctx.onMounts,
12632
13239
  initStatements: ctx.initStatements,
12633
- imports: [],
13240
+ imports: ctx.imports,
12634
13241
  templateImports: [],
12635
13242
  namedExports: [],
12636
13243
  localFunctions: ctx.localFunctions,
@@ -15391,6 +15998,15 @@ function emitTemplateCloneInline(template) {
15391
15998
  }
15392
15999
  return `const __tpl = document.createElement('template'); __tpl.innerHTML = \`${template}\`; return __tpl.content.firstElementChild.cloneNode(true)`;
15393
16000
  }
16001
+ function emitHoistedTemplateDecl(lines, indent, tplVar, skeletonTemplate) {
16002
+ const isSvg = templateRootIsSvg(skeletonTemplate);
16003
+ const html = isSvg ? `<svg>${skeletonTemplate}</svg>` : skeletonTemplate;
16004
+ lines.push(`${indent}const ${tplVar} = document.createElement('template')`);
16005
+ lines.push(`${indent}${tplVar}.innerHTML = \`${html}\``);
16006
+ }
16007
+ function hoistedCloneExpr(tplVar, skeletonTemplate) {
16008
+ return templateRootIsSvg(skeletonTemplate) ? `${tplVar}.content.firstElementChild.firstElementChild.cloneNode(true)` : `${tplVar}.content.firstElementChild.cloneNode(true)`;
16009
+ }
15394
16010
  function emitTemplateCloneLines(template, indent) {
15395
16011
  if (templateRootIsSvg(template)) {
15396
16012
  return [
@@ -15689,6 +16305,7 @@ function stringifyPlainLoop(lines, plan, topIndent = " ") {
15689
16305
  indexParam,
15690
16306
  mapPreambleWrapped,
15691
16307
  template,
16308
+ skeletonTemplate,
15692
16309
  reactiveEffects,
15693
16310
  childRefs,
15694
16311
  bodyIsMultiRoot,
@@ -15699,11 +16316,16 @@ function stringifyPlainLoop(lines, plan, topIndent = " ") {
15699
16316
  stringifyAnchoredLoop(lines, plan, topIndent, anchorKeyExpr);
15700
16317
  return;
15701
16318
  }
16319
+ const hoistedTpl = !bodyIsMultiRoot && skeletonTemplate ? skeletonTemplate : null;
16320
+ const tplVar = `__tpl_${markerId.replace(/[^A-Za-z0-9_$]/g, "_")}`;
16321
+ if (hoistedTpl) {
16322
+ emitHoistedTemplateDecl(lines, topIndent, tplVar, hoistedTpl);
16323
+ }
15702
16324
  const loopBfId = plan.profileLoopId ? `, ${JSON.stringify(plan.profileLoopId)}` : "";
15703
16325
  if (reactiveEffects === null && !bodyIsMultiRoot && childRefs.length === 0) {
15704
16326
  const unwrapInline = paramUnwrap ? `${paramUnwrap} ` : "";
15705
16327
  const preamble = mapPreambleWrapped ? `${mapPreambleWrapped}; ` : "";
15706
- const cloneExpr = emitTemplateCloneInline(template);
16328
+ const cloneExpr = hoistedTpl ? `return ${hoistedCloneExpr(tplVar, hoistedTpl)}` : emitTemplateCloneInline(template);
15707
16329
  lines.push(`${topIndent}mapArray(() => ${arrayExpr}, ${containerVar}, ${keyFn}, (${paramHead}, ${indexParam}, __existing) => { ${unwrapInline}${preamble}if (__existing) return __existing; ${cloneExpr} }, '${markerId}'${loopBfId})`);
15708
16330
  return;
15709
16331
  }
@@ -15713,12 +16335,16 @@ function stringifyPlainLoop(lines, plan, topIndent = " ") {
15713
16335
  lines.push(`${bodyIndent}${paramUnwrap}`);
15714
16336
  if (mapPreambleWrapped)
15715
16337
  lines.push(`${bodyIndent}${mapPreambleWrapped}`);
15716
- emitLoopItemElementSetup(lines, {
15717
- template,
15718
- bodyIsMultiRoot,
15719
- indent: bodyIndent,
15720
- singleRootLayout: "inline"
15721
- });
16338
+ if (hoistedTpl) {
16339
+ lines.push(`${bodyIndent}const __el = __existing ?? ${hoistedCloneExpr(tplVar, hoistedTpl)}`);
16340
+ } else {
16341
+ emitLoopItemElementSetup(lines, {
16342
+ template,
16343
+ bodyIsMultiRoot,
16344
+ indent: bodyIndent,
16345
+ singleRootLayout: "inline"
16346
+ });
16347
+ }
15722
16348
  if (reactiveEffects !== null) {
15723
16349
  stringifyReactiveEffects(lines, reactiveEffects, { indent: bodyIndent, elVar: "__el", bodyIsMultiRoot });
15724
16350
  }
@@ -16404,6 +17030,7 @@ function buildPlainLoopPlan(elem, profileComponentName) {
16404
17030
  indexParam: elem.index || "__idx",
16405
17031
  mapPreambleWrapped: elem.mapPreamble ? wrap(elem.mapPreamble) : "",
16406
17032
  template: elem.template,
17033
+ skeletonTemplate: elem.skeletonTemplate,
16407
17034
  reactiveEffects: hasReactive2 ? buildLoopReactiveEffectsPlan(elem, profileComponentName) : null,
16408
17035
  childRefs: buildChildRefBindings(elem.bindings.refs, elem.param, elem.paramBindings),
16409
17036
  bodyIsMultiRoot: elem.bodyIsMultiRoot ?? false,
@@ -17010,6 +17637,7 @@ function createContext(ir, scope, adapterCapabilities, profile) {
17010
17637
  initStatements: ir.metadata.initStatements ?? [],
17011
17638
  localFunctions: ir.metadata.localFunctions,
17012
17639
  localConstants: ir.metadata.localConstants,
17640
+ imports: ir.metadata.imports,
17013
17641
  propsParams: ir.metadata.propsParams,
17014
17642
  propsObjectName: ir.metadata.propsObjectName,
17015
17643
  restPropsName: ir.metadata.restPropsName,
@@ -17093,119 +17721,6 @@ ${externalImports.join(`
17093
17721
  return generatedCode.replace(IMPORT_PLACEHOLDER, allImports);
17094
17722
  }
17095
17723
 
17096
- // src/module-exports.ts
17097
- function generateModuleExports(ir, extraInlineExported = new Set, rewriteRelativeImport) {
17098
- const lines = [];
17099
- for (const constant of ir.metadata.localConstants) {
17100
- if (!constant.isExported)
17101
- continue;
17102
- const keyword = constant.declarationKind ?? "const";
17103
- if (!constant.value) {
17104
- lines.push(`export ${keyword} ${constant.name}`);
17105
- continue;
17106
- }
17107
- const value = constant.value.trim();
17108
- if (/^createContext\b/.test(value) || /^new WeakMap\b/.test(value))
17109
- continue;
17110
- lines.push(`export ${keyword} ${constant.name} = ${constant.value}`);
17111
- }
17112
- for (const func of ir.metadata.localFunctions) {
17113
- if (!func.isExported)
17114
- continue;
17115
- const params = func.typedParams !== undefined ? func.typedParams : func.params.map(formatParamWithType).join(", ");
17116
- const returnAnnotation = func.typedReturnType ? `: ${func.typedReturnType}` : "";
17117
- const body = func.typedBody ?? func.body;
17118
- const asyncKw = func.isAsync ? "async " : "";
17119
- lines.push(`export ${asyncKw}function ${func.name}(${params})${returnAnnotation} ${body}`);
17120
- }
17121
- const inlineExported = collectInlineExportedNames(ir);
17122
- for (const name of extraInlineExported)
17123
- inlineExported.add(name);
17124
- for (const block of ir.metadata.namedExports) {
17125
- const isReexportFrom = block.source !== null;
17126
- const survivingSpecs = block.specifiers.filter((spec) => {
17127
- if (isReexportFrom)
17128
- return true;
17129
- return !(inlineExported.has(spec.name) && spec.alias == null);
17130
- });
17131
- if (survivingSpecs.length === 0)
17132
- continue;
17133
- const specText = survivingSpecs.map((s) => {
17134
- const prefix = s.isTypeOnly ? "type " : "";
17135
- return s.alias ? `${prefix}${s.name} as ${s.alias}` : `${prefix}${s.name}`;
17136
- }).join(", ");
17137
- const typeKw = block.isTypeOnly ? "type " : "";
17138
- if (isReexportFrom) {
17139
- const source = rewriteRelativeImport && block.source.startsWith(".") ? rewriteRelativeImport(block.source) : block.source;
17140
- lines.push(`export ${typeKw}{ ${specText} } from '${source}'`);
17141
- } else {
17142
- lines.push(`export ${typeKw}{ ${specText} }`);
17143
- }
17144
- }
17145
- return lines.length > 0 ? lines.join(`
17146
- `) : null;
17147
- }
17148
- function collectInlineExportedNames(ir) {
17149
- const names = new Set;
17150
- for (const c of ir.metadata.localConstants) {
17151
- if (c.isExported)
17152
- names.add(c.name);
17153
- }
17154
- for (const f of ir.metadata.localFunctions) {
17155
- if (f.isExported)
17156
- names.add(f.name);
17157
- }
17158
- if (ir.metadata.isExported && ir.metadata.componentName) {
17159
- names.add(ir.metadata.componentName);
17160
- }
17161
- return names;
17162
- }
17163
- function formatParamWithType(p) {
17164
- const rest = p.isRest ? "..." : "";
17165
- const optional = p.optional ? "?" : "";
17166
- const typeAnnotation = p.type?.raw && p.type.raw !== "unknown" ? `: ${p.type.raw}` : "";
17167
- const defaultPart = p.defaultValue !== undefined ? ` = ${p.defaultValue}` : "";
17168
- return `${rest}${p.name}${optional}${typeAnnotation}${defaultPart}`;
17169
- }
17170
- function findReachableNames(primaryRefs, declarations) {
17171
- const allNames = new Set(declarations.map((d) => d.name));
17172
- const bodyMap = new Map(declarations.map((d) => [d.name, d.body]));
17173
- const reachable = new Set;
17174
- const queue = [];
17175
- for (const name of allNames) {
17176
- if (new RegExp(`\\b${name}\\b`).test(primaryRefs)) {
17177
- reachable.add(name);
17178
- queue.push(name);
17179
- }
17180
- }
17181
- while (queue.length > 0) {
17182
- const current = queue.shift();
17183
- const body = bodyMap.get(current) || "";
17184
- for (const name of allNames) {
17185
- if (!reachable.has(name) && new RegExp(`\\b${name}\\b`).test(body)) {
17186
- reachable.add(name);
17187
- queue.push(name);
17188
- }
17189
- }
17190
- }
17191
- return reachable;
17192
- }
17193
- function extractFunctionParams(value) {
17194
- const arrowMatch = value.match(/^(?:async\s*)?\(([^)]*)\)\s*(?::\s*[^=]+)?\s*=>/);
17195
- if (arrowMatch) {
17196
- return arrowMatch[1].split(",").map((p) => p.trim().split(":")[0].split("=")[0].trim()).filter(Boolean).join(", ");
17197
- }
17198
- const singleMatch = value.match(/^(?:async\s*)?(\w+)\s*=>/);
17199
- if (singleMatch) {
17200
- return singleMatch[1];
17201
- }
17202
- const funcMatch = value.match(/^(?:async\s*)?function\s*\w*\s*\(([^)]*)\)/);
17203
- if (funcMatch) {
17204
- return funcMatch[1].split(",").map((p) => p.trim().split(":")[0].split("=")[0].trim()).filter(Boolean).join(", ");
17205
- }
17206
- return "";
17207
- }
17208
-
17209
17724
  // src/css-layer-prefixer.ts
17210
17725
  function prefixClass(cls, layerName) {
17211
17726
  if (!cls || cls.startsWith("layer-"))
@@ -17713,16 +18228,14 @@ function extractSsrDefaults(metadata) {
17713
18228
  propsLike.add(metadata.propsObjectName);
17714
18229
  for (const p of metadata.propsParams)
17715
18230
  propsLike.add(p.name);
17716
- if (metadata.propsObjectName === null) {
17717
- for (const p of metadata.propsParams) {
17718
- if (p.isRest)
17719
- continue;
17720
- if (p.defaultValue !== undefined) {
17721
- const value = tryStaticEval(p.defaultValue, { bindings: {}, propsLike });
17722
- out[p.name] = { propName: p.name, value: resultToJsonable(value) };
17723
- } else {
17724
- out[p.name] = { propName: p.name, value: null };
17725
- }
18231
+ for (const p of metadata.propsParams) {
18232
+ if (p.isRest)
18233
+ continue;
18234
+ if (metadata.propsObjectName === null && p.defaultValue !== undefined) {
18235
+ const value = tryStaticEval(p.defaultValue, { bindings: {}, propsLike });
18236
+ out[p.name] = { propName: p.name, value: resultToJsonable(value) };
18237
+ } else {
18238
+ out[p.name] = { propName: p.name, value: null };
17726
18239
  }
17727
18240
  }
17728
18241
  if (metadata.restPropsName) {
@@ -18044,10 +18557,13 @@ import ts17 from "typescript";
18044
18557
  function collectContextConsumers(metadata) {
18045
18558
  const constants = metadata.localConstants ?? [];
18046
18559
  const contextDefaults = new Map;
18560
+ const contextDefaultKinds = new Map;
18047
18561
  for (const c of constants) {
18048
18562
  if (c.systemConstructKind !== "createContext" || c.value === undefined)
18049
18563
  continue;
18050
18564
  contextDefaults.set(c.name, parseCreateContextDefault(c.value));
18565
+ if (isObjectLiteralCreateContextDefault(c.value))
18566
+ contextDefaultKinds.set(c.name, "object");
18051
18567
  }
18052
18568
  if (contextDefaults.size === 0)
18053
18569
  return [];
@@ -18061,7 +18577,8 @@ function collectContextConsumers(metadata) {
18061
18577
  consumers.push({
18062
18578
  localName: c.name,
18063
18579
  contextName: ctxName,
18064
- defaultValue: contextDefaults.get(ctxName) ?? null
18580
+ defaultValue: contextDefaults.get(ctxName) ?? null,
18581
+ defaultKind: contextDefaultKinds.get(ctxName)
18065
18582
  });
18066
18583
  }
18067
18584
  return consumers;
@@ -18094,6 +18611,14 @@ function parseCreateContextDefault(source) {
18094
18611
  return false;
18095
18612
  return null;
18096
18613
  }
18614
+ function isObjectLiteralCreateContextDefault(source) {
18615
+ const expr = parseSingleExpression(source);
18616
+ if (!expr || !ts17.isCallExpression(expr))
18617
+ return false;
18618
+ if (expr.arguments.length === 0)
18619
+ return false;
18620
+ return ts17.isObjectLiteralExpression(expr.arguments[0]);
18621
+ }
18097
18622
  function parseSingleExpression(source) {
18098
18623
  const sf = ts17.createSourceFile("__ctx.ts", `(${source})`, ts17.ScriptTarget.Latest, false);
18099
18624
  const stmt = sf.statements[0];
@@ -18113,16 +18638,48 @@ function augmentInheritedPropAccesses(ir) {
18113
18638
  const booleanAttrProps = new Set;
18114
18639
  const accessed = new Set;
18115
18640
  const accessRe = new RegExp(`(?:^|[^\\w$.])${propsObj}\\.([A-Za-z_$][\\w$]*)`, "g");
18116
- const scan = (s) => {
18641
+ const scan = (s, also) => {
18117
18642
  if (!s)
18118
18643
  return;
18119
- for (const m of s.matchAll(accessRe))
18644
+ for (const m of s.matchAll(accessRe)) {
18120
18645
  accessed.add(m[1]);
18646
+ also?.add(m[1]);
18647
+ }
18648
+ };
18649
+ const coalesceLiteralTypes = new Map;
18650
+ const pinCoalesceLiterals = (s) => {
18651
+ if (!s || !s.includes(propsObj))
18652
+ return;
18653
+ const sf = ts17.createSourceFile("__aug.ts", `(${s})`, ts17.ScriptTarget.Latest, false);
18654
+ const visit3 = (n) => {
18655
+ if (ts17.isBinaryExpression(n) && (n.operatorToken.kind === ts17.SyntaxKind.QuestionQuestionToken || n.operatorToken.kind === ts17.SyntaxKind.BarBarToken)) {
18656
+ let left = n.left;
18657
+ while (ts17.isParenthesizedExpression(left))
18658
+ left = left.expression;
18659
+ if (ts17.isPropertyAccessExpression(left) && ts17.isIdentifier(left.expression) && left.expression.text === propsObj) {
18660
+ const name = left.name.text;
18661
+ let right = n.right;
18662
+ while (ts17.isParenthesizedExpression(right))
18663
+ right = right.expression;
18664
+ if (ts17.isPrefixUnaryExpression(right))
18665
+ right = right.operand;
18666
+ const kind = ts17.isNumericLiteral(right) ? "number" : right.kind === ts17.SyntaxKind.TrueKeyword || right.kind === ts17.SyntaxKind.FalseKeyword ? "boolean" : ts17.isStringLiteralLike(right) ? "string" : null;
18667
+ if (kind && !coalesceLiteralTypes.has(name))
18668
+ coalesceLiteralTypes.set(name, kind);
18669
+ }
18670
+ }
18671
+ ts17.forEachChild(n, visit3);
18672
+ };
18673
+ visit3(sf);
18121
18674
  };
18122
- for (const memo of ir.metadata.memos)
18675
+ for (const memo of ir.metadata.memos) {
18123
18676
  scan(memo.computation);
18124
- for (const signal of ir.metadata.signals)
18677
+ pinCoalesceLiterals(memo.computation);
18678
+ }
18679
+ for (const signal of ir.metadata.signals) {
18125
18680
  scan(signal.initialValue);
18681
+ pinCoalesceLiterals(signal.initialValue);
18682
+ }
18126
18683
  for (const stmt of ir.metadata.initStatements ?? [])
18127
18684
  scan(stmt.body);
18128
18685
  for (const eff of ir.metadata.effects ?? [])
@@ -18131,11 +18688,39 @@ function augmentInheritedPropAccesses(ir) {
18131
18688
  if (c.isModule)
18132
18689
  continue;
18133
18690
  scan(c.value);
18691
+ pinCoalesceLiterals(c.value);
18134
18692
  }
18135
18693
  const walk = (node) => {
18136
18694
  if (!node)
18137
18695
  return;
18696
+ const carrier = node;
18697
+ if (carrier.type === "expression") {
18698
+ scan(carrier.expr);
18699
+ pinCoalesceLiterals(carrier.expr);
18700
+ }
18701
+ scan(carrier.condition, bareRefProps);
18702
+ pinCoalesceLiterals(carrier.condition);
18703
+ scan(carrier.array, bareRefProps);
18138
18704
  const el = node;
18705
+ for (const prop of node.props ?? []) {
18706
+ const v = prop.value;
18707
+ if (v?.parts) {
18708
+ for (const part of v.parts) {
18709
+ if (part.type === "string")
18710
+ scan(part.value);
18711
+ else if (part.type === "ternary") {
18712
+ scan(part.condition);
18713
+ scan(part.whenTrue);
18714
+ scan(part.whenFalse);
18715
+ } else if (part.type === "lookup")
18716
+ scan(part.key);
18717
+ }
18718
+ }
18719
+ if (v?.kind === "expression" && typeof v.expr === "string") {
18720
+ scan(v.expr, bareRefProps);
18721
+ pinCoalesceLiterals(v.expr);
18722
+ }
18723
+ }
18139
18724
  for (const attr of el.attrs ?? []) {
18140
18725
  const v = attr.value;
18141
18726
  if (v?.parts) {
@@ -18182,11 +18767,13 @@ function augmentInheritedPropAccesses(ir) {
18182
18767
  let raw;
18183
18768
  if (booleanAttrProps.has(name))
18184
18769
  raw = "boolean";
18770
+ else if (coalesceLiteralTypes.has(name))
18771
+ raw = coalesceLiteralTypes.get(name);
18185
18772
  else if (bareRefProps.has(name))
18186
18773
  raw = "unknown";
18187
18774
  else
18188
18775
  raw = "string";
18189
- const type = raw === "boolean" ? { kind: "primitive", raw: "boolean", primitive: "boolean" } : raw === "string" ? { kind: "primitive", raw: "string", primitive: "string" } : { kind: "unknown", raw: "unknown" };
18776
+ const type = raw === "unknown" ? { kind: "unknown", raw: "unknown" } : { kind: "primitive", raw, primitive: raw };
18190
18777
  ir.metadata.propsParams.push({ name, type, optional: true });
18191
18778
  existing.add(name);
18192
18779
  }
@@ -18558,7 +19145,8 @@ function compileMultipleComponents(source, filePath, componentNames, options) {
18558
19145
  files.push({
18559
19146
  path: dir + output.componentName + adapter.extension,
18560
19147
  content: output.rawTemplate,
18561
- type: "markedTemplate"
19148
+ type: "markedTemplate",
19149
+ componentName: output.componentName
18562
19150
  });
18563
19151
  const ir = entries.find((e) => e.componentIR.metadata.componentName === output.componentName);
18564
19152
  const ssrDefaults = ir ? extractSsrDefaults(ir.componentIR.metadata) : undefined;
@@ -18566,7 +19154,8 @@ function compileMultipleComponents(source, filePath, componentNames, options) {
18566
19154
  files.push({
18567
19155
  path: dir + output.componentName + ".ssr-defaults.json",
18568
19156
  content: JSON.stringify(ssrDefaults),
18569
- type: "ssrDefaults"
19157
+ type: "ssrDefaults",
19158
+ componentName: output.componentName
18570
19159
  });
18571
19160
  }
18572
19161
  }
@@ -18862,7 +19451,8 @@ function compileJSX(source, filePath, options) {
18862
19451
  files.push({
18863
19452
  path: filePath.replace(/\.tsx?$/, adapter.extension),
18864
19453
  content,
18865
- type: "markedTemplate"
19454
+ type: "markedTemplate",
19455
+ componentName: componentIR.metadata.componentName
18866
19456
  });
18867
19457
  {
18868
19458
  const ssrDefaults = extractSsrDefaults(componentIR.metadata);
@@ -18870,7 +19460,8 @@ function compileJSX(source, filePath, options) {
18870
19460
  files.push({
18871
19461
  path: filePath.replace(/\.tsx?$/, ".ssr-defaults.json"),
18872
19462
  content: JSON.stringify(ssrDefaults),
18873
- type: "ssrDefaults"
19463
+ type: "ssrDefaults",
19464
+ componentName: componentIR.metadata.componentName
18874
19465
  });
18875
19466
  }
18876
19467
  }
@@ -19439,7 +20030,7 @@ function emitParsedExpr(expr, emitter) {
19439
20030
  return emitter.objectLiteral(expr.properties, expr.raw, emit);
19440
20031
  case "array-method":
19441
20032
  if (expr.method === "flat") {
19442
- return emitter.flatMethod(expr.object, expr.flatDepth, emit);
20033
+ return emitter.flatMethod(expr.object, expr.depthExpr ? { expr: expr.depthExpr } : expr.flatDepth, emit);
19443
20034
  }
19444
20035
  return emitter.arrayMethod(expr.method, expr.object, expr.args, emit);
19445
20036
  case "unsupported":
@@ -19512,39 +20103,6 @@ function isOmitBranch(node) {
19512
20103
  }
19513
20104
  return false;
19514
20105
  }
19515
- // src/lowering-registry.ts
19516
- var plugins = [];
19517
- function registerLoweringPlugin(plugin) {
19518
- const existing = plugins.findIndex((p) => p.name === plugin.name);
19519
- if (existing >= 0)
19520
- plugins[existing] = plugin;
19521
- else
19522
- plugins.push(plugin);
19523
- }
19524
- function getLoweringPlugins() {
19525
- return [...plugins];
19526
- }
19527
- function prepareLoweringMatchers(metadata) {
19528
- const matchers = [];
19529
- for (const plugin of plugins) {
19530
- const matcher = plugin.prepare(metadata);
19531
- if (matcher)
19532
- matchers.push(matcher);
19533
- }
19534
- return matchers;
19535
- }
19536
- function matchLoweringCall(callee, args, metadata) {
19537
- for (const matcher of prepareLoweringMatchers(metadata)) {
19538
- const node = matcher(callee, args);
19539
- if (node)
19540
- return node;
19541
- }
19542
- return null;
19543
- }
19544
- function __resetLoweringPluginsForTest(next = []) {
19545
- plugins.length = 0;
19546
- plugins.push(...next);
19547
- }
19548
20106
  // src/builtin-lowering-plugins.ts
19549
20107
  var queryHrefPlugin = {
19550
20108
  name: "queryHref",
@@ -19718,8 +20276,7 @@ function parseAndMerge(content, importsBySource, otherImports, codeSections) {
19718
20276
  }
19719
20277
  }
19720
20278
  // src/loop-destructure.ts
19721
- var SIMPLE_FIELD = /^\.[A-Za-z_$][\w$]*$/;
19722
- function isLowerableObjectRestDestructure(loop) {
20279
+ function isLowerableLoopDestructure(loop) {
19723
20280
  const bindings = loop.paramBindings;
19724
20281
  if (!bindings || bindings.length === 0)
19725
20282
  return false;
@@ -19731,18 +20288,20 @@ function isLowerableObjectRestDestructure(loop) {
19731
20288
  }
19732
20289
  for (const b of bindings) {
19733
20290
  if (b.rest) {
19734
- if (b.rest.kind !== "object")
20291
+ if (!b.segments)
19735
20292
  return false;
19736
- } else if (!SIMPLE_FIELD.test(b.path)) {
20293
+ } else if (!b.segments || b.segments.length === 0) {
19737
20294
  return false;
19738
20295
  }
19739
20296
  }
19740
- const restNames = bindings.filter((b) => b.rest).map((b) => b.name);
19741
- if (restNames.length === 0)
20297
+ const objectRestNames = bindings.filter((b) => b.rest?.kind === "object").map((b) => b.name);
20298
+ if (objectRestNames.length === 0)
19742
20299
  return true;
19743
- return !restNamesMisused(loop, restNames);
20300
+ return !restNamesMisused(loop, objectRestNames);
19744
20301
  }
20302
+ var isLowerableObjectRestDestructure = isLowerableLoopDestructure;
19745
20303
  function restNamesMisused(loop, names) {
20304
+ const nameSet = new Set(names);
19746
20305
  const valueUse = names.map((n) => new RegExp(`(?<![\\w.$])${escapeRe(n)}(?!\\s*\\??\\.)(?![\\w$])`));
19747
20306
  let misused = false;
19748
20307
  const check = (s) => {
@@ -19755,7 +20314,10 @@ function restNamesMisused(loop, names) {
19755
20314
  }
19756
20315
  }
19757
20316
  };
19758
- const attr = (v) => {
20317
+ const attr = (v, isIntrinsicElementAttrs) => {
20318
+ if (v.kind === "spread" && isIntrinsicElementAttrs && nameSet.has(v.expr.trim())) {
20319
+ return;
20320
+ }
19759
20321
  if (v.kind === "expression" || v.kind === "spread") {
19760
20322
  check(v.expr);
19761
20323
  check(v.templateExpr);
@@ -19815,16 +20377,16 @@ function restNamesMisused(loop, names) {
19815
20377
  visit3(node.alternate);
19816
20378
  break;
19817
20379
  case "element":
19818
- node.attrs.forEach((a) => attr(a.value));
20380
+ node.attrs.forEach((a) => attr(a.value, true));
19819
20381
  node.events.forEach((e) => check(e.handler));
19820
20382
  node.children.forEach(visit3);
19821
20383
  break;
19822
20384
  case "component":
19823
- node.props.forEach((p) => attr(p.value));
20385
+ node.props.forEach((p) => attr(p.value, false));
19824
20386
  node.children.forEach(visit3);
19825
20387
  break;
19826
20388
  case "provider":
19827
- attr(node.valueProp.value);
20389
+ attr(node.valueProp.value, false);
19828
20390
  node.children.forEach(visit3);
19829
20391
  break;
19830
20392
  case "fragment":
@@ -22491,8 +23053,10 @@ export {
22491
23053
  listComponentFunctions,
22492
23054
  jsxToIR,
22493
23055
  joinProfilerEvents,
23056
+ isValidHelperId,
22494
23057
  isSupported,
22495
23058
  isLowerableObjectRestDestructure,
23059
+ isLowerableLoopDestructure,
22496
23060
  isBooleanAttr,
22497
23061
  importsSearchParams,
22498
23062
  identifierPath,