@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,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/transform/templates.ts
4
4
  import { parse as parse2 } from "@babel/parser";
@@ -1521,6 +1521,7 @@ var INTRINSICS = {
1521
1521
  $effect: { export: "effect", module: REACTIVE_MODULE, returns: "effect" },
1522
1522
  $store: { export: "store", module: "@fluixi/reactive/store", returns: "store-handle" },
1523
1523
  $resource: { export: "resource", module: REACTIVE_MODULE, returns: "resource-handle" },
1524
+ $cachedResource: { export: "cachedResource", module: "@fluixi/reactive/cached-resource", returns: "resource-handle" },
1524
1525
  // No short runtime form for these yet, so the intrinsic stands for the `create*`
1525
1526
  // name. Still worth having: no import to write, and when a shorter form is
1526
1527
  // designed, the change is this line rather than a compiler pass.
@@ -1530,7 +1531,22 @@ var INTRINSICS = {
1530
1531
  // does. `untrack` suppresses dependency collection, which is exactly the kind of thing
1531
1532
  // dependency analysis must not guess at.
1532
1533
  $untrack: { export: "untrack", module: REACTIVE_MODULE, returns: "plain" },
1533
- $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" }
1534
+ $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" },
1535
+ // Scopes, scheduling and lifecycle: no value to classify, but the same authoring promise,
1536
+ // no import to write. `$cleanup` registers a callback on the current owner (`onCleanup`).
1537
+ $batch: { export: "batch", module: REACTIVE_MODULE, returns: "plain" },
1538
+ $cleanup: { export: "onCleanup", module: REACTIVE_MODULE, returns: "plain" },
1539
+ $root: { export: "root", module: REACTIVE_MODULE, returns: "plain" },
1540
+ $context: { export: "context", module: REACTIVE_MODULE, returns: "plain" },
1541
+ $renderEffect: { export: "renderEffect", module: REACTIVE_MODULE, returns: "effect" },
1542
+ $computed: { export: "computed", module: REACTIVE_MODULE, returns: "effect" },
1543
+ // Storage for where the code runs, and a signal kept in it. `$persisted` is a signal
1544
+ // handle as far as reads go: the same accessor with `set` on it.
1545
+ $storage: { export: "useStorage", module: "@fluixi/core/storage", returns: "plain" },
1546
+ $asyncStorage: { export: "useAsyncStorage", module: "@fluixi/core/storage", returns: "plain" },
1547
+ $persisted: { export: "persisted", module: "@fluixi/core/storage", returns: "signal-handle" },
1548
+ // An environment variable, public or server-only; the browser build refuses a secret.
1549
+ $env: { export: "env", module: "@fluixi/start/env", returns: "plain" }
1534
1550
  };
1535
1551
  var RUNTIME_PRIMITIVES = {
1536
1552
  signal: "signal-handle",
@@ -1541,8 +1557,11 @@ var RUNTIME_PRIMITIVES = {
1541
1557
  createMemo: "memo-accessor",
1542
1558
  createEffect: "effect",
1543
1559
  createRenderEffect: "effect",
1560
+ renderEffect: "effect",
1561
+ computed: "effect",
1544
1562
  createStore: "reactive-object",
1545
1563
  resource: "resource-handle",
1564
+ cachedResource: "resource-handle",
1546
1565
  createResource: "reactive-object",
1547
1566
  createSelector: "memo-accessor",
1548
1567
  createDeferred: "memo-accessor"
@@ -1551,6 +1570,7 @@ var PRIMITIVE_MODULES = [
1551
1570
  "@fluixi/reactive",
1552
1571
  "@fluixi/reactive/signal",
1553
1572
  "@fluixi/reactive/store",
1573
+ "@fluixi/reactive/cached-resource",
1554
1574
  "@fluixi/core"
1555
1575
  ];
1556
1576
  var INTRINSIC_CALL = new RegExp(`\\$(?:${Object.keys(INTRINSICS).map((n2) => n2.slice(1)).join("|")})\\s*\\(`);
@@ -1686,6 +1706,7 @@ function resolveReactiveBindings(program) {
1686
1706
  // src/transform/source-locations.ts
1687
1707
  var SOURCE_MARKER = "__fx_source";
1688
1708
  var COMPONENT_MARKER = "__fx_component";
1709
+ var CLASS_MARKER = "__fx_class";
1689
1710
  var HOLE_MARKER = "__fx_hole";
1690
1711
  var markHole = (accessor, line, column, name) => `(globalThis.${HOLE_MARKER} ?? ((f) => f))(${accessor},${line},${column}${name === void 0 ? "" : `,${JSON.stringify(name)}`})`;
1691
1712
  var PROPS_MARKER = "__fx_props";
@@ -1703,11 +1724,12 @@ var RUNTIME_PRIMITIVES2 = /* @__PURE__ */ new Set([
1703
1724
  "createStore",
1704
1725
  "createResource"
1705
1726
  ]);
1706
- function mayHaveReactiveCall(code) {
1727
+ function mayHaveSourceMarks(code) {
1707
1728
  for (const name of RUNTIME_PRIMITIVES2) {
1708
1729
  if (code.includes(name)) return true;
1709
1730
  }
1710
- return code.includes("$signal") || code.includes("$memo") || code.includes("$effect") || code.includes("$store");
1731
+ if (code.includes("$signal") || code.includes("$memo") || code.includes("$effect") || code.includes("$store")) return true;
1732
+ return /\bclass\b[^{;]*\bextends\b/.test(code);
1711
1733
  }
1712
1734
  var SINGLE_STATEMENT_PARENTS = /* @__PURE__ */ new Set([
1713
1735
  "IfStatement",
@@ -1726,6 +1748,7 @@ function isLoneBranch(statement, parent) {
1726
1748
  function isExport(node) {
1727
1749
  return node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration";
1728
1750
  }
1751
+ var CLASS_FIELDS = /* @__PURE__ */ new Set(["PropertyDefinition", "ClassProperty", "ClassPrivateProperty", "AccessorProperty"]);
1729
1752
  function isStatement(node) {
1730
1753
  return node.type.endsWith("Statement") || node.type === "VariableDeclaration";
1731
1754
  }
@@ -1746,6 +1769,14 @@ function walk3(node, visit, stack = []) {
1746
1769
  }
1747
1770
  function nameFor(stack) {
1748
1771
  const declarator = stack[stack.length - 1];
1772
+ if (declarator && CLASS_FIELDS.has(declarator.type)) {
1773
+ const key = declarator.key;
1774
+ if (key?.type === "Identifier" || key?.type === "PrivateIdentifier" || key?.type === "PrivateName") {
1775
+ const name = key.name ?? key.id?.name;
1776
+ return name;
1777
+ }
1778
+ return void 0;
1779
+ }
1749
1780
  if (declarator?.type !== "VariableDeclarator") return void 0;
1750
1781
  const id = declarator.id;
1751
1782
  if (id?.type === "Identifier") return id.name;
@@ -1794,6 +1825,17 @@ function collectSourceEdits(program, filename) {
1794
1825
  });
1795
1826
  }
1796
1827
  }
1828
+ if ((node.type === "ClassDeclaration" || node.type === "ClassExpression") && node.superClass && !node.declare) {
1829
+ const at = node.loc?.start;
1830
+ const body = node.body;
1831
+ if (at && body) {
1832
+ edits.push({
1833
+ start: body.start + 1,
1834
+ end: body.start + 1,
1835
+ code: `static{globalThis.${CLASS_MARKER}?.(this,${JSON.stringify(filename)},${at.line},${at.column});}`
1836
+ });
1837
+ }
1838
+ }
1797
1839
  if (node.type !== "CallExpression") return;
1798
1840
  const callee = node.callee;
1799
1841
  if (callee?.type !== "Identifier") return;
@@ -1803,6 +1845,26 @@ function collectSourceEdits(program, filename) {
1803
1845
  if (!intrinsic && !imported) return;
1804
1846
  const position = node.loc?.start;
1805
1847
  if (!position) return;
1848
+ for (let i = stack.length - 1; i >= 0; i--) {
1849
+ const up = stack[i];
1850
+ if (isStatement(up)) break;
1851
+ if (CLASS_FIELDS.has(up.type)) {
1852
+ if (claimed.has(node.start)) return;
1853
+ claimed.add(node.start);
1854
+ const label2 = nameFor(stack);
1855
+ const own = !up.static;
1856
+ const args2 = [
1857
+ JSON.stringify(filename),
1858
+ String(position.line),
1859
+ String(position.column),
1860
+ label2 ? JSON.stringify(label2) : own ? "undefined" : void 0,
1861
+ own ? "this" : void 0
1862
+ ].filter((a) => a !== void 0).join(",");
1863
+ edits.push({ start: node.start, end: node.start, code: `(globalThis.${SOURCE_MARKER}?.(${args2}),` });
1864
+ edits.push({ start: node.end, end: node.end, code: ")" });
1865
+ return;
1866
+ }
1867
+ }
1806
1868
  let statement;
1807
1869
  let parent;
1808
1870
  let index = -1;
@@ -2008,13 +2070,16 @@ function emitComponent(name, props, children, used, templates, at) {
2008
2070
  used.add("createComponent");
2009
2071
  const spreads = props.filter((p) => p.kind === "spread");
2010
2072
  const normal = props.filter((p) => p.kind !== "spread");
2011
- const entries = normal.map(
2012
- (p) => `get ${propKey(p.name)}() { return ${componentPropValue(p)}; }`
2013
- );
2073
+ const bodies = normal.map((p) => [p.name, componentPropValue(p)]);
2014
2074
  if (children.length > 0) {
2015
- entries.push(`get children() { return ${emitChildren(children, used, templates)}; }`);
2075
+ bodies.push(["children", emitChildren(children, used, templates)]);
2076
+ }
2077
+ let obj = `{ ${bodies.map(([name2, body]) => `get ${propKey(name2)}() { return ${body}; }`).join(", ")} }`;
2078
+ if (bodies.some(([, body]) => /\bthis\b/.test(body))) {
2079
+ const thunks = bodies.map(([name2, body]) => `${JSON.stringify(name2)}: () => ${body}`);
2080
+ const getters = bodies.map(([name2]) => `get ${propKey(name2)}() { return _$t[${JSON.stringify(name2)}](); }`);
2081
+ obj = `((_$t) => ({ ${getters.join(", ")} }))({ ${thunks.join(", ")} })`;
2016
2082
  }
2017
- let obj = `{ ${entries.join(", ")} }`;
2018
2083
  if (spreads.length > 0) {
2019
2084
  used.add("mergeProps");
2020
2085
  obj = `mergeProps(${spreads.map((s) => s.expr).join(", ")}, ${obj})`;
@@ -2096,7 +2161,8 @@ function emitNode(node, used, templates) {
2096
2161
  } else {
2097
2162
  used.add("spread");
2098
2163
  const svgFlag = node.svg ? ", isSVG: true" : "";
2099
- const customFlag = node.tag.includes("-") ? ", isCustomElement: true" : "";
2164
+ const accessors = node.props.filter((p) => p.kind !== "spread" && p.expr !== void 0 && p.reactive && p.name !== "ref").map((p) => p.name);
2165
+ const customFlag = node.tag.includes("-") ? `, isCustomElement: true, accessors: ${JSON.stringify(accessors)}` : "";
2100
2166
  parts.push(
2101
2167
  `spread({ element: ${el}, props: ${emitProps(node.props, null, used)}${svgFlag}${customFlag} });`
2102
2168
  );
@@ -2229,6 +2295,7 @@ var DELEGATED = /* @__PURE__ */ new Set([
2229
2295
  function escapeTemplate(s) {
2230
2296
  return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
2231
2297
  }
2298
+ var CONTAINER = "container";
2232
2299
  function capitalize(s) {
2233
2300
  return s.charAt(0).toUpperCase() + s.slice(1);
2234
2301
  }
@@ -2283,10 +2350,6 @@ var Lowerer = class {
2283
2350
  if (hasElse) i = elseIdx;
2284
2351
  continue;
2285
2352
  }
2286
- if (child.attributes.some((a) => a.kind === "EachDirective")) {
2287
- out.push(this.lowerEach(child));
2288
- continue;
2289
- }
2290
2353
  }
2291
2354
  const node = this.lowerNode(child);
2292
2355
  if (node) out.push(node);
@@ -2328,13 +2391,16 @@ var Lowerer = class {
2328
2391
  node.props.unshift({ name: "component", kind: "attr", expr: `() => ${paren(this.hole(isAttr.value.hole).code, "arrowBody")}` });
2329
2392
  return node;
2330
2393
  }
2394
+ const each = this.lowerEach(el);
2395
+ const children = each ? [each] : this.lowerChildren(el.children);
2396
+ if (el.tag === CONTAINER) return children.length === 1 ? children[0] : { kind: "fragment", children };
2331
2397
  const at = this.tagAt(el);
2332
2398
  return {
2333
2399
  kind: "element",
2334
2400
  tag: el.tag,
2335
2401
  svg: el.namespace === "svg",
2336
2402
  props: this.lowerAttributes(el.attributes),
2337
- children: this.lowerChildren(el.children),
2403
+ children,
2338
2404
  static: false,
2339
2405
  ...at ? { at } : {}
2340
2406
  };
@@ -2406,10 +2472,14 @@ var Lowerer = class {
2406
2472
  const at = this.tagAt(el);
2407
2473
  return { kind: "component", name: "Show", props, children: [child], ...at ? { at } : {}, ...cond.at ? { bindAt: cond.at } : {} };
2408
2474
  }
2409
- /** `<el each=${items} key?>${item => …}</el>` → `<For each>{item => <el>…</el>}</For>`. */
2475
+ /**
2476
+ * `<el each=${items} key?>${item => …}</el>` → `<el><For each>{item => …}</For></el>`: the
2477
+ * content is repeated per item. Content that is not a `${item => …}` arrow is repeated as
2478
+ * it is. Null when the element has no `each`.
2479
+ */
2410
2480
  lowerEach(el) {
2411
2481
  const eachDir = el.attributes.find((a) => a.kind === "EachDirective");
2412
- if (!eachDir || eachDir.kind !== "EachDirective") return this.lowerNode(el);
2482
+ if (!eachDir || eachDir.kind !== "EachDirective") return null;
2413
2483
  const each = this.hole(eachDir.hole);
2414
2484
  const props = [{ name: "each", kind: "attr", expr: each.code, reactive: each.reactive, ...each.at ? { at: each.at } : {} }];
2415
2485
  if (eachDir.key) {
@@ -2419,12 +2489,12 @@ var Lowerer = class {
2419
2489
  const arrow = this.itemArrow(el);
2420
2490
  let childrenCode;
2421
2491
  if (arrow) {
2422
- const bodyIR = { kind: "expr", code: arrow.body, reactive: arrow.bodyReactive };
2423
- const repeated = this.rebuildWithChildren(el, [bodyIR]);
2424
- childrenCode = `(${arrow.params.join(", ")}) => ${paren(this.emit(repeated), "arrowBody")}`;
2492
+ const body = arrow.bodyReactive ? `() => ${paren(arrow.body, "arrowBody")}` : paren(arrow.body, "arrowBody");
2493
+ childrenCode = `(${arrow.params.join(", ")}) => ${body}`;
2425
2494
  } else {
2426
- const repeated = this.stripAndLower(el, (a) => a.kind === "EachDirective");
2427
- childrenCode = `() => ${paren(this.emit(repeated), "arrowBody")}`;
2495
+ const content = this.lowerChildren(el.children);
2496
+ const ir = content.length === 1 ? content[0] : { kind: "fragment", children: content };
2497
+ childrenCode = `() => ${paren(this.emit(ir), "arrowBody")}`;
2428
2498
  }
2429
2499
  const children = [{ kind: "expr", code: childrenCode, reactive: false }];
2430
2500
  const at = this.tagAt(el);
@@ -2441,25 +2511,6 @@ var Lowerer = class {
2441
2511
  const clone = { ...el, attributes: el.attributes.filter((a) => !drop(a)) };
2442
2512
  return clone.kind === "Element" ? this.lowerElement(clone) : this.lowerComponent(clone);
2443
2513
  }
2444
- /** Same element (minus each/key) but with explicit IR children. */
2445
- rebuildWithChildren(el, children) {
2446
- const props = this.lowerAttributes(
2447
- el.attributes.filter((a) => a.kind !== "EachDirective")
2448
- );
2449
- if (el.kind === "Element") {
2450
- const at = this.tagAt(el);
2451
- return {
2452
- kind: "element",
2453
- tag: el.tag,
2454
- svg: el.namespace === "svg",
2455
- props,
2456
- children,
2457
- static: false,
2458
- ...at ? { at } : {}
2459
- };
2460
- }
2461
- return { kind: "component", name: el.tag, props, children };
2462
- }
2463
2514
  // --- attributes ---------------------------------------------------------
2464
2515
  lowerAttributes(attrs) {
2465
2516
  const props = [];
@@ -2778,6 +2829,22 @@ var DELEGATED2 = /* @__PURE__ */ new Set([
2778
2829
  "mouseup"
2779
2830
  ]);
2780
2831
  var isReactive = (node) => isReactiveShape(shapeOf(node));
2832
+ var CONTAINER2 = "container";
2833
+ function directive(node, name) {
2834
+ if (node.type !== "JSXElement") return void 0;
2835
+ const opening = node.openingElement;
2836
+ if (opening.name.type !== "JSXIdentifier" || opening.name.name[0] !== opening.name.name[0].toLowerCase()) return void 0;
2837
+ return opening.attributes.find((a) => a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === name);
2838
+ }
2839
+ function directiveExpr(attr) {
2840
+ const v = attr.value;
2841
+ return v?.type === "JSXExpressionContainer" && v.expression?.type !== "JSXEmptyExpression" ? v.expression : void 0;
2842
+ }
2843
+ function emitIR(ir, ctx) {
2844
+ const { code, imports, templates } = imperativeBackend.emit(ir, {});
2845
+ for (const s of imports) ctx.used.add(s);
2846
+ return inlineTemplates(code, templates);
2847
+ }
2781
2848
  var posOf = (node, ctx) => {
2782
2849
  if (!ctx.sourceFile) return void 0;
2783
2850
  const at = node.loc?.start;
@@ -2886,7 +2953,16 @@ function buildProps(attrs, ctx) {
2886
2953
  }
2887
2954
  function buildChildren(children, ctx) {
2888
2955
  const out = [];
2889
- for (const child of children) {
2956
+ for (let i = 0; i < children.length; i++) {
2957
+ const child = children[i];
2958
+ if (directive(child, "if")) {
2959
+ let next = i + 1;
2960
+ while (next < children.length && children[next].type === "JSXText" && !cleanTemplateText(children[next].value)) next++;
2961
+ const fallback = directive(children[next], "else") ? children[next] : null;
2962
+ out.push(buildIf(child, fallback, ctx));
2963
+ if (fallback) i = next;
2964
+ continue;
2965
+ }
2890
2966
  if (child.type === "JSXText") {
2891
2967
  const text = cleanTemplateText(child.value);
2892
2968
  if (text) out.push({ kind: "text", value: text });
@@ -2923,19 +2999,68 @@ function readLoad(attrs) {
2923
2999
  }
2924
3000
  return void 0;
2925
3001
  }
3002
+ function buildIf(node, fallback, ctx) {
3003
+ const cond = directiveExpr(directive(node, "if"));
3004
+ const props = [
3005
+ cond ? { name: "when", kind: "attr", expr: ctx.code(cond), reactive: isReactive(cond), at: posOf(cond, ctx) } : { name: "when", kind: "attr", literal: true }
3006
+ ];
3007
+ if (fallback) {
3008
+ props.push({ name: "fallback", kind: "attr", expr: emitIR(buildElement(fallback, ctx, ["else"]), ctx), jsxElement: true });
3009
+ }
3010
+ return { kind: "component", name: "Show", props, children: [buildElement(node, ctx, ["if"])] };
3011
+ }
3012
+ function buildEach(node, ctx) {
3013
+ const attr = directive(node, "each");
3014
+ if (!attr) return null;
3015
+ const list = directiveExpr(attr);
3016
+ const props = [
3017
+ list ? { name: "each", kind: "attr", expr: ctx.code(list), reactive: isReactive(list), at: posOf(list, ctx) } : { name: "each", kind: "attr", expr: "[]" }
3018
+ ];
3019
+ const key = directive(node, "key");
3020
+ if (key) {
3021
+ const keyExpr = directiveExpr(key);
3022
+ const by = isString(key.value) ? `(item) => item[${JSON.stringify(literalOf(key.value))}]` : keyExpr ? ctx.code(keyExpr) : void 0;
3023
+ if (by) props.push({ name: "by", kind: "attr", expr: by });
3024
+ }
3025
+ const content = node.children.filter((c) => !(c.type === "JSXText" && !cleanTemplateText(c.value)));
3026
+ const only = content.length === 1 && content[0].type === "JSXExpressionContainer" ? content[0].expression : null;
3027
+ let render;
3028
+ if (only && (only.type === "ArrowFunctionExpression" || only.type === "FunctionExpression")) {
3029
+ const fn = ctx.code(only);
3030
+ const live = only.type === "ArrowFunctionExpression" && only.body.type !== "BlockStatement" && isReactive(only.body);
3031
+ render = live ? `(...args) => () => (${fn})(...args)` : fn;
3032
+ } else {
3033
+ const built = buildChildren(node.children, ctx);
3034
+ const ir = built.length === 1 ? built[0] : { kind: "fragment", children: built };
3035
+ render = `() => ${paren(emitIR(ir, ctx), "arrowBody")}`;
3036
+ }
3037
+ return { kind: "component", name: "For", props, children: [{ kind: "expr", code: render, reactive: false }] };
3038
+ }
3039
+ function buildElement(node, ctx, handled) {
3040
+ const skip = new Set(handled);
3041
+ const directives = /* @__PURE__ */ new Set(["if", "else", "each", ...directive(node, "each") ? ["key"] : []]);
3042
+ const attributes = node.openingElement.attributes.filter(
3043
+ (a) => !(a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && (skip.has(a.name.name) || directives.has(a.name.name)))
3044
+ );
3045
+ const { tag } = tagInfo(node.openingElement.name, ctx);
3046
+ const each = buildEach(node, ctx);
3047
+ const children = each ? [each] : buildChildren(node.children, ctx);
3048
+ if (tag === CONTAINER2) return children.length === 1 ? children[0] : { kind: "fragment", children };
3049
+ const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
3050
+ return { kind: "element", tag, svg: SVG_TAGS.has(tag), props: buildProps(attributes, ctx), children, static: false, ...at ? { at } : {} };
3051
+ }
2926
3052
  function buildNode(node, ctx) {
2927
3053
  if (node.type === "JSXFragment") {
2928
3054
  return { kind: "fragment", children: buildChildren(node.children, ctx) };
2929
3055
  }
3056
+ if (directive(node, "if")) return buildIf(node, null, ctx);
2930
3057
  const { tag, component } = tagInfo(node.openingElement.name, ctx);
3058
+ if (!component) return buildElement(node, ctx, []);
2931
3059
  const props = buildProps(node.openingElement.attributes, ctx);
2932
3060
  const children = buildChildren(node.children, ctx);
2933
3061
  const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
2934
- if (component) {
2935
- const load = readLoad(node.openingElement.attributes);
2936
- return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
2937
- }
2938
- return { kind: "element", tag, svg: SVG_TAGS.has(tag), props, children, static: false, ...at ? { at } : {} };
3062
+ const load = readLoad(node.openingElement.attributes);
3063
+ return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
2939
3064
  }
2940
3065
  function buildJsxIR(node, ctx) {
2941
3066
  const ir = buildNode(node, ctx);
@@ -3391,10 +3516,34 @@ function collectIntrinsicEdits(program) {
3391
3516
  });
3392
3517
  return { edits, diagnostics, imports };
3393
3518
  }
3519
+ function avoidBindingClashes(program, result, bound) {
3520
+ const importedAsIs = /* @__PURE__ */ new Set();
3521
+ for (const stmt of program.body ?? []) {
3522
+ if (stmt.type !== "ImportDeclaration") continue;
3523
+ const module = stmt.source.value;
3524
+ for (const spec of stmt.specifiers ?? []) {
3525
+ if (spec.type !== "ImportSpecifier") continue;
3526
+ const imported = spec.imported.name ?? spec.imported.value;
3527
+ if (imported === spec.local.name && result.imports.get(module)?.has(imported)) {
3528
+ importedAsIs.add(imported);
3529
+ }
3530
+ }
3531
+ }
3532
+ const aliases = /* @__PURE__ */ new Map();
3533
+ for (const names of result.imports.values()) {
3534
+ for (const name of names) if (bound.has(name) && !importedAsIs.has(name)) aliases.set(name, `__fx_${name}`);
3535
+ }
3536
+ if (!aliases.size) return;
3537
+ for (const edit of result.edits) {
3538
+ const alias = aliases.get(edit.code);
3539
+ if (alias) edit.code = alias;
3540
+ }
3541
+ result.aliases = aliases;
3542
+ }
3394
3543
  function intrinsicImports(result, bound) {
3395
3544
  const lines = [];
3396
3545
  for (const [module, names] of [...result.imports].sort(([a], [b]) => a.localeCompare(b))) {
3397
- const wanted = [...names].filter((name) => !bound.has(name)).sort();
3546
+ const wanted = [...names].filter((name) => result.aliases?.has(name) || !bound.has(name)).sort().map((name) => result.aliases?.has(name) ? `${name} as ${result.aliases.get(name)}` : name);
3398
3547
  if (wanted.length) lines.push(`import { ${wanted.join(", ")} } from ${JSON.stringify(module)};`);
3399
3548
  }
3400
3549
  return lines.length ? `${lines.join("\n")}
@@ -3579,7 +3728,7 @@ function transformTemplates(code, id, options = {}) {
3579
3728
  const wantsJsx = format !== "lit";
3580
3729
  const mayHaveTemplate = wantsLit && (code.includes(`${litTag}\``) || code.includes("svg`"));
3581
3730
  const wantsIntrinsics = options.intrinsics !== false && mayHaveIntrinsic(code);
3582
- const wantsSources = options.sourceLocations === true && mayHaveReactiveCall(code);
3731
+ const wantsSources = options.sourceLocations === true && mayHaveSourceMarks(code);
3583
3732
  const sourceName = relativeToRoot(id, options.sourceRoot);
3584
3733
  if (!mayHaveTemplate && !(wantsJsx && code.includes("<")) && !wantsIntrinsics && !wantsSources) return null;
3585
3734
  const ast = parse2(code, {
@@ -3594,6 +3743,7 @@ function transformTemplates(code, id, options = {}) {
3594
3743
  });
3595
3744
  const props = options.propsDestructure === false ? { edits: [], diagnostics: [], used: /* @__PURE__ */ new Set() } : collectPropsEdits(ast.program, code, { runtimeModule: options.runtimeModule });
3596
3745
  const intrinsics = options.intrinsics === false ? { edits: [], diagnostics: [], imports: /* @__PURE__ */ new Map() } : collectIntrinsicEdits(ast.program);
3746
+ if (intrinsics.edits.length) avoidBindingClashes(ast.program, intrinsics, moduleBindings(ast.program));
3597
3747
  const sources = options.sourceLocations === true ? collectSourceEdits(ast.program, sourceName) : { edits: [], marked: 0 };
3598
3748
  const silent = found.length === 0 && props.edits.length === 0 && sources.edits.length === 0 && intrinsics.edits.length === 0 && intrinsics.diagnostics.length === 0 && props.diagnostics.length === 0;
3599
3749
  if (silent) return null;
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/transform/intrinsics.ts
21
21
  var intrinsics_exports = {};
22
22
  __export(intrinsics_exports, {
23
+ avoidBindingClashes: () => avoidBindingClashes,
23
24
  collectIntrinsicEdits: () => collectIntrinsicEdits,
24
25
  intrinsicImports: () => intrinsicImports
25
26
  });
@@ -33,6 +34,7 @@ var INTRINSICS = {
33
34
  $effect: { export: "effect", module: REACTIVE_MODULE, returns: "effect" },
34
35
  $store: { export: "store", module: "@fluixi/reactive/store", returns: "store-handle" },
35
36
  $resource: { export: "resource", module: REACTIVE_MODULE, returns: "resource-handle" },
37
+ $cachedResource: { export: "cachedResource", module: "@fluixi/reactive/cached-resource", returns: "resource-handle" },
36
38
  // No short runtime form for these yet, so the intrinsic stands for the `create*`
37
39
  // name. Still worth having: no import to write, and when a shorter form is
38
40
  // designed, the change is this line rather than a compiler pass.
@@ -42,7 +44,22 @@ var INTRINSICS = {
42
44
  // does. `untrack` suppresses dependency collection, which is exactly the kind of thing
43
45
  // dependency analysis must not guess at.
44
46
  $untrack: { export: "untrack", module: REACTIVE_MODULE, returns: "plain" },
45
- $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" }
47
+ $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" },
48
+ // Scopes, scheduling and lifecycle: no value to classify, but the same authoring promise,
49
+ // no import to write. `$cleanup` registers a callback on the current owner (`onCleanup`).
50
+ $batch: { export: "batch", module: REACTIVE_MODULE, returns: "plain" },
51
+ $cleanup: { export: "onCleanup", module: REACTIVE_MODULE, returns: "plain" },
52
+ $root: { export: "root", module: REACTIVE_MODULE, returns: "plain" },
53
+ $context: { export: "context", module: REACTIVE_MODULE, returns: "plain" },
54
+ $renderEffect: { export: "renderEffect", module: REACTIVE_MODULE, returns: "effect" },
55
+ $computed: { export: "computed", module: REACTIVE_MODULE, returns: "effect" },
56
+ // Storage for where the code runs, and a signal kept in it. `$persisted` is a signal
57
+ // handle as far as reads go: the same accessor with `set` on it.
58
+ $storage: { export: "useStorage", module: "@fluixi/core/storage", returns: "plain" },
59
+ $asyncStorage: { export: "useAsyncStorage", module: "@fluixi/core/storage", returns: "plain" },
60
+ $persisted: { export: "persisted", module: "@fluixi/core/storage", returns: "signal-handle" },
61
+ // An environment variable, public or server-only; the browser build refuses a secret.
62
+ $env: { export: "env", module: "@fluixi/start/env", returns: "plain" }
46
63
  };
47
64
  var RUNTIME_PRIMITIVES = {
48
65
  signal: "signal-handle",
@@ -53,8 +70,11 @@ var RUNTIME_PRIMITIVES = {
53
70
  createMemo: "memo-accessor",
54
71
  createEffect: "effect",
55
72
  createRenderEffect: "effect",
73
+ renderEffect: "effect",
74
+ computed: "effect",
56
75
  createStore: "reactive-object",
57
76
  resource: "resource-handle",
77
+ cachedResource: "resource-handle",
58
78
  createResource: "reactive-object",
59
79
  createSelector: "memo-accessor",
60
80
  createDeferred: "memo-accessor"
@@ -63,6 +83,7 @@ var PRIMITIVE_MODULES = [
63
83
  "@fluixi/reactive",
64
84
  "@fluixi/reactive/signal",
65
85
  "@fluixi/reactive/store",
86
+ "@fluixi/reactive/cached-resource",
66
87
  "@fluixi/core"
67
88
  ];
68
89
  var INTRINSIC_CALL = new RegExp(`\\$(?:${Object.keys(INTRINSICS).map((n) => n.slice(1)).join("|")})\\s*\\(`);
@@ -205,10 +226,34 @@ function collectIntrinsicEdits(program) {
205
226
  });
206
227
  return { edits, diagnostics, imports };
207
228
  }
229
+ function avoidBindingClashes(program, result, bound) {
230
+ const importedAsIs = /* @__PURE__ */ new Set();
231
+ for (const stmt of program.body ?? []) {
232
+ if (stmt.type !== "ImportDeclaration") continue;
233
+ const module2 = stmt.source.value;
234
+ for (const spec of stmt.specifiers ?? []) {
235
+ if (spec.type !== "ImportSpecifier") continue;
236
+ const imported = spec.imported.name ?? spec.imported.value;
237
+ if (imported === spec.local.name && result.imports.get(module2)?.has(imported)) {
238
+ importedAsIs.add(imported);
239
+ }
240
+ }
241
+ }
242
+ const aliases = /* @__PURE__ */ new Map();
243
+ for (const names of result.imports.values()) {
244
+ for (const name of names) if (bound.has(name) && !importedAsIs.has(name)) aliases.set(name, `__fx_${name}`);
245
+ }
246
+ if (!aliases.size) return;
247
+ for (const edit of result.edits) {
248
+ const alias = aliases.get(edit.code);
249
+ if (alias) edit.code = alias;
250
+ }
251
+ result.aliases = aliases;
252
+ }
208
253
  function intrinsicImports(result, bound) {
209
254
  const lines = [];
210
255
  for (const [module2, names] of [...result.imports].sort(([a], [b]) => a.localeCompare(b))) {
211
- const wanted = [...names].filter((name) => !bound.has(name)).sort();
256
+ const wanted = [...names].filter((name) => result.aliases?.has(name) || !bound.has(name)).sort().map((name) => result.aliases?.has(name) ? `${name} as ${result.aliases.get(name)}` : name);
212
257
  if (wanted.length) lines.push(`import { ${wanted.join(", ")} } from ${JSON.stringify(module2)};`);
213
258
  }
214
259
  return lines.length ? `${lines.join("\n")}
@@ -14,8 +14,20 @@ export interface IntrinsicsResult {
14
14
  diagnostics: IntrinsicDiagnostic[];
15
15
  /** Runtime exports the rewritten code needs, by module. */
16
16
  imports: Map<string, Set<string>>;
17
+ /** Runtime exports imported under another name, because the module binds theirs. */
18
+ aliases?: Map<string, string>;
17
19
  }
18
20
  /** Find the `$` calls in a module and say what they compile to. */
19
21
  export declare function collectIntrinsicEdits(program: unknown): IntrinsicsResult;
22
+ /**
23
+ * Point a rewritten call past a local binding of the same name.
24
+ *
25
+ * `$env('X')` becomes `env('X')`, and the import of `env` used to be skipped when the module
26
+ * already bound `env`, on the assumption that it was the same import. A config module with its
27
+ * own `const env = {…}` is common, and there the call reached the app's object instead. Such a
28
+ * name is now imported under a private alias. A module that imports that very export from that
29
+ * very module keeps the old behaviour: the binding is the runtime already.
30
+ */
31
+ export declare function avoidBindingClashes(program: unknown, result: IntrinsicsResult, bound: Set<string>): void;
20
32
  /** The import statements a result needs, minus anything the module already binds. */
21
33
  export declare function intrinsicImports(result: IntrinsicsResult, bound: Set<string>): string;
@@ -14,9 +14,21 @@ export interface IntrinsicsResult {
14
14
  diagnostics: IntrinsicDiagnostic[];
15
15
  /** Runtime exports the rewritten code needs, by module. */
16
16
  imports: Map<string, Set<string>>;
17
+ /** Runtime exports imported under another name, because the module binds theirs. */
18
+ aliases?: Map<string, string>;
17
19
  }
18
20
  /** Find the `$` calls in a module and say what they compile to. */
19
21
  export declare function collectIntrinsicEdits(program: unknown): IntrinsicsResult;
22
+ /**
23
+ * Point a rewritten call past a local binding of the same name.
24
+ *
25
+ * `$env('X')` becomes `env('X')`, and the import of `env` used to be skipped when the module
26
+ * already bound `env`, on the assumption that it was the same import. A config module with its
27
+ * own `const env = {…}` is common, and there the call reached the app's object instead. Such a
28
+ * name is now imported under a private alias. A module that imports that very export from that
29
+ * very module keeps the old behaviour: the binding is the runtime already.
30
+ */
31
+ export declare function avoidBindingClashes(program: unknown, result: IntrinsicsResult, bound: Set<string>): void;
20
32
  /** The import statements a result needs, minus anything the module already binds. */
21
33
  export declare function intrinsicImports(result: IntrinsicsResult, bound: Set<string>): string;
22
34
  //# sourceMappingURL=intrinsics.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"intrinsics.d.ts","sourceRoot":"","sources":["../../src/transform/intrinsics.ts"],"names":[],"mappings":"AAwBA,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,mBAAmB;IAClC,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,WAAW,EAAE,mBAAmB,EAAE,CAAC;IACnC,2DAA2D;IAC3D,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;CACnC;AAaD,mEAAmE;AACnE,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,gBAAgB,CAuCxE;AAED,qFAAqF;AACrF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAOrF"}
1
+ {"version":3,"file":"intrinsics.d.ts","sourceRoot":"","sources":["../../src/transform/intrinsics.ts"],"names":[],"mappings":"AAwBA,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,mBAAmB;IAClC,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,WAAW,EAAE,mBAAmB,EAAE,CAAC;IACnC,2DAA2D;IAC3D,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAClC,oFAAoF;IACpF,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/B;AAaD,mEAAmE;AACnE,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,gBAAgB,CAuCxE;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAwBxG;AAED,qFAAqF;AACrF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAUrF"}
@@ -66,11 +66,53 @@ export function collectIntrinsicEdits(program) {
66
66
  });
67
67
  return { edits, diagnostics, imports };
68
68
  }
69
+ /**
70
+ * Point a rewritten call past a local binding of the same name.
71
+ *
72
+ * `$env('X')` becomes `env('X')`, and the import of `env` used to be skipped when the module
73
+ * already bound `env`, on the assumption that it was the same import. A config module with its
74
+ * own `const env = {…}` is common, and there the call reached the app's object instead. Such a
75
+ * name is now imported under a private alias. A module that imports that very export from that
76
+ * very module keeps the old behaviour: the binding is the runtime already.
77
+ */
78
+ export function avoidBindingClashes(program, result, bound) {
79
+ const importedAsIs = new Set();
80
+ for (const stmt of program.body ?? []) {
81
+ if (stmt.type !== 'ImportDeclaration')
82
+ continue;
83
+ const module = stmt.source.value;
84
+ for (const spec of stmt.specifiers ?? []) {
85
+ if (spec.type !== 'ImportSpecifier')
86
+ continue;
87
+ const imported = (spec.imported.name ?? spec.imported.value);
88
+ if (imported === spec.local.name && result.imports.get(module)?.has(imported)) {
89
+ importedAsIs.add(imported);
90
+ }
91
+ }
92
+ }
93
+ const aliases = new Map();
94
+ for (const names of result.imports.values()) {
95
+ for (const name of names)
96
+ if (bound.has(name) && !importedAsIs.has(name))
97
+ aliases.set(name, `__fx_${name}`);
98
+ }
99
+ if (!aliases.size)
100
+ return;
101
+ for (const edit of result.edits) {
102
+ const alias = aliases.get(edit.code);
103
+ if (alias)
104
+ edit.code = alias;
105
+ }
106
+ result.aliases = aliases;
107
+ }
69
108
  /** The import statements a result needs, minus anything the module already binds. */
70
109
  export function intrinsicImports(result, bound) {
71
110
  const lines = [];
72
111
  for (const [module, names] of [...result.imports].sort(([a], [b]) => a.localeCompare(b))) {
73
- const wanted = [...names].filter((name) => !bound.has(name)).sort();
112
+ const wanted = [...names]
113
+ .filter((name) => result.aliases?.has(name) || !bound.has(name))
114
+ .sort()
115
+ .map((name) => (result.aliases?.has(name) ? `${name} as ${result.aliases.get(name)}` : name));
74
116
  if (wanted.length)
75
117
  lines.push(`import { ${wanted.join(', ')} } from ${JSON.stringify(module)};`);
76
118
  }