@fluixi/compiler 1.0.0-alpha.92 → 1.0.0-alpha.93

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 (68) hide show
  1. package/dist/analyze/intrinsics.cjs +18 -0
  2. package/dist/analyze/intrinsics.d.ts.map +1 -1
  3. package/dist/analyze/intrinsics.js +18 -0
  4. package/dist/analyze/intrinsics.mjs +18 -0
  5. package/dist/analyze/reactive-bindings.cjs +18 -0
  6. package/dist/analyze/reactive-bindings.mjs +18 -0
  7. package/dist/analyze/static-graph.cjs +22 -4
  8. package/dist/analyze/static-graph.d.ts.map +1 -1
  9. package/dist/analyze/static-graph.js +6 -4
  10. package/dist/analyze/static-graph.mjs +22 -4
  11. package/dist/{babel-GI375YZ2.mjs → babel-RJHJQVEM.mjs} +4 -4
  12. package/dist/{chunk-QZ4CJPYP.mjs → chunk-56QSOVVL.mjs} +1 -1
  13. package/dist/{chunk-XJ5H2MXY.mjs → chunk-OD53TMER.mjs} +19 -1
  14. package/dist/{chunk-DWNWN5RH.mjs → chunk-R7PPFBCL.mjs} +1 -1
  15. package/dist/{chunk-VOUI6XPV.mjs → chunk-TJIH7TLX.mjs} +1 -1
  16. package/dist/{chunk-VC6X44FS.mjs → chunk-U7HCFLZ2.mjs} +153 -48
  17. package/dist/codegen/backends/imperative.cjs +24 -6
  18. package/dist/codegen/backends/imperative.d.ts.map +1 -1
  19. package/dist/codegen/backends/imperative.js +19 -4
  20. package/dist/codegen/backends/imperative.mjs +24 -6
  21. package/dist/frontend/babel/build-ir-lit.cjs +120 -43
  22. package/dist/frontend/babel/build-ir-lit.mjs +120 -43
  23. package/dist/frontend/babel/build-ir.cjs +120 -43
  24. package/dist/frontend/babel/build-ir.mjs +120 -43
  25. package/dist/frontend/babel/index.cjs +121 -44
  26. package/dist/frontend/babel/index.mjs +121 -44
  27. package/dist/frontend/babel/lower-template.cjs +120 -43
  28. package/dist/frontend/babel/lower-template.mjs +120 -43
  29. package/dist/frontend/babel/plugin.cjs +120 -43
  30. package/dist/frontend/babel/plugin.mjs +120 -43
  31. package/dist/index.cjs +41 -38
  32. package/dist/index.mjs +41 -38
  33. package/dist/integrations.cjs +176 -52
  34. package/dist/integrations.mjs +8 -8
  35. package/dist/lower/compile-template.cjs +41 -38
  36. package/dist/lower/compile-template.mjs +41 -38
  37. package/dist/lower/jsx.cjs +509 -6
  38. package/dist/lower/jsx.d.ts.map +1 -1
  39. package/dist/lower/jsx.js +114 -6
  40. package/dist/lower/jsx.mjs +509 -6
  41. package/dist/lower/template.cjs +40 -37
  42. package/dist/lower/template.d.ts.map +1 -1
  43. package/dist/lower/template.js +22 -30
  44. package/dist/lower/template.mjs +40 -37
  45. package/dist/resolve/builtins.cjs +1 -1
  46. package/dist/resolve/builtins.mjs +1 -1
  47. package/dist/resolve/component-globals.cjs +14 -0
  48. package/dist/resolve/component-globals.mjs +14 -0
  49. package/dist/resolve/write-globals.cjs +14 -0
  50. package/dist/resolve/write-globals.mjs +14 -0
  51. package/dist/{server-fns-BBZRZUPP.mjs → server-fns-ZFRDIQ2X.mjs} +4 -4
  52. package/dist/{server-functions-VEGLIFCE.mjs → server-functions-CTDXX67X.mjs} +3 -3
  53. package/dist/transform/index.cjs +170 -47
  54. package/dist/transform/index.mjs +170 -47
  55. package/dist/transform/intrinsics.cjs +18 -0
  56. package/dist/transform/intrinsics.mjs +18 -0
  57. package/dist/transform/source-locations.cjs +64 -3
  58. package/dist/transform/source-locations.d.cts +7 -2
  59. package/dist/transform/source-locations.d.ts +7 -2
  60. package/dist/transform/source-locations.d.ts.map +1 -1
  61. package/dist/transform/source-locations.js +68 -3
  62. package/dist/transform/source-locations.mjs +64 -3
  63. package/dist/transform/templates.cjs +169 -46
  64. package/dist/transform/templates.js +2 -2
  65. package/dist/transform/templates.mjs +169 -46
  66. package/dist/{transform-UWZ7ZGJ2.mjs → transform-QWWQDCXE.mjs} +7 -7
  67. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  68. package/package.json +9 -3
