@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
package/dist/index.cjs CHANGED
@@ -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;
@@ -41,6 +41,7 @@ __export(index_exports, {
41
41
  imperativeBackend: () => imperativeBackend,
42
42
  isReactiveShape: () => isReactiveShape,
43
43
  lowerTemplate: () => lowerTemplate,
44
+ patternCandidates: () => patternCandidates,
44
45
  resolveComponentSources: () => resolveComponentSources,
45
46
  routerComponents: () => routerComponents,
46
47
  walk: () => walk,
@@ -327,6 +328,7 @@ var INTRINSICS = {
327
328
  $effect: { export: "effect", module: REACTIVE_MODULE, returns: "effect" },
328
329
  $store: { export: "store", module: "@fluixi/reactive/store", returns: "store-handle" },
329
330
  $resource: { export: "resource", module: REACTIVE_MODULE, returns: "resource-handle" },
331
+ $cachedResource: { export: "cachedResource", module: "@fluixi/reactive/cached-resource", returns: "resource-handle" },
330
332
  // No short runtime form for these yet, so the intrinsic stands for the `create*`
331
333
  // name. Still worth having: no import to write, and when a shorter form is
332
334
  // designed, the change is this line rather than a compiler pass.
@@ -336,7 +338,22 @@ var INTRINSICS = {
336
338
  // does. `untrack` suppresses dependency collection, which is exactly the kind of thing
337
339
  // dependency analysis must not guess at.
338
340
  $untrack: { export: "untrack", module: REACTIVE_MODULE, returns: "plain" },
339
- $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" }
341
+ $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" },
342
+ // Scopes, scheduling and lifecycle: no value to classify, but the same authoring promise,
343
+ // no import to write. `$cleanup` registers a callback on the current owner (`onCleanup`).
344
+ $batch: { export: "batch", module: REACTIVE_MODULE, returns: "plain" },
345
+ $cleanup: { export: "onCleanup", module: REACTIVE_MODULE, returns: "plain" },
346
+ $root: { export: "root", module: REACTIVE_MODULE, returns: "plain" },
347
+ $context: { export: "context", module: REACTIVE_MODULE, returns: "plain" },
348
+ $renderEffect: { export: "renderEffect", module: REACTIVE_MODULE, returns: "effect" },
349
+ $computed: { export: "computed", module: REACTIVE_MODULE, returns: "effect" },
350
+ // Storage for where the code runs, and a signal kept in it. `$persisted` is a signal
351
+ // handle as far as reads go: the same accessor with `set` on it.
352
+ $storage: { export: "useStorage", module: "@fluixi/core/storage", returns: "plain" },
353
+ $asyncStorage: { export: "useAsyncStorage", module: "@fluixi/core/storage", returns: "plain" },
354
+ $persisted: { export: "persisted", module: "@fluixi/core/storage", returns: "signal-handle" },
355
+ // An environment variable, public or server-only; the browser build refuses a secret.
356
+ $env: { export: "env", module: "@fluixi/start/env", returns: "plain" }
340
357
  };
341
358
  var INTRINSIC_CALL = new RegExp(`\\$(?:${Object.keys(INTRINSICS).map((n) => n.slice(1)).join("|")})\\s*\\(`);
342
359
 
@@ -512,13 +529,16 @@ function emitComponent(name, props, children, used, templates, at) {
512
529
  used.add("createComponent");
513
530
  const spreads = props.filter((p) => p.kind === "spread");
514
531
  const normal = props.filter((p) => p.kind !== "spread");
515
- const entries = normal.map(
516
- (p) => `get ${propKey(p.name)}() { return ${componentPropValue(p)}; }`
517
- );
532
+ const bodies = normal.map((p) => [p.name, componentPropValue(p)]);
518
533
  if (children.length > 0) {
519
- entries.push(`get children() { return ${emitChildren(children, used, templates)}; }`);
534
+ bodies.push(["children", emitChildren(children, used, templates)]);
535
+ }
536
+ let obj = `{ ${bodies.map(([name2, body]) => `get ${propKey(name2)}() { return ${body}; }`).join(", ")} }`;
537
+ if (bodies.some(([, body]) => /\bthis\b/.test(body))) {
538
+ const thunks = bodies.map(([name2, body]) => `${JSON.stringify(name2)}: () => ${body}`);
539
+ const getters = bodies.map(([name2]) => `get ${propKey(name2)}() { return _$t[${JSON.stringify(name2)}](); }`);
540
+ obj = `((_$t) => ({ ${getters.join(", ")} }))({ ${thunks.join(", ")} })`;
520
541
  }
521
- let obj = `{ ${entries.join(", ")} }`;
522
542
  if (spreads.length > 0) {
523
543
  used.add("mergeProps");
524
544
  obj = `mergeProps(${spreads.map((s) => s.expr).join(", ")}, ${obj})`;
@@ -600,7 +620,8 @@ function emitNode(node, used, templates) {
600
620
  } else {
601
621
  used.add("spread");
602
622
  const svgFlag = node.svg ? ", isSVG: true" : "";
603
- const customFlag = node.tag.includes("-") ? ", isCustomElement: true" : "";
623
+ const accessors = node.props.filter((p) => p.kind !== "spread" && p.expr !== void 0 && p.reactive && p.name !== "ref").map((p) => p.name);
624
+ const customFlag = node.tag.includes("-") ? `, isCustomElement: true, accessors: ${JSON.stringify(accessors)}` : "";
604
625
  parts.push(
605
626
  `spread({ element: ${el}, props: ${emitProps(node.props, null, used)}${svgFlag}${customFlag} });`
606
627
  );
@@ -671,6 +692,7 @@ var DELEGATED = /* @__PURE__ */ new Set([
671
692
  function escapeTemplate(s) {
672
693
  return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
673
694
  }
695
+ var CONTAINER = "container";
674
696
  function capitalize(s) {
675
697
  return s.charAt(0).toUpperCase() + s.slice(1);
676
698
  }
@@ -725,10 +747,6 @@ var Lowerer = class {
725
747
  if (hasElse) i = elseIdx;
726
748
  continue;
727
749
  }
728
- if (child.attributes.some((a) => a.kind === "EachDirective")) {
729
- out.push(this.lowerEach(child));
730
- continue;
731
- }
732
750
  }
733
751
  const node = this.lowerNode(child);
734
752
  if (node) out.push(node);
@@ -770,13 +788,16 @@ var Lowerer = class {
770
788
  node.props.unshift({ name: "component", kind: "attr", expr: `() => ${paren(this.hole(isAttr.value.hole).code, "arrowBody")}` });
771
789
  return node;
772
790
  }
791
+ const each = this.lowerEach(el);
792
+ const children = each ? [each] : this.lowerChildren(el.children);
793
+ if (el.tag === CONTAINER) return children.length === 1 ? children[0] : { kind: "fragment", children };
773
794
  const at = this.tagAt(el);
774
795
  return {
775
796
  kind: "element",
776
797
  tag: el.tag,
777
798
  svg: el.namespace === "svg",
778
799
  props: this.lowerAttributes(el.attributes),
779
- children: this.lowerChildren(el.children),
800
+ children,
780
801
  static: false,
781
802
  ...at ? { at } : {}
782
803
  };
@@ -848,10 +869,14 @@ var Lowerer = class {
848
869
  const at = this.tagAt(el);
849
870
  return { kind: "component", name: "Show", props, children: [child], ...at ? { at } : {}, ...cond.at ? { bindAt: cond.at } : {} };
850
871
  }
851
- /** `<el each=${items} key?>${item => …}</el>` → `<For each>{item => <el>…</el>}</For>`. */
872
+ /**
873
+ * `<el each=${items} key?>${item => …}</el>` → `<el><For each>{item => …}</For></el>`: the
874
+ * content is repeated per item. Content that is not a `${item => …}` arrow is repeated as
875
+ * it is. Null when the element has no `each`.
876
+ */
852
877
  lowerEach(el) {
853
878
  const eachDir = el.attributes.find((a) => a.kind === "EachDirective");
854
- if (!eachDir || eachDir.kind !== "EachDirective") return this.lowerNode(el);
879
+ if (!eachDir || eachDir.kind !== "EachDirective") return null;
855
880
  const each = this.hole(eachDir.hole);
856
881
  const props = [{ name: "each", kind: "attr", expr: each.code, reactive: each.reactive, ...each.at ? { at: each.at } : {} }];
857
882
  if (eachDir.key) {
@@ -861,12 +886,12 @@ var Lowerer = class {
861
886
  const arrow = this.itemArrow(el);
862
887
  let childrenCode;
863
888
  if (arrow) {
864
- const bodyIR = { kind: "expr", code: arrow.body, reactive: arrow.bodyReactive };
865
- const repeated = this.rebuildWithChildren(el, [bodyIR]);
866
- childrenCode = `(${arrow.params.join(", ")}) => ${paren(this.emit(repeated), "arrowBody")}`;
889
+ const body = arrow.bodyReactive ? `() => ${paren(arrow.body, "arrowBody")}` : paren(arrow.body, "arrowBody");
890
+ childrenCode = `(${arrow.params.join(", ")}) => ${body}`;
867
891
  } else {
868
- const repeated = this.stripAndLower(el, (a) => a.kind === "EachDirective");
869
- childrenCode = `() => ${paren(this.emit(repeated), "arrowBody")}`;
892
+ const content = this.lowerChildren(el.children);
893
+ const ir = content.length === 1 ? content[0] : { kind: "fragment", children: content };
894
+ childrenCode = `() => ${paren(this.emit(ir), "arrowBody")}`;
870
895
  }
871
896
  const children = [{ kind: "expr", code: childrenCode, reactive: false }];
872
897
  const at = this.tagAt(el);
@@ -883,25 +908,6 @@ var Lowerer = class {
883
908
  const clone = { ...el, attributes: el.attributes.filter((a) => !drop(a)) };
884
909
  return clone.kind === "Element" ? this.lowerElement(clone) : this.lowerComponent(clone);
885
910
  }
886
- /** Same element (minus each/key) but with explicit IR children. */
887
- rebuildWithChildren(el, children) {
888
- const props = this.lowerAttributes(
889
- el.attributes.filter((a) => a.kind !== "EachDirective")
890
- );
891
- if (el.kind === "Element") {
892
- const at = this.tagAt(el);
893
- return {
894
- kind: "element",
895
- tag: el.tag,
896
- svg: el.namespace === "svg",
897
- props,
898
- children,
899
- static: false,
900
- ...at ? { at } : {}
901
- };
902
- }
903
- return { kind: "component", name: el.tag, props, children };
904
- }
905
911
  // --- attributes ---------------------------------------------------------
906
912
  lowerAttributes(attrs) {
907
913
  const props = [];
@@ -1440,7 +1446,8 @@ function componentGlobals(rules, options = {}) {
1440
1446
  builtins = true,
1441
1447
  intrinsics = true,
1442
1448
  generator = "@fluixi/compiler",
1443
- specifier = (module2) => module2
1449
+ specifier = (module2) => module2,
1450
+ candidates = []
1444
1451
  } = options;
1445
1452
  const all = [...rules];
1446
1453
  if (builtins) {
@@ -1452,7 +1459,8 @@ function componentGlobals(rules, options = {}) {
1452
1459
  const shadowedByDom = [];
1453
1460
  const unavailable = [];
1454
1461
  const resolved = /* @__PURE__ */ new Map();
1455
- for (const name of [...resolver.names()].sort()) {
1462
+ const names = /* @__PURE__ */ new Set([...resolver.names(), ...[...candidates].filter((n) => /^[A-Za-z_$][\w$]*$/.test(n))]);
1463
+ for (const name of [...names].sort()) {
1456
1464
  const target = resolver.resolve(name);
1457
1465
  if (!target) continue;
1458
1466
  const pkg = packageOf(target.module);
@@ -1463,7 +1471,11 @@ function componentGlobals(rules, options = {}) {
1463
1471
  resolved.set(name, target);
1464
1472
  }
1465
1473
  }
1466
- const intrinsicModules = intrinsics ? [...new Set(Object.values(INTRINSICS).map((i) => i.module))] : [];
1474
+ const usableIntrinsics = intrinsics ? Object.entries(INTRINSICS).filter(([, i]) => {
1475
+ const pkg = packageOf(i.module);
1476
+ return !available || !pkg || available.has(pkg);
1477
+ }) : [];
1478
+ const intrinsicModules = [...new Set(usableIntrinsics.map(([, i]) => i.module))];
1467
1479
  const modules = [
1468
1480
  .../* @__PURE__ */ new Set([...declared.map((name) => resolved.get(name).module), ...intrinsicModules])
1469
1481
  ].sort();
@@ -1483,15 +1495,18 @@ function componentGlobals(rules, options = {}) {
1483
1495
  return `// import { ${n} } from '${module2 ? specifier(module2) : ""}';
1484
1496
  `;
1485
1497
  }).join("") : "";
1486
- const intrinsicLines = intrinsics ? Object.entries(INTRINSICS).map(
1498
+ const intrinsicLines = usableIntrinsics.map(
1487
1499
  ([name, intrinsic]) => ` const ${name}: typeof ${namespaceAlias(intrinsic.module)}.${intrinsic.export};`
1488
- ) : [];
1500
+ );
1489
1501
  const patternRules = rules.filter((rule) => "match" in rule).length;
1490
- const patternNote = patternRules > 0 ? `//
1502
+ const patternNote = patternRules === 0 ? "" : options.candidates ? `//
1503
+ // ${patternRules} pattern rule${patternRules === 1 ? "" : "s"}: declared for the files the module path
1504
+ // points at. A name with no such file still resolves at build time; declare it by hand.
1505
+ ` : `//
1491
1506
  // ${patternRules} pattern rule${patternRules === 1 ? "" : "s"} cannot be declared:
1492
1507
  // a pattern matches unboundedly many names, so there is no list to emit. Those
1493
1508
  // tags still resolve at build time; declare them by hand if the editor needs them.
1494
- ` : "";
1509
+ `;
1495
1510
  const source = `// GENERATED by ${generator}, do not edit.
1496
1511
  //
1497
1512
  // The components \`resolve\` supplies without an import. This describes what the
@@ -1550,6 +1565,30 @@ function projectDependencies(root) {
1550
1565
  return void 0;
1551
1566
  }
1552
1567
  }
1568
+ function patternCandidates(rules, root) {
1569
+ const out = [];
1570
+ for (const rule of rules) {
1571
+ if (!("match" in rule) || !rule.module.startsWith("/")) continue;
1572
+ const dir = (0, import_node_path.dirname)(rule.module);
1573
+ const file = (0, import_node_path.basename)(rule.module);
1574
+ const group = file.match(/\$\d+/);
1575
+ if (dir.includes("$") || !group || file.indexOf("$", group.index + 1) !== -1) continue;
1576
+ const before = file.slice(0, group.index);
1577
+ const after = file.slice(group.index + group[0].length);
1578
+ let entries;
1579
+ try {
1580
+ entries = (0, import_node_fs.readdirSync)((0, import_node_path.resolve)(root, dir.slice(1)));
1581
+ } catch {
1582
+ continue;
1583
+ }
1584
+ for (const entry of entries) {
1585
+ if (entry.length > before.length + after.length && entry.startsWith(before) && entry.endsWith(after)) {
1586
+ out.push(entry.slice(before.length, entry.length - after.length));
1587
+ }
1588
+ }
1589
+ }
1590
+ return out;
1591
+ }
1553
1592
  function writeComponentGlobals(options, root) {
1554
1593
  if (options.globals === false) return { status: "skipped", reason: "disabled" };
1555
1594
  const rules = options.resolve ?? [];
@@ -1565,6 +1604,7 @@ function writeComponentGlobals(options, root) {
1565
1604
  }
1566
1605
  const { source, declared } = componentGlobals(rules, {
1567
1606
  available: projectDependencies(root),
1607
+ candidates: patternCandidates(rules, root),
1568
1608
  specifier: (module2) => forTypeScript(module2, (0, import_node_path.dirname)(target), root)
1569
1609
  });
1570
1610
  if (existing === source) {
package/dist/index.mjs CHANGED
@@ -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/analyze/reactive-expr.ts
4
4
  var STABLE_MEMBER = "children";
@@ -281,6 +281,7 @@ var INTRINSICS = {
281
281
  $effect: { export: "effect", module: REACTIVE_MODULE, returns: "effect" },
282
282
  $store: { export: "store", module: "@fluixi/reactive/store", returns: "store-handle" },
283
283
  $resource: { export: "resource", module: REACTIVE_MODULE, returns: "resource-handle" },
284
+ $cachedResource: { export: "cachedResource", module: "@fluixi/reactive/cached-resource", returns: "resource-handle" },
284
285
  // No short runtime form for these yet, so the intrinsic stands for the `create*`
285
286
  // name. Still worth having: no import to write, and when a shorter form is
286
287
  // designed, the change is this line rather than a compiler pass.
@@ -290,7 +291,22 @@ var INTRINSICS = {
290
291
  // does. `untrack` suppresses dependency collection, which is exactly the kind of thing
291
292
  // dependency analysis must not guess at.
292
293
  $untrack: { export: "untrack", module: REACTIVE_MODULE, returns: "plain" },
293
- $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" }
294
+ $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" },
295
+ // Scopes, scheduling and lifecycle: no value to classify, but the same authoring promise,
296
+ // no import to write. `$cleanup` registers a callback on the current owner (`onCleanup`).
297
+ $batch: { export: "batch", module: REACTIVE_MODULE, returns: "plain" },
298
+ $cleanup: { export: "onCleanup", module: REACTIVE_MODULE, returns: "plain" },
299
+ $root: { export: "root", module: REACTIVE_MODULE, returns: "plain" },
300
+ $context: { export: "context", module: REACTIVE_MODULE, returns: "plain" },
301
+ $renderEffect: { export: "renderEffect", module: REACTIVE_MODULE, returns: "effect" },
302
+ $computed: { export: "computed", module: REACTIVE_MODULE, returns: "effect" },
303
+ // Storage for where the code runs, and a signal kept in it. `$persisted` is a signal
304
+ // handle as far as reads go: the same accessor with `set` on it.
305
+ $storage: { export: "useStorage", module: "@fluixi/core/storage", returns: "plain" },
306
+ $asyncStorage: { export: "useAsyncStorage", module: "@fluixi/core/storage", returns: "plain" },
307
+ $persisted: { export: "persisted", module: "@fluixi/core/storage", returns: "signal-handle" },
308
+ // An environment variable, public or server-only; the browser build refuses a secret.
309
+ $env: { export: "env", module: "@fluixi/start/env", returns: "plain" }
294
310
  };
295
311
  var INTRINSIC_CALL = new RegExp(`\\$(?:${Object.keys(INTRINSICS).map((n) => n.slice(1)).join("|")})\\s*\\(`);
296
312
 
@@ -466,13 +482,16 @@ function emitComponent(name, props, children, used, templates, at) {
466
482
  used.add("createComponent");
467
483
  const spreads = props.filter((p) => p.kind === "spread");
468
484
  const normal = props.filter((p) => p.kind !== "spread");
469
- const entries = normal.map(
470
- (p) => `get ${propKey(p.name)}() { return ${componentPropValue(p)}; }`
471
- );
485
+ const bodies = normal.map((p) => [p.name, componentPropValue(p)]);
472
486
  if (children.length > 0) {
473
- entries.push(`get children() { return ${emitChildren(children, used, templates)}; }`);
487
+ bodies.push(["children", emitChildren(children, used, templates)]);
488
+ }
489
+ let obj = `{ ${bodies.map(([name2, body]) => `get ${propKey(name2)}() { return ${body}; }`).join(", ")} }`;
490
+ if (bodies.some(([, body]) => /\bthis\b/.test(body))) {
491
+ const thunks = bodies.map(([name2, body]) => `${JSON.stringify(name2)}: () => ${body}`);
492
+ const getters = bodies.map(([name2]) => `get ${propKey(name2)}() { return _$t[${JSON.stringify(name2)}](); }`);
493
+ obj = `((_$t) => ({ ${getters.join(", ")} }))({ ${thunks.join(", ")} })`;
474
494
  }
475
- let obj = `{ ${entries.join(", ")} }`;
476
495
  if (spreads.length > 0) {
477
496
  used.add("mergeProps");
478
497
  obj = `mergeProps(${spreads.map((s) => s.expr).join(", ")}, ${obj})`;
@@ -554,7 +573,8 @@ function emitNode(node, used, templates) {
554
573
  } else {
555
574
  used.add("spread");
556
575
  const svgFlag = node.svg ? ", isSVG: true" : "";
557
- const customFlag = node.tag.includes("-") ? ", isCustomElement: true" : "";
576
+ const accessors = node.props.filter((p) => p.kind !== "spread" && p.expr !== void 0 && p.reactive && p.name !== "ref").map((p) => p.name);
577
+ const customFlag = node.tag.includes("-") ? `, isCustomElement: true, accessors: ${JSON.stringify(accessors)}` : "";
558
578
  parts.push(
559
579
  `spread({ element: ${el}, props: ${emitProps(node.props, null, used)}${svgFlag}${customFlag} });`
560
580
  );
@@ -625,6 +645,7 @@ var DELEGATED = /* @__PURE__ */ new Set([
625
645
  function escapeTemplate(s) {
626
646
  return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
627
647
  }
648
+ var CONTAINER = "container";
628
649
  function capitalize(s) {
629
650
  return s.charAt(0).toUpperCase() + s.slice(1);
630
651
  }
@@ -679,10 +700,6 @@ var Lowerer = class {
679
700
  if (hasElse) i = elseIdx;
680
701
  continue;
681
702
  }
682
- if (child.attributes.some((a) => a.kind === "EachDirective")) {
683
- out.push(this.lowerEach(child));
684
- continue;
685
- }
686
703
  }
687
704
  const node = this.lowerNode(child);
688
705
  if (node) out.push(node);
@@ -724,13 +741,16 @@ var Lowerer = class {
724
741
  node.props.unshift({ name: "component", kind: "attr", expr: `() => ${paren(this.hole(isAttr.value.hole).code, "arrowBody")}` });
725
742
  return node;
726
743
  }
744
+ const each = this.lowerEach(el);
745
+ const children = each ? [each] : this.lowerChildren(el.children);
746
+ if (el.tag === CONTAINER) return children.length === 1 ? children[0] : { kind: "fragment", children };
727
747
  const at = this.tagAt(el);
728
748
  return {
729
749
  kind: "element",
730
750
  tag: el.tag,
731
751
  svg: el.namespace === "svg",
732
752
  props: this.lowerAttributes(el.attributes),
733
- children: this.lowerChildren(el.children),
753
+ children,
734
754
  static: false,
735
755
  ...at ? { at } : {}
736
756
  };
@@ -802,10 +822,14 @@ var Lowerer = class {
802
822
  const at = this.tagAt(el);
803
823
  return { kind: "component", name: "Show", props, children: [child], ...at ? { at } : {}, ...cond.at ? { bindAt: cond.at } : {} };
804
824
  }
805
- /** `<el each=${items} key?>${item => …}</el>` → `<For each>{item => <el>…</el>}</For>`. */
825
+ /**
826
+ * `<el each=${items} key?>${item => …}</el>` → `<el><For each>{item => …}</For></el>`: the
827
+ * content is repeated per item. Content that is not a `${item => …}` arrow is repeated as
828
+ * it is. Null when the element has no `each`.
829
+ */
806
830
  lowerEach(el) {
807
831
  const eachDir = el.attributes.find((a) => a.kind === "EachDirective");
808
- if (!eachDir || eachDir.kind !== "EachDirective") return this.lowerNode(el);
832
+ if (!eachDir || eachDir.kind !== "EachDirective") return null;
809
833
  const each = this.hole(eachDir.hole);
810
834
  const props = [{ name: "each", kind: "attr", expr: each.code, reactive: each.reactive, ...each.at ? { at: each.at } : {} }];
811
835
  if (eachDir.key) {
@@ -815,12 +839,12 @@ var Lowerer = class {
815
839
  const arrow = this.itemArrow(el);
816
840
  let childrenCode;
817
841
  if (arrow) {
818
- const bodyIR = { kind: "expr", code: arrow.body, reactive: arrow.bodyReactive };
819
- const repeated = this.rebuildWithChildren(el, [bodyIR]);
820
- childrenCode = `(${arrow.params.join(", ")}) => ${paren(this.emit(repeated), "arrowBody")}`;
842
+ const body = arrow.bodyReactive ? `() => ${paren(arrow.body, "arrowBody")}` : paren(arrow.body, "arrowBody");
843
+ childrenCode = `(${arrow.params.join(", ")}) => ${body}`;
821
844
  } else {
822
- const repeated = this.stripAndLower(el, (a) => a.kind === "EachDirective");
823
- childrenCode = `() => ${paren(this.emit(repeated), "arrowBody")}`;
845
+ const content = this.lowerChildren(el.children);
846
+ const ir = content.length === 1 ? content[0] : { kind: "fragment", children: content };
847
+ childrenCode = `() => ${paren(this.emit(ir), "arrowBody")}`;
824
848
  }
825
849
  const children = [{ kind: "expr", code: childrenCode, reactive: false }];
826
850
  const at = this.tagAt(el);
@@ -837,25 +861,6 @@ var Lowerer = class {
837
861
  const clone = { ...el, attributes: el.attributes.filter((a) => !drop(a)) };
838
862
  return clone.kind === "Element" ? this.lowerElement(clone) : this.lowerComponent(clone);
839
863
  }
840
- /** Same element (minus each/key) but with explicit IR children. */
841
- rebuildWithChildren(el, children) {
842
- const props = this.lowerAttributes(
843
- el.attributes.filter((a) => a.kind !== "EachDirective")
844
- );
845
- if (el.kind === "Element") {
846
- const at = this.tagAt(el);
847
- return {
848
- kind: "element",
849
- tag: el.tag,
850
- svg: el.namespace === "svg",
851
- props,
852
- children,
853
- static: false,
854
- ...at ? { at } : {}
855
- };
856
- }
857
- return { kind: "component", name: el.tag, props, children };
858
- }
859
864
  // --- attributes ---------------------------------------------------------
860
865
  lowerAttributes(attrs) {
861
866
  const props = [];
@@ -1394,7 +1399,8 @@ function componentGlobals(rules, options = {}) {
1394
1399
  builtins = true,
1395
1400
  intrinsics = true,
1396
1401
  generator = "@fluixi/compiler",
1397
- specifier = (module) => module
1402
+ specifier = (module) => module,
1403
+ candidates = []
1398
1404
  } = options;
1399
1405
  const all = [...rules];
1400
1406
  if (builtins) {
@@ -1406,7 +1412,8 @@ function componentGlobals(rules, options = {}) {
1406
1412
  const shadowedByDom = [];
1407
1413
  const unavailable = [];
1408
1414
  const resolved = /* @__PURE__ */ new Map();
1409
- for (const name of [...resolver.names()].sort()) {
1415
+ const names = /* @__PURE__ */ new Set([...resolver.names(), ...[...candidates].filter((n) => /^[A-Za-z_$][\w$]*$/.test(n))]);
1416
+ for (const name of [...names].sort()) {
1410
1417
  const target = resolver.resolve(name);
1411
1418
  if (!target) continue;
1412
1419
  const pkg = packageOf(target.module);
@@ -1417,7 +1424,11 @@ function componentGlobals(rules, options = {}) {
1417
1424
  resolved.set(name, target);
1418
1425
  }
1419
1426
  }
1420
- const intrinsicModules = intrinsics ? [...new Set(Object.values(INTRINSICS).map((i) => i.module))] : [];
1427
+ const usableIntrinsics = intrinsics ? Object.entries(INTRINSICS).filter(([, i]) => {
1428
+ const pkg = packageOf(i.module);
1429
+ return !available || !pkg || available.has(pkg);
1430
+ }) : [];
1431
+ const intrinsicModules = [...new Set(usableIntrinsics.map(([, i]) => i.module))];
1421
1432
  const modules = [
1422
1433
  .../* @__PURE__ */ new Set([...declared.map((name) => resolved.get(name).module), ...intrinsicModules])
1423
1434
  ].sort();
@@ -1437,15 +1448,18 @@ function componentGlobals(rules, options = {}) {
1437
1448
  return `// import { ${n} } from '${module ? specifier(module) : ""}';
1438
1449
  `;
1439
1450
  }).join("") : "";
1440
- const intrinsicLines = intrinsics ? Object.entries(INTRINSICS).map(
1451
+ const intrinsicLines = usableIntrinsics.map(
1441
1452
  ([name, intrinsic]) => ` const ${name}: typeof ${namespaceAlias(intrinsic.module)}.${intrinsic.export};`
1442
- ) : [];
1453
+ );
1443
1454
  const patternRules = rules.filter((rule) => "match" in rule).length;
1444
- const patternNote = patternRules > 0 ? `//
1455
+ const patternNote = patternRules === 0 ? "" : options.candidates ? `//
1456
+ // ${patternRules} pattern rule${patternRules === 1 ? "" : "s"}: declared for the files the module path
1457
+ // points at. A name with no such file still resolves at build time; declare it by hand.
1458
+ ` : `//
1445
1459
  // ${patternRules} pattern rule${patternRules === 1 ? "" : "s"} cannot be declared:
1446
1460
  // a pattern matches unboundedly many names, so there is no list to emit. Those
1447
1461
  // tags still resolve at build time; declare them by hand if the editor needs them.
1448
- ` : "";
1462
+ `;
1449
1463
  const source = `// GENERATED by ${generator}, do not edit.
1450
1464
  //
1451
1465
  // The components \`resolve\` supplies without an import. This describes what the
@@ -1461,8 +1475,8 @@ ${[...lines, ...intrinsicLines].join("\n")}
1461
1475
  }
1462
1476
 
1463
1477
  // src/resolve/write-globals.ts
1464
- import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
1465
- import { dirname, relative, sep, resolve as resolvePath } from "node:path";
1478
+ import { mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
1479
+ import { basename, dirname, relative, sep, resolve as resolvePath } from "node:path";
1466
1480
  var GENERATED_MARKER = "GENERATED by @fluixi/compiler";
1467
1481
  var DEFAULT_GLOBALS_PATH = "fluixi.d.ts";
1468
1482
  function coveredByInclude(root, target) {
@@ -1504,6 +1518,30 @@ function projectDependencies(root) {
1504
1518
  return void 0;
1505
1519
  }
1506
1520
  }
1521
+ function patternCandidates(rules, root) {
1522
+ const out = [];
1523
+ for (const rule of rules) {
1524
+ if (!("match" in rule) || !rule.module.startsWith("/")) continue;
1525
+ const dir = dirname(rule.module);
1526
+ const file = basename(rule.module);
1527
+ const group = file.match(/\$\d+/);
1528
+ if (dir.includes("$") || !group || file.indexOf("$", group.index + 1) !== -1) continue;
1529
+ const before = file.slice(0, group.index);
1530
+ const after = file.slice(group.index + group[0].length);
1531
+ let entries;
1532
+ try {
1533
+ entries = readdirSync(resolvePath(root, dir.slice(1)));
1534
+ } catch {
1535
+ continue;
1536
+ }
1537
+ for (const entry of entries) {
1538
+ if (entry.length > before.length + after.length && entry.startsWith(before) && entry.endsWith(after)) {
1539
+ out.push(entry.slice(before.length, entry.length - after.length));
1540
+ }
1541
+ }
1542
+ }
1543
+ return out;
1544
+ }
1507
1545
  function writeComponentGlobals(options, root) {
1508
1546
  if (options.globals === false) return { status: "skipped", reason: "disabled" };
1509
1547
  const rules = options.resolve ?? [];
@@ -1519,6 +1557,7 @@ function writeComponentGlobals(options, root) {
1519
1557
  }
1520
1558
  const { source, declared } = componentGlobals(rules, {
1521
1559
  available: projectDependencies(root),
1560
+ candidates: patternCandidates(rules, root),
1522
1561
  specifier: (module) => forTypeScript(module, dirname(target), root)
1523
1562
  });
1524
1563
  if (existing === source) {
@@ -1579,6 +1618,7 @@ export {
1579
1618
  imperativeBackend,
1580
1619
  isReactiveShape,
1581
1620
  lowerTemplate,
1621
+ patternCandidates,
1582
1622
  resolveComponentSources,
1583
1623
  routerComponents,
1584
1624
  walk,