@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,16 +1,16 @@
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
  import {
3
3
  INTRINSICS,
4
4
  builtinComponentMap,
5
5
  createComponentResolver
6
- } from "./chunk-S4J4OCWK.mjs";
6
+ } from "./chunk-OD53TMER.mjs";
7
7
  import {
8
8
  __require
9
- } from "./chunk-QX6Y3OAR.mjs";
9
+ } from "./chunk-TJIH7TLX.mjs";
10
10
 
11
11
  // src/resolve/write-globals.ts
12
- import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
13
- import { dirname, relative, sep, resolve as resolvePath } from "node:path";
12
+ import { mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
13
+ import { basename, dirname, relative, sep, resolve as resolvePath } from "node:path";
14
14
 
15
15
  // src/resolve/component-globals.ts
16
16
  var DOM_GLOBALS = /* @__PURE__ */ new Set([
@@ -50,7 +50,8 @@ function componentGlobals(rules, options = {}) {
50
50
  builtins = true,
51
51
  intrinsics = true,
52
52
  generator = "@fluixi/compiler",
53
- specifier = (module) => module
53
+ specifier = (module) => module,
54
+ candidates = []
54
55
  } = options;
55
56
  const all = [...rules];
56
57
  if (builtins) {
@@ -62,7 +63,8 @@ function componentGlobals(rules, options = {}) {
62
63
  const shadowedByDom = [];
63
64
  const unavailable = [];
64
65
  const resolved = /* @__PURE__ */ new Map();
65
- for (const name of [...resolver.names()].sort()) {
66
+ const names = /* @__PURE__ */ new Set([...resolver.names(), ...[...candidates].filter((n) => /^[A-Za-z_$][\w$]*$/.test(n))]);
67
+ for (const name of [...names].sort()) {
66
68
  const target = resolver.resolve(name);
67
69
  if (!target) continue;
68
70
  const pkg = packageOf(target.module);
@@ -73,7 +75,11 @@ function componentGlobals(rules, options = {}) {
73
75
  resolved.set(name, target);
74
76
  }
75
77
  }
76
- const intrinsicModules = intrinsics ? [...new Set(Object.values(INTRINSICS).map((i) => i.module))] : [];
78
+ const usableIntrinsics = intrinsics ? Object.entries(INTRINSICS).filter(([, i]) => {
79
+ const pkg = packageOf(i.module);
80
+ return !available || !pkg || available.has(pkg);
81
+ }) : [];
82
+ const intrinsicModules = [...new Set(usableIntrinsics.map(([, i]) => i.module))];
77
83
  const modules = [
78
84
  .../* @__PURE__ */ new Set([...declared.map((name) => resolved.get(name).module), ...intrinsicModules])
79
85
  ].sort();
@@ -93,15 +99,18 @@ function componentGlobals(rules, options = {}) {
93
99
  return `// import { ${n} } from '${module ? specifier(module) : ""}';
94
100
  `;
95
101
  }).join("") : "";
96
- const intrinsicLines = intrinsics ? Object.entries(INTRINSICS).map(
102
+ const intrinsicLines = usableIntrinsics.map(
97
103
  ([name, intrinsic]) => ` const ${name}: typeof ${namespaceAlias(intrinsic.module)}.${intrinsic.export};`
98
- ) : [];
104
+ );
99
105
  const patternRules = rules.filter((rule) => "match" in rule).length;
100
- const patternNote = patternRules > 0 ? `//
106
+ const patternNote = patternRules === 0 ? "" : options.candidates ? `//
107
+ // ${patternRules} pattern rule${patternRules === 1 ? "" : "s"}: declared for the files the module path
108
+ // points at. A name with no such file still resolves at build time; declare it by hand.
109
+ ` : `//
101
110
  // ${patternRules} pattern rule${patternRules === 1 ? "" : "s"} cannot be declared:
102
111
  // a pattern matches unboundedly many names, so there is no list to emit. Those
103
112
  // tags still resolve at build time; declare them by hand if the editor needs them.
104
- ` : "";
113
+ `;
105
114
  const source = `// GENERATED by ${generator}, do not edit.
106
115
  //
107
116
  // The components \`resolve\` supplies without an import. This describes what the
@@ -158,6 +167,30 @@ function projectDependencies(root) {
158
167
  return void 0;
159
168
  }
160
169
  }
170
+ function patternCandidates(rules, root) {
171
+ const out = [];
172
+ for (const rule of rules) {
173
+ if (!("match" in rule) || !rule.module.startsWith("/")) continue;
174
+ const dir = dirname(rule.module);
175
+ const file = basename(rule.module);
176
+ const group = file.match(/\$\d+/);
177
+ if (dir.includes("$") || !group || file.indexOf("$", group.index + 1) !== -1) continue;
178
+ const before = file.slice(0, group.index);
179
+ const after = file.slice(group.index + group[0].length);
180
+ let entries;
181
+ try {
182
+ entries = readdirSync(resolvePath(root, dir.slice(1)));
183
+ } catch {
184
+ continue;
185
+ }
186
+ for (const entry of entries) {
187
+ if (entry.length > before.length + after.length && entry.startsWith(before) && entry.endsWith(after)) {
188
+ out.push(entry.slice(before.length, entry.length - after.length));
189
+ }
190
+ }
191
+ }
192
+ return out;
193
+ }
161
194
  function writeComponentGlobals(options, root) {
162
195
  if (options.globals === false) return { status: "skipped", reason: "disabled" };
163
196
  const rules = options.resolve ?? [];
@@ -173,6 +206,7 @@ function writeComponentGlobals(options, root) {
173
206
  }
174
207
  const { source, declared } = componentGlobals(rules, {
175
208
  available: projectDependencies(root),
209
+ candidates: patternCandidates(rules, root),
176
210
  specifier: (module) => forTypeScript(module, dirname(target), root)
177
211
  });
178
212
  if (existing === source) {
@@ -199,12 +233,12 @@ function warnIfUnreachable(root, target) {
199
233
  }
200
234
 
201
235
  // src/integrations.ts
202
- import { readdirSync, readFileSync as readFileSync2 } from "node:fs";
236
+ import { readdirSync as readdirSync2, readFileSync as readFileSync2 } from "node:fs";
203
237
  import { join as _join } from "node:path";
204
238
  async function createBabelPlugin(options = {}) {
205
239
  let pluginModule = null;
206
240
  try {
207
- pluginModule = await import("./babel-VBLQRHQ2.mjs");
241
+ pluginModule = await import("./babel-RJHJQVEM.mjs");
208
242
  } catch (e) {
209
243
  console.warn("Could not load the babel front-end:", e);
210
244
  }
@@ -375,7 +409,7 @@ function transformOptions(options, id) {
375
409
  }
376
410
  async function rewriteProps(code, id, options) {
377
411
  if (options.propsDestructure === false) return code;
378
- const { transformProps } = await import("./transform-D536WXGM.mjs");
412
+ const { transformProps } = await import("./transform-QWWQDCXE.mjs");
379
413
  const result = transformProps(code, id);
380
414
  reportDiagnostics({ propsDiagnostics: result.diagnostics }, id);
381
415
  return result.rewritten > 0 ? result.code : code;
@@ -398,7 +432,7 @@ function reportDiagnostics(result, id) {
398
432
  }
399
433
  async function compileModule(code, id, options) {
400
434
  if (!options.babel) {
401
- const { transformTemplates } = await import("./transform-D536WXGM.mjs");
435
+ const { transformTemplates } = await import("./transform-QWWQDCXE.mjs");
402
436
  const result2 = transformTemplates(code, id, transformOptions(options, id));
403
437
  reportDiagnostics(result2, id);
404
438
  return result2;
@@ -473,7 +507,7 @@ function hasServerDirective(source) {
473
507
  function findServerFnModules(dir, out = []) {
474
508
  let entries;
475
509
  try {
476
- entries = readdirSync(dir, { withFileTypes: true });
510
+ entries = readdirSync2(dir, { withFileTypes: true });
477
511
  } catch {
478
512
  return out;
479
513
  }
@@ -517,10 +551,10 @@ export { isServerFnRequest, handleServerFn } from '@fluixi/start/server-fn';
517
551
  if (!code.includes("use server")) return null;
518
552
  const args = { ssr: !!opts?.ssr, filename: id, root };
519
553
  if (options.babel) {
520
- const { transformServerFunctions } = await import("./server-functions-7PUFLRGF.mjs");
554
+ const { transformServerFunctions } = await import("./server-functions-CTDXX67X.mjs");
521
555
  return transformServerFunctions(code, args);
522
556
  }
523
- const { transformServerFns } = await import("./server-fns-J56G3PFP.mjs");
557
+ const { transformServerFns } = await import("./server-fns-ZFRDIQ2X.mjs");
524
558
  return transformServerFns(code, args);
525
559
  }
526
560
  };
@@ -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 __defProp = Object.defineProperty;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -468,6 +468,7 @@ var INTRINSICS = {
468
468
  $effect: { export: "effect", module: REACTIVE_MODULE, returns: "effect" },
469
469
  $store: { export: "store", module: "@fluixi/reactive/store", returns: "store-handle" },
470
470
  $resource: { export: "resource", module: REACTIVE_MODULE, returns: "resource-handle" },
471
+ $cachedResource: { export: "cachedResource", module: "@fluixi/reactive/cached-resource", returns: "resource-handle" },
471
472
  // No short runtime form for these yet, so the intrinsic stands for the `create*`
472
473
  // name. Still worth having: no import to write, and when a shorter form is
473
474
  // designed, the change is this line rather than a compiler pass.
@@ -477,7 +478,22 @@ var INTRINSICS = {
477
478
  // does. `untrack` suppresses dependency collection, which is exactly the kind of thing
478
479
  // dependency analysis must not guess at.
479
480
  $untrack: { export: "untrack", module: REACTIVE_MODULE, returns: "plain" },
480
- $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" }
481
+ $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" },
482
+ // Scopes, scheduling and lifecycle: no value to classify, but the same authoring promise,
483
+ // no import to write. `$cleanup` registers a callback on the current owner (`onCleanup`).
484
+ $batch: { export: "batch", module: REACTIVE_MODULE, returns: "plain" },
485
+ $cleanup: { export: "onCleanup", module: REACTIVE_MODULE, returns: "plain" },
486
+ $root: { export: "root", module: REACTIVE_MODULE, returns: "plain" },
487
+ $context: { export: "context", module: REACTIVE_MODULE, returns: "plain" },
488
+ $renderEffect: { export: "renderEffect", module: REACTIVE_MODULE, returns: "effect" },
489
+ $computed: { export: "computed", module: REACTIVE_MODULE, returns: "effect" },
490
+ // Storage for where the code runs, and a signal kept in it. `$persisted` is a signal
491
+ // handle as far as reads go: the same accessor with `set` on it.
492
+ $storage: { export: "useStorage", module: "@fluixi/core/storage", returns: "plain" },
493
+ $asyncStorage: { export: "useAsyncStorage", module: "@fluixi/core/storage", returns: "plain" },
494
+ $persisted: { export: "persisted", module: "@fluixi/core/storage", returns: "signal-handle" },
495
+ // An environment variable, public or server-only; the browser build refuses a secret.
496
+ $env: { export: "env", module: "@fluixi/start/env", returns: "plain" }
481
497
  };
482
498
  var INTRINSIC_CALL = new RegExp(`\\$(?:${Object.keys(INTRINSICS).map((n) => n.slice(1)).join("|")})\\s*\\(`);
483
499
 
@@ -653,13 +669,16 @@ function emitComponent(name, props, children, used, templates, at) {
653
669
  used.add("createComponent");
654
670
  const spreads = props.filter((p) => p.kind === "spread");
655
671
  const normal = props.filter((p) => p.kind !== "spread");
656
- const entries = normal.map(
657
- (p) => `get ${propKey(p.name)}() { return ${componentPropValue(p)}; }`
658
- );
672
+ const bodies = normal.map((p) => [p.name, componentPropValue(p)]);
659
673
  if (children.length > 0) {
660
- entries.push(`get children() { return ${emitChildren(children, used, templates)}; }`);
674
+ bodies.push(["children", emitChildren(children, used, templates)]);
675
+ }
676
+ let obj = `{ ${bodies.map(([name2, body]) => `get ${propKey(name2)}() { return ${body}; }`).join(", ")} }`;
677
+ if (bodies.some(([, body]) => /\bthis\b/.test(body))) {
678
+ const thunks = bodies.map(([name2, body]) => `${JSON.stringify(name2)}: () => ${body}`);
679
+ const getters = bodies.map(([name2]) => `get ${propKey(name2)}() { return _$t[${JSON.stringify(name2)}](); }`);
680
+ obj = `((_$t) => ({ ${getters.join(", ")} }))({ ${thunks.join(", ")} })`;
661
681
  }
662
- let obj = `{ ${entries.join(", ")} }`;
663
682
  if (spreads.length > 0) {
664
683
  used.add("mergeProps");
665
684
  obj = `mergeProps(${spreads.map((s) => s.expr).join(", ")}, ${obj})`;
@@ -741,7 +760,8 @@ function emitNode(node, used, templates) {
741
760
  } else {
742
761
  used.add("spread");
743
762
  const svgFlag = node.svg ? ", isSVG: true" : "";
744
- const customFlag = node.tag.includes("-") ? ", isCustomElement: true" : "";
763
+ const accessors = node.props.filter((p) => p.kind !== "spread" && p.expr !== void 0 && p.reactive && p.name !== "ref").map((p) => p.name);
764
+ const customFlag = node.tag.includes("-") ? `, isCustomElement: true, accessors: ${JSON.stringify(accessors)}` : "";
745
765
  parts.push(
746
766
  `spread({ element: ${el}, props: ${emitProps(node.props, null, used)}${svgFlag}${customFlag} });`
747
767
  );
@@ -872,6 +892,7 @@ var DELEGATED = /* @__PURE__ */ new Set([
872
892
  function escapeTemplate(s) {
873
893
  return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
874
894
  }
895
+ var CONTAINER = "container";
875
896
  function capitalize(s) {
876
897
  return s.charAt(0).toUpperCase() + s.slice(1);
877
898
  }
@@ -926,10 +947,6 @@ var Lowerer = class {
926
947
  if (hasElse) i = elseIdx;
927
948
  continue;
928
949
  }
929
- if (child.attributes.some((a) => a.kind === "EachDirective")) {
930
- out.push(this.lowerEach(child));
931
- continue;
932
- }
933
950
  }
934
951
  const node = this.lowerNode(child);
935
952
  if (node) out.push(node);
@@ -971,13 +988,16 @@ var Lowerer = class {
971
988
  node.props.unshift({ name: "component", kind: "attr", expr: `() => ${paren(this.hole(isAttr.value.hole).code, "arrowBody")}` });
972
989
  return node;
973
990
  }
991
+ const each = this.lowerEach(el);
992
+ const children = each ? [each] : this.lowerChildren(el.children);
993
+ if (el.tag === CONTAINER) return children.length === 1 ? children[0] : { kind: "fragment", children };
974
994
  const at = this.tagAt(el);
975
995
  return {
976
996
  kind: "element",
977
997
  tag: el.tag,
978
998
  svg: el.namespace === "svg",
979
999
  props: this.lowerAttributes(el.attributes),
980
- children: this.lowerChildren(el.children),
1000
+ children,
981
1001
  static: false,
982
1002
  ...at ? { at } : {}
983
1003
  };
@@ -1049,10 +1069,14 @@ var Lowerer = class {
1049
1069
  const at = this.tagAt(el);
1050
1070
  return { kind: "component", name: "Show", props, children: [child], ...at ? { at } : {}, ...cond.at ? { bindAt: cond.at } : {} };
1051
1071
  }
1052
- /** `<el each=${items} key?>${item => …}</el>` → `<For each>{item => <el>…</el>}</For>`. */
1072
+ /**
1073
+ * `<el each=${items} key?>${item => …}</el>` → `<el><For each>{item => …}</For></el>`: the
1074
+ * content is repeated per item. Content that is not a `${item => …}` arrow is repeated as
1075
+ * it is. Null when the element has no `each`.
1076
+ */
1053
1077
  lowerEach(el) {
1054
1078
  const eachDir = el.attributes.find((a) => a.kind === "EachDirective");
1055
- if (!eachDir || eachDir.kind !== "EachDirective") return this.lowerNode(el);
1079
+ if (!eachDir || eachDir.kind !== "EachDirective") return null;
1056
1080
  const each = this.hole(eachDir.hole);
1057
1081
  const props = [{ name: "each", kind: "attr", expr: each.code, reactive: each.reactive, ...each.at ? { at: each.at } : {} }];
1058
1082
  if (eachDir.key) {
@@ -1062,12 +1086,12 @@ var Lowerer = class {
1062
1086
  const arrow = this.itemArrow(el);
1063
1087
  let childrenCode;
1064
1088
  if (arrow) {
1065
- const bodyIR = { kind: "expr", code: arrow.body, reactive: arrow.bodyReactive };
1066
- const repeated = this.rebuildWithChildren(el, [bodyIR]);
1067
- childrenCode = `(${arrow.params.join(", ")}) => ${paren(this.emit(repeated), "arrowBody")}`;
1089
+ const body = arrow.bodyReactive ? `() => ${paren(arrow.body, "arrowBody")}` : paren(arrow.body, "arrowBody");
1090
+ childrenCode = `(${arrow.params.join(", ")}) => ${body}`;
1068
1091
  } else {
1069
- const repeated = this.stripAndLower(el, (a) => a.kind === "EachDirective");
1070
- childrenCode = `() => ${paren(this.emit(repeated), "arrowBody")}`;
1092
+ const content = this.lowerChildren(el.children);
1093
+ const ir = content.length === 1 ? content[0] : { kind: "fragment", children: content };
1094
+ childrenCode = `() => ${paren(this.emit(ir), "arrowBody")}`;
1071
1095
  }
1072
1096
  const children = [{ kind: "expr", code: childrenCode, reactive: false }];
1073
1097
  const at = this.tagAt(el);
@@ -1084,25 +1108,6 @@ var Lowerer = class {
1084
1108
  const clone = { ...el, attributes: el.attributes.filter((a) => !drop(a)) };
1085
1109
  return clone.kind === "Element" ? this.lowerElement(clone) : this.lowerComponent(clone);
1086
1110
  }
1087
- /** Same element (minus each/key) but with explicit IR children. */
1088
- rebuildWithChildren(el, children) {
1089
- const props = this.lowerAttributes(
1090
- el.attributes.filter((a) => a.kind !== "EachDirective")
1091
- );
1092
- if (el.kind === "Element") {
1093
- const at = this.tagAt(el);
1094
- return {
1095
- kind: "element",
1096
- tag: el.tag,
1097
- svg: el.namespace === "svg",
1098
- props,
1099
- children,
1100
- static: false,
1101
- ...at ? { at } : {}
1102
- };
1103
- }
1104
- return { kind: "component", name: el.tag, props, children };
1105
- }
1106
1111
  // --- attributes ---------------------------------------------------------
1107
1112
  lowerAttributes(attrs) {
1108
1113
  const props = [];
@@ -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/optimize/analyze-static.ts
4
4
  function propReason(prop) {
@@ -442,6 +442,7 @@ var INTRINSICS = {
442
442
  $effect: { export: "effect", module: REACTIVE_MODULE, returns: "effect" },
443
443
  $store: { export: "store", module: "@fluixi/reactive/store", returns: "store-handle" },
444
444
  $resource: { export: "resource", module: REACTIVE_MODULE, returns: "resource-handle" },
445
+ $cachedResource: { export: "cachedResource", module: "@fluixi/reactive/cached-resource", returns: "resource-handle" },
445
446
  // No short runtime form for these yet, so the intrinsic stands for the `create*`
446
447
  // name. Still worth having: no import to write, and when a shorter form is
447
448
  // designed, the change is this line rather than a compiler pass.
@@ -451,7 +452,22 @@ var INTRINSICS = {
451
452
  // does. `untrack` suppresses dependency collection, which is exactly the kind of thing
452
453
  // dependency analysis must not guess at.
453
454
  $untrack: { export: "untrack", module: REACTIVE_MODULE, returns: "plain" },
454
- $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" }
455
+ $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" },
456
+ // Scopes, scheduling and lifecycle: no value to classify, but the same authoring promise,
457
+ // no import to write. `$cleanup` registers a callback on the current owner (`onCleanup`).
458
+ $batch: { export: "batch", module: REACTIVE_MODULE, returns: "plain" },
459
+ $cleanup: { export: "onCleanup", module: REACTIVE_MODULE, returns: "plain" },
460
+ $root: { export: "root", module: REACTIVE_MODULE, returns: "plain" },
461
+ $context: { export: "context", module: REACTIVE_MODULE, returns: "plain" },
462
+ $renderEffect: { export: "renderEffect", module: REACTIVE_MODULE, returns: "effect" },
463
+ $computed: { export: "computed", module: REACTIVE_MODULE, returns: "effect" },
464
+ // Storage for where the code runs, and a signal kept in it. `$persisted` is a signal
465
+ // handle as far as reads go: the same accessor with `set` on it.
466
+ $storage: { export: "useStorage", module: "@fluixi/core/storage", returns: "plain" },
467
+ $asyncStorage: { export: "useAsyncStorage", module: "@fluixi/core/storage", returns: "plain" },
468
+ $persisted: { export: "persisted", module: "@fluixi/core/storage", returns: "signal-handle" },
469
+ // An environment variable, public or server-only; the browser build refuses a secret.
470
+ $env: { export: "env", module: "@fluixi/start/env", returns: "plain" }
455
471
  };
456
472
  var INTRINSIC_CALL = new RegExp(`\\$(?:${Object.keys(INTRINSICS).map((n) => n.slice(1)).join("|")})\\s*\\(`);
457
473
 
@@ -627,13 +643,16 @@ function emitComponent(name, props, children, used, templates, at) {
627
643
  used.add("createComponent");
628
644
  const spreads = props.filter((p) => p.kind === "spread");
629
645
  const normal = props.filter((p) => p.kind !== "spread");
630
- const entries = normal.map(
631
- (p) => `get ${propKey(p.name)}() { return ${componentPropValue(p)}; }`
632
- );
646
+ const bodies = normal.map((p) => [p.name, componentPropValue(p)]);
633
647
  if (children.length > 0) {
634
- entries.push(`get children() { return ${emitChildren(children, used, templates)}; }`);
648
+ bodies.push(["children", emitChildren(children, used, templates)]);
649
+ }
650
+ let obj = `{ ${bodies.map(([name2, body]) => `get ${propKey(name2)}() { return ${body}; }`).join(", ")} }`;
651
+ if (bodies.some(([, body]) => /\bthis\b/.test(body))) {
652
+ const thunks = bodies.map(([name2, body]) => `${JSON.stringify(name2)}: () => ${body}`);
653
+ const getters = bodies.map(([name2]) => `get ${propKey(name2)}() { return _$t[${JSON.stringify(name2)}](); }`);
654
+ obj = `((_$t) => ({ ${getters.join(", ")} }))({ ${thunks.join(", ")} })`;
635
655
  }
636
- let obj = `{ ${entries.join(", ")} }`;
637
656
  if (spreads.length > 0) {
638
657
  used.add("mergeProps");
639
658
  obj = `mergeProps(${spreads.map((s) => s.expr).join(", ")}, ${obj})`;
@@ -715,7 +734,8 @@ function emitNode(node, used, templates) {
715
734
  } else {
716
735
  used.add("spread");
717
736
  const svgFlag = node.svg ? ", isSVG: true" : "";
718
- const customFlag = node.tag.includes("-") ? ", isCustomElement: true" : "";
737
+ const accessors = node.props.filter((p) => p.kind !== "spread" && p.expr !== void 0 && p.reactive && p.name !== "ref").map((p) => p.name);
738
+ const customFlag = node.tag.includes("-") ? `, isCustomElement: true, accessors: ${JSON.stringify(accessors)}` : "";
719
739
  parts.push(
720
740
  `spread({ element: ${el}, props: ${emitProps(node.props, null, used)}${svgFlag}${customFlag} });`
721
741
  );
@@ -848,6 +868,7 @@ var DELEGATED = /* @__PURE__ */ new Set([
848
868
  function escapeTemplate(s) {
849
869
  return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
850
870
  }
871
+ var CONTAINER = "container";
851
872
  function capitalize(s) {
852
873
  return s.charAt(0).toUpperCase() + s.slice(1);
853
874
  }
@@ -902,10 +923,6 @@ var Lowerer = class {
902
923
  if (hasElse) i = elseIdx;
903
924
  continue;
904
925
  }
905
- if (child.attributes.some((a) => a.kind === "EachDirective")) {
906
- out.push(this.lowerEach(child));
907
- continue;
908
- }
909
926
  }
910
927
  const node = this.lowerNode(child);
911
928
  if (node) out.push(node);
@@ -947,13 +964,16 @@ var Lowerer = class {
947
964
  node.props.unshift({ name: "component", kind: "attr", expr: `() => ${paren(this.hole(isAttr.value.hole).code, "arrowBody")}` });
948
965
  return node;
949
966
  }
967
+ const each = this.lowerEach(el);
968
+ const children = each ? [each] : this.lowerChildren(el.children);
969
+ if (el.tag === CONTAINER) return children.length === 1 ? children[0] : { kind: "fragment", children };
950
970
  const at = this.tagAt(el);
951
971
  return {
952
972
  kind: "element",
953
973
  tag: el.tag,
954
974
  svg: el.namespace === "svg",
955
975
  props: this.lowerAttributes(el.attributes),
956
- children: this.lowerChildren(el.children),
976
+ children,
957
977
  static: false,
958
978
  ...at ? { at } : {}
959
979
  };
@@ -1025,10 +1045,14 @@ var Lowerer = class {
1025
1045
  const at = this.tagAt(el);
1026
1046
  return { kind: "component", name: "Show", props, children: [child], ...at ? { at } : {}, ...cond.at ? { bindAt: cond.at } : {} };
1027
1047
  }
1028
- /** `<el each=${items} key?>${item => …}</el>` → `<For each>{item => <el>…</el>}</For>`. */
1048
+ /**
1049
+ * `<el each=${items} key?>${item => …}</el>` → `<el><For each>{item => …}</For></el>`: the
1050
+ * content is repeated per item. Content that is not a `${item => …}` arrow is repeated as
1051
+ * it is. Null when the element has no `each`.
1052
+ */
1029
1053
  lowerEach(el) {
1030
1054
  const eachDir = el.attributes.find((a) => a.kind === "EachDirective");
1031
- if (!eachDir || eachDir.kind !== "EachDirective") return this.lowerNode(el);
1055
+ if (!eachDir || eachDir.kind !== "EachDirective") return null;
1032
1056
  const each = this.hole(eachDir.hole);
1033
1057
  const props = [{ name: "each", kind: "attr", expr: each.code, reactive: each.reactive, ...each.at ? { at: each.at } : {} }];
1034
1058
  if (eachDir.key) {
@@ -1038,12 +1062,12 @@ var Lowerer = class {
1038
1062
  const arrow = this.itemArrow(el);
1039
1063
  let childrenCode;
1040
1064
  if (arrow) {
1041
- const bodyIR = { kind: "expr", code: arrow.body, reactive: arrow.bodyReactive };
1042
- const repeated = this.rebuildWithChildren(el, [bodyIR]);
1043
- childrenCode = `(${arrow.params.join(", ")}) => ${paren(this.emit(repeated), "arrowBody")}`;
1065
+ const body = arrow.bodyReactive ? `() => ${paren(arrow.body, "arrowBody")}` : paren(arrow.body, "arrowBody");
1066
+ childrenCode = `(${arrow.params.join(", ")}) => ${body}`;
1044
1067
  } else {
1045
- const repeated = this.stripAndLower(el, (a) => a.kind === "EachDirective");
1046
- childrenCode = `() => ${paren(this.emit(repeated), "arrowBody")}`;
1068
+ const content = this.lowerChildren(el.children);
1069
+ const ir = content.length === 1 ? content[0] : { kind: "fragment", children: content };
1070
+ childrenCode = `() => ${paren(this.emit(ir), "arrowBody")}`;
1047
1071
  }
1048
1072
  const children = [{ kind: "expr", code: childrenCode, reactive: false }];
1049
1073
  const at = this.tagAt(el);
@@ -1060,25 +1084,6 @@ var Lowerer = class {
1060
1084
  const clone = { ...el, attributes: el.attributes.filter((a) => !drop(a)) };
1061
1085
  return clone.kind === "Element" ? this.lowerElement(clone) : this.lowerComponent(clone);
1062
1086
  }
1063
- /** Same element (minus each/key) but with explicit IR children. */
1064
- rebuildWithChildren(el, children) {
1065
- const props = this.lowerAttributes(
1066
- el.attributes.filter((a) => a.kind !== "EachDirective")
1067
- );
1068
- if (el.kind === "Element") {
1069
- const at = this.tagAt(el);
1070
- return {
1071
- kind: "element",
1072
- tag: el.tag,
1073
- svg: el.namespace === "svg",
1074
- props,
1075
- children,
1076
- static: false,
1077
- ...at ? { at } : {}
1078
- };
1079
- }
1080
- return { kind: "component", name: el.tag, props, children };
1081
- }
1082
1087
  // --- attributes ---------------------------------------------------------
1083
1088
  lowerAttributes(attrs) {
1084
1089
  const props = [];