@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
@@ -469,6 +469,7 @@ var INTRINSICS = {
469
469
  $effect: { export: "effect", module: REACTIVE_MODULE, returns: "effect" },
470
470
  $store: { export: "store", module: "@fluixi/reactive/store", returns: "store-handle" },
471
471
  $resource: { export: "resource", module: REACTIVE_MODULE, returns: "resource-handle" },
472
+ $cachedResource: { export: "cachedResource", module: "@fluixi/reactive/cached-resource", returns: "resource-handle" },
472
473
  // No short runtime form for these yet, so the intrinsic stands for the `create*`
473
474
  // name. Still worth having: no import to write, and when a shorter form is
474
475
  // designed, the change is this line rather than a compiler pass.
@@ -478,7 +479,22 @@ var INTRINSICS = {
478
479
  // does. `untrack` suppresses dependency collection, which is exactly the kind of thing
479
480
  // dependency analysis must not guess at.
480
481
  $untrack: { export: "untrack", module: REACTIVE_MODULE, returns: "plain" },
481
- $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" }
482
+ $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" },
483
+ // Scopes, scheduling and lifecycle: no value to classify, but the same authoring promise,
484
+ // no import to write. `$cleanup` registers a callback on the current owner (`onCleanup`).
485
+ $batch: { export: "batch", module: REACTIVE_MODULE, returns: "plain" },
486
+ $cleanup: { export: "onCleanup", module: REACTIVE_MODULE, returns: "plain" },
487
+ $root: { export: "root", module: REACTIVE_MODULE, returns: "plain" },
488
+ $context: { export: "context", module: REACTIVE_MODULE, returns: "plain" },
489
+ $renderEffect: { export: "renderEffect", module: REACTIVE_MODULE, returns: "effect" },
490
+ $computed: { export: "computed", module: REACTIVE_MODULE, returns: "effect" },
491
+ // Storage for where the code runs, and a signal kept in it. `$persisted` is a signal
492
+ // handle as far as reads go: the same accessor with `set` on it.
493
+ $storage: { export: "useStorage", module: "@fluixi/core/storage", returns: "plain" },
494
+ $asyncStorage: { export: "useAsyncStorage", module: "@fluixi/core/storage", returns: "plain" },
495
+ $persisted: { export: "persisted", module: "@fluixi/core/storage", returns: "signal-handle" },
496
+ // An environment variable, public or server-only; the browser build refuses a secret.
497
+ $env: { export: "env", module: "@fluixi/start/env", returns: "plain" }
482
498
  };
483
499
  var INTRINSIC_CALL = new RegExp(`\\$(?:${Object.keys(INTRINSICS).map((n) => n.slice(1)).join("|")})\\s*\\(`);
484
500
 
@@ -654,13 +670,16 @@ function emitComponent(name, props, children, used, templates, at) {
654
670
  used.add("createComponent");
655
671
  const spreads = props.filter((p) => p.kind === "spread");
656
672
  const normal = props.filter((p) => p.kind !== "spread");
657
- const entries = normal.map(
658
- (p) => `get ${propKey(p.name)}() { return ${componentPropValue(p)}; }`
659
- );
673
+ const bodies = normal.map((p) => [p.name, componentPropValue(p)]);
660
674
  if (children.length > 0) {
661
- entries.push(`get children() { return ${emitChildren(children, used, templates)}; }`);
675
+ bodies.push(["children", emitChildren(children, used, templates)]);
676
+ }
677
+ let obj = `{ ${bodies.map(([name2, body]) => `get ${propKey(name2)}() { return ${body}; }`).join(", ")} }`;
678
+ if (bodies.some(([, body]) => /\bthis\b/.test(body))) {
679
+ const thunks = bodies.map(([name2, body]) => `${JSON.stringify(name2)}: () => ${body}`);
680
+ const getters = bodies.map(([name2]) => `get ${propKey(name2)}() { return _$t[${JSON.stringify(name2)}](); }`);
681
+ obj = `((_$t) => ({ ${getters.join(", ")} }))({ ${thunks.join(", ")} })`;
662
682
  }
663
- let obj = `{ ${entries.join(", ")} }`;
664
683
  if (spreads.length > 0) {
665
684
  used.add("mergeProps");
666
685
  obj = `mergeProps(${spreads.map((s) => s.expr).join(", ")}, ${obj})`;
@@ -742,7 +761,8 @@ function emitNode(node, used, templates) {
742
761
  } else {
743
762
  used.add("spread");
744
763
  const svgFlag = node.svg ? ", isSVG: true" : "";
745
- const customFlag = node.tag.includes("-") ? ", isCustomElement: true" : "";
764
+ const accessors = node.props.filter((p) => p.kind !== "spread" && p.expr !== void 0 && p.reactive && p.name !== "ref").map((p) => p.name);
765
+ const customFlag = node.tag.includes("-") ? `, isCustomElement: true, accessors: ${JSON.stringify(accessors)}` : "";
746
766
  parts.push(
747
767
  `spread({ element: ${el}, props: ${emitProps(node.props, null, used)}${svgFlag}${customFlag} });`
748
768
  );
@@ -820,6 +840,7 @@ var DELEGATED = /* @__PURE__ */ new Set([
820
840
  function escapeTemplate(s) {
821
841
  return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
822
842
  }
843
+ var CONTAINER = "container";
823
844
  function capitalize(s) {
824
845
  return s.charAt(0).toUpperCase() + s.slice(1);
825
846
  }
@@ -874,10 +895,6 @@ var Lowerer = class {
874
895
  if (hasElse) i = elseIdx;
875
896
  continue;
876
897
  }
877
- if (child.attributes.some((a) => a.kind === "EachDirective")) {
878
- out.push(this.lowerEach(child));
879
- continue;
880
- }
881
898
  }
882
899
  const node = this.lowerNode(child);
883
900
  if (node) out.push(node);
@@ -919,13 +936,16 @@ var Lowerer = class {
919
936
  node.props.unshift({ name: "component", kind: "attr", expr: `() => ${paren(this.hole(isAttr.value.hole).code, "arrowBody")}` });
920
937
  return node;
921
938
  }
939
+ const each = this.lowerEach(el);
940
+ const children = each ? [each] : this.lowerChildren(el.children);
941
+ if (el.tag === CONTAINER) return children.length === 1 ? children[0] : { kind: "fragment", children };
922
942
  const at = this.tagAt(el);
923
943
  return {
924
944
  kind: "element",
925
945
  tag: el.tag,
926
946
  svg: el.namespace === "svg",
927
947
  props: this.lowerAttributes(el.attributes),
928
- children: this.lowerChildren(el.children),
948
+ children,
929
949
  static: false,
930
950
  ...at ? { at } : {}
931
951
  };
@@ -997,10 +1017,14 @@ var Lowerer = class {
997
1017
  const at = this.tagAt(el);
998
1018
  return { kind: "component", name: "Show", props, children: [child], ...at ? { at } : {}, ...cond.at ? { bindAt: cond.at } : {} };
999
1019
  }
1000
- /** `<el each=${items} key?>${item => …}</el>` → `<For each>{item => <el>…</el>}</For>`. */
1020
+ /**
1021
+ * `<el each=${items} key?>${item => …}</el>` → `<el><For each>{item => …}</For></el>`: the
1022
+ * content is repeated per item. Content that is not a `${item => …}` arrow is repeated as
1023
+ * it is. Null when the element has no `each`.
1024
+ */
1001
1025
  lowerEach(el) {
1002
1026
  const eachDir = el.attributes.find((a) => a.kind === "EachDirective");
1003
- if (!eachDir || eachDir.kind !== "EachDirective") return this.lowerNode(el);
1027
+ if (!eachDir || eachDir.kind !== "EachDirective") return null;
1004
1028
  const each = this.hole(eachDir.hole);
1005
1029
  const props = [{ name: "each", kind: "attr", expr: each.code, reactive: each.reactive, ...each.at ? { at: each.at } : {} }];
1006
1030
  if (eachDir.key) {
@@ -1010,12 +1034,12 @@ var Lowerer = class {
1010
1034
  const arrow = this.itemArrow(el);
1011
1035
  let childrenCode;
1012
1036
  if (arrow) {
1013
- const bodyIR = { kind: "expr", code: arrow.body, reactive: arrow.bodyReactive };
1014
- const repeated = this.rebuildWithChildren(el, [bodyIR]);
1015
- childrenCode = `(${arrow.params.join(", ")}) => ${paren(this.emit(repeated), "arrowBody")}`;
1037
+ const body = arrow.bodyReactive ? `() => ${paren(arrow.body, "arrowBody")}` : paren(arrow.body, "arrowBody");
1038
+ childrenCode = `(${arrow.params.join(", ")}) => ${body}`;
1016
1039
  } else {
1017
- const repeated = this.stripAndLower(el, (a) => a.kind === "EachDirective");
1018
- childrenCode = `() => ${paren(this.emit(repeated), "arrowBody")}`;
1040
+ const content = this.lowerChildren(el.children);
1041
+ const ir = content.length === 1 ? content[0] : { kind: "fragment", children: content };
1042
+ childrenCode = `() => ${paren(this.emit(ir), "arrowBody")}`;
1019
1043
  }
1020
1044
  const children = [{ kind: "expr", code: childrenCode, reactive: false }];
1021
1045
  const at = this.tagAt(el);
@@ -1032,25 +1056,6 @@ var Lowerer = class {
1032
1056
  const clone = { ...el, attributes: el.attributes.filter((a) => !drop(a)) };
1033
1057
  return clone.kind === "Element" ? this.lowerElement(clone) : this.lowerComponent(clone);
1034
1058
  }
1035
- /** Same element (minus each/key) but with explicit IR children. */
1036
- rebuildWithChildren(el, children) {
1037
- const props = this.lowerAttributes(
1038
- el.attributes.filter((a) => a.kind !== "EachDirective")
1039
- );
1040
- if (el.kind === "Element") {
1041
- const at = this.tagAt(el);
1042
- return {
1043
- kind: "element",
1044
- tag: el.tag,
1045
- svg: el.namespace === "svg",
1046
- props,
1047
- children,
1048
- static: false,
1049
- ...at ? { at } : {}
1050
- };
1051
- }
1052
- return { kind: "component", name: el.tag, props, children };
1053
- }
1054
1059
  // --- attributes ---------------------------------------------------------
1055
1060
  lowerAttributes(attrs) {
1056
1061
  const props = [];
@@ -1462,6 +1467,22 @@ var DELEGATED2 = /* @__PURE__ */ new Set([
1462
1467
  "mouseup"
1463
1468
  ]);
1464
1469
  var isReactive = (node) => isReactiveShape(shapeOf(node));
1470
+ var CONTAINER2 = "container";
1471
+ function directive(node, name) {
1472
+ if (node.type !== "JSXElement") return void 0;
1473
+ const opening = node.openingElement;
1474
+ if (opening.name.type !== "JSXIdentifier" || opening.name.name[0] !== opening.name.name[0].toLowerCase()) return void 0;
1475
+ return opening.attributes.find((a) => a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === name);
1476
+ }
1477
+ function directiveExpr(attr) {
1478
+ const v = attr.value;
1479
+ return v?.type === "JSXExpressionContainer" && v.expression?.type !== "JSXEmptyExpression" ? v.expression : void 0;
1480
+ }
1481
+ function emitIR(ir, ctx) {
1482
+ const { code, imports, templates } = imperativeBackend.emit(ir, {});
1483
+ for (const s of imports) ctx.used.add(s);
1484
+ return inlineTemplates(code, templates);
1485
+ }
1465
1486
  var posOf = (node, ctx) => {
1466
1487
  if (!ctx.sourceFile) return void 0;
1467
1488
  const at = node.loc?.start;
@@ -1570,7 +1591,16 @@ function buildProps(attrs, ctx) {
1570
1591
  }
1571
1592
  function buildChildren(children, ctx) {
1572
1593
  const out = [];
1573
- for (const child of children) {
1594
+ for (let i = 0; i < children.length; i++) {
1595
+ const child = children[i];
1596
+ if (directive(child, "if")) {
1597
+ let next = i + 1;
1598
+ while (next < children.length && children[next].type === "JSXText" && !cleanTemplateText(children[next].value)) next++;
1599
+ const fallback = directive(children[next], "else") ? children[next] : null;
1600
+ out.push(buildIf(child, fallback, ctx));
1601
+ if (fallback) i = next;
1602
+ continue;
1603
+ }
1574
1604
  if (child.type === "JSXText") {
1575
1605
  const text = cleanTemplateText(child.value);
1576
1606
  if (text) out.push({ kind: "text", value: text });
@@ -1607,19 +1637,68 @@ function readLoad(attrs) {
1607
1637
  }
1608
1638
  return void 0;
1609
1639
  }
1640
+ function buildIf(node, fallback, ctx) {
1641
+ const cond = directiveExpr(directive(node, "if"));
1642
+ const props = [
1643
+ cond ? { name: "when", kind: "attr", expr: ctx.code(cond), reactive: isReactive(cond), at: posOf(cond, ctx) } : { name: "when", kind: "attr", literal: true }
1644
+ ];
1645
+ if (fallback) {
1646
+ props.push({ name: "fallback", kind: "attr", expr: emitIR(buildElement(fallback, ctx, ["else"]), ctx), jsxElement: true });
1647
+ }
1648
+ return { kind: "component", name: "Show", props, children: [buildElement(node, ctx, ["if"])] };
1649
+ }
1650
+ function buildEach(node, ctx) {
1651
+ const attr = directive(node, "each");
1652
+ if (!attr) return null;
1653
+ const list = directiveExpr(attr);
1654
+ const props = [
1655
+ list ? { name: "each", kind: "attr", expr: ctx.code(list), reactive: isReactive(list), at: posOf(list, ctx) } : { name: "each", kind: "attr", expr: "[]" }
1656
+ ];
1657
+ const key = directive(node, "key");
1658
+ if (key) {
1659
+ const keyExpr = directiveExpr(key);
1660
+ const by = isString(key.value) ? `(item) => item[${JSON.stringify(literalOf(key.value))}]` : keyExpr ? ctx.code(keyExpr) : void 0;
1661
+ if (by) props.push({ name: "by", kind: "attr", expr: by });
1662
+ }
1663
+ const content = node.children.filter((c) => !(c.type === "JSXText" && !cleanTemplateText(c.value)));
1664
+ const only = content.length === 1 && content[0].type === "JSXExpressionContainer" ? content[0].expression : null;
1665
+ let render;
1666
+ if (only && (only.type === "ArrowFunctionExpression" || only.type === "FunctionExpression")) {
1667
+ const fn = ctx.code(only);
1668
+ const live = only.type === "ArrowFunctionExpression" && only.body.type !== "BlockStatement" && isReactive(only.body);
1669
+ render = live ? `(...args) => () => (${fn})(...args)` : fn;
1670
+ } else {
1671
+ const built = buildChildren(node.children, ctx);
1672
+ const ir = built.length === 1 ? built[0] : { kind: "fragment", children: built };
1673
+ render = `() => ${paren(emitIR(ir, ctx), "arrowBody")}`;
1674
+ }
1675
+ return { kind: "component", name: "For", props, children: [{ kind: "expr", code: render, reactive: false }] };
1676
+ }
1677
+ function buildElement(node, ctx, handled) {
1678
+ const skip = new Set(handled);
1679
+ const directives = /* @__PURE__ */ new Set(["if", "else", "each", ...directive(node, "each") ? ["key"] : []]);
1680
+ const attributes = node.openingElement.attributes.filter(
1681
+ (a) => !(a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && (skip.has(a.name.name) || directives.has(a.name.name)))
1682
+ );
1683
+ const { tag } = tagInfo(node.openingElement.name, ctx);
1684
+ const each = buildEach(node, ctx);
1685
+ const children = each ? [each] : buildChildren(node.children, ctx);
1686
+ if (tag === CONTAINER2) return children.length === 1 ? children[0] : { kind: "fragment", children };
1687
+ const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
1688
+ return { kind: "element", tag, svg: SVG_TAGS.has(tag), props: buildProps(attributes, ctx), children, static: false, ...at ? { at } : {} };
1689
+ }
1610
1690
  function buildNode(node, ctx) {
1611
1691
  if (node.type === "JSXFragment") {
1612
1692
  return { kind: "fragment", children: buildChildren(node.children, ctx) };
1613
1693
  }
1694
+ if (directive(node, "if")) return buildIf(node, null, ctx);
1614
1695
  const { tag, component } = tagInfo(node.openingElement.name, ctx);
1696
+ if (!component) return buildElement(node, ctx, []);
1615
1697
  const props = buildProps(node.openingElement.attributes, ctx);
1616
1698
  const children = buildChildren(node.children, ctx);
1617
1699
  const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
1618
- if (component) {
1619
- const load = readLoad(node.openingElement.attributes);
1620
- return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
1621
- }
1622
- return { kind: "element", tag, svg: SVG_TAGS.has(tag), props, children, static: false, ...at ? { at } : {} };
1700
+ const load = readLoad(node.openingElement.attributes);
1701
+ return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
1623
1702
  }
1624
1703
  function buildJsxIR(node, ctx) {
1625
1704
  const ir = buildNode(node, ctx);
@@ -435,6 +435,7 @@ var INTRINSICS = {
435
435
  $effect: { export: "effect", module: REACTIVE_MODULE, returns: "effect" },
436
436
  $store: { export: "store", module: "@fluixi/reactive/store", returns: "store-handle" },
437
437
  $resource: { export: "resource", module: REACTIVE_MODULE, returns: "resource-handle" },
438
+ $cachedResource: { export: "cachedResource", module: "@fluixi/reactive/cached-resource", returns: "resource-handle" },
438
439
  // No short runtime form for these yet, so the intrinsic stands for the `create*`
439
440
  // name. Still worth having: no import to write, and when a shorter form is
440
441
  // designed, the change is this line rather than a compiler pass.
@@ -444,7 +445,22 @@ var INTRINSICS = {
444
445
  // does. `untrack` suppresses dependency collection, which is exactly the kind of thing
445
446
  // dependency analysis must not guess at.
446
447
  $untrack: { export: "untrack", module: REACTIVE_MODULE, returns: "plain" },
447
- $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" }
448
+ $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" },
449
+ // Scopes, scheduling and lifecycle: no value to classify, but the same authoring promise,
450
+ // no import to write. `$cleanup` registers a callback on the current owner (`onCleanup`).
451
+ $batch: { export: "batch", module: REACTIVE_MODULE, returns: "plain" },
452
+ $cleanup: { export: "onCleanup", module: REACTIVE_MODULE, returns: "plain" },
453
+ $root: { export: "root", module: REACTIVE_MODULE, returns: "plain" },
454
+ $context: { export: "context", module: REACTIVE_MODULE, returns: "plain" },
455
+ $renderEffect: { export: "renderEffect", module: REACTIVE_MODULE, returns: "effect" },
456
+ $computed: { export: "computed", module: REACTIVE_MODULE, returns: "effect" },
457
+ // Storage for where the code runs, and a signal kept in it. `$persisted` is a signal
458
+ // handle as far as reads go: the same accessor with `set` on it.
459
+ $storage: { export: "useStorage", module: "@fluixi/core/storage", returns: "plain" },
460
+ $asyncStorage: { export: "useAsyncStorage", module: "@fluixi/core/storage", returns: "plain" },
461
+ $persisted: { export: "persisted", module: "@fluixi/core/storage", returns: "signal-handle" },
462
+ // An environment variable, public or server-only; the browser build refuses a secret.
463
+ $env: { export: "env", module: "@fluixi/start/env", returns: "plain" }
448
464
  };
449
465
  var INTRINSIC_CALL = new RegExp(`\\$(?:${Object.keys(INTRINSICS).map((n) => n.slice(1)).join("|")})\\s*\\(`);
450
466
 
@@ -620,13 +636,16 @@ function emitComponent(name, props, children, used, templates, at) {
620
636
  used.add("createComponent");
621
637
  const spreads = props.filter((p) => p.kind === "spread");
622
638
  const normal = props.filter((p) => p.kind !== "spread");
623
- const entries = normal.map(
624
- (p) => `get ${propKey(p.name)}() { return ${componentPropValue(p)}; }`
625
- );
639
+ const bodies = normal.map((p) => [p.name, componentPropValue(p)]);
626
640
  if (children.length > 0) {
627
- entries.push(`get children() { return ${emitChildren(children, used, templates)}; }`);
641
+ bodies.push(["children", emitChildren(children, used, templates)]);
642
+ }
643
+ let obj = `{ ${bodies.map(([name2, body]) => `get ${propKey(name2)}() { return ${body}; }`).join(", ")} }`;
644
+ if (bodies.some(([, body]) => /\bthis\b/.test(body))) {
645
+ const thunks = bodies.map(([name2, body]) => `${JSON.stringify(name2)}: () => ${body}`);
646
+ const getters = bodies.map(([name2]) => `get ${propKey(name2)}() { return _$t[${JSON.stringify(name2)}](); }`);
647
+ obj = `((_$t) => ({ ${getters.join(", ")} }))({ ${thunks.join(", ")} })`;
628
648
  }
629
- let obj = `{ ${entries.join(", ")} }`;
630
649
  if (spreads.length > 0) {
631
650
  used.add("mergeProps");
632
651
  obj = `mergeProps(${spreads.map((s) => s.expr).join(", ")}, ${obj})`;
@@ -708,7 +727,8 @@ function emitNode(node, used, templates) {
708
727
  } else {
709
728
  used.add("spread");
710
729
  const svgFlag = node.svg ? ", isSVG: true" : "";
711
- const customFlag = node.tag.includes("-") ? ", isCustomElement: true" : "";
730
+ const accessors = node.props.filter((p) => p.kind !== "spread" && p.expr !== void 0 && p.reactive && p.name !== "ref").map((p) => p.name);
731
+ const customFlag = node.tag.includes("-") ? `, isCustomElement: true, accessors: ${JSON.stringify(accessors)}` : "";
712
732
  parts.push(
713
733
  `spread({ element: ${el}, props: ${emitProps(node.props, null, used)}${svgFlag}${customFlag} });`
714
734
  );
@@ -788,6 +808,7 @@ var DELEGATED = /* @__PURE__ */ new Set([
788
808
  function escapeTemplate(s) {
789
809
  return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
790
810
  }
811
+ var CONTAINER = "container";
791
812
  function capitalize(s) {
792
813
  return s.charAt(0).toUpperCase() + s.slice(1);
793
814
  }
@@ -842,10 +863,6 @@ var Lowerer = class {
842
863
  if (hasElse) i = elseIdx;
843
864
  continue;
844
865
  }
845
- if (child.attributes.some((a) => a.kind === "EachDirective")) {
846
- out.push(this.lowerEach(child));
847
- continue;
848
- }
849
866
  }
850
867
  const node = this.lowerNode(child);
851
868
  if (node) out.push(node);
@@ -887,13 +904,16 @@ var Lowerer = class {
887
904
  node.props.unshift({ name: "component", kind: "attr", expr: `() => ${paren(this.hole(isAttr.value.hole).code, "arrowBody")}` });
888
905
  return node;
889
906
  }
907
+ const each = this.lowerEach(el);
908
+ const children = each ? [each] : this.lowerChildren(el.children);
909
+ if (el.tag === CONTAINER) return children.length === 1 ? children[0] : { kind: "fragment", children };
890
910
  const at = this.tagAt(el);
891
911
  return {
892
912
  kind: "element",
893
913
  tag: el.tag,
894
914
  svg: el.namespace === "svg",
895
915
  props: this.lowerAttributes(el.attributes),
896
- children: this.lowerChildren(el.children),
916
+ children,
897
917
  static: false,
898
918
  ...at ? { at } : {}
899
919
  };
@@ -965,10 +985,14 @@ var Lowerer = class {
965
985
  const at = this.tagAt(el);
966
986
  return { kind: "component", name: "Show", props, children: [child], ...at ? { at } : {}, ...cond.at ? { bindAt: cond.at } : {} };
967
987
  }
968
- /** `<el each=${items} key?>${item => …}</el>` → `<For each>{item => <el>…</el>}</For>`. */
988
+ /**
989
+ * `<el each=${items} key?>${item => …}</el>` → `<el><For each>{item => …}</For></el>`: the
990
+ * content is repeated per item. Content that is not a `${item => …}` arrow is repeated as
991
+ * it is. Null when the element has no `each`.
992
+ */
969
993
  lowerEach(el) {
970
994
  const eachDir = el.attributes.find((a) => a.kind === "EachDirective");
971
- if (!eachDir || eachDir.kind !== "EachDirective") return this.lowerNode(el);
995
+ if (!eachDir || eachDir.kind !== "EachDirective") return null;
972
996
  const each = this.hole(eachDir.hole);
973
997
  const props = [{ name: "each", kind: "attr", expr: each.code, reactive: each.reactive, ...each.at ? { at: each.at } : {} }];
974
998
  if (eachDir.key) {
@@ -978,12 +1002,12 @@ var Lowerer = class {
978
1002
  const arrow = this.itemArrow(el);
979
1003
  let childrenCode;
980
1004
  if (arrow) {
981
- const bodyIR = { kind: "expr", code: arrow.body, reactive: arrow.bodyReactive };
982
- const repeated = this.rebuildWithChildren(el, [bodyIR]);
983
- childrenCode = `(${arrow.params.join(", ")}) => ${paren(this.emit(repeated), "arrowBody")}`;
1005
+ const body = arrow.bodyReactive ? `() => ${paren(arrow.body, "arrowBody")}` : paren(arrow.body, "arrowBody");
1006
+ childrenCode = `(${arrow.params.join(", ")}) => ${body}`;
984
1007
  } else {
985
- const repeated = this.stripAndLower(el, (a) => a.kind === "EachDirective");
986
- childrenCode = `() => ${paren(this.emit(repeated), "arrowBody")}`;
1008
+ const content = this.lowerChildren(el.children);
1009
+ const ir = content.length === 1 ? content[0] : { kind: "fragment", children: content };
1010
+ childrenCode = `() => ${paren(this.emit(ir), "arrowBody")}`;
987
1011
  }
988
1012
  const children = [{ kind: "expr", code: childrenCode, reactive: false }];
989
1013
  const at = this.tagAt(el);
@@ -1000,25 +1024,6 @@ var Lowerer = class {
1000
1024
  const clone = { ...el, attributes: el.attributes.filter((a) => !drop(a)) };
1001
1025
  return clone.kind === "Element" ? this.lowerElement(clone) : this.lowerComponent(clone);
1002
1026
  }
1003
- /** Same element (minus each/key) but with explicit IR children. */
1004
- rebuildWithChildren(el, children) {
1005
- const props = this.lowerAttributes(
1006
- el.attributes.filter((a) => a.kind !== "EachDirective")
1007
- );
1008
- if (el.kind === "Element") {
1009
- const at = this.tagAt(el);
1010
- return {
1011
- kind: "element",
1012
- tag: el.tag,
1013
- svg: el.namespace === "svg",
1014
- props,
1015
- children,
1016
- static: false,
1017
- ...at ? { at } : {}
1018
- };
1019
- }
1020
- return { kind: "component", name: el.tag, props, children };
1021
- }
1022
1027
  // --- attributes ---------------------------------------------------------
1023
1028
  lowerAttributes(attrs) {
1024
1029
  const props = [];
@@ -1430,6 +1435,22 @@ var DELEGATED2 = /* @__PURE__ */ new Set([
1430
1435
  "mouseup"
1431
1436
  ]);
1432
1437
  var isReactive = (node) => isReactiveShape(shapeOf(node));
1438
+ var CONTAINER2 = "container";
1439
+ function directive(node, name) {
1440
+ if (node.type !== "JSXElement") return void 0;
1441
+ const opening = node.openingElement;
1442
+ if (opening.name.type !== "JSXIdentifier" || opening.name.name[0] !== opening.name.name[0].toLowerCase()) return void 0;
1443
+ return opening.attributes.find((a) => a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === name);
1444
+ }
1445
+ function directiveExpr(attr) {
1446
+ const v = attr.value;
1447
+ return v?.type === "JSXExpressionContainer" && v.expression?.type !== "JSXEmptyExpression" ? v.expression : void 0;
1448
+ }
1449
+ function emitIR(ir, ctx) {
1450
+ const { code, imports, templates } = imperativeBackend.emit(ir, {});
1451
+ for (const s of imports) ctx.used.add(s);
1452
+ return inlineTemplates(code, templates);
1453
+ }
1433
1454
  var posOf = (node, ctx) => {
1434
1455
  if (!ctx.sourceFile) return void 0;
1435
1456
  const at = node.loc?.start;
@@ -1538,7 +1559,16 @@ function buildProps(attrs, ctx) {
1538
1559
  }
1539
1560
  function buildChildren(children, ctx) {
1540
1561
  const out = [];
1541
- for (const child of children) {
1562
+ for (let i = 0; i < children.length; i++) {
1563
+ const child = children[i];
1564
+ if (directive(child, "if")) {
1565
+ let next = i + 1;
1566
+ while (next < children.length && children[next].type === "JSXText" && !cleanTemplateText(children[next].value)) next++;
1567
+ const fallback = directive(children[next], "else") ? children[next] : null;
1568
+ out.push(buildIf(child, fallback, ctx));
1569
+ if (fallback) i = next;
1570
+ continue;
1571
+ }
1542
1572
  if (child.type === "JSXText") {
1543
1573
  const text = cleanTemplateText(child.value);
1544
1574
  if (text) out.push({ kind: "text", value: text });
@@ -1575,19 +1605,68 @@ function readLoad(attrs) {
1575
1605
  }
1576
1606
  return void 0;
1577
1607
  }
1608
+ function buildIf(node, fallback, ctx) {
1609
+ const cond = directiveExpr(directive(node, "if"));
1610
+ const props = [
1611
+ cond ? { name: "when", kind: "attr", expr: ctx.code(cond), reactive: isReactive(cond), at: posOf(cond, ctx) } : { name: "when", kind: "attr", literal: true }
1612
+ ];
1613
+ if (fallback) {
1614
+ props.push({ name: "fallback", kind: "attr", expr: emitIR(buildElement(fallback, ctx, ["else"]), ctx), jsxElement: true });
1615
+ }
1616
+ return { kind: "component", name: "Show", props, children: [buildElement(node, ctx, ["if"])] };
1617
+ }
1618
+ function buildEach(node, ctx) {
1619
+ const attr = directive(node, "each");
1620
+ if (!attr) return null;
1621
+ const list = directiveExpr(attr);
1622
+ const props = [
1623
+ list ? { name: "each", kind: "attr", expr: ctx.code(list), reactive: isReactive(list), at: posOf(list, ctx) } : { name: "each", kind: "attr", expr: "[]" }
1624
+ ];
1625
+ const key = directive(node, "key");
1626
+ if (key) {
1627
+ const keyExpr = directiveExpr(key);
1628
+ const by = isString(key.value) ? `(item) => item[${JSON.stringify(literalOf(key.value))}]` : keyExpr ? ctx.code(keyExpr) : void 0;
1629
+ if (by) props.push({ name: "by", kind: "attr", expr: by });
1630
+ }
1631
+ const content = node.children.filter((c) => !(c.type === "JSXText" && !cleanTemplateText(c.value)));
1632
+ const only = content.length === 1 && content[0].type === "JSXExpressionContainer" ? content[0].expression : null;
1633
+ let render;
1634
+ if (only && (only.type === "ArrowFunctionExpression" || only.type === "FunctionExpression")) {
1635
+ const fn = ctx.code(only);
1636
+ const live = only.type === "ArrowFunctionExpression" && only.body.type !== "BlockStatement" && isReactive(only.body);
1637
+ render = live ? `(...args) => () => (${fn})(...args)` : fn;
1638
+ } else {
1639
+ const built = buildChildren(node.children, ctx);
1640
+ const ir = built.length === 1 ? built[0] : { kind: "fragment", children: built };
1641
+ render = `() => ${paren(emitIR(ir, ctx), "arrowBody")}`;
1642
+ }
1643
+ return { kind: "component", name: "For", props, children: [{ kind: "expr", code: render, reactive: false }] };
1644
+ }
1645
+ function buildElement(node, ctx, handled) {
1646
+ const skip = new Set(handled);
1647
+ const directives = /* @__PURE__ */ new Set(["if", "else", "each", ...directive(node, "each") ? ["key"] : []]);
1648
+ const attributes = node.openingElement.attributes.filter(
1649
+ (a) => !(a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && (skip.has(a.name.name) || directives.has(a.name.name)))
1650
+ );
1651
+ const { tag } = tagInfo(node.openingElement.name, ctx);
1652
+ const each = buildEach(node, ctx);
1653
+ const children = each ? [each] : buildChildren(node.children, ctx);
1654
+ if (tag === CONTAINER2) return children.length === 1 ? children[0] : { kind: "fragment", children };
1655
+ const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
1656
+ return { kind: "element", tag, svg: SVG_TAGS.has(tag), props: buildProps(attributes, ctx), children, static: false, ...at ? { at } : {} };
1657
+ }
1578
1658
  function buildNode(node, ctx) {
1579
1659
  if (node.type === "JSXFragment") {
1580
1660
  return { kind: "fragment", children: buildChildren(node.children, ctx) };
1581
1661
  }
1662
+ if (directive(node, "if")) return buildIf(node, null, ctx);
1582
1663
  const { tag, component } = tagInfo(node.openingElement.name, ctx);
1664
+ if (!component) return buildElement(node, ctx, []);
1583
1665
  const props = buildProps(node.openingElement.attributes, ctx);
1584
1666
  const children = buildChildren(node.children, ctx);
1585
1667
  const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
1586
- if (component) {
1587
- const load = readLoad(node.openingElement.attributes);
1588
- return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
1589
- }
1590
- return { kind: "element", tag, svg: SVG_TAGS.has(tag), props, children, static: false, ...at ? { at } : {} };
1668
+ const load = readLoad(node.openingElement.attributes);
1669
+ return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
1591
1670
  }
1592
1671
  function buildJsxIR(node, ctx) {
1593
1672
  const ir = buildNode(node, ctx);