@fluixi/compiler 1.0.0-alpha.91 → 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 (81) hide show
  1. package/dist/analyze/intrinsics.cjs +21 -1
  2. package/dist/analyze/intrinsics.d.ts.map +1 -1
  3. package/dist/analyze/intrinsics.js +20 -0
  4. package/dist/analyze/intrinsics.mjs +21 -1
  5. package/dist/analyze/reactive-bindings.cjs +21 -1
  6. package/dist/analyze/reactive-bindings.mjs +21 -1
  7. package/dist/analyze/static-graph.cjs +25 -5
  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 +25 -5
  11. package/dist/{babel-VBLQRHQ2.mjs → babel-RJHJQVEM.mjs} +4 -4
  12. package/dist/{chunk-D53J7ICY.mjs → chunk-56QSOVVL.mjs} +1 -1
  13. package/dist/{chunk-S4J4OCWK.mjs → chunk-OD53TMER.mjs} +22 -2
  14. package/dist/{chunk-TWDJUXWZ.mjs → chunk-R7PPFBCL.mjs} +1 -1
  15. package/dist/{chunk-QX6Y3OAR.mjs → chunk-TJIH7TLX.mjs} +1 -1
  16. package/dist/{chunk-3JWMCK4A.mjs → chunk-U7HCFLZ2.mjs} +153 -48
  17. package/dist/codegen/backends/imperative.cjs +27 -7
  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 +27 -7
  21. package/dist/frontend/babel/build-ir-lit.cjs +123 -44
  22. package/dist/frontend/babel/build-ir-lit.mjs +123 -44
  23. package/dist/frontend/babel/build-ir.cjs +123 -44
  24. package/dist/frontend/babel/build-ir.mjs +123 -44
  25. package/dist/frontend/babel/index.cjs +124 -45
  26. package/dist/frontend/babel/index.mjs +124 -45
  27. package/dist/frontend/babel/lower-template.cjs +123 -44
  28. package/dist/frontend/babel/lower-template.mjs +123 -44
  29. package/dist/frontend/babel/plugin.cjs +123 -44
  30. package/dist/frontend/babel/plugin.mjs +123 -44
  31. package/dist/index.cjs +86 -46
  32. package/dist/index.mjs +88 -48
  33. package/dist/integrations.cjs +246 -61
  34. package/dist/integrations.mjs +53 -19
  35. package/dist/lower/compile-template.cjs +44 -39
  36. package/dist/lower/compile-template.mjs +44 -39
  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 +43 -38
  42. package/dist/lower/template.d.ts.map +1 -1
  43. package/dist/lower/template.js +22 -30
  44. package/dist/lower/template.mjs +43 -38
  45. package/dist/resolve/builtins.cjs +1 -1
  46. package/dist/resolve/builtins.mjs +1 -1
  47. package/dist/resolve/component-globals.cjs +33 -8
  48. package/dist/resolve/component-globals.d.cts +8 -0
  49. package/dist/resolve/component-globals.d.ts +8 -0
  50. package/dist/resolve/component-globals.d.ts.map +1 -1
  51. package/dist/resolve/component-globals.js +21 -12
  52. package/dist/resolve/component-globals.mjs +33 -8
  53. package/dist/resolve/write-globals.cjs +59 -8
  54. package/dist/resolve/write-globals.d.cts +6 -0
  55. package/dist/resolve/write-globals.d.ts +6 -0
  56. package/dist/resolve/write-globals.d.ts.map +1 -1
  57. package/dist/resolve/write-globals.js +35 -2
  58. package/dist/resolve/write-globals.mjs +61 -10
  59. package/dist/{server-fns-J56G3PFP.mjs → server-fns-ZFRDIQ2X.mjs} +4 -4
  60. package/dist/{server-functions-7PUFLRGF.mjs → server-functions-CTDXX67X.mjs} +3 -3
  61. package/dist/transform/index.cjs +199 -49
  62. package/dist/transform/index.mjs +199 -49
  63. package/dist/transform/intrinsics.cjs +47 -2
  64. package/dist/transform/intrinsics.d.cts +12 -0
  65. package/dist/transform/intrinsics.d.ts +12 -0
  66. package/dist/transform/intrinsics.d.ts.map +1 -1
  67. package/dist/transform/intrinsics.js +43 -1
  68. package/dist/transform/intrinsics.mjs +47 -2
  69. package/dist/transform/source-locations.cjs +67 -4
  70. package/dist/transform/source-locations.d.cts +7 -2
  71. package/dist/transform/source-locations.d.ts +7 -2
  72. package/dist/transform/source-locations.d.ts.map +1 -1
  73. package/dist/transform/source-locations.js +68 -3
  74. package/dist/transform/source-locations.mjs +67 -4
  75. package/dist/transform/templates.cjs +198 -48
  76. package/dist/transform/templates.d.ts.map +1 -1
  77. package/dist/transform/templates.js +5 -3
  78. package/dist/transform/templates.mjs +198 -48
  79. package/dist/{transform-D536WXGM.mjs → transform-QWWQDCXE.mjs} +33 -8
  80. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  81. package/package.json +9 -3