@@ -481,6 +481,7 @@ var INTRINSICS = {
481
481
  $effect: { export: "effect", module: REACTIVE_MODULE, returns: "effect" },
482
482
  $store: { export: "store", module: "@fluixi/reactive/store", returns: "store-handle" },
483
483
  $resource: { export: "resource", module: REACTIVE_MODULE, returns: "resource-handle" },
484
+ $cachedResource: { export: "cachedResource", module: "@fluixi/reactive/cached-resource", returns: "resource-handle" },
484
485
  // No short runtime form for these yet, so the intrinsic stands for the `create*`
485
486
  // name. Still worth having: no import to write, and when a shorter form is
486
487
  // designed, the change is this line rather than a compiler pass.
@@ -491,6 +492,19 @@ var INTRINSICS = {
491
492
  // dependency analysis must not guess at.
492
493
  $untrack: { export: "untrack", module: REACTIVE_MODULE, returns: "plain" },
493
494
  $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" },
495
+ // Scopes, scheduling and lifecycle: no value to classify, but the same authoring promise,
496
+ // no import to write. `$cleanup` registers a callback on the current owner (`onCleanup`).
497
+ $batch: { export: "batch", module: REACTIVE_MODULE, returns: "plain" },
498
+ $cleanup: { export: "onCleanup", module: REACTIVE_MODULE, returns: "plain" },
499
+ $root: { export: "root", module: REACTIVE_MODULE, returns: "plain" },
500
+ $context: { export: "context", module: REACTIVE_MODULE, returns: "plain" },
501
+ $renderEffect: { export: "renderEffect", module: REACTIVE_MODULE, returns: "effect" },
502
+ $computed: { export: "computed", module: REACTIVE_MODULE, returns: "effect" },
503
+ // Storage for where the code runs, and a signal kept in it. `$persisted` is a signal
504
+ // handle as far as reads go: the same accessor with `set` on it.
505
+ $storage: { export: "useStorage", module: "@fluixi/core/storage", returns: "plain" },
506
+ $asyncStorage: { export: "useAsyncStorage", module: "@fluixi/core/storage", returns: "plain" },
507
+ $persisted: { export: "persisted", module: "@fluixi/core/storage", returns: "signal-handle" },
494
508
  // An environment variable, public or server-only; the browser build refuses a secret.
495
509
  $env: { export: "env", module: "@fluixi/start/env", returns: "plain" }
496
510
  };
@@ -503,8 +517,11 @@ var RUNTIME_PRIMITIVES = {
503
517
  createMemo: "memo-accessor",
504
518
  createEffect: "effect",
505
519
  createRenderEffect: "effect",
520
+ renderEffect: "effect",
521
+ computed: "effect",
506
522
  createStore: "reactive-object",
507
523
  resource: "resource-handle",
524
+ cachedResource: "resource-handle",
508
525
  createResource: "reactive-object",
509
526
  createSelector: "memo-accessor",
510
527
  createDeferred: "memo-accessor"
@@ -513,6 +530,7 @@ var PRIMITIVE_MODULES = [
513
530
  "@fluixi/reactive",
514
531
  "@fluixi/reactive/signal",
515
532
  "@fluixi/reactive/store",
533
+ "@fluixi/reactive/cached-resource",
516
534
  "@fluixi/core"
517
535
  ];
518
536
  var INTRINSIC_CALL = new RegExp(`\\$(?:${Object.keys(INTRINSICS).map((n) => n.slice(1)).join("|")})\\s*\\(`);
@@ -648,6 +666,7 @@ function resolveReactiveBindings(program) {
648
666
  // src/transform/source-locations.ts
649
667
  var SOURCE_MARKER = "__fx_source";
650
668
  var COMPONENT_MARKER = "__fx_component";
669
+ var CLASS_MARKER = "__fx_class";
651
670
  var HOLE_MARKER = "__fx_hole";
652
671
  var markHole = (accessor, line, column, name) => `(globalThis.${HOLE_MARKER} ?? ((f) => f))(${accessor},${line},${column}${name === void 0 ? "" : `,${JSON.stringify(name)}`})`;
653
672
  var PROPS_MARKER = "__fx_props";
@@ -665,11 +684,12 @@ var RUNTIME_PRIMITIVES2 = /* @__PURE__ */ new Set([
665
684
  "createStore",
666
685
  "createResource"
667
686
  ]);
668
- function mayHaveReactiveCall(code) {
687
+ function mayHaveSourceMarks(code) {
669
688
  for (const name of RUNTIME_PRIMITIVES2) {
670
689
  if (code.includes(name)) return true;
671
690
  }
672
- return code.includes("$signal") || code.includes("$memo") || code.includes("$effect") || code.includes("$store");
691
+ if (code.includes("$signal") || code.includes("$memo") || code.includes("$effect") || code.includes("$store")) return true;
692
+ return /\bclass\b[^{;]*\bextends\b/.test(code);
673
693
  }
674
694
  var SINGLE_STATEMENT_PARENTS = /* @__PURE__ */ new Set([
675
695
  "IfStatement",
@@ -688,6 +708,7 @@ function isLoneBranch(statement, parent) {
688
708
  function isExport(node) {
689
709
  return node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration";
690
710
  }
711
+ var CLASS_FIELDS = /* @__PURE__ */ new Set(["PropertyDefinition", "ClassProperty", "ClassPrivateProperty", "AccessorProperty"]);
691
712
  function isStatement(node) {
692
713
  return node.type.endsWith("Statement") || node.type === "VariableDeclaration";
693
714
  }
@@ -708,6 +729,14 @@ function walk3(node, visit, stack = []) {
708
729
  }
709
730
  function nameFor(stack) {
710
731
  const declarator = stack[stack.length - 1];
732
+ if (declarator && CLASS_FIELDS.has(declarator.type)) {
733
+ const key = declarator.key;
734
+ if (key?.type === "Identifier" || key?.type === "PrivateIdentifier" || key?.type === "PrivateName") {
735
+ const name = key.name ?? key.id?.name;
736
+ return name;
737
+ }
738
+ return void 0;
739
+ }
711
740
  if (declarator?.type !== "VariableDeclarator") return void 0;
712
741
  const id = declarator.id;
713
742
  if (id?.type === "Identifier") return id.name;
@@ -756,6 +785,17 @@ function collectSourceEdits(program, filename) {
756
785
  });
757
786
  }
758
787
  }
788
+ if ((node.type === "ClassDeclaration" || node.type === "ClassExpression") && node.superClass && !node.declare) {
789
+ const at = node.loc?.start;
790
+ const body = node.body;
791
+ if (at && body) {
792
+ edits.push({
793
+ start: body.start + 1,
794
+ end: body.start + 1,
795
+ code: `static{globalThis.${CLASS_MARKER}?.(this,${JSON.stringify(filename)},${at.line},${at.column});}`
796
+ });
797
+ }
798
+ }
759
799
  if (node.type !== "CallExpression") return;
760
800
  const callee = node.callee;
761
801
  if (callee?.type !== "Identifier") return;
@@ -765,6 +805,26 @@ function collectSourceEdits(program, filename) {
765
805
  if (!intrinsic && !imported) return;
766
806
  const position = node.loc?.start;
767
807
  if (!position) return;
808
+ for (let i = stack.length - 1; i >= 0; i--) {
809
+ const up = stack[i];
810
+ if (isStatement(up)) break;
811
+ if (CLASS_FIELDS.has(up.type)) {
812
+ if (claimed.has(node.start)) return;
813
+ claimed.add(node.start);
814
+ const label2 = nameFor(stack);
815
+ const own = !up.static;
816
+ const args2 = [
817
+ JSON.stringify(filename),
818
+ String(position.line),
819
+ String(position.column),
820
+ label2 ? JSON.stringify(label2) : own ? "undefined" : void 0,
821
+ own ? "this" : void 0
822
+ ].filter((a) => a !== void 0).join(",");
823
+ edits.push({ start: node.start, end: node.start, code: `(globalThis.${SOURCE_MARKER}?.(${args2}),` });
824
+ edits.push({ start: node.end, end: node.end, code: ")" });
825
+ return;
826
+ }
827
+ }
768
828
  let statement;
769
829
  let parent;
770
830
  let index = -1;
@@ -970,13 +1030,16 @@ function emitComponent(name, props, children, used, templates, at) {
970
1030
  used.add("createComponent");
971
1031
  const spreads = props.filter((p) => p.kind === "spread");
972
1032
  const normal = props.filter((p) => p.kind !== "spread");
973
- const entries = normal.map(
974
- (p) => `get ${propKey(p.name)}() { return ${componentPropValue(p)}; }`
975
- );
1033
+ const bodies = normal.map((p) => [p.name, componentPropValue(p)]);
976
1034
  if (children.length > 0) {
977
- entries.push(`get children() { return ${emitChildren(children, used, templates)}; }`);
1035
+ bodies.push(["children", emitChildren(children, used, templates)]);
1036
+ }
1037
+ let obj = `{ ${bodies.map(([name2, body]) => `get ${propKey(name2)}() { return ${body}; }`).join(", ")} }`;
1038
+ if (bodies.some(([, body]) => /\bthis\b/.test(body))) {
1039
+ const thunks = bodies.map(([name2, body]) => `${JSON.stringify(name2)}: () => ${body}`);
1040
+ const getters = bodies.map(([name2]) => `get ${propKey(name2)}() { return _$t[${JSON.stringify(name2)}](); }`);
1041
+ obj = `((_$t) => ({ ${getters.join(", ")} }))({ ${thunks.join(", ")} })`;
978
1042
  }
979
- let obj = `{ ${entries.join(", ")} }`;
980
1043
  if (spreads.length > 0) {
981
1044
  used.add("mergeProps");
982
1045
  obj = `mergeProps(${spreads.map((s) => s.expr).join(", ")}, ${obj})`;
@@ -1058,7 +1121,8 @@ function emitNode(node, used, templates) {
1058
1121
  } else {
1059
1122
  used.add("spread");
1060
1123
  const svgFlag = node.svg ? ", isSVG: true" : "";
1061
- const customFlag = node.tag.includes("-") ? ", isCustomElement: true" : "";
1124
+ const accessors = node.props.filter((p) => p.kind !== "spread" && p.expr !== void 0 && p.reactive && p.name !== "ref").map((p) => p.name);
1125
+ const customFlag = node.tag.includes("-") ? `, isCustomElement: true, accessors: ${JSON.stringify(accessors)}` : "";
1062
1126
  parts.push(
1063
1127
  `spread({ element: ${el}, props: ${emitProps(node.props, null, used)}${svgFlag}${customFlag} });`
1064
1128
  );
@@ -1189,6 +1253,7 @@ var DELEGATED = /* @__PURE__ */ new Set([
1189
1253
  function escapeTemplate(s) {
1190
1254
  return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
1191
1255
  }
1256
+ var CONTAINER = "container";
1192
1257
  function capitalize(s) {
1193
1258
  return s.charAt(0).toUpperCase() + s.slice(1);
1194
1259
  }
@@ -1243,10 +1308,6 @@ var Lowerer = class {
1243
1308
  if (hasElse) i = elseIdx;
1244
1309
  continue;
1245
1310
  }
1246
- if (child.attributes.some((a) => a.kind === "EachDirective")) {
1247
- out.push(this.lowerEach(child));
1248
- continue;
1249
- }
1250
1311
  }
1251
1312
  const node = this.lowerNode(child);
1252
1313
  if (node) out.push(node);
@@ -1288,13 +1349,16 @@ var Lowerer = class {
1288
1349
  node.props.unshift({ name: "component", kind: "attr", expr: `() => ${paren(this.hole(isAttr.value.hole).code, "arrowBody")}` });
1289
1350
  return node;
1290
1351
  }
1352
+ const each = this.lowerEach(el);
1353
+ const children = each ? [each] : this.lowerChildren(el.children);
1354
+ if (el.tag === CONTAINER) return children.length === 1 ? children[0] : { kind: "fragment", children };
1291
1355
  const at = this.tagAt(el);
1292
1356
  return {
1293
1357
  kind: "element",
1294
1358
  tag: el.tag,
1295
1359
  svg: el.namespace === "svg",
1296
1360
  props: this.lowerAttributes(el.attributes),
1297
- children: this.lowerChildren(el.children),
1361
+ children,
1298
1362
  static: false,
1299
1363
  ...at ? { at } : {}
1300
1364
  };
@@ -1366,10 +1430,14 @@ var Lowerer = class {
1366
1430
  const at = this.tagAt(el);
1367
1431
  return { kind: "component", name: "Show", props, children: [child], ...at ? { at } : {}, ...cond.at ? { bindAt: cond.at } : {} };
1368
1432
  }
1369
- /** `<el each=${items} key?>${item => …}</el>` → `<For each>{item => <el>…</el>}</For>`. */
1433
+ /**
1434
+ * `<el each=${items} key?>${item => …}</el>` → `<el><For each>{item => …}</For></el>`: the
1435
+ * content is repeated per item. Content that is not a `${item => …}` arrow is repeated as
1436
+ * it is. Null when the element has no `each`.
1437
+ */
1370
1438
  lowerEach(el) {
1371
1439
  const eachDir = el.attributes.find((a) => a.kind === "EachDirective");
1372
- if (!eachDir || eachDir.kind !== "EachDirective") return this.lowerNode(el);
1440
+ if (!eachDir || eachDir.kind !== "EachDirective") return null;
1373
1441
  const each = this.hole(eachDir.hole);
1374
1442
  const props = [{ name: "each", kind: "attr", expr: each.code, reactive: each.reactive, ...each.at ? { at: each.at } : {} }];
1375
1443
  if (eachDir.key) {
@@ -1379,12 +1447,12 @@ var Lowerer = class {
1379
1447
  const arrow = this.itemArrow(el);
1380
1448
  let childrenCode;
1381
1449
  if (arrow) {
1382
- const bodyIR = { kind: "expr", code: arrow.body, reactive: arrow.bodyReactive };
1383
- const repeated = this.rebuildWithChildren(el, [bodyIR]);
1384
- childrenCode = `(${arrow.params.join(", ")}) => ${paren(this.emit(repeated), "arrowBody")}`;
1450
+ const body = arrow.bodyReactive ? `() => ${paren(arrow.body, "arrowBody")}` : paren(arrow.body, "arrowBody");
1451
+ childrenCode = `(${arrow.params.join(", ")}) => ${body}`;
1385
1452
  } else {
1386
- const repeated = this.stripAndLower(el, (a) => a.kind === "EachDirective");
1387
- childrenCode = `() => ${paren(this.emit(repeated), "arrowBody")}`;
1453
+ const content = this.lowerChildren(el.children);
1454
+ const ir = content.length === 1 ? content[0] : { kind: "fragment", children: content };
1455
+ childrenCode = `() => ${paren(this.emit(ir), "arrowBody")}`;
1388
1456
  }
1389
1457
  const children = [{ kind: "expr", code: childrenCode, reactive: false }];
1390
1458
  const at = this.tagAt(el);
@@ -1401,25 +1469,6 @@ var Lowerer = class {
1401
1469
  const clone = { ...el, attributes: el.attributes.filter((a) => !drop(a)) };
1402
1470
  return clone.kind === "Element" ? this.lowerElement(clone) : this.lowerComponent(clone);
1403
1471
  }
1404
- /** Same element (minus each/key) but with explicit IR children. */
1405
- rebuildWithChildren(el, children) {
1406
- const props = this.lowerAttributes(
1407
- el.attributes.filter((a) => a.kind !== "EachDirective")
1408
- );
1409
- if (el.kind === "Element") {
1410
- const at = this.tagAt(el);
1411
- return {
1412
- kind: "element",
1413
- tag: el.tag,
1414
- svg: el.namespace === "svg",
1415
- props,
1416
- children,
1417
- static: false,
1418
- ...at ? { at } : {}
1419
- };
1420
- }
1421
- return { kind: "component", name: el.tag, props, children };
1422
- }
1423
1472
  // --- attributes ---------------------------------------------------------
1424
1473
  lowerAttributes(attrs) {
1425
1474
  const props = [];
@@ -1738,6 +1787,22 @@ var DELEGATED2 = /* @__PURE__ */ new Set([
1738
1787
  "mouseup"
1739
1788
  ]);
1740
1789
  var isReactive = (node) => isReactiveShape(shapeOf(node));
1790
+ var CONTAINER2 = "container";
1791
+ function directive(node, name) {
1792
+ if (node.type !== "JSXElement") return void 0;
1793
+ const opening = node.openingElement;
1794
+ if (opening.name.type !== "JSXIdentifier" || opening.name.name[0] !== opening.name.name[0].toLowerCase()) return void 0;
1795
+ return opening.attributes.find((a) => a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === name);
1796
+ }
1797
+ function directiveExpr(attr) {
1798
+ const v = attr.value;
1799
+ return v?.type === "JSXExpressionContainer" && v.expression?.type !== "JSXEmptyExpression" ? v.expression : void 0;
1800
+ }
1801
+ function emitIR(ir, ctx) {
1802
+ const { code, imports, templates } = imperativeBackend.emit(ir, {});
1803
+ for (const s of imports) ctx.used.add(s);
1804
+ return inlineTemplates(code, templates);
1805
+ }
1741
1806
  var posOf = (node, ctx) => {
1742
1807
  if (!ctx.sourceFile) return void 0;
1743
1808
  const at = node.loc?.start;
@@ -1846,7 +1911,16 @@ function buildProps(attrs, ctx) {
1846
1911
  }
1847
1912
  function buildChildren(children, ctx) {
1848
1913
  const out = [];
1849
- for (const child of children) {
1914
+ for (let i = 0; i < children.length; i++) {
1915
+ const child = children[i];
1916
+ if (directive(child, "if")) {
1917
+ let next = i + 1;
1918
+ while (next < children.length && children[next].type === "JSXText" && !cleanTemplateText(children[next].value)) next++;
1919
+ const fallback = directive(children[next], "else") ? children[next] : null;
1920
+ out.push(buildIf(child, fallback, ctx));
1921
+ if (fallback) i = next;
1922
+ continue;
1923
+ }
1850
1924
  if (child.type === "JSXText") {
1851
1925
  const text = cleanTemplateText(child.value);
1852
1926
  if (text) out.push({ kind: "text", value: text });
@@ -1883,19 +1957,68 @@ function readLoad(attrs) {
1883
1957
  }
1884
1958
  return void 0;
1885
1959
  }
1960
+ function buildIf(node, fallback, ctx) {
1961
+ const cond = directiveExpr(directive(node, "if"));
1962
+ const props = [
1963
+ cond ? { name: "when", kind: "attr", expr: ctx.code(cond), reactive: isReactive(cond), at: posOf(cond, ctx) } : { name: "when", kind: "attr", literal: true }
1964
+ ];
1965
+ if (fallback) {
1966
+ props.push({ name: "fallback", kind: "attr", expr: emitIR(buildElement(fallback, ctx, ["else"]), ctx), jsxElement: true });
1967
+ }
1968
+ return { kind: "component", name: "Show", props, children: [buildElement(node, ctx, ["if"])] };
1969
+ }
1970
+ function buildEach(node, ctx) {
1971
+ const attr = directive(node, "each");
1972
+ if (!attr) return null;
1973
+ const list = directiveExpr(attr);
1974
+ const props = [
1975
+ list ? { name: "each", kind: "attr", expr: ctx.code(list), reactive: isReactive(list), at: posOf(list, ctx) } : { name: "each", kind: "attr", expr: "[]" }
1976
+ ];
1977
+ const key = directive(node, "key");
1978
+ if (key) {
1979
+ const keyExpr = directiveExpr(key);
1980
+ const by = isString(key.value) ? `(item) => item[${JSON.stringify(literalOf(key.value))}]` : keyExpr ? ctx.code(keyExpr) : void 0;
1981
+ if (by) props.push({ name: "by", kind: "attr", expr: by });
1982
+ }
1983
+ const content = node.children.filter((c) => !(c.type === "JSXText" && !cleanTemplateText(c.value)));
1984
+ const only = content.length === 1 && content[0].type === "JSXExpressionContainer" ? content[0].expression : null;
1985
+ let render;
1986
+ if (only && (only.type === "ArrowFunctionExpression" || only.type === "FunctionExpression")) {
1987
+ const fn = ctx.code(only);
1988
+ const live = only.type === "ArrowFunctionExpression" && only.body.type !== "BlockStatement" && isReactive(only.body);
1989
+ render = live ? `(...args) => () => (${fn})(...args)` : fn;
1990
+ } else {
1991
+ const built = buildChildren(node.children, ctx);
1992
+ const ir = built.length === 1 ? built[0] : { kind: "fragment", children: built };
1993
+ render = `() => ${paren(emitIR(ir, ctx), "arrowBody")}`;
1994
+ }
1995
+ return { kind: "component", name: "For", props, children: [{ kind: "expr", code: render, reactive: false }] };
1996
+ }
1997
+ function buildElement(node, ctx, handled) {
1998
+ const skip = new Set(handled);
1999
+ const directives = /* @__PURE__ */ new Set(["if", "else", "each", ...directive(node, "each") ? ["key"] : []]);
2000
+ const attributes = node.openingElement.attributes.filter(
2001
+ (a) => !(a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && (skip.has(a.name.name) || directives.has(a.name.name)))
2002
+ );
2003
+ const { tag } = tagInfo(node.openingElement.name, ctx);
2004
+ const each = buildEach(node, ctx);
2005
+ const children = each ? [each] : buildChildren(node.children, ctx);
2006
+ if (tag === CONTAINER2) return children.length === 1 ? children[0] : { kind: "fragment", children };
2007
+ const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
2008
+ return { kind: "element", tag, svg: SVG_TAGS.has(tag), props: buildProps(attributes, ctx), children, static: false, ...at ? { at } : {} };
2009
+ }
1886
2010
  function buildNode(node, ctx) {
1887
2011
  if (node.type === "JSXFragment") {
1888
2012
  return { kind: "fragment", children: buildChildren(node.children, ctx) };
1889
2013
  }
2014
+ if (directive(node, "if")) return buildIf(node, null, ctx);
1890
2015
  const { tag, component } = tagInfo(node.openingElement.name, ctx);
2016
+ if (!component) return buildElement(node, ctx, []);
1891
2017
  const props = buildProps(node.openingElement.attributes, ctx);
1892
2018
  const children = buildChildren(node.children, ctx);
1893
2019
  const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
1894
- if (component) {
1895
- const load = readLoad(node.openingElement.attributes);
1896
- return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
1897
- }
1898
- return { kind: "element", tag, svg: SVG_TAGS.has(tag), props, children, static: false, ...at ? { at } : {} };
2020
+ const load = readLoad(node.openingElement.attributes);
2021
+ return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
1899
2022
  }
1900
2023
  function buildJsxIR(node, ctx) {
1901
2024
  const ir = buildNode(node, ctx);
@@ -2541,7 +2664,7 @@ function transformTemplates(code, id, options = {}) {
2541
2664
  const wantsJsx = format !== "lit";
2542
2665
  const mayHaveTemplate = wantsLit && (code.includes(`${litTag}\``) || code.includes("svg`"));
2543
2666
  const wantsIntrinsics = options.intrinsics !== false && mayHaveIntrinsic(code);
2544
- const wantsSources = options.sourceLocations === true && mayHaveReactiveCall(code);
2667
+ const wantsSources = options.sourceLocations === true && mayHaveSourceMarks(code);
2545
2668
  const sourceName = relativeToRoot(id, options.sourceRoot);
2546
2669
  if (!mayHaveTemplate && !(wantsJsx && code.includes("<")) && !wantsIntrinsics && !wantsSources) return null;
2547
2670
  const ast = (0, import_parser2.parse)(code, {
@@ -21,7 +21,7 @@ import { imperativeBackend } from '../codegen/backends/imperative.js';
21
21
  import { moduleBindings } from './bindings.js';
22
22
  import { collectPropsEdits } from './props.js';
23
23
  import { avoidBindingClashes, collectIntrinsicEdits, intrinsicImports } from './intrinsics.js';
24
- import { collectSourceEdits, mayHaveReactiveCall } from './source-locations.js';
24
+ import { collectSourceEdits, mayHaveSourceMarks } from './source-locations.js';
25
25
  import { mayHaveIntrinsic } from '../analyze/intrinsics.js';
26
26
  import { declaredNames } from '../analyze/reactive-bindings.js';
27
27
  import { DOM_CONTROL_FLOW, CORE_CONTROL_FLOW, ROUTER_COMPONENTS } from '../resolve/builtins.js';
@@ -303,7 +303,7 @@ export function transformTemplates(code, id, options = {}) {
303
303
  const wantsIntrinsics = options.intrinsics !== false && mayHaveIntrinsic(code);
304
304
  // A module can be nothing but reactive calls and still need marking, so the skip check
305
305
  // has to look for those too when source locations are wanted.
306
- const wantsSources = options.sourceLocations === true && mayHaveReactiveCall(code);
306
+ const wantsSources = options.sourceLocations === true && mayHaveSourceMarks(code);
307
307
  // What a mark calls this file. The declaration marks and the usage marks are read back by
308
308
  // name, so both passes have to spell it the same way.
309
309
  const sourceName = relativeToRoot(id, options.sourceRoot);