@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
@@ -1,4 +1,4 @@
1
- /*! @fluixi/compiler v1.0.0-alpha.92 | (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.
@@ -486,6 +487,19 @@ var INTRINSICS = {
486
487
  // dependency analysis must not guess at.
487
488
  $untrack: { export: "untrack", module: REACTIVE_MODULE, returns: "plain" },
488
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" },
489
503
  // An environment variable, public or server-only; the browser build refuses a secret.
490
504
  $env: { export: "env", module: "@fluixi/start/env", returns: "plain" }
491
505
  };
@@ -663,13 +677,16 @@ function emitComponent(name, props, children, used, templates, at) {
663
677
  used.add("createComponent");
664
678
  const spreads = props.filter((p) => p.kind === "spread");
665
679
  const normal = props.filter((p) => p.kind !== "spread");
666
- const entries = normal.map(
667
- (p) => `get ${propKey(p.name)}() { return ${componentPropValue(p)}; }`
668
- );
680
+ const bodies = normal.map((p) => [p.name, componentPropValue(p)]);
669
681
  if (children.length > 0) {
670
- 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(", ")} })`;
671
689
  }
672
- let obj = `{ ${entries.join(", ")} }`;
673
690
  if (spreads.length > 0) {
674
691
  used.add("mergeProps");
675
692
  obj = `mergeProps(${spreads.map((s) => s.expr).join(", ")}, ${obj})`;
@@ -751,7 +768,8 @@ function emitNode(node, used, templates) {
751
768
  } else {
752
769
  used.add("spread");
753
770
  const svgFlag = node.svg ? ", isSVG: true" : "";
754
- 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)}` : "";
755
773
  parts.push(
756
774
  `spread({ element: ${el}, props: ${emitProps(node.props, null, used)}${svgFlag}${customFlag} });`
757
775
  );
@@ -829,6 +847,7 @@ var DELEGATED = /* @__PURE__ */ new Set([
829
847
  function escapeTemplate(s) {
830
848
  return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
831
849
  }
850
+ var CONTAINER = "container";
832
851
  function capitalize(s) {
833
852
  return s.charAt(0).toUpperCase() + s.slice(1);
834
853
  }
@@ -883,10 +902,6 @@ var Lowerer = class {
883
902
  if (hasElse) i = elseIdx;
884
903
  continue;
885
904
  }
886
- if (child.attributes.some((a) => a.kind === "EachDirective")) {
887
- out.push(this.lowerEach(child));
888
- continue;
889
- }
890
905
  }
891
906
  const node = this.lowerNode(child);
892
907
  if (node) out.push(node);
@@ -928,13 +943,16 @@ var Lowerer = class {
928
943
  node.props.unshift({ name: "component", kind: "attr", expr: `() => ${paren(this.hole(isAttr.value.hole).code, "arrowBody")}` });
929
944
  return node;
930
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 };
931
949
  const at = this.tagAt(el);
932
950
  return {
933
951
  kind: "element",
934
952
  tag: el.tag,
935
953
  svg: el.namespace === "svg",
936
954
  props: this.lowerAttributes(el.attributes),
937
- children: this.lowerChildren(el.children),
955
+ children,
938
956
  static: false,
939
957
  ...at ? { at } : {}
940
958
  };
@@ -1006,10 +1024,14 @@ var Lowerer = class {
1006
1024
  const at = this.tagAt(el);
1007
1025
  return { kind: "component", name: "Show", props, children: [child], ...at ? { at } : {}, ...cond.at ? { bindAt: cond.at } : {} };
1008
1026
  }
1009
- /** `<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
+ */
1010
1032
  lowerEach(el) {
1011
1033
  const eachDir = el.attributes.find((a) => a.kind === "EachDirective");
1012
- if (!eachDir || eachDir.kind !== "EachDirective") return this.lowerNode(el);
1034
+ if (!eachDir || eachDir.kind !== "EachDirective") return null;
1013
1035
  const each = this.hole(eachDir.hole);
1014
1036
  const props = [{ name: "each", kind: "attr", expr: each.code, reactive: each.reactive, ...each.at ? { at: each.at } : {} }];
1015
1037
  if (eachDir.key) {
@@ -1019,12 +1041,12 @@ var Lowerer = class {
1019
1041
  const arrow = this.itemArrow(el);
1020
1042
  let childrenCode;
1021
1043
  if (arrow) {
1022
- const bodyIR = { kind: "expr", code: arrow.body, reactive: arrow.bodyReactive };
1023
- const repeated = this.rebuildWithChildren(el, [bodyIR]);
1024
- 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}`;
1025
1046
  } else {
1026
- const repeated = this.stripAndLower(el, (a) => a.kind === "EachDirective");
1027
- 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")}`;
1028
1050
  }
1029
1051
  const children = [{ kind: "expr", code: childrenCode, reactive: false }];
1030
1052
  const at = this.tagAt(el);
@@ -1041,25 +1063,6 @@ var Lowerer = class {
1041
1063
  const clone = { ...el, attributes: el.attributes.filter((a) => !drop(a)) };
1042
1064
  return clone.kind === "Element" ? this.lowerElement(clone) : this.lowerComponent(clone);
1043
1065
  }
1044
- /** Same element (minus each/key) but with explicit IR children. */
1045
- rebuildWithChildren(el, children) {
1046
- const props = this.lowerAttributes(
1047
- el.attributes.filter((a) => a.kind !== "EachDirective")
1048
- );
1049
- if (el.kind === "Element") {
1050
- const at = this.tagAt(el);
1051
- return {
1052
- kind: "element",
1053
- tag: el.tag,
1054
- svg: el.namespace === "svg",
1055
- props,
1056
- children,
1057
- static: false,
1058
- ...at ? { at } : {}
1059
- };
1060
- }
1061
- return { kind: "component", name: el.tag, props, children };
1062
- }
1063
1066
  // --- attributes ---------------------------------------------------------
1064
1067
  lowerAttributes(attrs) {
1065
1068
  const props = [];
@@ -1471,6 +1474,22 @@ var DELEGATED2 = /* @__PURE__ */ new Set([
1471
1474
  "mouseup"
1472
1475
  ]);
1473
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
+ }
1474
1493
  var posOf = (node, ctx) => {
1475
1494
  if (!ctx.sourceFile) return void 0;
1476
1495
  const at = node.loc?.start;
@@ -1579,7 +1598,16 @@ function buildProps(attrs, ctx) {
1579
1598
  }
1580
1599
  function buildChildren(children, ctx) {
1581
1600
  const out = [];
1582
- 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
+ }
1583
1611
  if (child.type === "JSXText") {
1584
1612
  const text = cleanTemplateText(child.value);
1585
1613
  if (text) out.push({ kind: "text", value: text });
@@ -1616,19 +1644,68 @@ function readLoad(attrs) {
1616
1644
  }
1617
1645
  return void 0;
1618
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
+ }
1619
1697
  function buildNode(node, ctx) {
1620
1698
  if (node.type === "JSXFragment") {
1621
1699
  return { kind: "fragment", children: buildChildren(node.children, ctx) };
1622
1700
  }
1701
+ if (directive(node, "if")) return buildIf(node, null, ctx);
1623
1702
  const { tag, component } = tagInfo(node.openingElement.name, ctx);
1703
+ if (!component) return buildElement(node, ctx, []);
1624
1704
  const props = buildProps(node.openingElement.attributes, ctx);
1625
1705
  const children = buildChildren(node.children, ctx);
1626
1706
  const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
1627
- if (component) {
1628
- const load = readLoad(node.openingElement.attributes);
1629
- return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
1630
- }
1631
- 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 } : {} };
1632
1709
  }
1633
1710
  function buildJsxIR(node, ctx) {
1634
1711
  const ir = buildNode(node, ctx);
@@ -1,4 +1,4 @@
1
- /*! @fluixi/compiler v1.0.0-alpha.92 | (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.
@@ -447,6 +448,19 @@ var INTRINSICS = {
447
448
  // dependency analysis must not guess at.
448
449
  $untrack: { export: "untrack", module: REACTIVE_MODULE, returns: "plain" },
449
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" },
450
464
  // An environment variable, public or server-only; the browser build refuses a secret.
451
465
  $env: { export: "env", module: "@fluixi/start/env", returns: "plain" }
452
466
  };
@@ -624,13 +638,16 @@ function emitComponent(name, props, children, used, templates, at) {
624
638
  used.add("createComponent");
625
639
  const spreads = props.filter((p) => p.kind === "spread");
626
640
  const normal = props.filter((p) => p.kind !== "spread");
627
- const entries = normal.map(
628
- (p) => `get ${propKey(p.name)}() { return ${componentPropValue(p)}; }`
629
- );
641
+ const bodies = normal.map((p) => [p.name, componentPropValue(p)]);
630
642
  if (children.length > 0) {
631
- 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(", ")} })`;
632
650
  }
633
- let obj = `{ ${entries.join(", ")} }`;
634
651
  if (spreads.length > 0) {
635
652
  used.add("mergeProps");
636
653
  obj = `mergeProps(${spreads.map((s) => s.expr).join(", ")}, ${obj})`;
@@ -712,7 +729,8 @@ function emitNode(node, used, templates) {
712
729
  } else {
713
730
  used.add("spread");
714
731
  const svgFlag = node.svg ? ", isSVG: true" : "";
715
- 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)}` : "";
716
734
  parts.push(
717
735
  `spread({ element: ${el}, props: ${emitProps(node.props, null, used)}${svgFlag}${customFlag} });`
718
736
  );
@@ -792,6 +810,7 @@ var DELEGATED = /* @__PURE__ */ new Set([
792
810
  function escapeTemplate(s) {
793
811
  return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
794
812
  }
813
+ var CONTAINER = "container";
795
814
  function capitalize(s) {
796
815
  return s.charAt(0).toUpperCase() + s.slice(1);
797
816
  }
@@ -846,10 +865,6 @@ var Lowerer = class {
846
865
  if (hasElse) i = elseIdx;
847
866
  continue;
848
867
  }
849
- if (child.attributes.some((a) => a.kind === "EachDirective")) {
850
- out.push(this.lowerEach(child));
851
- continue;
852
- }
853
868
  }
854
869
  const node = this.lowerNode(child);
855
870
  if (node) out.push(node);
@@ -891,13 +906,16 @@ var Lowerer = class {
891
906
  node.props.unshift({ name: "component", kind: "attr", expr: `() => ${paren(this.hole(isAttr.value.hole).code, "arrowBody")}` });
892
907
  return node;
893
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 };
894
912
  const at = this.tagAt(el);
895
913
  return {
896
914
  kind: "element",
897
915
  tag: el.tag,
898
916
  svg: el.namespace === "svg",
899
917
  props: this.lowerAttributes(el.attributes),
900
- children: this.lowerChildren(el.children),
918
+ children,
901
919
  static: false,
902
920
  ...at ? { at } : {}
903
921
  };
@@ -969,10 +987,14 @@ var Lowerer = class {
969
987
  const at = this.tagAt(el);
970
988
  return { kind: "component", name: "Show", props, children: [child], ...at ? { at } : {}, ...cond.at ? { bindAt: cond.at } : {} };
971
989
  }
972
- /** `<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
+ */
973
995
  lowerEach(el) {
974
996
  const eachDir = el.attributes.find((a) => a.kind === "EachDirective");
975
- if (!eachDir || eachDir.kind !== "EachDirective") return this.lowerNode(el);
997
+ if (!eachDir || eachDir.kind !== "EachDirective") return null;
976
998
  const each = this.hole(eachDir.hole);
977
999
  const props = [{ name: "each", kind: "attr", expr: each.code, reactive: each.reactive, ...each.at ? { at: each.at } : {} }];
978
1000
  if (eachDir.key) {
@@ -982,12 +1004,12 @@ var Lowerer = class {
982
1004
  const arrow = this.itemArrow(el);
983
1005
  let childrenCode;
984
1006
  if (arrow) {
985
- const bodyIR = { kind: "expr", code: arrow.body, reactive: arrow.bodyReactive };
986
- const repeated = this.rebuildWithChildren(el, [bodyIR]);
987
- 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}`;
988
1009
  } else {
989
- const repeated = this.stripAndLower(el, (a) => a.kind === "EachDirective");
990
- 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")}`;
991
1013
  }
992
1014
  const children = [{ kind: "expr", code: childrenCode, reactive: false }];
993
1015
  const at = this.tagAt(el);
@@ -1004,25 +1026,6 @@ var Lowerer = class {
1004
1026
  const clone = { ...el, attributes: el.attributes.filter((a) => !drop(a)) };
1005
1027
  return clone.kind === "Element" ? this.lowerElement(clone) : this.lowerComponent(clone);
1006
1028
  }
1007
- /** Same element (minus each/key) but with explicit IR children. */
1008
- rebuildWithChildren(el, children) {
1009
- const props = this.lowerAttributes(
1010
- el.attributes.filter((a) => a.kind !== "EachDirective")
1011
- );
1012
- if (el.kind === "Element") {
1013
- const at = this.tagAt(el);
1014
- return {
1015
- kind: "element",
1016
- tag: el.tag,
1017
- svg: el.namespace === "svg",
1018
- props,
1019
- children,
1020
- static: false,
1021
- ...at ? { at } : {}
1022
- };
1023
- }
1024
- return { kind: "component", name: el.tag, props, children };
1025
- }
1026
1029
  // --- attributes ---------------------------------------------------------
1027
1030
  lowerAttributes(attrs) {
1028
1031
  const props = [];
@@ -1434,6 +1437,22 @@ var DELEGATED2 = /* @__PURE__ */ new Set([
1434
1437
  "mouseup"
1435
1438
  ]);
1436
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
+ }
1437
1456
  var posOf = (node, ctx) => {
1438
1457
  if (!ctx.sourceFile) return void 0;
1439
1458
  const at = node.loc?.start;
@@ -1542,7 +1561,16 @@ function buildProps(attrs, ctx) {
1542
1561
  }
1543
1562
  function buildChildren(children, ctx) {
1544
1563
  const out = [];
1545
- 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
+ }
1546
1574
  if (child.type === "JSXText") {
1547
1575
  const text = cleanTemplateText(child.value);
1548
1576
  if (text) out.push({ kind: "text", value: text });
@@ -1579,19 +1607,68 @@ function readLoad(attrs) {
1579
1607
  }
1580
1608
  return void 0;
1581
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
+ }
1582
1660
  function buildNode(node, ctx) {
1583
1661
  if (node.type === "JSXFragment") {
1584
1662
  return { kind: "fragment", children: buildChildren(node.children, ctx) };
1585
1663
  }
1664
+ if (directive(node, "if")) return buildIf(node, null, ctx);
1586
1665
  const { tag, component } = tagInfo(node.openingElement.name, ctx);
1666
+ if (!component) return buildElement(node, ctx, []);
1587
1667
  const props = buildProps(node.openingElement.attributes, ctx);
1588
1668
  const children = buildChildren(node.children, ctx);
1589
1669
  const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
1590
- if (component) {
1591
- const load = readLoad(node.openingElement.attributes);
1592
- return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
1593
- }
1594
- 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 } : {} };
1595
1672
  }
1596
1673
  function buildJsxIR(node, ctx) {
1597
1674
  const ir = buildNode(node, ctx);