@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
@@ -302,6 +302,7 @@ var INTRINSICS = {
302
302
  $effect: { export: "effect", module: REACTIVE_MODULE, returns: "effect" },
303
303
  $store: { export: "store", module: "@fluixi/reactive/store", returns: "store-handle" },
304
304
  $resource: { export: "resource", module: REACTIVE_MODULE, returns: "resource-handle" },
305
+ $cachedResource: { export: "cachedResource", module: "@fluixi/reactive/cached-resource", returns: "resource-handle" },
305
306
  // No short runtime form for these yet, so the intrinsic stands for the `create*`
306
307
  // name. Still worth having: no import to write, and when a shorter form is
307
308
  // designed, the change is this line rather than a compiler pass.
@@ -312,6 +313,19 @@ var INTRINSICS = {
312
313
  // dependency analysis must not guess at.
313
314
  $untrack: { export: "untrack", module: REACTIVE_MODULE, returns: "plain" },
314
315
  $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" },
316
+ // Scopes, scheduling and lifecycle: no value to classify, but the same authoring promise,
317
+ // no import to write. `$cleanup` registers a callback on the current owner (`onCleanup`).
318
+ $batch: { export: "batch", module: REACTIVE_MODULE, returns: "plain" },
319
+ $cleanup: { export: "onCleanup", module: REACTIVE_MODULE, returns: "plain" },
320
+ $root: { export: "root", module: REACTIVE_MODULE, returns: "plain" },
321
+ $context: { export: "context", module: REACTIVE_MODULE, returns: "plain" },
322
+ $renderEffect: { export: "renderEffect", module: REACTIVE_MODULE, returns: "effect" },
323
+ $computed: { export: "computed", module: REACTIVE_MODULE, returns: "effect" },
324
+ // Storage for where the code runs, and a signal kept in it. `$persisted` is a signal
325
+ // handle as far as reads go: the same accessor with `set` on it.
326
+ $storage: { export: "useStorage", module: "@fluixi/core/storage", returns: "plain" },
327
+ $asyncStorage: { export: "useAsyncStorage", module: "@fluixi/core/storage", returns: "plain" },
328
+ $persisted: { export: "persisted", module: "@fluixi/core/storage", returns: "signal-handle" },
315
329
  // An environment variable, public or server-only; the browser build refuses a secret.
316
330
  $env: { export: "env", module: "@fluixi/start/env", returns: "plain" }
317
331
  };
@@ -489,13 +503,16 @@ function emitComponent(name, props, children, used, templates, at) {
489
503
  used.add("createComponent");
490
504
  const spreads = props.filter((p) => p.kind === "spread");
491
505
  const normal = props.filter((p) => p.kind !== "spread");
492
- const entries = normal.map(
493
- (p) => `get ${propKey(p.name)}() { return ${componentPropValue(p)}; }`
494
- );
506
+ const bodies = normal.map((p) => [p.name, componentPropValue(p)]);
495
507
  if (children.length > 0) {
496
- entries.push(`get children() { return ${emitChildren(children, used, templates)}; }`);
508
+ bodies.push(["children", emitChildren(children, used, templates)]);
509
+ }
510
+ let obj = `{ ${bodies.map(([name2, body]) => `get ${propKey(name2)}() { return ${body}; }`).join(", ")} }`;
511
+ if (bodies.some(([, body]) => /\bthis\b/.test(body))) {
512
+ const thunks = bodies.map(([name2, body]) => `${JSON.stringify(name2)}: () => ${body}`);
513
+ const getters = bodies.map(([name2]) => `get ${propKey(name2)}() { return _$t[${JSON.stringify(name2)}](); }`);
514
+ obj = `((_$t) => ({ ${getters.join(", ")} }))({ ${thunks.join(", ")} })`;
497
515
  }
498
- let obj = `{ ${entries.join(", ")} }`;
499
516
  if (spreads.length > 0) {
500
517
  used.add("mergeProps");
501
518
  obj = `mergeProps(${spreads.map((s) => s.expr).join(", ")}, ${obj})`;
@@ -577,7 +594,8 @@ function emitNode(node, used, templates) {
577
594
  } else {
578
595
  used.add("spread");
579
596
  const svgFlag = node.svg ? ", isSVG: true" : "";
580
- const customFlag = node.tag.includes("-") ? ", isCustomElement: true" : "";
597
+ const accessors = node.props.filter((p) => p.kind !== "spread" && p.expr !== void 0 && p.reactive && p.name !== "ref").map((p) => p.name);
598
+ const customFlag = node.tag.includes("-") ? `, isCustomElement: true, accessors: ${JSON.stringify(accessors)}` : "";
581
599
  parts.push(
582
600
  `spread({ element: ${el}, props: ${emitProps(node.props, null, used)}${svgFlag}${customFlag} });`
583
601
  );
@@ -648,6 +666,7 @@ var DELEGATED = /* @__PURE__ */ new Set([
648
666
  function escapeTemplate(s) {
649
667
  return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
650
668
  }
669
+ var CONTAINER = "container";
651
670
  function capitalize(s) {
652
671
  return s.charAt(0).toUpperCase() + s.slice(1);
653
672
  }
@@ -702,10 +721,6 @@ var Lowerer = class {
702
721
  if (hasElse) i = elseIdx;
703
722
  continue;
704
723
  }
705
- if (child.attributes.some((a) => a.kind === "EachDirective")) {
706
- out.push(this.lowerEach(child));
707
- continue;
708
- }
709
724
  }
710
725
  const node = this.lowerNode(child);
711
726
  if (node) out.push(node);
@@ -747,13 +762,16 @@ var Lowerer = class {
747
762
  node.props.unshift({ name: "component", kind: "attr", expr: `() => ${paren(this.hole(isAttr.value.hole).code, "arrowBody")}` });
748
763
  return node;
749
764
  }
765
+ const each = this.lowerEach(el);
766
+ const children = each ? [each] : this.lowerChildren(el.children);
767
+ if (el.tag === CONTAINER) return children.length === 1 ? children[0] : { kind: "fragment", children };
750
768
  const at = this.tagAt(el);
751
769
  return {
752
770
  kind: "element",
753
771
  tag: el.tag,
754
772
  svg: el.namespace === "svg",
755
773
  props: this.lowerAttributes(el.attributes),
756
- children: this.lowerChildren(el.children),
774
+ children,
757
775
  static: false,
758
776
  ...at ? { at } : {}
759
777
  };
@@ -825,10 +843,14 @@ var Lowerer = class {
825
843
  const at = this.tagAt(el);
826
844
  return { kind: "component", name: "Show", props, children: [child], ...at ? { at } : {}, ...cond.at ? { bindAt: cond.at } : {} };
827
845
  }
828
- /** `<el each=${items} key?>${item => …}</el>` → `<For each>{item => <el>…</el>}</For>`. */
846
+ /**
847
+ * `<el each=${items} key?>${item => …}</el>` → `<el><For each>{item => …}</For></el>`: the
848
+ * content is repeated per item. Content that is not a `${item => …}` arrow is repeated as
849
+ * it is. Null when the element has no `each`.
850
+ */
829
851
  lowerEach(el) {
830
852
  const eachDir = el.attributes.find((a) => a.kind === "EachDirective");
831
- if (!eachDir || eachDir.kind !== "EachDirective") return this.lowerNode(el);
853
+ if (!eachDir || eachDir.kind !== "EachDirective") return null;
832
854
  const each = this.hole(eachDir.hole);
833
855
  const props = [{ name: "each", kind: "attr", expr: each.code, reactive: each.reactive, ...each.at ? { at: each.at } : {} }];
834
856
  if (eachDir.key) {
@@ -838,12 +860,12 @@ var Lowerer = class {
838
860
  const arrow = this.itemArrow(el);
839
861
  let childrenCode;
840
862
  if (arrow) {
841
- const bodyIR = { kind: "expr", code: arrow.body, reactive: arrow.bodyReactive };
842
- const repeated = this.rebuildWithChildren(el, [bodyIR]);
843
- childrenCode = `(${arrow.params.join(", ")}) => ${paren(this.emit(repeated), "arrowBody")}`;
863
+ const body = arrow.bodyReactive ? `() => ${paren(arrow.body, "arrowBody")}` : paren(arrow.body, "arrowBody");
864
+ childrenCode = `(${arrow.params.join(", ")}) => ${body}`;
844
865
  } else {
845
- const repeated = this.stripAndLower(el, (a) => a.kind === "EachDirective");
846
- childrenCode = `() => ${paren(this.emit(repeated), "arrowBody")}`;
866
+ const content = this.lowerChildren(el.children);
867
+ const ir = content.length === 1 ? content[0] : { kind: "fragment", children: content };
868
+ childrenCode = `() => ${paren(this.emit(ir), "arrowBody")}`;
847
869
  }
848
870
  const children = [{ kind: "expr", code: childrenCode, reactive: false }];
849
871
  const at = this.tagAt(el);
@@ -860,25 +882,6 @@ var Lowerer = class {
860
882
  const clone = { ...el, attributes: el.attributes.filter((a) => !drop(a)) };
861
883
  return clone.kind === "Element" ? this.lowerElement(clone) : this.lowerComponent(clone);
862
884
  }
863
- /** Same element (minus each/key) but with explicit IR children. */
864
- rebuildWithChildren(el, children) {
865
- const props = this.lowerAttributes(
866
- el.attributes.filter((a) => a.kind !== "EachDirective")
867
- );
868
- if (el.kind === "Element") {
869
- const at = this.tagAt(el);
870
- return {
871
- kind: "element",
872
- tag: el.tag,
873
- svg: el.namespace === "svg",
874
- props,
875
- children,
876
- static: false,
877
- ...at ? { at } : {}
878
- };
879
- }
880
- return { kind: "component", name: el.tag, props, children };
881
- }
882
885
  // --- attributes ---------------------------------------------------------
883
886
  lowerAttributes(attrs) {
884
887
  const props = [];
@@ -1248,6 +1251,22 @@ var DELEGATED2 = /* @__PURE__ */ new Set([
1248
1251
  "mouseup"
1249
1252
  ]);
1250
1253
  var isReactive = (node) => isReactiveShape(shapeOf(node));
1254
+ var CONTAINER2 = "container";
1255
+ function directive(node, name) {
1256
+ if (node.type !== "JSXElement") return void 0;
1257
+ const opening = node.openingElement;
1258
+ if (opening.name.type !== "JSXIdentifier" || opening.name.name[0] !== opening.name.name[0].toLowerCase()) return void 0;
1259
+ return opening.attributes.find((a) => a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === name);
1260
+ }
1261
+ function directiveExpr(attr) {
1262
+ const v = attr.value;
1263
+ return v?.type === "JSXExpressionContainer" && v.expression?.type !== "JSXEmptyExpression" ? v.expression : void 0;
1264
+ }
1265
+ function emitIR(ir, ctx) {
1266
+ const { code, imports, templates } = imperativeBackend.emit(ir, {});
1267
+ for (const s of imports) ctx.used.add(s);
1268
+ return inlineTemplates(code, templates);
1269
+ }
1251
1270
  var posOf = (node, ctx) => {
1252
1271
  if (!ctx.sourceFile) return void 0;
1253
1272
  const at = node.loc?.start;
@@ -1356,7 +1375,16 @@ function buildProps(attrs, ctx) {
1356
1375
  }
1357
1376
  function buildChildren(children, ctx) {
1358
1377
  const out = [];
1359
- for (const child of children) {
1378
+ for (let i = 0; i < children.length; i++) {
1379
+ const child = children[i];
1380
+ if (directive(child, "if")) {
1381
+ let next = i + 1;
1382
+ while (next < children.length && children[next].type === "JSXText" && !cleanTemplateText(children[next].value)) next++;
1383
+ const fallback = directive(children[next], "else") ? children[next] : null;
1384
+ out.push(buildIf(child, fallback, ctx));
1385
+ if (fallback) i = next;
1386
+ continue;
1387
+ }
1360
1388
  if (child.type === "JSXText") {
1361
1389
  const text = cleanTemplateText(child.value);
1362
1390
  if (text) out.push({ kind: "text", value: text });
@@ -1393,19 +1421,68 @@ function readLoad(attrs) {
1393
1421
  }
1394
1422
  return void 0;
1395
1423
  }
1424
+ function buildIf(node, fallback, ctx) {
1425
+ const cond = directiveExpr(directive(node, "if"));
1426
+ const props = [
1427
+ cond ? { name: "when", kind: "attr", expr: ctx.code(cond), reactive: isReactive(cond), at: posOf(cond, ctx) } : { name: "when", kind: "attr", literal: true }
1428
+ ];
1429
+ if (fallback) {
1430
+ props.push({ name: "fallback", kind: "attr", expr: emitIR(buildElement(fallback, ctx, ["else"]), ctx), jsxElement: true });
1431
+ }
1432
+ return { kind: "component", name: "Show", props, children: [buildElement(node, ctx, ["if"])] };
1433
+ }
1434
+ function buildEach(node, ctx) {
1435
+ const attr = directive(node, "each");
1436
+ if (!attr) return null;
1437
+ const list = directiveExpr(attr);
1438
+ const props = [
1439
+ list ? { name: "each", kind: "attr", expr: ctx.code(list), reactive: isReactive(list), at: posOf(list, ctx) } : { name: "each", kind: "attr", expr: "[]" }
1440
+ ];
1441
+ const key = directive(node, "key");
1442
+ if (key) {
1443
+ const keyExpr = directiveExpr(key);
1444
+ const by = isString(key.value) ? `(item) => item[${JSON.stringify(literalOf(key.value))}]` : keyExpr ? ctx.code(keyExpr) : void 0;
1445
+ if (by) props.push({ name: "by", kind: "attr", expr: by });
1446
+ }
1447
+ const content = node.children.filter((c) => !(c.type === "JSXText" && !cleanTemplateText(c.value)));
1448
+ const only = content.length === 1 && content[0].type === "JSXExpressionContainer" ? content[0].expression : null;
1449
+ let render;
1450
+ if (only && (only.type === "ArrowFunctionExpression" || only.type === "FunctionExpression")) {
1451
+ const fn = ctx.code(only);
1452
+ const live = only.type === "ArrowFunctionExpression" && only.body.type !== "BlockStatement" && isReactive(only.body);
1453
+ render = live ? `(...args) => () => (${fn})(...args)` : fn;
1454
+ } else {
1455
+ const built = buildChildren(node.children, ctx);
1456
+ const ir = built.length === 1 ? built[0] : { kind: "fragment", children: built };
1457
+ render = `() => ${paren(emitIR(ir, ctx), "arrowBody")}`;
1458
+ }
1459
+ return { kind: "component", name: "For", props, children: [{ kind: "expr", code: render, reactive: false }] };
1460
+ }
1461
+ function buildElement(node, ctx, handled) {
1462
+ const skip = new Set(handled);
1463
+ const directives = /* @__PURE__ */ new Set(["if", "else", "each", ...directive(node, "each") ? ["key"] : []]);
1464
+ const attributes = node.openingElement.attributes.filter(
1465
+ (a) => !(a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && (skip.has(a.name.name) || directives.has(a.name.name)))
1466
+ );
1467
+ const { tag } = tagInfo(node.openingElement.name, ctx);
1468
+ const each = buildEach(node, ctx);
1469
+ const children = each ? [each] : buildChildren(node.children, ctx);
1470
+ if (tag === CONTAINER2) return children.length === 1 ? children[0] : { kind: "fragment", children };
1471
+ const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
1472
+ return { kind: "element", tag, svg: SVG_TAGS.has(tag), props: buildProps(attributes, ctx), children, static: false, ...at ? { at } : {} };
1473
+ }
1396
1474
  function buildNode(node, ctx) {
1397
1475
  if (node.type === "JSXFragment") {
1398
1476
  return { kind: "fragment", children: buildChildren(node.children, ctx) };
1399
1477
  }
1478
+ if (directive(node, "if")) return buildIf(node, null, ctx);
1400
1479
  const { tag, component } = tagInfo(node.openingElement.name, ctx);
1480
+ if (!component) return buildElement(node, ctx, []);
1401
1481
  const props = buildProps(node.openingElement.attributes, ctx);
1402
1482
  const children = buildChildren(node.children, ctx);
1403
1483
  const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
1404
- if (component) {
1405
- const load = readLoad(node.openingElement.attributes);
1406
- return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
1407
- }
1408
- return { kind: "element", tag, svg: SVG_TAGS.has(tag), props, children, static: false, ...at ? { at } : {} };
1484
+ const load = readLoad(node.openingElement.attributes);
1485
+ return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
1409
1486
  }
1410
1487
  function buildJsxIR(node, ctx) {
1411
1488
  const ir = buildNode(node, ctx);
@@ -268,6 +268,7 @@ var INTRINSICS = {
268
268
  $effect: { export: "effect", module: REACTIVE_MODULE, returns: "effect" },
269
269
  $store: { export: "store", module: "@fluixi/reactive/store", returns: "store-handle" },
270
270
  $resource: { export: "resource", module: REACTIVE_MODULE, returns: "resource-handle" },
271
+ $cachedResource: { export: "cachedResource", module: "@fluixi/reactive/cached-resource", returns: "resource-handle" },
271
272
  // No short runtime form for these yet, so the intrinsic stands for the `create*`
272
273
  // name. Still worth having: no import to write, and when a shorter form is
273
274
  // designed, the change is this line rather than a compiler pass.
@@ -278,6 +279,19 @@ var INTRINSICS = {
278
279
  // dependency analysis must not guess at.
279
280
  $untrack: { export: "untrack", module: REACTIVE_MODULE, returns: "plain" },
280
281
  $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" },
282
+ // Scopes, scheduling and lifecycle: no value to classify, but the same authoring promise,
283
+ // no import to write. `$cleanup` registers a callback on the current owner (`onCleanup`).
284
+ $batch: { export: "batch", module: REACTIVE_MODULE, returns: "plain" },
285
+ $cleanup: { export: "onCleanup", module: REACTIVE_MODULE, returns: "plain" },
286
+ $root: { export: "root", module: REACTIVE_MODULE, returns: "plain" },
287
+ $context: { export: "context", module: REACTIVE_MODULE, returns: "plain" },
288
+ $renderEffect: { export: "renderEffect", module: REACTIVE_MODULE, returns: "effect" },
289
+ $computed: { export: "computed", module: REACTIVE_MODULE, returns: "effect" },
290
+ // Storage for where the code runs, and a signal kept in it. `$persisted` is a signal
291
+ // handle as far as reads go: the same accessor with `set` on it.
292
+ $storage: { export: "useStorage", module: "@fluixi/core/storage", returns: "plain" },
293
+ $asyncStorage: { export: "useAsyncStorage", module: "@fluixi/core/storage", returns: "plain" },
294
+ $persisted: { export: "persisted", module: "@fluixi/core/storage", returns: "signal-handle" },
281
295
  // An environment variable, public or server-only; the browser build refuses a secret.
282
296
  $env: { export: "env", module: "@fluixi/start/env", returns: "plain" }
283
297
  };
@@ -455,13 +469,16 @@ function emitComponent(name, props, children, used, templates, at) {
455
469
  used.add("createComponent");
456
470
  const spreads = props.filter((p) => p.kind === "spread");
457
471
  const normal = props.filter((p) => p.kind !== "spread");
458
- const entries = normal.map(
459
- (p) => `get ${propKey(p.name)}() { return ${componentPropValue(p)}; }`
460
- );
472
+ const bodies = normal.map((p) => [p.name, componentPropValue(p)]);
461
473
  if (children.length > 0) {
462
- entries.push(`get children() { return ${emitChildren(children, used, templates)}; }`);
474
+ bodies.push(["children", emitChildren(children, used, templates)]);
475
+ }
476
+ let obj = `{ ${bodies.map(([name2, body]) => `get ${propKey(name2)}() { return ${body}; }`).join(", ")} }`;
477
+ if (bodies.some(([, body]) => /\bthis\b/.test(body))) {
478
+ const thunks = bodies.map(([name2, body]) => `${JSON.stringify(name2)}: () => ${body}`);
479
+ const getters = bodies.map(([name2]) => `get ${propKey(name2)}() { return _$t[${JSON.stringify(name2)}](); }`);
480
+ obj = `((_$t) => ({ ${getters.join(", ")} }))({ ${thunks.join(", ")} })`;
463
481
  }
464
- let obj = `{ ${entries.join(", ")} }`;
465
482
  if (spreads.length > 0) {
466
483
  used.add("mergeProps");
467
484
  obj = `mergeProps(${spreads.map((s) => s.expr).join(", ")}, ${obj})`;
@@ -543,7 +560,8 @@ function emitNode(node, used, templates) {
543
560
  } else {
544
561
  used.add("spread");
545
562
  const svgFlag = node.svg ? ", isSVG: true" : "";
546
- const customFlag = node.tag.includes("-") ? ", isCustomElement: true" : "";
563
+ const accessors = node.props.filter((p) => p.kind !== "spread" && p.expr !== void 0 && p.reactive && p.name !== "ref").map((p) => p.name);
564
+ const customFlag = node.tag.includes("-") ? `, isCustomElement: true, accessors: ${JSON.stringify(accessors)}` : "";
547
565
  parts.push(
548
566
  `spread({ element: ${el}, props: ${emitProps(node.props, null, used)}${svgFlag}${customFlag} });`
549
567
  );
@@ -614,6 +632,7 @@ var DELEGATED = /* @__PURE__ */ new Set([
614
632
  function escapeTemplate(s) {
615
633
  return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
616
634
  }
635
+ var CONTAINER = "container";
617
636
  function capitalize(s) {
618
637
  return s.charAt(0).toUpperCase() + s.slice(1);
619
638
  }
@@ -668,10 +687,6 @@ var Lowerer = class {
668
687
  if (hasElse) i = elseIdx;
669
688
  continue;
670
689
  }
671
- if (child.attributes.some((a) => a.kind === "EachDirective")) {
672
- out.push(this.lowerEach(child));
673
- continue;
674
- }
675
690
  }
676
691
  const node = this.lowerNode(child);
677
692
  if (node) out.push(node);
@@ -713,13 +728,16 @@ var Lowerer = class {
713
728
  node.props.unshift({ name: "component", kind: "attr", expr: `() => ${paren(this.hole(isAttr.value.hole).code, "arrowBody")}` });
714
729
  return node;
715
730
  }
731
+ const each = this.lowerEach(el);
732
+ const children = each ? [each] : this.lowerChildren(el.children);
733
+ if (el.tag === CONTAINER) return children.length === 1 ? children[0] : { kind: "fragment", children };
716
734
  const at = this.tagAt(el);
717
735
  return {
718
736
  kind: "element",
719
737
  tag: el.tag,
720
738
  svg: el.namespace === "svg",
721
739
  props: this.lowerAttributes(el.attributes),
722
- children: this.lowerChildren(el.children),
740
+ children,
723
741
  static: false,
724
742
  ...at ? { at } : {}
725
743
  };
@@ -791,10 +809,14 @@ var Lowerer = class {
791
809
  const at = this.tagAt(el);
792
810
  return { kind: "component", name: "Show", props, children: [child], ...at ? { at } : {}, ...cond.at ? { bindAt: cond.at } : {} };
793
811
  }
794
- /** `<el each=${items} key?>${item => …}</el>` → `<For each>{item => <el>…</el>}</For>`. */
812
+ /**
813
+ * `<el each=${items} key?>${item => …}</el>` → `<el><For each>{item => …}</For></el>`: the
814
+ * content is repeated per item. Content that is not a `${item => …}` arrow is repeated as
815
+ * it is. Null when the element has no `each`.
816
+ */
795
817
  lowerEach(el) {
796
818
  const eachDir = el.attributes.find((a) => a.kind === "EachDirective");
797
- if (!eachDir || eachDir.kind !== "EachDirective") return this.lowerNode(el);
819
+ if (!eachDir || eachDir.kind !== "EachDirective") return null;
798
820
  const each = this.hole(eachDir.hole);
799
821
  const props = [{ name: "each", kind: "attr", expr: each.code, reactive: each.reactive, ...each.at ? { at: each.at } : {} }];
800
822
  if (eachDir.key) {
@@ -804,12 +826,12 @@ var Lowerer = class {
804
826
  const arrow = this.itemArrow(el);
805
827
  let childrenCode;
806
828
  if (arrow) {
807
- const bodyIR = { kind: "expr", code: arrow.body, reactive: arrow.bodyReactive };
808
- const repeated = this.rebuildWithChildren(el, [bodyIR]);
809
- childrenCode = `(${arrow.params.join(", ")}) => ${paren(this.emit(repeated), "arrowBody")}`;
829
+ const body = arrow.bodyReactive ? `() => ${paren(arrow.body, "arrowBody")}` : paren(arrow.body, "arrowBody");
830
+ childrenCode = `(${arrow.params.join(", ")}) => ${body}`;
810
831
  } else {
811
- const repeated = this.stripAndLower(el, (a) => a.kind === "EachDirective");
812
- childrenCode = `() => ${paren(this.emit(repeated), "arrowBody")}`;
832
+ const content = this.lowerChildren(el.children);
833
+ const ir = content.length === 1 ? content[0] : { kind: "fragment", children: content };
834
+ childrenCode = `() => ${paren(this.emit(ir), "arrowBody")}`;
813
835
  }
814
836
  const children = [{ kind: "expr", code: childrenCode, reactive: false }];
815
837
  const at = this.tagAt(el);
@@ -826,25 +848,6 @@ var Lowerer = class {
826
848
  const clone = { ...el, attributes: el.attributes.filter((a) => !drop(a)) };
827
849
  return clone.kind === "Element" ? this.lowerElement(clone) : this.lowerComponent(clone);
828
850
  }
829
- /** Same element (minus each/key) but with explicit IR children. */
830
- rebuildWithChildren(el, children) {
831
- const props = this.lowerAttributes(
832
- el.attributes.filter((a) => a.kind !== "EachDirective")
833
- );
834
- if (el.kind === "Element") {
835
- const at = this.tagAt(el);
836
- return {
837
- kind: "element",
838
- tag: el.tag,
839
- svg: el.namespace === "svg",
840
- props,
841
- children,
842
- static: false,
843
- ...at ? { at } : {}
844
- };
845
- }
846
- return { kind: "component", name: el.tag, props, children };
847
- }
848
851
  // --- attributes ---------------------------------------------------------
849
852
  lowerAttributes(attrs) {
850
853
  const props = [];
@@ -1214,6 +1217,22 @@ var DELEGATED2 = /* @__PURE__ */ new Set([
1214
1217
  "mouseup"
1215
1218
  ]);
1216
1219
  var isReactive = (node) => isReactiveShape(shapeOf(node));
1220
+ var CONTAINER2 = "container";
1221
+ function directive(node, name) {
1222
+ if (node.type !== "JSXElement") return void 0;
1223
+ const opening = node.openingElement;
1224
+ if (opening.name.type !== "JSXIdentifier" || opening.name.name[0] !== opening.name.name[0].toLowerCase()) return void 0;
1225
+ return opening.attributes.find((a) => a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === name);
1226
+ }
1227
+ function directiveExpr(attr) {
1228
+ const v = attr.value;
1229
+ return v?.type === "JSXExpressionContainer" && v.expression?.type !== "JSXEmptyExpression" ? v.expression : void 0;
1230
+ }
1231
+ function emitIR(ir, ctx) {
1232
+ const { code, imports, templates } = imperativeBackend.emit(ir, {});
1233
+ for (const s of imports) ctx.used.add(s);
1234
+ return inlineTemplates(code, templates);
1235
+ }
1217
1236
  var posOf = (node, ctx) => {
1218
1237
  if (!ctx.sourceFile) return void 0;
1219
1238
  const at = node.loc?.start;
@@ -1322,7 +1341,16 @@ function buildProps(attrs, ctx) {
1322
1341
  }
1323
1342
  function buildChildren(children, ctx) {
1324
1343
  const out = [];
1325
- for (const child of children) {
1344
+ for (let i = 0; i < children.length; i++) {
1345
+ const child = children[i];
1346
+ if (directive(child, "if")) {
1347
+ let next = i + 1;
1348
+ while (next < children.length && children[next].type === "JSXText" && !cleanTemplateText(children[next].value)) next++;
1349
+ const fallback = directive(children[next], "else") ? children[next] : null;
1350
+ out.push(buildIf(child, fallback, ctx));
1351
+ if (fallback) i = next;
1352
+ continue;
1353
+ }
1326
1354
  if (child.type === "JSXText") {
1327
1355
  const text = cleanTemplateText(child.value);
1328
1356
  if (text) out.push({ kind: "text", value: text });
@@ -1359,19 +1387,68 @@ function readLoad(attrs) {
1359
1387
  }
1360
1388
  return void 0;
1361
1389
  }
1390
+ function buildIf(node, fallback, ctx) {
1391
+ const cond = directiveExpr(directive(node, "if"));
1392
+ const props = [
1393
+ cond ? { name: "when", kind: "attr", expr: ctx.code(cond), reactive: isReactive(cond), at: posOf(cond, ctx) } : { name: "when", kind: "attr", literal: true }
1394
+ ];
1395
+ if (fallback) {
1396
+ props.push({ name: "fallback", kind: "attr", expr: emitIR(buildElement(fallback, ctx, ["else"]), ctx), jsxElement: true });
1397
+ }
1398
+ return { kind: "component", name: "Show", props, children: [buildElement(node, ctx, ["if"])] };
1399
+ }
1400
+ function buildEach(node, ctx) {
1401
+ const attr = directive(node, "each");
1402
+ if (!attr) return null;
1403
+ const list = directiveExpr(attr);
1404
+ const props = [
1405
+ list ? { name: "each", kind: "attr", expr: ctx.code(list), reactive: isReactive(list), at: posOf(list, ctx) } : { name: "each", kind: "attr", expr: "[]" }
1406
+ ];
1407
+ const key = directive(node, "key");
1408
+ if (key) {
1409
+ const keyExpr = directiveExpr(key);
1410
+ const by = isString(key.value) ? `(item) => item[${JSON.stringify(literalOf(key.value))}]` : keyExpr ? ctx.code(keyExpr) : void 0;
1411
+ if (by) props.push({ name: "by", kind: "attr", expr: by });
1412
+ }
1413
+ const content = node.children.filter((c) => !(c.type === "JSXText" && !cleanTemplateText(c.value)));
1414
+ const only = content.length === 1 && content[0].type === "JSXExpressionContainer" ? content[0].expression : null;
1415
+ let render;
1416
+ if (only && (only.type === "ArrowFunctionExpression" || only.type === "FunctionExpression")) {
1417
+ const fn = ctx.code(only);
1418
+ const live = only.type === "ArrowFunctionExpression" && only.body.type !== "BlockStatement" && isReactive(only.body);
1419
+ render = live ? `(...args) => () => (${fn})(...args)` : fn;
1420
+ } else {
1421
+ const built = buildChildren(node.children, ctx);
1422
+ const ir = built.length === 1 ? built[0] : { kind: "fragment", children: built };
1423
+ render = `() => ${paren(emitIR(ir, ctx), "arrowBody")}`;
1424
+ }
1425
+ return { kind: "component", name: "For", props, children: [{ kind: "expr", code: render, reactive: false }] };
1426
+ }
1427
+ function buildElement(node, ctx, handled) {
1428
+ const skip = new Set(handled);
1429
+ const directives = /* @__PURE__ */ new Set(["if", "else", "each", ...directive(node, "each") ? ["key"] : []]);
1430
+ const attributes = node.openingElement.attributes.filter(
1431
+ (a) => !(a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && (skip.has(a.name.name) || directives.has(a.name.name)))
1432
+ );
1433
+ const { tag } = tagInfo(node.openingElement.name, ctx);
1434
+ const each = buildEach(node, ctx);
1435
+ const children = each ? [each] : buildChildren(node.children, ctx);
1436
+ if (tag === CONTAINER2) return children.length === 1 ? children[0] : { kind: "fragment", children };
1437
+ const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
1438
+ return { kind: "element", tag, svg: SVG_TAGS.has(tag), props: buildProps(attributes, ctx), children, static: false, ...at ? { at } : {} };
1439
+ }
1362
1440
  function buildNode(node, ctx) {
1363
1441
  if (node.type === "JSXFragment") {
1364
1442
  return { kind: "fragment", children: buildChildren(node.children, ctx) };
1365
1443
  }
1444
+ if (directive(node, "if")) return buildIf(node, null, ctx);
1366
1445
  const { tag, component } = tagInfo(node.openingElement.name, ctx);
1446
+ if (!component) return buildElement(node, ctx, []);
1367
1447
  const props = buildProps(node.openingElement.attributes, ctx);
1368
1448
  const children = buildChildren(node.children, ctx);
1369
1449
  const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
1370
- if (component) {
1371
- const load = readLoad(node.openingElement.attributes);
1372
- return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
1373
- }
1374
- return { kind: "element", tag, svg: SVG_TAGS.has(tag), props, children, static: false, ...at ? { at } : {} };
1450
+ const load = readLoad(node.openingElement.attributes);
1451
+ return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
1375
1452
  }
1376
1453
  function buildJsxIR(node, ctx) {
1377
1454
  const ir = buildNode(node, ctx);