@@ -1,4 +1,4 @@
1
- /*! @fluixi/compiler v1.0.0-alpha.91 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
1
+ /*! @fluixi/compiler v1.0.0-alpha.93 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
2
  "use strict";
3
3
  var __create = Object.create;
4
4
  var __defProp = Object.defineProperty;
@@ -476,6 +476,7 @@ var INTRINSICS = {
476
476
  $effect: { export: "effect", module: REACTIVE_MODULE, returns: "effect" },
477
477
  $store: { export: "store", module: "@fluixi/reactive/store", returns: "store-handle" },
478
478
  $resource: { export: "resource", module: REACTIVE_MODULE, returns: "resource-handle" },
479
+ $cachedResource: { export: "cachedResource", module: "@fluixi/reactive/cached-resource", returns: "resource-handle" },
479
480
  // No short runtime form for these yet, so the intrinsic stands for the `create*`
480
481
  // name. Still worth having: no import to write, and when a shorter form is
481
482
  // designed, the change is this line rather than a compiler pass.
@@ -485,7 +486,22 @@ var INTRINSICS = {
485
486
  // does. `untrack` suppresses dependency collection, which is exactly the kind of thing
486
487
  // dependency analysis must not guess at.
487
488
  $untrack: { export: "untrack", module: REACTIVE_MODULE, returns: "plain" },
488
- $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" }
489
+ $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" },
490
+ // Scopes, scheduling and lifecycle: no value to classify, but the same authoring promise,
491
+ // no import to write. `$cleanup` registers a callback on the current owner (`onCleanup`).
492
+ $batch: { export: "batch", module: REACTIVE_MODULE, returns: "plain" },
493
+ $cleanup: { export: "onCleanup", module: REACTIVE_MODULE, returns: "plain" },
494
+ $root: { export: "root", module: REACTIVE_MODULE, returns: "plain" },
495
+ $context: { export: "context", module: REACTIVE_MODULE, returns: "plain" },
496
+ $renderEffect: { export: "renderEffect", module: REACTIVE_MODULE, returns: "effect" },
497
+ $computed: { export: "computed", module: REACTIVE_MODULE, returns: "effect" },
498
+ // Storage for where the code runs, and a signal kept in it. `$persisted` is a signal
499
+ // handle as far as reads go: the same accessor with `set` on it.
500
+ $storage: { export: "useStorage", module: "@fluixi/core/storage", returns: "plain" },
501
+ $asyncStorage: { export: "useAsyncStorage", module: "@fluixi/core/storage", returns: "plain" },
502
+ $persisted: { export: "persisted", module: "@fluixi/core/storage", returns: "signal-handle" },
503
+ // An environment variable, public or server-only; the browser build refuses a secret.
504
+ $env: { export: "env", module: "@fluixi/start/env", returns: "plain" }
489
505
  };
490
506
  var INTRINSIC_CALL = new RegExp(`\\$(?:${Object.keys(INTRINSICS).map((n) => n.slice(1)).join("|")})\\s*\\(`);
491
507
 
@@ -661,13 +677,16 @@ function emitComponent(name, props, children, used, templates, at) {
661
677
  used.add("createComponent");
662
678
  const spreads = props.filter((p) => p.kind === "spread");
663
679
  const normal = props.filter((p) => p.kind !== "spread");
664
- const entries = normal.map(
665
- (p) => `get ${propKey(p.name)}() { return ${componentPropValue(p)}; }`
666
- );
680
+ const bodies = normal.map((p) => [p.name, componentPropValue(p)]);
667
681
  if (children.length > 0) {
668
- entries.push(`get children() { return ${emitChildren(children, used, templates)}; }`);
682
+ bodies.push(["children", emitChildren(children, used, templates)]);
683
+ }
684
+ let obj = `{ ${bodies.map(([name2, body]) => `get ${propKey(name2)}() { return ${body}; }`).join(", ")} }`;
685
+ if (bodies.some(([, body]) => /\bthis\b/.test(body))) {
686
+ const thunks = bodies.map(([name2, body]) => `${JSON.stringify(name2)}: () => ${body}`);
687
+ const getters = bodies.map(([name2]) => `get ${propKey(name2)}() { return _$t[${JSON.stringify(name2)}](); }`);
688
+ obj = `((_$t) => ({ ${getters.join(", ")} }))({ ${thunks.join(", ")} })`;
669
689
  }
670
- let obj = `{ ${entries.join(", ")} }`;
671
690
  if (spreads.length > 0) {
672
691
  used.add("mergeProps");
673
692
  obj = `mergeProps(${spreads.map((s) => s.expr).join(", ")}, ${obj})`;
@@ -749,7 +768,8 @@ function emitNode(node, used, templates) {
749
768
  } else {
750
769
  used.add("spread");
751
770
  const svgFlag = node.svg ? ", isSVG: true" : "";
752
- const customFlag = node.tag.includes("-") ? ", isCustomElement: true" : "";
771
+ const accessors = node.props.filter((p) => p.kind !== "spread" && p.expr !== void 0 && p.reactive && p.name !== "ref").map((p) => p.name);
772
+ const customFlag = node.tag.includes("-") ? `, isCustomElement: true, accessors: ${JSON.stringify(accessors)}` : "";
753
773
  parts.push(
754
774
  `spread({ element: ${el}, props: ${emitProps(node.props, null, used)}${svgFlag}${customFlag} });`
755
775
  );
@@ -827,6 +847,7 @@ var DELEGATED = /* @__PURE__ */ new Set([
827
847
  function escapeTemplate(s) {
828
848
  return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
829
849
  }
850
+ var CONTAINER = "container";
830
851
  function capitalize(s) {
831
852
  return s.charAt(0).toUpperCase() + s.slice(1);
832
853
  }
@@ -881,10 +902,6 @@ var Lowerer = class {
881
902
  if (hasElse) i = elseIdx;
882
903
  continue;
883
904
  }
884
- if (child.attributes.some((a) => a.kind === "EachDirective")) {
885
- out.push(this.lowerEach(child));
886
- continue;
887
- }
888
905
  }
889
906
  const node = this.lowerNode(child);
890
907
  if (node) out.push(node);
@@ -926,13 +943,16 @@ var Lowerer = class {
926
943
  node.props.unshift({ name: "component", kind: "attr", expr: `() => ${paren(this.hole(isAttr.value.hole).code, "arrowBody")}` });
927
944
  return node;
928
945
  }
946
+ const each = this.lowerEach(el);
947
+ const children = each ? [each] : this.lowerChildren(el.children);
948
+ if (el.tag === CONTAINER) return children.length === 1 ? children[0] : { kind: "fragment", children };
929
949
  const at = this.tagAt(el);
930
950
  return {
931
951
  kind: "element",
932
952
  tag: el.tag,
933
953
  svg: el.namespace === "svg",
934
954
  props: this.lowerAttributes(el.attributes),
935
- children: this.lowerChildren(el.children),
955
+ children,
936
956
  static: false,
937
957
  ...at ? { at } : {}
938
958
  };
@@ -1004,10 +1024,14 @@ var Lowerer = class {
1004
1024
  const at = this.tagAt(el);
1005
1025
  return { kind: "component", name: "Show", props, children: [child], ...at ? { at } : {}, ...cond.at ? { bindAt: cond.at } : {} };
1006
1026
  }
1007
- /** `<el each=${items} key?>${item => …}</el>` → `<For each>{item => <el>…</el>}</For>`. */
1027
+ /**
1028
+ * `<el each=${items} key?>${item => …}</el>` → `<el><For each>{item => …}</For></el>`: the
1029
+ * content is repeated per item. Content that is not a `${item => …}` arrow is repeated as
1030
+ * it is. Null when the element has no `each`.
1031
+ */
1008
1032
  lowerEach(el) {
1009
1033
  const eachDir = el.attributes.find((a) => a.kind === "EachDirective");
1010
- if (!eachDir || eachDir.kind !== "EachDirective") return this.lowerNode(el);
1034
+ if (!eachDir || eachDir.kind !== "EachDirective") return null;
1011
1035
  const each = this.hole(eachDir.hole);
1012
1036
  const props = [{ name: "each", kind: "attr", expr: each.code, reactive: each.reactive, ...each.at ? { at: each.at } : {} }];
1013
1037
  if (eachDir.key) {
@@ -1017,12 +1041,12 @@ var Lowerer = class {
1017
1041
  const arrow = this.itemArrow(el);
1018
1042
  let childrenCode;
1019
1043
  if (arrow) {
1020
- const bodyIR = { kind: "expr", code: arrow.body, reactive: arrow.bodyReactive };
1021
- const repeated = this.rebuildWithChildren(el, [bodyIR]);
1022
- childrenCode = `(${arrow.params.join(", ")}) => ${paren(this.emit(repeated), "arrowBody")}`;
1044
+ const body = arrow.bodyReactive ? `() => ${paren(arrow.body, "arrowBody")}` : paren(arrow.body, "arrowBody");
1045
+ childrenCode = `(${arrow.params.join(", ")}) => ${body}`;
1023
1046
  } else {
1024
- const repeated = this.stripAndLower(el, (a) => a.kind === "EachDirective");
1025
- childrenCode = `() => ${paren(this.emit(repeated), "arrowBody")}`;
1047
+ const content = this.lowerChildren(el.children);
1048
+ const ir = content.length === 1 ? content[0] : { kind: "fragment", children: content };
1049
+ childrenCode = `() => ${paren(this.emit(ir), "arrowBody")}`;
1026
1050
  }
1027
1051
  const children = [{ kind: "expr", code: childrenCode, reactive: false }];
1028
1052
  const at = this.tagAt(el);
@@ -1039,25 +1063,6 @@ var Lowerer = class {
1039
1063
  const clone = { ...el, attributes: el.attributes.filter((a) => !drop(a)) };
1040
1064
  return clone.kind === "Element" ? this.lowerElement(clone) : this.lowerComponent(clone);
1041
1065
  }
1042
- /** Same element (minus each/key) but with explicit IR children. */
1043
- rebuildWithChildren(el, children) {
1044
- const props = this.lowerAttributes(
1045
- el.attributes.filter((a) => a.kind !== "EachDirective")
1046
- );
1047
- if (el.kind === "Element") {
1048
- const at = this.tagAt(el);
1049
- return {
1050
- kind: "element",
1051
- tag: el.tag,
1052
- svg: el.namespace === "svg",
1053
- props,
1054
- children,
1055
- static: false,
1056
- ...at ? { at } : {}
1057
- };
1058
- }
1059
- return { kind: "component", name: el.tag, props, children };
1060
- }
1061
1066
  // --- attributes ---------------------------------------------------------
1062
1067
  lowerAttributes(attrs) {
1063
1068
  const props = [];
@@ -1469,6 +1474,22 @@ var DELEGATED2 = /* @__PURE__ */ new Set([
1469
1474
  "mouseup"
1470
1475
  ]);
1471
1476
  var isReactive = (node) => isReactiveShape(shapeOf(node));
1477
+ var CONTAINER2 = "container";
1478
+ function directive(node, name) {
1479
+ if (node.type !== "JSXElement") return void 0;
1480
+ const opening = node.openingElement;
1481
+ if (opening.name.type !== "JSXIdentifier" || opening.name.name[0] !== opening.name.name[0].toLowerCase()) return void 0;
1482
+ return opening.attributes.find((a) => a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === name);
1483
+ }
1484
+ function directiveExpr(attr) {
1485
+ const v = attr.value;
1486
+ return v?.type === "JSXExpressionContainer" && v.expression?.type !== "JSXEmptyExpression" ? v.expression : void 0;
1487
+ }
1488
+ function emitIR(ir, ctx) {
1489
+ const { code, imports, templates } = imperativeBackend.emit(ir, {});
1490
+ for (const s of imports) ctx.used.add(s);
1491
+ return inlineTemplates(code, templates);
1492
+ }
1472
1493
  var posOf = (node, ctx) => {
1473
1494
  if (!ctx.sourceFile) return void 0;
1474
1495
  const at = node.loc?.start;
@@ -1577,7 +1598,16 @@ function buildProps(attrs, ctx) {
1577
1598
  }
1578
1599
  function buildChildren(children, ctx) {
1579
1600
  const out = [];
1580
- for (const child of children) {
1601
+ for (let i = 0; i < children.length; i++) {
1602
+ const child = children[i];
1603
+ if (directive(child, "if")) {
1604
+ let next = i + 1;
1605
+ while (next < children.length && children[next].type === "JSXText" && !cleanTemplateText(children[next].value)) next++;
1606
+ const fallback = directive(children[next], "else") ? children[next] : null;
1607
+ out.push(buildIf(child, fallback, ctx));
1608
+ if (fallback) i = next;
1609
+ continue;
1610
+ }
1581
1611
  if (child.type === "JSXText") {
1582
1612
  const text = cleanTemplateText(child.value);
1583
1613
  if (text) out.push({ kind: "text", value: text });
@@ -1614,19 +1644,68 @@ function readLoad(attrs) {
1614
1644
  }
1615
1645
  return void 0;
1616
1646
  }
1647
+ function buildIf(node, fallback, ctx) {
1648
+ const cond = directiveExpr(directive(node, "if"));
1649
+ const props = [
1650
+ cond ? { name: "when", kind: "attr", expr: ctx.code(cond), reactive: isReactive(cond), at: posOf(cond, ctx) } : { name: "when", kind: "attr", literal: true }
1651
+ ];
1652
+ if (fallback) {
1653
+ props.push({ name: "fallback", kind: "attr", expr: emitIR(buildElement(fallback, ctx, ["else"]), ctx), jsxElement: true });
1654
+ }
1655
+ return { kind: "component", name: "Show", props, children: [buildElement(node, ctx, ["if"])] };
1656
+ }
1657
+ function buildEach(node, ctx) {
1658
+ const attr = directive(node, "each");
1659
+ if (!attr) return null;
1660
+ const list = directiveExpr(attr);
1661
+ const props = [
1662
+ list ? { name: "each", kind: "attr", expr: ctx.code(list), reactive: isReactive(list), at: posOf(list, ctx) } : { name: "each", kind: "attr", expr: "[]" }
1663
+ ];
1664
+ const key = directive(node, "key");
1665
+ if (key) {
1666
+ const keyExpr = directiveExpr(key);
1667
+ const by = isString(key.value) ? `(item) => item[${JSON.stringify(literalOf(key.value))}]` : keyExpr ? ctx.code(keyExpr) : void 0;
1668
+ if (by) props.push({ name: "by", kind: "attr", expr: by });
1669
+ }
1670
+ const content = node.children.filter((c) => !(c.type === "JSXText" && !cleanTemplateText(c.value)));
1671
+ const only = content.length === 1 && content[0].type === "JSXExpressionContainer" ? content[0].expression : null;
1672
+ let render;
1673
+ if (only && (only.type === "ArrowFunctionExpression" || only.type === "FunctionExpression")) {
1674
+ const fn = ctx.code(only);
1675
+ const live = only.type === "ArrowFunctionExpression" && only.body.type !== "BlockStatement" && isReactive(only.body);
1676
+ render = live ? `(...args) => () => (${fn})(...args)` : fn;
1677
+ } else {
1678
+ const built = buildChildren(node.children, ctx);
1679
+ const ir = built.length === 1 ? built[0] : { kind: "fragment", children: built };
1680
+ render = `() => ${paren(emitIR(ir, ctx), "arrowBody")}`;
1681
+ }
1682
+ return { kind: "component", name: "For", props, children: [{ kind: "expr", code: render, reactive: false }] };
1683
+ }
1684
+ function buildElement(node, ctx, handled) {
1685
+ const skip = new Set(handled);
1686
+ const directives = /* @__PURE__ */ new Set(["if", "else", "each", ...directive(node, "each") ? ["key"] : []]);
1687
+ const attributes = node.openingElement.attributes.filter(
1688
+ (a) => !(a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && (skip.has(a.name.name) || directives.has(a.name.name)))
1689
+ );
1690
+ const { tag } = tagInfo(node.openingElement.name, ctx);
1691
+ const each = buildEach(node, ctx);
1692
+ const children = each ? [each] : buildChildren(node.children, ctx);
1693
+ if (tag === CONTAINER2) return children.length === 1 ? children[0] : { kind: "fragment", children };
1694
+ const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
1695
+ return { kind: "element", tag, svg: SVG_TAGS.has(tag), props: buildProps(attributes, ctx), children, static: false, ...at ? { at } : {} };
1696
+ }
1617
1697
  function buildNode(node, ctx) {
1618
1698
  if (node.type === "JSXFragment") {
1619
1699
  return { kind: "fragment", children: buildChildren(node.children, ctx) };
1620
1700
  }
1701
+ if (directive(node, "if")) return buildIf(node, null, ctx);
1621
1702
  const { tag, component } = tagInfo(node.openingElement.name, ctx);
1703
+ if (!component) return buildElement(node, ctx, []);
1622
1704
  const props = buildProps(node.openingElement.attributes, ctx);
1623
1705
  const children = buildChildren(node.children, ctx);
1624
1706
  const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
1625
- if (component) {
1626
- const load = readLoad(node.openingElement.attributes);
1627
- return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
1628
- }
1629
- return { kind: "element", tag, svg: SVG_TAGS.has(tag), props, children, static: false, ...at ? { at } : {} };
1707
+ const load = readLoad(node.openingElement.attributes);
1708
+ return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
1630
1709
  }
1631
1710
  function buildJsxIR(node, ctx) {
1632
1711
  const ir = buildNode(node, ctx);
@@ -1,4 +1,4 @@
1
- /*! @fluixi/compiler v1.0.0-alpha.91 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
1
+ /*! @fluixi/compiler v1.0.0-alpha.93 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
2
 
3
3
  // src/frontend/babel/plugin.ts
4
4
  import { declare } from "@babel/helper-plugin-utils";
@@ -437,6 +437,7 @@ var INTRINSICS = {
437
437
  $effect: { export: "effect", module: REACTIVE_MODULE, returns: "effect" },
438
438
  $store: { export: "store", module: "@fluixi/reactive/store", returns: "store-handle" },
439
439
  $resource: { export: "resource", module: REACTIVE_MODULE, returns: "resource-handle" },
440
+ $cachedResource: { export: "cachedResource", module: "@fluixi/reactive/cached-resource", returns: "resource-handle" },
440
441
  // No short runtime form for these yet, so the intrinsic stands for the `create*`
441
442
  // name. Still worth having: no import to write, and when a shorter form is
442
443
  // designed, the change is this line rather than a compiler pass.
@@ -446,7 +447,22 @@ var INTRINSICS = {
446
447
  // does. `untrack` suppresses dependency collection, which is exactly the kind of thing
447
448
  // dependency analysis must not guess at.
448
449
  $untrack: { export: "untrack", module: REACTIVE_MODULE, returns: "plain" },
449
- $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" }
450
+ $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" },
451
+ // Scopes, scheduling and lifecycle: no value to classify, but the same authoring promise,
452
+ // no import to write. `$cleanup` registers a callback on the current owner (`onCleanup`).
453
+ $batch: { export: "batch", module: REACTIVE_MODULE, returns: "plain" },
454
+ $cleanup: { export: "onCleanup", module: REACTIVE_MODULE, returns: "plain" },
455
+ $root: { export: "root", module: REACTIVE_MODULE, returns: "plain" },
456
+ $context: { export: "context", module: REACTIVE_MODULE, returns: "plain" },
457
+ $renderEffect: { export: "renderEffect", module: REACTIVE_MODULE, returns: "effect" },
458
+ $computed: { export: "computed", module: REACTIVE_MODULE, returns: "effect" },
459
+ // Storage for where the code runs, and a signal kept in it. `$persisted` is a signal
460
+ // handle as far as reads go: the same accessor with `set` on it.
461
+ $storage: { export: "useStorage", module: "@fluixi/core/storage", returns: "plain" },
462
+ $asyncStorage: { export: "useAsyncStorage", module: "@fluixi/core/storage", returns: "plain" },
463
+ $persisted: { export: "persisted", module: "@fluixi/core/storage", returns: "signal-handle" },
464
+ // An environment variable, public or server-only; the browser build refuses a secret.
465
+ $env: { export: "env", module: "@fluixi/start/env", returns: "plain" }
450
466
  };
451
467
  var INTRINSIC_CALL = new RegExp(`\\$(?:${Object.keys(INTRINSICS).map((n) => n.slice(1)).join("|")})\\s*\\(`);
452
468
 
@@ -622,13 +638,16 @@ function emitComponent(name, props, children, used, templates, at) {
622
638
  used.add("createComponent");
623
639
  const spreads = props.filter((p) => p.kind === "spread");
624
640
  const normal = props.filter((p) => p.kind !== "spread");
625
- const entries = normal.map(
626
- (p) => `get ${propKey(p.name)}() { return ${componentPropValue(p)}; }`
627
- );
641
+ const bodies = normal.map((p) => [p.name, componentPropValue(p)]);
628
642
  if (children.length > 0) {
629
- entries.push(`get children() { return ${emitChildren(children, used, templates)}; }`);
643
+ bodies.push(["children", emitChildren(children, used, templates)]);
644
+ }
645
+ let obj = `{ ${bodies.map(([name2, body]) => `get ${propKey(name2)}() { return ${body}; }`).join(", ")} }`;
646
+ if (bodies.some(([, body]) => /\bthis\b/.test(body))) {
647
+ const thunks = bodies.map(([name2, body]) => `${JSON.stringify(name2)}: () => ${body}`);
648
+ const getters = bodies.map(([name2]) => `get ${propKey(name2)}() { return _$t[${JSON.stringify(name2)}](); }`);
649
+ obj = `((_$t) => ({ ${getters.join(", ")} }))({ ${thunks.join(", ")} })`;
630
650
  }
631
- let obj = `{ ${entries.join(", ")} }`;
632
651
  if (spreads.length > 0) {
633
652
  used.add("mergeProps");
634
653
  obj = `mergeProps(${spreads.map((s) => s.expr).join(", ")}, ${obj})`;
@@ -710,7 +729,8 @@ function emitNode(node, used, templates) {
710
729
  } else {
711
730
  used.add("spread");
712
731
  const svgFlag = node.svg ? ", isSVG: true" : "";
713
- const customFlag = node.tag.includes("-") ? ", isCustomElement: true" : "";
732
+ const accessors = node.props.filter((p) => p.kind !== "spread" && p.expr !== void 0 && p.reactive && p.name !== "ref").map((p) => p.name);
733
+ const customFlag = node.tag.includes("-") ? `, isCustomElement: true, accessors: ${JSON.stringify(accessors)}` : "";
714
734
  parts.push(
715
735
  `spread({ element: ${el}, props: ${emitProps(node.props, null, used)}${svgFlag}${customFlag} });`
716
736
  );
@@ -790,6 +810,7 @@ var DELEGATED = /* @__PURE__ */ new Set([
790
810
  function escapeTemplate(s) {
791
811
  return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
792
812
  }
813
+ var CONTAINER = "container";
793
814
  function capitalize(s) {
794
815
  return s.charAt(0).toUpperCase() + s.slice(1);
795
816
  }
@@ -844,10 +865,6 @@ var Lowerer = class {
844
865
  if (hasElse) i = elseIdx;
845
866
  continue;
846
867
  }
847
- if (child.attributes.some((a) => a.kind === "EachDirective")) {
848
- out.push(this.lowerEach(child));
849
- continue;
850
- }
851
868
  }
852
869
  const node = this.lowerNode(child);
853
870
  if (node) out.push(node);
@@ -889,13 +906,16 @@ var Lowerer = class {
889
906
  node.props.unshift({ name: "component", kind: "attr", expr: `() => ${paren(this.hole(isAttr.value.hole).code, "arrowBody")}` });
890
907
  return node;
891
908
  }
909
+ const each = this.lowerEach(el);
910
+ const children = each ? [each] : this.lowerChildren(el.children);
911
+ if (el.tag === CONTAINER) return children.length === 1 ? children[0] : { kind: "fragment", children };
892
912
  const at = this.tagAt(el);
893
913
  return {
894
914
  kind: "element",
895
915
  tag: el.tag,
896
916
  svg: el.namespace === "svg",
897
917
  props: this.lowerAttributes(el.attributes),
898
- children: this.lowerChildren(el.children),
918
+ children,
899
919
  static: false,
900
920
  ...at ? { at } : {}
901
921
  };
@@ -967,10 +987,14 @@ var Lowerer = class {
967
987
  const at = this.tagAt(el);
968
988
  return { kind: "component", name: "Show", props, children: [child], ...at ? { at } : {}, ...cond.at ? { bindAt: cond.at } : {} };
969
989
  }
970
- /** `<el each=${items} key?>${item => …}</el>` → `<For each>{item => <el>…</el>}</For>`. */
990
+ /**
991
+ * `<el each=${items} key?>${item => …}</el>` → `<el><For each>{item => …}</For></el>`: the
992
+ * content is repeated per item. Content that is not a `${item => …}` arrow is repeated as
993
+ * it is. Null when the element has no `each`.
994
+ */
971
995
  lowerEach(el) {
972
996
  const eachDir = el.attributes.find((a) => a.kind === "EachDirective");
973
- if (!eachDir || eachDir.kind !== "EachDirective") return this.lowerNode(el);
997
+ if (!eachDir || eachDir.kind !== "EachDirective") return null;
974
998
  const each = this.hole(eachDir.hole);
975
999
  const props = [{ name: "each", kind: "attr", expr: each.code, reactive: each.reactive, ...each.at ? { at: each.at } : {} }];
976
1000
  if (eachDir.key) {
@@ -980,12 +1004,12 @@ var Lowerer = class {
980
1004
  const arrow = this.itemArrow(el);
981
1005
  let childrenCode;
982
1006
  if (arrow) {
983
- const bodyIR = { kind: "expr", code: arrow.body, reactive: arrow.bodyReactive };
984
- const repeated = this.rebuildWithChildren(el, [bodyIR]);
985
- childrenCode = `(${arrow.params.join(", ")}) => ${paren(this.emit(repeated), "arrowBody")}`;
1007
+ const body = arrow.bodyReactive ? `() => ${paren(arrow.body, "arrowBody")}` : paren(arrow.body, "arrowBody");
1008
+ childrenCode = `(${arrow.params.join(", ")}) => ${body}`;
986
1009
  } else {
987
- const repeated = this.stripAndLower(el, (a) => a.kind === "EachDirective");
988
- childrenCode = `() => ${paren(this.emit(repeated), "arrowBody")}`;
1010
+ const content = this.lowerChildren(el.children);
1011
+ const ir = content.length === 1 ? content[0] : { kind: "fragment", children: content };
1012
+ childrenCode = `() => ${paren(this.emit(ir), "arrowBody")}`;
989
1013
  }
990
1014
  const children = [{ kind: "expr", code: childrenCode, reactive: false }];
991
1015
  const at = this.tagAt(el);
@@ -1002,25 +1026,6 @@ var Lowerer = class {
1002
1026
  const clone = { ...el, attributes: el.attributes.filter((a) => !drop(a)) };
1003
1027
  return clone.kind === "Element" ? this.lowerElement(clone) : this.lowerComponent(clone);
1004
1028
  }
1005
- /** Same element (minus each/key) but with explicit IR children. */
1006
- rebuildWithChildren(el, children) {
1007
- const props = this.lowerAttributes(
1008
- el.attributes.filter((a) => a.kind !== "EachDirective")
1009
- );
1010
- if (el.kind === "Element") {
1011
- const at = this.tagAt(el);
1012
- return {
1013
- kind: "element",
1014
- tag: el.tag,
1015
- svg: el.namespace === "svg",
1016
- props,
1017
- children,
1018
- static: false,
1019
- ...at ? { at } : {}
1020
- };
1021
- }
1022
- return { kind: "component", name: el.tag, props, children };
1023
- }
1024
1029
  // --- attributes ---------------------------------------------------------
1025
1030
  lowerAttributes(attrs) {
1026
1031
  const props = [];
@@ -1432,6 +1437,22 @@ var DELEGATED2 = /* @__PURE__ */ new Set([
1432
1437
  "mouseup"
1433
1438
  ]);
1434
1439
  var isReactive = (node) => isReactiveShape(shapeOf(node));
1440
+ var CONTAINER2 = "container";
1441
+ function directive(node, name) {
1442
+ if (node.type !== "JSXElement") return void 0;
1443
+ const opening = node.openingElement;
1444
+ if (opening.name.type !== "JSXIdentifier" || opening.name.name[0] !== opening.name.name[0].toLowerCase()) return void 0;
1445
+ return opening.attributes.find((a) => a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === name);
1446
+ }
1447
+ function directiveExpr(attr) {
1448
+ const v = attr.value;
1449
+ return v?.type === "JSXExpressionContainer" && v.expression?.type !== "JSXEmptyExpression" ? v.expression : void 0;
1450
+ }
1451
+ function emitIR(ir, ctx) {
1452
+ const { code, imports, templates } = imperativeBackend.emit(ir, {});
1453
+ for (const s of imports) ctx.used.add(s);
1454
+ return inlineTemplates(code, templates);
1455
+ }
1435
1456
  var posOf = (node, ctx) => {
1436
1457
  if (!ctx.sourceFile) return void 0;
1437
1458
  const at = node.loc?.start;
@@ -1540,7 +1561,16 @@ function buildProps(attrs, ctx) {
1540
1561
  }
1541
1562
  function buildChildren(children, ctx) {
1542
1563
  const out = [];
1543
- for (const child of children) {
1564
+ for (let i = 0; i < children.length; i++) {
1565
+ const child = children[i];
1566
+ if (directive(child, "if")) {
1567
+ let next = i + 1;
1568
+ while (next < children.length && children[next].type === "JSXText" && !cleanTemplateText(children[next].value)) next++;
1569
+ const fallback = directive(children[next], "else") ? children[next] : null;
1570
+ out.push(buildIf(child, fallback, ctx));
1571
+ if (fallback) i = next;
1572
+ continue;
1573
+ }
1544
1574
  if (child.type === "JSXText") {
1545
1575
  const text = cleanTemplateText(child.value);
1546
1576
  if (text) out.push({ kind: "text", value: text });
@@ -1577,19 +1607,68 @@ function readLoad(attrs) {
1577
1607
  }
1578
1608
  return void 0;
1579
1609
  }
1610
+ function buildIf(node, fallback, ctx) {
1611
+ const cond = directiveExpr(directive(node, "if"));
1612
+ const props = [
1613
+ cond ? { name: "when", kind: "attr", expr: ctx.code(cond), reactive: isReactive(cond), at: posOf(cond, ctx) } : { name: "when", kind: "attr", literal: true }
1614
+ ];
1615
+ if (fallback) {
1616
+ props.push({ name: "fallback", kind: "attr", expr: emitIR(buildElement(fallback, ctx, ["else"]), ctx), jsxElement: true });
1617
+ }
1618
+ return { kind: "component", name: "Show", props, children: [buildElement(node, ctx, ["if"])] };
1619
+ }
1620
+ function buildEach(node, ctx) {
1621
+ const attr = directive(node, "each");
1622
+ if (!attr) return null;
1623
+ const list = directiveExpr(attr);
1624
+ const props = [
1625
+ list ? { name: "each", kind: "attr", expr: ctx.code(list), reactive: isReactive(list), at: posOf(list, ctx) } : { name: "each", kind: "attr", expr: "[]" }
1626
+ ];
1627
+ const key = directive(node, "key");
1628
+ if (key) {
1629
+ const keyExpr = directiveExpr(key);
1630
+ const by = isString(key.value) ? `(item) => item[${JSON.stringify(literalOf(key.value))}]` : keyExpr ? ctx.code(keyExpr) : void 0;
1631
+ if (by) props.push({ name: "by", kind: "attr", expr: by });
1632
+ }
1633
+ const content = node.children.filter((c) => !(c.type === "JSXText" && !cleanTemplateText(c.value)));
1634
+ const only = content.length === 1 && content[0].type === "JSXExpressionContainer" ? content[0].expression : null;
1635
+ let render;
1636
+ if (only && (only.type === "ArrowFunctionExpression" || only.type === "FunctionExpression")) {
1637
+ const fn = ctx.code(only);
1638
+ const live = only.type === "ArrowFunctionExpression" && only.body.type !== "BlockStatement" && isReactive(only.body);
1639
+ render = live ? `(...args) => () => (${fn})(...args)` : fn;
1640
+ } else {
1641
+ const built = buildChildren(node.children, ctx);
1642
+ const ir = built.length === 1 ? built[0] : { kind: "fragment", children: built };
1643
+ render = `() => ${paren(emitIR(ir, ctx), "arrowBody")}`;
1644
+ }
1645
+ return { kind: "component", name: "For", props, children: [{ kind: "expr", code: render, reactive: false }] };
1646
+ }
1647
+ function buildElement(node, ctx, handled) {
1648
+ const skip = new Set(handled);
1649
+ const directives = /* @__PURE__ */ new Set(["if", "else", "each", ...directive(node, "each") ? ["key"] : []]);
1650
+ const attributes = node.openingElement.attributes.filter(
1651
+ (a) => !(a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && (skip.has(a.name.name) || directives.has(a.name.name)))
1652
+ );
1653
+ const { tag } = tagInfo(node.openingElement.name, ctx);
1654
+ const each = buildEach(node, ctx);
1655
+ const children = each ? [each] : buildChildren(node.children, ctx);
1656
+ if (tag === CONTAINER2) return children.length === 1 ? children[0] : { kind: "fragment", children };
1657
+ const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
1658
+ return { kind: "element", tag, svg: SVG_TAGS.has(tag), props: buildProps(attributes, ctx), children, static: false, ...at ? { at } : {} };
1659
+ }
1580
1660
  function buildNode(node, ctx) {
1581
1661
  if (node.type === "JSXFragment") {
1582
1662
  return { kind: "fragment", children: buildChildren(node.children, ctx) };
1583
1663
  }
1664
+ if (directive(node, "if")) return buildIf(node, null, ctx);
1584
1665
  const { tag, component } = tagInfo(node.openingElement.name, ctx);
1666
+ if (!component) return buildElement(node, ctx, []);
1585
1667
  const props = buildProps(node.openingElement.attributes, ctx);
1586
1668
  const children = buildChildren(node.children, ctx);
1587
1669
  const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
1588
- if (component) {
1589
- const load = readLoad(node.openingElement.attributes);
1590
- return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
1591
- }
1592
- return { kind: "element", tag, svg: SVG_TAGS.has(tag), props, children, static: false, ...at ? { at } : {} };
1670
+ const load = readLoad(node.openingElement.attributes);
1671
+ return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
1593
1672
  }
1594
1673
  function buildJsxIR(node, ctx) {
1595
1674
  const ir = buildNode(node, ctx);