@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
@@ -283,6 +283,7 @@ var INTRINSICS = {
283
283
  $effect: { export: "effect", module: REACTIVE_MODULE, returns: "effect" },
284
284
  $store: { export: "store", module: "@fluixi/reactive/store", returns: "store-handle" },
285
285
  $resource: { export: "resource", module: REACTIVE_MODULE, returns: "resource-handle" },
286
+ $cachedResource: { export: "cachedResource", module: "@fluixi/reactive/cached-resource", returns: "resource-handle" },
286
287
  // No short runtime form for these yet, so the intrinsic stands for the `create*`
287
288
  // name. Still worth having: no import to write, and when a shorter form is
288
289
  // designed, the change is this line rather than a compiler pass.
@@ -292,7 +293,22 @@ var INTRINSICS = {
292
293
  // does. `untrack` suppresses dependency collection, which is exactly the kind of thing
293
294
  // dependency analysis must not guess at.
294
295
  $untrack: { export: "untrack", module: REACTIVE_MODULE, returns: "plain" },
295
- $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" }
296
+ $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" },
297
+ // Scopes, scheduling and lifecycle: no value to classify, but the same authoring promise,
298
+ // no import to write. `$cleanup` registers a callback on the current owner (`onCleanup`).
299
+ $batch: { export: "batch", module: REACTIVE_MODULE, returns: "plain" },
300
+ $cleanup: { export: "onCleanup", module: REACTIVE_MODULE, returns: "plain" },
301
+ $root: { export: "root", module: REACTIVE_MODULE, returns: "plain" },
302
+ $context: { export: "context", module: REACTIVE_MODULE, returns: "plain" },
303
+ $renderEffect: { export: "renderEffect", module: REACTIVE_MODULE, returns: "effect" },
304
+ $computed: { export: "computed", module: REACTIVE_MODULE, returns: "effect" },
305
+ // Storage for where the code runs, and a signal kept in it. `$persisted` is a signal
306
+ // handle as far as reads go: the same accessor with `set` on it.
307
+ $storage: { export: "useStorage", module: "@fluixi/core/storage", returns: "plain" },
308
+ $asyncStorage: { export: "useAsyncStorage", module: "@fluixi/core/storage", returns: "plain" },
309
+ $persisted: { export: "persisted", module: "@fluixi/core/storage", returns: "signal-handle" },
310
+ // An environment variable, public or server-only; the browser build refuses a secret.
311
+ $env: { export: "env", module: "@fluixi/start/env", returns: "plain" }
296
312
  };
297
313
  var INTRINSIC_CALL = new RegExp(`\\$(?:${Object.keys(INTRINSICS).map((n) => n.slice(1)).join("|")})\\s*\\(`);
298
314
 
@@ -468,13 +484,16 @@ function emitComponent(name, props, children, used, templates, at) {
468
484
  used.add("createComponent");
469
485
  const spreads = props.filter((p) => p.kind === "spread");
470
486
  const normal = props.filter((p) => p.kind !== "spread");
471
- const entries = normal.map(
472
- (p) => `get ${propKey(p.name)}() { return ${componentPropValue(p)}; }`
473
- );
487
+ const bodies = normal.map((p) => [p.name, componentPropValue(p)]);
474
488
  if (children.length > 0) {
475
- entries.push(`get children() { return ${emitChildren(children, used, templates)}; }`);
489
+ bodies.push(["children", emitChildren(children, used, templates)]);
490
+ }
491
+ let obj = `{ ${bodies.map(([name2, body]) => `get ${propKey(name2)}() { return ${body}; }`).join(", ")} }`;
492
+ if (bodies.some(([, body]) => /\bthis\b/.test(body))) {
493
+ const thunks = bodies.map(([name2, body]) => `${JSON.stringify(name2)}: () => ${body}`);
494
+ const getters = bodies.map(([name2]) => `get ${propKey(name2)}() { return _$t[${JSON.stringify(name2)}](); }`);
495
+ obj = `((_$t) => ({ ${getters.join(", ")} }))({ ${thunks.join(", ")} })`;
476
496
  }
477
- let obj = `{ ${entries.join(", ")} }`;
478
497
  if (spreads.length > 0) {
479
498
  used.add("mergeProps");
480
499
  obj = `mergeProps(${spreads.map((s) => s.expr).join(", ")}, ${obj})`;
@@ -556,7 +575,8 @@ function emitNode(node, used, templates) {
556
575
  } else {
557
576
  used.add("spread");
558
577
  const svgFlag = node.svg ? ", isSVG: true" : "";
559
- const customFlag = node.tag.includes("-") ? ", isCustomElement: true" : "";
578
+ const accessors = node.props.filter((p) => p.kind !== "spread" && p.expr !== void 0 && p.reactive && p.name !== "ref").map((p) => p.name);
579
+ const customFlag = node.tag.includes("-") ? `, isCustomElement: true, accessors: ${JSON.stringify(accessors)}` : "";
560
580
  parts.push(
561
581
  `spread({ element: ${el}, props: ${emitProps(node.props, null, used)}${svgFlag}${customFlag} });`
562
582
  );
@@ -627,6 +647,7 @@ var DELEGATED = /* @__PURE__ */ new Set([
627
647
  function escapeTemplate(s) {
628
648
  return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
629
649
  }
650
+ var CONTAINER = "container";
630
651
  function capitalize(s) {
631
652
  return s.charAt(0).toUpperCase() + s.slice(1);
632
653
  }
@@ -681,10 +702,6 @@ var Lowerer = class {
681
702
  if (hasElse) i = elseIdx;
682
703
  continue;
683
704
  }
684
- if (child.attributes.some((a) => a.kind === "EachDirective")) {
685
- out.push(this.lowerEach(child));
686
- continue;
687
- }
688
705
  }
689
706
  const node = this.lowerNode(child);
690
707
  if (node) out.push(node);
@@ -726,13 +743,16 @@ var Lowerer = class {
726
743
  node.props.unshift({ name: "component", kind: "attr", expr: `() => ${paren(this.hole(isAttr.value.hole).code, "arrowBody")}` });
727
744
  return node;
728
745
  }
746
+ const each = this.lowerEach(el);
747
+ const children = each ? [each] : this.lowerChildren(el.children);
748
+ if (el.tag === CONTAINER) return children.length === 1 ? children[0] : { kind: "fragment", children };
729
749
  const at = this.tagAt(el);
730
750
  return {
731
751
  kind: "element",
732
752
  tag: el.tag,
733
753
  svg: el.namespace === "svg",
734
754
  props: this.lowerAttributes(el.attributes),
735
- children: this.lowerChildren(el.children),
755
+ children,
736
756
  static: false,
737
757
  ...at ? { at } : {}
738
758
  };
@@ -804,10 +824,14 @@ var Lowerer = class {
804
824
  const at = this.tagAt(el);
805
825
  return { kind: "component", name: "Show", props, children: [child], ...at ? { at } : {}, ...cond.at ? { bindAt: cond.at } : {} };
806
826
  }
807
- /** `<el each=${items} key?>${item => …}</el>` → `<For each>{item => <el>…</el>}</For>`. */
827
+ /**
828
+ * `<el each=${items} key?>${item => …}</el>` → `<el><For each>{item => …}</For></el>`: the
829
+ * content is repeated per item. Content that is not a `${item => …}` arrow is repeated as
830
+ * it is. Null when the element has no `each`.
831
+ */
808
832
  lowerEach(el) {
809
833
  const eachDir = el.attributes.find((a) => a.kind === "EachDirective");
810
- if (!eachDir || eachDir.kind !== "EachDirective") return this.lowerNode(el);
834
+ if (!eachDir || eachDir.kind !== "EachDirective") return null;
811
835
  const each = this.hole(eachDir.hole);
812
836
  const props = [{ name: "each", kind: "attr", expr: each.code, reactive: each.reactive, ...each.at ? { at: each.at } : {} }];
813
837
  if (eachDir.key) {
@@ -817,12 +841,12 @@ var Lowerer = class {
817
841
  const arrow = this.itemArrow(el);
818
842
  let childrenCode;
819
843
  if (arrow) {
820
- const bodyIR = { kind: "expr", code: arrow.body, reactive: arrow.bodyReactive };
821
- const repeated = this.rebuildWithChildren(el, [bodyIR]);
822
- childrenCode = `(${arrow.params.join(", ")}) => ${paren(this.emit(repeated), "arrowBody")}`;
844
+ const body = arrow.bodyReactive ? `() => ${paren(arrow.body, "arrowBody")}` : paren(arrow.body, "arrowBody");
845
+ childrenCode = `(${arrow.params.join(", ")}) => ${body}`;
823
846
  } else {
824
- const repeated = this.stripAndLower(el, (a) => a.kind === "EachDirective");
825
- childrenCode = `() => ${paren(this.emit(repeated), "arrowBody")}`;
847
+ const content = this.lowerChildren(el.children);
848
+ const ir = content.length === 1 ? content[0] : { kind: "fragment", children: content };
849
+ childrenCode = `() => ${paren(this.emit(ir), "arrowBody")}`;
826
850
  }
827
851
  const children = [{ kind: "expr", code: childrenCode, reactive: false }];
828
852
  const at = this.tagAt(el);
@@ -839,25 +863,6 @@ var Lowerer = class {
839
863
  const clone = { ...el, attributes: el.attributes.filter((a) => !drop(a)) };
840
864
  return clone.kind === "Element" ? this.lowerElement(clone) : this.lowerComponent(clone);
841
865
  }
842
- /** Same element (minus each/key) but with explicit IR children. */
843
- rebuildWithChildren(el, children) {
844
- const props = this.lowerAttributes(
845
- el.attributes.filter((a) => a.kind !== "EachDirective")
846
- );
847
- if (el.kind === "Element") {
848
- const at = this.tagAt(el);
849
- return {
850
- kind: "element",
851
- tag: el.tag,
852
- svg: el.namespace === "svg",
853
- props,
854
- children,
855
- static: false,
856
- ...at ? { at } : {}
857
- };
858
- }
859
- return { kind: "component", name: el.tag, props, children };
860
- }
861
866
  // --- attributes ---------------------------------------------------------
862
867
  lowerAttributes(attrs) {
863
868
  const props = [];
@@ -1 +1 @@
1
- {"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/lower/template.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAML,KAAK,KAAK,EAGX,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,EAAsB,MAAM,gBAAgB,CAAC;AAM/E;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,sFAAsF;IACtF,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,QAAQ,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,KAAK,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,OAAO,CAAA;KAAE,CAAC;IAClE,iFAAiF;IACjF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;;OAMG;IACH,EAAE,CAAC,EAAE,YAAY,CAAC;CACnB;AAmhBD;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,KAAK,EAAE,EACf,KAAK,EAAE,YAAY,EAAE,EACrB,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,EACjB,IAAI,GAAE;IAAE,GAAG,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CAAO,GACnH,MAAM,CAGR"}
1
+ {"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/lower/template.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAML,KAAK,KAAK,EAGX,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,EAAsB,MAAM,gBAAgB,CAAC;AAM/E;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,sFAAsF;IACtF,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,QAAQ,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,KAAK,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,OAAO,CAAA;KAAE,CAAC;IAClE,iFAAiF;IACjF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;;OAMG;IACH,EAAE,CAAC,EAAE,YAAY,CAAC;CACnB;AAwgBD;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,KAAK,EAAE,EACf,KAAK,EAAE,YAAY,EAAE,EACrB,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,EACjB,IAAI,GAAE;IAAE,GAAG,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CAAO,GACnH,MAAM,CAGR"}
@@ -19,6 +19,8 @@ const DELEGATED = new Set([
19
19
  function escapeTemplate(s) {
20
20
  return s.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${');
21
21
  }
22
+ /** The tag that carries directives and renders only its content. Never a custom element: no dash. */
23
+ const CONTAINER = 'container';
22
24
  function capitalize(s) {
23
25
  return s.charAt(0).toUpperCase() + s.slice(1);
24
26
  }
@@ -88,10 +90,6 @@ class Lowerer {
88
90
  i = elseIdx;
89
91
  continue;
90
92
  }
91
- if (child.attributes.some((a) => a.kind === 'EachDirective')) {
92
- out.push(this.lowerEach(child));
93
- continue;
94
- }
95
93
  }
96
94
  const node = this.lowerNode(child);
97
95
  if (node)
@@ -137,13 +135,19 @@ class Lowerer {
137
135
  node.props.unshift({ name: 'component', kind: 'attr', expr: `() => ${paren(this.hole(isAttr.value.hole).code, 'arrowBody')}` });
138
136
  return node;
139
137
  }
138
+ // `each` repeats the element's content, never the element: `<ul each>` is one list.
139
+ const each = this.lowerEach(el);
140
+ const children = each ? [each] : this.lowerChildren(el.children);
141
+ // A `<container>` is only what it holds. It carries the directives and is never rendered.
142
+ if (el.tag === CONTAINER)
143
+ return children.length === 1 ? children[0] : { kind: 'fragment', children };
140
144
  const at = this.tagAt(el);
141
145
  return {
142
146
  kind: 'element',
143
147
  tag: el.tag,
144
148
  svg: el.namespace === 'svg',
145
149
  props: this.lowerAttributes(el.attributes),
146
- children: this.lowerChildren(el.children),
150
+ children,
147
151
  static: false,
148
152
  ...(at ? { at } : {}),
149
153
  };
@@ -233,11 +237,15 @@ class Lowerer {
233
237
  const at = this.tagAt(el);
234
238
  return { kind: 'component', name: 'Show', props, children: [child], ...(at ? { at } : {}), ...(cond.at ? { bindAt: cond.at } : {}) };
235
239
  }
236
- /** `<el each=${items} key?>${item => …}</el>` → `<For each>{item => <el>…</el>}</For>`. */
240
+ /**
241
+ * `<el each=${items} key?>${item => …}</el>` → `<el><For each>{item => …}</For></el>`: the
242
+ * content is repeated per item. Content that is not a `${item => …}` arrow is repeated as
243
+ * it is. Null when the element has no `each`.
244
+ */
237
245
  lowerEach(el) {
238
246
  const eachDir = el.attributes.find((a) => a.kind === 'EachDirective');
239
247
  if (!eachDir || eachDir.kind !== 'EachDirective')
240
- return this.lowerNode(el);
248
+ return null;
241
249
  const each = this.hole(eachDir.hole);
242
250
  const props = [{ name: 'each', kind: 'attr', expr: each.code, reactive: each.reactive, ...(each.at ? { at: each.at } : {}) }];
243
251
  if (eachDir.key) {
@@ -248,17 +256,18 @@ class Lowerer {
248
256
  : this.hole(eachDir.key.hole).code;
249
257
  props.push({ name: 'by', kind: 'attr', expr: by });
250
258
  }
251
- // The element minus its each/key, repeated per item.
252
259
  const arrow = this.itemArrow(el);
253
260
  let childrenCode;
254
261
  if (arrow) {
255
- const bodyIR = { kind: 'expr', code: arrow.body, reactive: arrow.bodyReactive };
256
- const repeated = this.rebuildWithChildren(el, [bodyIR]);
257
- childrenCode = `(${arrow.params.join(', ')}) => ${paren(this.emit(repeated), 'arrowBody')}`;
262
+ // A body that reads signals, `${(it) => it.label + count()}`, stays live: it is handed
263
+ // back as an accessor for the runtime to bind, as it was inside the repeated element.
264
+ const body = arrow.bodyReactive ? `() => ${paren(arrow.body, 'arrowBody')}` : paren(arrow.body, 'arrowBody');
265
+ childrenCode = `(${arrow.params.join(', ')}) => ${body}`;
258
266
  }
259
267
  else {
260
- const repeated = this.stripAndLower(el, (a) => a.kind === 'EachDirective');
261
- childrenCode = `() => ${paren(this.emit(repeated), 'arrowBody')}`;
268
+ const content = this.lowerChildren(el.children);
269
+ const ir = content.length === 1 ? content[0] : { kind: 'fragment', children: content };
270
+ childrenCode = `() => ${paren(this.emit(ir), 'arrowBody')}`;
262
271
  }
263
272
  const children = [{ kind: 'expr', code: childrenCode, reactive: false }];
264
273
  const at = this.tagAt(el);
@@ -276,23 +285,6 @@ class Lowerer {
276
285
  const clone = { ...el, attributes: el.attributes.filter((a) => !drop(a)) };
277
286
  return clone.kind === 'Element' ? this.lowerElement(clone) : this.lowerComponent(clone);
278
287
  }
279
- /** Same element (minus each/key) but with explicit IR children. */
280
- rebuildWithChildren(el, children) {
281
- const props = this.lowerAttributes(el.attributes.filter((a) => a.kind !== 'EachDirective'));
282
- if (el.kind === 'Element') {
283
- const at = this.tagAt(el);
284
- return {
285
- kind: 'element',
286
- tag: el.tag,
287
- svg: el.namespace === 'svg',
288
- props,
289
- children,
290
- static: false,
291
- ...(at ? { at } : {}),
292
- };
293
- }
294
- return { kind: 'component', name: el.tag, props, children };
295
- }
296
288
  // --- attributes ---------------------------------------------------------
297
289
  lowerAttributes(attrs) {
298
290
  const props = [];
@@ -261,6 +261,7 @@ var INTRINSICS = {
261
261
  $effect: { export: "effect", module: REACTIVE_MODULE, returns: "effect" },
262
262
  $store: { export: "store", module: "@fluixi/reactive/store", returns: "store-handle" },
263
263
  $resource: { export: "resource", module: REACTIVE_MODULE, returns: "resource-handle" },
264
+ $cachedResource: { export: "cachedResource", module: "@fluixi/reactive/cached-resource", returns: "resource-handle" },
264
265
  // No short runtime form for these yet, so the intrinsic stands for the `create*`
265
266
  // name. Still worth having: no import to write, and when a shorter form is
266
267
  // designed, the change is this line rather than a compiler pass.
@@ -270,7 +271,22 @@ var INTRINSICS = {
270
271
  // does. `untrack` suppresses dependency collection, which is exactly the kind of thing
271
272
  // dependency analysis must not guess at.
272
273
  $untrack: { export: "untrack", module: REACTIVE_MODULE, returns: "plain" },
273
- $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" }
274
+ $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" },
275
+ // Scopes, scheduling and lifecycle: no value to classify, but the same authoring promise,
276
+ // no import to write. `$cleanup` registers a callback on the current owner (`onCleanup`).
277
+ $batch: { export: "batch", module: REACTIVE_MODULE, returns: "plain" },
278
+ $cleanup: { export: "onCleanup", module: REACTIVE_MODULE, returns: "plain" },
279
+ $root: { export: "root", module: REACTIVE_MODULE, returns: "plain" },
280
+ $context: { export: "context", module: REACTIVE_MODULE, returns: "plain" },
281
+ $renderEffect: { export: "renderEffect", module: REACTIVE_MODULE, returns: "effect" },
282
+ $computed: { export: "computed", module: REACTIVE_MODULE, returns: "effect" },
283
+ // Storage for where the code runs, and a signal kept in it. `$persisted` is a signal
284
+ // handle as far as reads go: the same accessor with `set` on it.
285
+ $storage: { export: "useStorage", module: "@fluixi/core/storage", returns: "plain" },
286
+ $asyncStorage: { export: "useAsyncStorage", module: "@fluixi/core/storage", returns: "plain" },
287
+ $persisted: { export: "persisted", module: "@fluixi/core/storage", returns: "signal-handle" },
288
+ // An environment variable, public or server-only; the browser build refuses a secret.
289
+ $env: { export: "env", module: "@fluixi/start/env", returns: "plain" }
274
290
  };
275
291
  var INTRINSIC_CALL = new RegExp(`\\$(?:${Object.keys(INTRINSICS).map((n) => n.slice(1)).join("|")})\\s*\\(`);
276
292
 
@@ -446,13 +462,16 @@ function emitComponent(name, props, children, used, templates, at) {
446
462
  used.add("createComponent");
447
463
  const spreads = props.filter((p) => p.kind === "spread");
448
464
  const normal = props.filter((p) => p.kind !== "spread");
449
- const entries = normal.map(
450
- (p) => `get ${propKey(p.name)}() { return ${componentPropValue(p)}; }`
451
- );
465
+ const bodies = normal.map((p) => [p.name, componentPropValue(p)]);
452
466
  if (children.length > 0) {
453
- entries.push(`get children() { return ${emitChildren(children, used, templates)}; }`);
467
+ bodies.push(["children", emitChildren(children, used, templates)]);
468
+ }
469
+ let obj = `{ ${bodies.map(([name2, body]) => `get ${propKey(name2)}() { return ${body}; }`).join(", ")} }`;
470
+ if (bodies.some(([, body]) => /\bthis\b/.test(body))) {
471
+ const thunks = bodies.map(([name2, body]) => `${JSON.stringify(name2)}: () => ${body}`);
472
+ const getters = bodies.map(([name2]) => `get ${propKey(name2)}() { return _$t[${JSON.stringify(name2)}](); }`);
473
+ obj = `((_$t) => ({ ${getters.join(", ")} }))({ ${thunks.join(", ")} })`;
454
474
  }
455
- let obj = `{ ${entries.join(", ")} }`;
456
475
  if (spreads.length > 0) {
457
476
  used.add("mergeProps");
458
477
  obj = `mergeProps(${spreads.map((s) => s.expr).join(", ")}, ${obj})`;
@@ -534,7 +553,8 @@ function emitNode(node, used, templates) {
534
553
  } else {
535
554
  used.add("spread");
536
555
  const svgFlag = node.svg ? ", isSVG: true" : "";
537
- const customFlag = node.tag.includes("-") ? ", isCustomElement: true" : "";
556
+ const accessors = node.props.filter((p) => p.kind !== "spread" && p.expr !== void 0 && p.reactive && p.name !== "ref").map((p) => p.name);
557
+ const customFlag = node.tag.includes("-") ? `, isCustomElement: true, accessors: ${JSON.stringify(accessors)}` : "";
538
558
  parts.push(
539
559
  `spread({ element: ${el}, props: ${emitProps(node.props, null, used)}${svgFlag}${customFlag} });`
540
560
  );
@@ -605,6 +625,7 @@ var DELEGATED = /* @__PURE__ */ new Set([
605
625
  function escapeTemplate(s) {
606
626
  return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
607
627
  }
628
+ var CONTAINER = "container";
608
629
  function capitalize(s) {
609
630
  return s.charAt(0).toUpperCase() + s.slice(1);
610
631
  }
@@ -659,10 +680,6 @@ var Lowerer = class {
659
680
  if (hasElse) i = elseIdx;
660
681
  continue;
661
682
  }
662
- if (child.attributes.some((a) => a.kind === "EachDirective")) {
663
- out.push(this.lowerEach(child));
664
- continue;
665
- }
666
683
  }
667
684
  const node = this.lowerNode(child);
668
685
  if (node) out.push(node);
@@ -704,13 +721,16 @@ var Lowerer = class {
704
721
  node.props.unshift({ name: "component", kind: "attr", expr: `() => ${paren(this.hole(isAttr.value.hole).code, "arrowBody")}` });
705
722
  return node;
706
723
  }
724
+ const each = this.lowerEach(el);
725
+ const children = each ? [each] : this.lowerChildren(el.children);
726
+ if (el.tag === CONTAINER) return children.length === 1 ? children[0] : { kind: "fragment", children };
707
727
  const at = this.tagAt(el);
708
728
  return {
709
729
  kind: "element",
710
730
  tag: el.tag,
711
731
  svg: el.namespace === "svg",
712
732
  props: this.lowerAttributes(el.attributes),
713
- children: this.lowerChildren(el.children),
733
+ children,
714
734
  static: false,
715
735
  ...at ? { at } : {}
716
736
  };
@@ -782,10 +802,14 @@ var Lowerer = class {
782
802
  const at = this.tagAt(el);
783
803
  return { kind: "component", name: "Show", props, children: [child], ...at ? { at } : {}, ...cond.at ? { bindAt: cond.at } : {} };
784
804
  }
785
- /** `<el each=${items} key?>${item => …}</el>` → `<For each>{item => <el>…</el>}</For>`. */
805
+ /**
806
+ * `<el each=${items} key?>${item => …}</el>` → `<el><For each>{item => …}</For></el>`: the
807
+ * content is repeated per item. Content that is not a `${item => …}` arrow is repeated as
808
+ * it is. Null when the element has no `each`.
809
+ */
786
810
  lowerEach(el) {
787
811
  const eachDir = el.attributes.find((a) => a.kind === "EachDirective");
788
- if (!eachDir || eachDir.kind !== "EachDirective") return this.lowerNode(el);
812
+ if (!eachDir || eachDir.kind !== "EachDirective") return null;
789
813
  const each = this.hole(eachDir.hole);
790
814
  const props = [{ name: "each", kind: "attr", expr: each.code, reactive: each.reactive, ...each.at ? { at: each.at } : {} }];
791
815
  if (eachDir.key) {
@@ -795,12 +819,12 @@ var Lowerer = class {
795
819
  const arrow = this.itemArrow(el);
796
820
  let childrenCode;
797
821
  if (arrow) {
798
- const bodyIR = { kind: "expr", code: arrow.body, reactive: arrow.bodyReactive };
799
- const repeated = this.rebuildWithChildren(el, [bodyIR]);
800
- childrenCode = `(${arrow.params.join(", ")}) => ${paren(this.emit(repeated), "arrowBody")}`;
822
+ const body = arrow.bodyReactive ? `() => ${paren(arrow.body, "arrowBody")}` : paren(arrow.body, "arrowBody");
823
+ childrenCode = `(${arrow.params.join(", ")}) => ${body}`;
801
824
  } else {
802
- const repeated = this.stripAndLower(el, (a) => a.kind === "EachDirective");
803
- childrenCode = `() => ${paren(this.emit(repeated), "arrowBody")}`;
825
+ const content = this.lowerChildren(el.children);
826
+ const ir = content.length === 1 ? content[0] : { kind: "fragment", children: content };
827
+ childrenCode = `() => ${paren(this.emit(ir), "arrowBody")}`;
804
828
  }
805
829
  const children = [{ kind: "expr", code: childrenCode, reactive: false }];
806
830
  const at = this.tagAt(el);
@@ -817,25 +841,6 @@ var Lowerer = class {
817
841
  const clone = { ...el, attributes: el.attributes.filter((a) => !drop(a)) };
818
842
  return clone.kind === "Element" ? this.lowerElement(clone) : this.lowerComponent(clone);
819
843
  }
820
- /** Same element (minus each/key) but with explicit IR children. */
821
- rebuildWithChildren(el, children) {
822
- const props = this.lowerAttributes(
823
- el.attributes.filter((a) => a.kind !== "EachDirective")
824
- );
825
- if (el.kind === "Element") {
826
- const at = this.tagAt(el);
827
- return {
828
- kind: "element",
829
- tag: el.tag,
830
- svg: el.namespace === "svg",
831
- props,
832
- children,
833
- static: false,
834
- ...at ? { at } : {}
835
- };
836
- }
837
- return { kind: "component", name: el.tag, props, children };
838
- }
839
844
  // --- attributes ---------------------------------------------------------
840
845
  lowerAttributes(attrs) {
841
846
  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
  "use strict";
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -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/resolve/builtins.ts
4
4
  var DOM_CONTROL_FLOW = [
@@ -124,6 +124,7 @@ var INTRINSICS = {
124
124
  $effect: { export: "effect", module: REACTIVE_MODULE, returns: "effect" },
125
125
  $store: { export: "store", module: "@fluixi/reactive/store", returns: "store-handle" },
126
126
  $resource: { export: "resource", module: REACTIVE_MODULE, returns: "resource-handle" },
127
+ $cachedResource: { export: "cachedResource", module: "@fluixi/reactive/cached-resource", returns: "resource-handle" },
127
128
  // No short runtime form for these yet, so the intrinsic stands for the `create*`
128
129
  // name. Still worth having: no import to write, and when a shorter form is
129
130
  // designed, the change is this line rather than a compiler pass.
@@ -133,7 +134,22 @@ var INTRINSICS = {
133
134
  // does. `untrack` suppresses dependency collection, which is exactly the kind of thing
134
135
  // dependency analysis must not guess at.
135
136
  $untrack: { export: "untrack", module: REACTIVE_MODULE, returns: "plain" },
136
- $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" }
137
+ $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" },
138
+ // Scopes, scheduling and lifecycle: no value to classify, but the same authoring promise,
139
+ // no import to write. `$cleanup` registers a callback on the current owner (`onCleanup`).
140
+ $batch: { export: "batch", module: REACTIVE_MODULE, returns: "plain" },
141
+ $cleanup: { export: "onCleanup", module: REACTIVE_MODULE, returns: "plain" },
142
+ $root: { export: "root", module: REACTIVE_MODULE, returns: "plain" },
143
+ $context: { export: "context", module: REACTIVE_MODULE, returns: "plain" },
144
+ $renderEffect: { export: "renderEffect", module: REACTIVE_MODULE, returns: "effect" },
145
+ $computed: { export: "computed", module: REACTIVE_MODULE, returns: "effect" },
146
+ // Storage for where the code runs, and a signal kept in it. `$persisted` is a signal
147
+ // handle as far as reads go: the same accessor with `set` on it.
148
+ $storage: { export: "useStorage", module: "@fluixi/core/storage", returns: "plain" },
149
+ $asyncStorage: { export: "useAsyncStorage", module: "@fluixi/core/storage", returns: "plain" },
150
+ $persisted: { export: "persisted", module: "@fluixi/core/storage", returns: "signal-handle" },
151
+ // An environment variable, public or server-only; the browser build refuses a secret.
152
+ $env: { export: "env", module: "@fluixi/start/env", returns: "plain" }
137
153
  };
138
154
  var INTRINSIC_CALL = new RegExp(`\\$(?:${Object.keys(INTRINSICS).map((n) => n.slice(1)).join("|")})\\s*\\(`);
139
155
 
@@ -175,7 +191,8 @@ function componentGlobals(rules, options = {}) {
175
191
  builtins = true,
176
192
  intrinsics = true,
177
193
  generator = "@fluixi/compiler",
178
- specifier = (module2) => module2
194
+ specifier = (module2) => module2,
195
+ candidates = []
179
196
  } = options;
180
197
  const all = [...rules];
181
198
  if (builtins) {
@@ -187,7 +204,8 @@ function componentGlobals(rules, options = {}) {
187
204
  const shadowedByDom = [];
188
205
  const unavailable = [];
189
206
  const resolved = /* @__PURE__ */ new Map();
190
- for (const name of [...resolver.names()].sort()) {
207
+ const names = /* @__PURE__ */ new Set([...resolver.names(), ...[...candidates].filter((n) => /^[A-Za-z_$][\w$]*$/.test(n))]);
208
+ for (const name of [...names].sort()) {
191
209
  const target = resolver.resolve(name);
192
210
  if (!target) continue;
193
211
  const pkg = packageOf(target.module);
@@ -198,7 +216,11 @@ function componentGlobals(rules, options = {}) {
198
216
  resolved.set(name, target);
199
217
  }
200
218
  }
201
- const intrinsicModules = intrinsics ? [...new Set(Object.values(INTRINSICS).map((i) => i.module))] : [];
219
+ const usableIntrinsics = intrinsics ? Object.entries(INTRINSICS).filter(([, i]) => {
220
+ const pkg = packageOf(i.module);
221
+ return !available || !pkg || available.has(pkg);
222
+ }) : [];
223
+ const intrinsicModules = [...new Set(usableIntrinsics.map(([, i]) => i.module))];
202
224
  const modules = [
203
225
  .../* @__PURE__ */ new Set([...declared.map((name) => resolved.get(name).module), ...intrinsicModules])
204
226
  ].sort();
@@ -218,15 +240,18 @@ function componentGlobals(rules, options = {}) {
218
240
  return `// import { ${n} } from '${module2 ? specifier(module2) : ""}';
219
241
  `;
220
242
  }).join("") : "";
221
- const intrinsicLines = intrinsics ? Object.entries(INTRINSICS).map(
243
+ const intrinsicLines = usableIntrinsics.map(
222
244
  ([name, intrinsic]) => ` const ${name}: typeof ${namespaceAlias(intrinsic.module)}.${intrinsic.export};`
223
- ) : [];
245
+ );
224
246
  const patternRules = rules.filter((rule) => "match" in rule).length;
225
- const patternNote = patternRules > 0 ? `//
247
+ const patternNote = patternRules === 0 ? "" : options.candidates ? `//
248
+ // ${patternRules} pattern rule${patternRules === 1 ? "" : "s"}: declared for the files the module path
249
+ // points at. A name with no such file still resolves at build time; declare it by hand.
250
+ ` : `//
226
251
  // ${patternRules} pattern rule${patternRules === 1 ? "" : "s"} cannot be declared:
227
252
  // a pattern matches unboundedly many names, so there is no list to emit. Those
228
253
  // tags still resolve at build time; declare them by hand if the editor needs them.
229
- ` : "";
254
+ `;
230
255
  const source = `// GENERATED by ${generator}, do not edit.
231
256
  //
232
257
  // The components \`resolve\` supplies without an import. This describes what the
@@ -46,6 +46,14 @@ export interface ComponentGlobalsOptions {
46
46
  * "Cannot find name '$signal'" for code that builds correctly.
47
47
  */
48
48
  intrinsics?: boolean;
49
+ /**
50
+ * Names to try against the pattern rules.
51
+ *
52
+ * A pattern has no name list of its own, but the files it points at do: the caller lists
53
+ * them (the generator stays off the filesystem) and each name the resolver answers is
54
+ * declared like any other.
55
+ */
56
+ candidates?: Iterable<string>;
49
57
  /** Attribution written into the header. */
50
58
  generator?: string;
51
59
  /**
@@ -46,6 +46,14 @@ export interface ComponentGlobalsOptions {
46
46
  * "Cannot find name '$signal'" for code that builds correctly.
47
47
  */
48
48
  intrinsics?: boolean;
49
+ /**
50
+ * Names to try against the pattern rules.
51
+ *
52
+ * A pattern has no name list of its own, but the files it points at do: the caller lists
53
+ * them (the generator stays off the filesystem) and each name the resolver answers is
54
+ * declared like any other.
55
+ */
56
+ candidates?: Iterable<string>;
49
57
  /** Attribution written into the header. */
50
58
  generator?: string;
51
59
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"component-globals.d.ts","sourceRoot":"","sources":["../../src/resolve/component-globals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAEL,KAAK,qBAAqB,EAE3B,MAAM,yBAAyB,CAAC;AAmCjC,MAAM,WAAW,uBAAuB;IACtC;;;;;;;;;;OAUG;IACH,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC7B;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;CACxC;AAED,MAAM,WAAW,sBAAsB;IACrC,mCAAmC;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,0DAA0D;IAC1D,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,qEAAqE;IACrE,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB;;;;;;OAMG;IACH,YAAY,EAAE,MAAM,CAAC;CACtB;AAgBD,0FAA0F;AAC1F,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,SAAS,qBAAqB,EAAE,EACvC,OAAO,GAAE,uBAA4B,GACpC,sBAAsB,CA2GxB"}
1
+ {"version":3,"file":"component-globals.d.ts","sourceRoot":"","sources":["../../src/resolve/component-globals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAEL,KAAK,qBAAqB,EAE3B,MAAM,yBAAyB,CAAC;AAmCjC,MAAM,WAAW,uBAAuB;IACtC;;;;;;;;;;OAUG;IACH,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC7B;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC9B,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;CACxC;AAED,MAAM,WAAW,sBAAsB;IACrC,mCAAmC;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,0DAA0D;IAC1D,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,qEAAqE;IACrE,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB;;;;;;OAMG;IACH,YAAY,EAAE,MAAM,CAAC;CACtB;AAgBD,0FAA0F;AAC1F,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,SAAS,qBAAqB,EAAE,EACvC,OAAO,GAAE,uBAA4B,GACpC,sBAAsB,CAoHxB"}
@@ -65,7 +65,7 @@ function namespaceAlias(module) {
65
65
  }
66
66
  /** Build the ambient `.d.ts` for the components a project's `resolve` config supplies. */
67
67
  export function componentGlobals(rules, options = {}) {
68
- const { available: availableList, builtins = true, intrinsics = true, generator = '@fluixi/compiler', specifier = (module) => module, } = options;
68
+ const { available: availableList, builtins = true, intrinsics = true, generator = '@fluixi/compiler', specifier = (module) => module, candidates = [], } = options;
69
69
  const all = [...rules];
70
70
  if (builtins) {
71
71
  // Appended, so an explicit rule for the same name still wins, the resolver is
@@ -78,9 +78,9 @@ export function componentGlobals(rules, options = {}) {
78
78
  const shadowedByDom = [];
79
79
  const unavailable = [];
80
80
  const resolved = new Map();
81
- for (const name of [...resolver.names()].sort()) {
81
+ const names = new Set([...resolver.names(), ...[...candidates].filter((n) => /^[A-Za-z_$][\w$]*$/.test(n))]);
82
+ for (const name of [...names].sort()) {
82
83
  const target = resolver.resolve(name);
83
- /* c8 ignore next */
84
84
  if (!target)
85
85
  continue;
86
86
  const pkg = packageOf(target.module);
@@ -97,7 +97,15 @@ export function componentGlobals(rules, options = {}) {
97
97
  // '$signal'" for code that compiles. tsc doesn't load language-service plugins, so an
98
98
  // ambient declaration is the only thing that answers for both the editor and the
99
99
  // command line.
100
- const intrinsicModules = intrinsics ? [...new Set(Object.values(INTRINSICS).map((i) => i.module))] : [];
100
+ // Only the intrinsics whose package the project has: `$env` comes from `@fluixi/start`, and
101
+ // an app without it would otherwise get a declaration that fails to resolve.
102
+ const usableIntrinsics = intrinsics
103
+ ? Object.entries(INTRINSICS).filter(([, i]) => {
104
+ const pkg = packageOf(i.module);
105
+ return !available || !pkg || available.has(pkg);
106
+ })
107
+ : [];
108
+ const intrinsicModules = [...new Set(usableIntrinsics.map(([, i]) => i.module))];
101
109
  const modules = [
102
110
  ...new Set([...declared.map((name) => resolved.get(name).module), ...intrinsicModules]),
103
111
  ].sort();
@@ -130,15 +138,16 @@ export function componentGlobals(rules, options = {}) {
130
138
  // `typeof ns.signal` rather than an alias: the intrinsic is a global that stands for
131
139
  // the runtime call, so it takes the runtime's signature without claiming to be that
132
140
  // symbol: go-to-definition on `$signal` should land on what it compiles to.
133
- const intrinsicLines = intrinsics
134
- ? Object.entries(INTRINSICS).map(([name, intrinsic]) => ` const ${name}: typeof ${namespaceAlias(intrinsic.module)}.${intrinsic.export};`)
135
- : [];
141
+ const intrinsicLines = usableIntrinsics.map(([name, intrinsic]) => ` const ${name}: typeof ${namespaceAlias(intrinsic.module)}.${intrinsic.export};`);
136
142
  const patternRules = rules.filter((rule) => 'match' in rule).length;
137
- const patternNote = patternRules > 0
138
- ? `//\n// ${patternRules} pattern rule${patternRules === 1 ? '' : 's'} cannot be declared:\n` +
139
- `// a pattern matches unboundedly many names, so there is no list to emit. Those\n` +
140
- `// tags still resolve at build time; declare them by hand if the editor needs them.\n`
141
- : '';
143
+ const patternNote = patternRules === 0
144
+ ? ''
145
+ : options.candidates
146
+ ? `//\n// ${patternRules} pattern rule${patternRules === 1 ? '' : 's'}: declared for the files the module path\n` +
147
+ `// points at. A name with no such file still resolves at build time; declare it by hand.\n`
148
+ : `//\n// ${patternRules} pattern rule${patternRules === 1 ? '' : 's'} cannot be declared:\n` +
149
+ `// a pattern matches unboundedly many names, so there is no list to emit. Those\n` +
150
+ `// tags still resolve at build time; declare them by hand if the editor needs them.\n`;
142
151
  const source = `// GENERATED by ${generator}, do not edit.\n` +
143
152
  `//\n` +
144
153
  `// The components \`resolve\` supplies without an import. This describes what the\n` +