@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
@@ -251,6 +251,7 @@ var INTRINSICS = {
251
251
  $effect: { export: "effect", module: REACTIVE_MODULE, returns: "effect" },
252
252
  $store: { export: "store", module: "@fluixi/reactive/store", returns: "store-handle" },
253
253
  $resource: { export: "resource", module: REACTIVE_MODULE, returns: "resource-handle" },
254
+ $cachedResource: { export: "cachedResource", module: "@fluixi/reactive/cached-resource", returns: "resource-handle" },
254
255
  // No short runtime form for these yet, so the intrinsic stands for the `create*`
255
256
  // name. Still worth having: no import to write, and when a shorter form is
256
257
  // designed, the change is this line rather than a compiler pass.
@@ -260,7 +261,22 @@ var INTRINSICS = {
260
261
  // does. `untrack` suppresses dependency collection, which is exactly the kind of thing
261
262
  // dependency analysis must not guess at.
262
263
  $untrack: { export: "untrack", module: REACTIVE_MODULE, returns: "plain" },
263
- $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" }
264
+ $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" },
265
+ // Scopes, scheduling and lifecycle: no value to classify, but the same authoring promise,
266
+ // no import to write. `$cleanup` registers a callback on the current owner (`onCleanup`).
267
+ $batch: { export: "batch", module: REACTIVE_MODULE, returns: "plain" },
268
+ $cleanup: { export: "onCleanup", module: REACTIVE_MODULE, returns: "plain" },
269
+ $root: { export: "root", module: REACTIVE_MODULE, returns: "plain" },
270
+ $context: { export: "context", module: REACTIVE_MODULE, returns: "plain" },
271
+ $renderEffect: { export: "renderEffect", module: REACTIVE_MODULE, returns: "effect" },
272
+ $computed: { export: "computed", module: REACTIVE_MODULE, returns: "effect" },
273
+ // Storage for where the code runs, and a signal kept in it. `$persisted` is a signal
274
+ // handle as far as reads go: the same accessor with `set` on it.
275
+ $storage: { export: "useStorage", module: "@fluixi/core/storage", returns: "plain" },
276
+ $asyncStorage: { export: "useAsyncStorage", module: "@fluixi/core/storage", returns: "plain" },
277
+ $persisted: { export: "persisted", module: "@fluixi/core/storage", returns: "signal-handle" },
278
+ // An environment variable, public or server-only; the browser build refuses a secret.
279
+ $env: { export: "env", module: "@fluixi/start/env", returns: "plain" }
264
280
  };
265
281
  var INTRINSIC_CALL = new RegExp(`\\$(?:${Object.keys(INTRINSICS).map((n) => n.slice(1)).join("|")})\\s*\\(`);
266
282
 
@@ -436,13 +452,16 @@ function emitComponent(name, props, children, used, templates, at) {
436
452
  used.add("createComponent");
437
453
  const spreads = props.filter((p) => p.kind === "spread");
438
454
  const normal = props.filter((p) => p.kind !== "spread");
439
- const entries = normal.map(
440
- (p) => `get ${propKey(p.name)}() { return ${componentPropValue(p)}; }`
441
- );
455
+ const bodies = normal.map((p) => [p.name, componentPropValue(p)]);
442
456
  if (children.length > 0) {
443
- entries.push(`get children() { return ${emitChildren(children, used, templates)}; }`);
457
+ bodies.push(["children", emitChildren(children, used, templates)]);
458
+ }
459
+ let obj = `{ ${bodies.map(([name2, body]) => `get ${propKey(name2)}() { return ${body}; }`).join(", ")} }`;
460
+ if (bodies.some(([, body]) => /\bthis\b/.test(body))) {
461
+ const thunks = bodies.map(([name2, body]) => `${JSON.stringify(name2)}: () => ${body}`);
462
+ const getters = bodies.map(([name2]) => `get ${propKey(name2)}() { return _$t[${JSON.stringify(name2)}](); }`);
463
+ obj = `((_$t) => ({ ${getters.join(", ")} }))({ ${thunks.join(", ")} })`;
444
464
  }
445
- let obj = `{ ${entries.join(", ")} }`;
446
465
  if (spreads.length > 0) {
447
466
  used.add("mergeProps");
448
467
  obj = `mergeProps(${spreads.map((s) => s.expr).join(", ")}, ${obj})`;
@@ -524,7 +543,8 @@ function emitNode(node, used, templates) {
524
543
  } else {
525
544
  used.add("spread");
526
545
  const svgFlag = node.svg ? ", isSVG: true" : "";
527
- const customFlag = node.tag.includes("-") ? ", isCustomElement: true" : "";
546
+ const accessors = node.props.filter((p) => p.kind !== "spread" && p.expr !== void 0 && p.reactive && p.name !== "ref").map((p) => p.name);
547
+ const customFlag = node.tag.includes("-") ? `, isCustomElement: true, accessors: ${JSON.stringify(accessors)}` : "";
528
548
  parts.push(
529
549
  `spread({ element: ${el}, props: ${emitProps(node.props, null, used)}${svgFlag}${customFlag} });`
530
550
  );
@@ -651,6 +671,7 @@ var DELEGATED = /* @__PURE__ */ new Set([
651
671
  function escapeTemplate(s) {
652
672
  return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
653
673
  }
674
+ var CONTAINER = "container";
654
675
  function capitalize(s) {
655
676
  return s.charAt(0).toUpperCase() + s.slice(1);
656
677
  }
@@ -705,10 +726,6 @@ var Lowerer = class {
705
726
  if (hasElse) i = elseIdx;
706
727
  continue;
707
728
  }
708
- if (child.attributes.some((a) => a.kind === "EachDirective")) {
709
- out.push(this.lowerEach(child));
710
- continue;
711
- }
712
729
  }
713
730
  const node = this.lowerNode(child);
714
731
  if (node) out.push(node);
@@ -750,13 +767,16 @@ var Lowerer = class {
750
767
  node.props.unshift({ name: "component", kind: "attr", expr: `() => ${paren(this.hole(isAttr.value.hole).code, "arrowBody")}` });
751
768
  return node;
752
769
  }
770
+ const each = this.lowerEach(el);
771
+ const children = each ? [each] : this.lowerChildren(el.children);
772
+ if (el.tag === CONTAINER) return children.length === 1 ? children[0] : { kind: "fragment", children };
753
773
  const at = this.tagAt(el);
754
774
  return {
755
775
  kind: "element",
756
776
  tag: el.tag,
757
777
  svg: el.namespace === "svg",
758
778
  props: this.lowerAttributes(el.attributes),
759
- children: this.lowerChildren(el.children),
779
+ children,
760
780
  static: false,
761
781
  ...at ? { at } : {}
762
782
  };
@@ -828,10 +848,14 @@ var Lowerer = class {
828
848
  const at = this.tagAt(el);
829
849
  return { kind: "component", name: "Show", props, children: [child], ...at ? { at } : {}, ...cond.at ? { bindAt: cond.at } : {} };
830
850
  }
831
- /** `<el each=${items} key?>${item => …}</el>` → `<For each>{item => <el>…</el>}</For>`. */
851
+ /**
852
+ * `<el each=${items} key?>${item => …}</el>` → `<el><For each>{item => …}</For></el>`: the
853
+ * content is repeated per item. Content that is not a `${item => …}` arrow is repeated as
854
+ * it is. Null when the element has no `each`.
855
+ */
832
856
  lowerEach(el) {
833
857
  const eachDir = el.attributes.find((a) => a.kind === "EachDirective");
834
- if (!eachDir || eachDir.kind !== "EachDirective") return this.lowerNode(el);
858
+ if (!eachDir || eachDir.kind !== "EachDirective") return null;
835
859
  const each = this.hole(eachDir.hole);
836
860
  const props = [{ name: "each", kind: "attr", expr: each.code, reactive: each.reactive, ...each.at ? { at: each.at } : {} }];
837
861
  if (eachDir.key) {
@@ -841,12 +865,12 @@ var Lowerer = class {
841
865
  const arrow = this.itemArrow(el);
842
866
  let childrenCode;
843
867
  if (arrow) {
844
- const bodyIR = { kind: "expr", code: arrow.body, reactive: arrow.bodyReactive };
845
- const repeated = this.rebuildWithChildren(el, [bodyIR]);
846
- childrenCode = `(${arrow.params.join(", ")}) => ${paren(this.emit(repeated), "arrowBody")}`;
868
+ const body = arrow.bodyReactive ? `() => ${paren(arrow.body, "arrowBody")}` : paren(arrow.body, "arrowBody");
869
+ childrenCode = `(${arrow.params.join(", ")}) => ${body}`;
847
870
  } else {
848
- const repeated = this.stripAndLower(el, (a) => a.kind === "EachDirective");
849
- childrenCode = `() => ${paren(this.emit(repeated), "arrowBody")}`;
871
+ const content = this.lowerChildren(el.children);
872
+ const ir = content.length === 1 ? content[0] : { kind: "fragment", children: content };
873
+ childrenCode = `() => ${paren(this.emit(ir), "arrowBody")}`;
850
874
  }
851
875
  const children = [{ kind: "expr", code: childrenCode, reactive: false }];
852
876
  const at = this.tagAt(el);
@@ -863,25 +887,6 @@ var Lowerer = class {
863
887
  const clone = { ...el, attributes: el.attributes.filter((a) => !drop(a)) };
864
888
  return clone.kind === "Element" ? this.lowerElement(clone) : this.lowerComponent(clone);
865
889
  }
866
- /** Same element (minus each/key) but with explicit IR children. */
867
- rebuildWithChildren(el, children) {
868
- const props = this.lowerAttributes(
869
- el.attributes.filter((a) => a.kind !== "EachDirective")
870
- );
871
- if (el.kind === "Element") {
872
- const at = this.tagAt(el);
873
- return {
874
- kind: "element",
875
- tag: el.tag,
876
- svg: el.namespace === "svg",
877
- props,
878
- children,
879
- static: false,
880
- ...at ? { at } : {}
881
- };
882
- }
883
- return { kind: "component", name: el.tag, props, children };
884
- }
885
890
  // --- attributes ---------------------------------------------------------
886
891
  lowerAttributes(attrs) {
887
892
  const props = [];
@@ -1293,6 +1298,22 @@ var DELEGATED2 = /* @__PURE__ */ new Set([
1293
1298
  "mouseup"
1294
1299
  ]);
1295
1300
  var isReactive = (node) => isReactiveShape(shapeOf(node));
1301
+ var CONTAINER2 = "container";
1302
+ function directive(node, name) {
1303
+ if (node.type !== "JSXElement") return void 0;
1304
+ const opening = node.openingElement;
1305
+ if (opening.name.type !== "JSXIdentifier" || opening.name.name[0] !== opening.name.name[0].toLowerCase()) return void 0;
1306
+ return opening.attributes.find((a) => a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === name);
1307
+ }
1308
+ function directiveExpr(attr) {
1309
+ const v = attr.value;
1310
+ return v?.type === "JSXExpressionContainer" && v.expression?.type !== "JSXEmptyExpression" ? v.expression : void 0;
1311
+ }
1312
+ function emitIR(ir, ctx) {
1313
+ const { code, imports, templates } = imperativeBackend.emit(ir, {});
1314
+ for (const s of imports) ctx.used.add(s);
1315
+ return inlineTemplates(code, templates);
1316
+ }
1296
1317
  var posOf = (node, ctx) => {
1297
1318
  if (!ctx.sourceFile) return void 0;
1298
1319
  const at = node.loc?.start;
@@ -1401,7 +1422,16 @@ function buildProps(attrs, ctx) {
1401
1422
  }
1402
1423
  function buildChildren(children, ctx) {
1403
1424
  const out = [];
1404
- for (const child of children) {
1425
+ for (let i = 0; i < children.length; i++) {
1426
+ const child = children[i];
1427
+ if (directive(child, "if")) {
1428
+ let next = i + 1;
1429
+ while (next < children.length && children[next].type === "JSXText" && !cleanTemplateText(children[next].value)) next++;
1430
+ const fallback = directive(children[next], "else") ? children[next] : null;
1431
+ out.push(buildIf(child, fallback, ctx));
1432
+ if (fallback) i = next;
1433
+ continue;
1434
+ }
1405
1435
  if (child.type === "JSXText") {
1406
1436
  const text = cleanTemplateText(child.value);
1407
1437
  if (text) out.push({ kind: "text", value: text });
@@ -1438,19 +1468,68 @@ function readLoad(attrs) {
1438
1468
  }
1439
1469
  return void 0;
1440
1470
  }
1471
+ function buildIf(node, fallback, ctx) {
1472
+ const cond = directiveExpr(directive(node, "if"));
1473
+ const props = [
1474
+ cond ? { name: "when", kind: "attr", expr: ctx.code(cond), reactive: isReactive(cond), at: posOf(cond, ctx) } : { name: "when", kind: "attr", literal: true }
1475
+ ];
1476
+ if (fallback) {
1477
+ props.push({ name: "fallback", kind: "attr", expr: emitIR(buildElement(fallback, ctx, ["else"]), ctx), jsxElement: true });
1478
+ }
1479
+ return { kind: "component", name: "Show", props, children: [buildElement(node, ctx, ["if"])] };
1480
+ }
1481
+ function buildEach(node, ctx) {
1482
+ const attr = directive(node, "each");
1483
+ if (!attr) return null;
1484
+ const list = directiveExpr(attr);
1485
+ const props = [
1486
+ list ? { name: "each", kind: "attr", expr: ctx.code(list), reactive: isReactive(list), at: posOf(list, ctx) } : { name: "each", kind: "attr", expr: "[]" }
1487
+ ];
1488
+ const key = directive(node, "key");
1489
+ if (key) {
1490
+ const keyExpr = directiveExpr(key);
1491
+ const by = isString(key.value) ? `(item) => item[${JSON.stringify(literalOf(key.value))}]` : keyExpr ? ctx.code(keyExpr) : void 0;
1492
+ if (by) props.push({ name: "by", kind: "attr", expr: by });
1493
+ }
1494
+ const content = node.children.filter((c) => !(c.type === "JSXText" && !cleanTemplateText(c.value)));
1495
+ const only = content.length === 1 && content[0].type === "JSXExpressionContainer" ? content[0].expression : null;
1496
+ let render;
1497
+ if (only && (only.type === "ArrowFunctionExpression" || only.type === "FunctionExpression")) {
1498
+ const fn = ctx.code(only);
1499
+ const live = only.type === "ArrowFunctionExpression" && only.body.type !== "BlockStatement" && isReactive(only.body);
1500
+ render = live ? `(...args) => () => (${fn})(...args)` : fn;
1501
+ } else {
1502
+ const built = buildChildren(node.children, ctx);
1503
+ const ir = built.length === 1 ? built[0] : { kind: "fragment", children: built };
1504
+ render = `() => ${paren(emitIR(ir, ctx), "arrowBody")}`;
1505
+ }
1506
+ return { kind: "component", name: "For", props, children: [{ kind: "expr", code: render, reactive: false }] };
1507
+ }
1508
+ function buildElement(node, ctx, handled) {
1509
+ const skip = new Set(handled);
1510
+ const directives = /* @__PURE__ */ new Set(["if", "else", "each", ...directive(node, "each") ? ["key"] : []]);
1511
+ const attributes = node.openingElement.attributes.filter(
1512
+ (a) => !(a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && (skip.has(a.name.name) || directives.has(a.name.name)))
1513
+ );
1514
+ const { tag } = tagInfo(node.openingElement.name, ctx);
1515
+ const each = buildEach(node, ctx);
1516
+ const children = each ? [each] : buildChildren(node.children, ctx);
1517
+ if (tag === CONTAINER2) return children.length === 1 ? children[0] : { kind: "fragment", children };
1518
+ const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
1519
+ return { kind: "element", tag, svg: SVG_TAGS.has(tag), props: buildProps(attributes, ctx), children, static: false, ...at ? { at } : {} };
1520
+ }
1441
1521
  function buildNode(node, ctx) {
1442
1522
  if (node.type === "JSXFragment") {
1443
1523
  return { kind: "fragment", children: buildChildren(node.children, ctx) };
1444
1524
  }
1525
+ if (directive(node, "if")) return buildIf(node, null, ctx);
1445
1526
  const { tag, component } = tagInfo(node.openingElement.name, ctx);
1527
+ if (!component) return buildElement(node, ctx, []);
1446
1528
  const props = buildProps(node.openingElement.attributes, ctx);
1447
1529
  const children = buildChildren(node.children, ctx);
1448
1530
  const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
1449
- if (component) {
1450
- const load = readLoad(node.openingElement.attributes);
1451
- return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
1452
- }
1453
- return { kind: "element", tag, svg: SVG_TAGS.has(tag), props, children, static: false, ...at ? { at } : {} };
1531
+ const load = readLoad(node.openingElement.attributes);
1532
+ return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
1454
1533
  }
1455
1534
  function buildJsxIR(node, ctx) {
1456
1535
  const ir = buildNode(node, ctx);
@@ -214,6 +214,7 @@ var INTRINSICS = {
214
214
  $effect: { export: "effect", module: REACTIVE_MODULE, returns: "effect" },
215
215
  $store: { export: "store", module: "@fluixi/reactive/store", returns: "store-handle" },
216
216
  $resource: { export: "resource", module: REACTIVE_MODULE, returns: "resource-handle" },
217
+ $cachedResource: { export: "cachedResource", module: "@fluixi/reactive/cached-resource", returns: "resource-handle" },
217
218
  // No short runtime form for these yet, so the intrinsic stands for the `create*`
218
219
  // name. Still worth having: no import to write, and when a shorter form is
219
220
  // designed, the change is this line rather than a compiler pass.
@@ -223,7 +224,22 @@ var INTRINSICS = {
223
224
  // does. `untrack` suppresses dependency collection, which is exactly the kind of thing
224
225
  // dependency analysis must not guess at.
225
226
  $untrack: { export: "untrack", module: REACTIVE_MODULE, returns: "plain" },
226
- $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" }
227
+ $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" },
228
+ // Scopes, scheduling and lifecycle: no value to classify, but the same authoring promise,
229
+ // no import to write. `$cleanup` registers a callback on the current owner (`onCleanup`).
230
+ $batch: { export: "batch", module: REACTIVE_MODULE, returns: "plain" },
231
+ $cleanup: { export: "onCleanup", module: REACTIVE_MODULE, returns: "plain" },
232
+ $root: { export: "root", module: REACTIVE_MODULE, returns: "plain" },
233
+ $context: { export: "context", module: REACTIVE_MODULE, returns: "plain" },
234
+ $renderEffect: { export: "renderEffect", module: REACTIVE_MODULE, returns: "effect" },
235
+ $computed: { export: "computed", module: REACTIVE_MODULE, returns: "effect" },
236
+ // Storage for where the code runs, and a signal kept in it. `$persisted` is a signal
237
+ // handle as far as reads go: the same accessor with `set` on it.
238
+ $storage: { export: "useStorage", module: "@fluixi/core/storage", returns: "plain" },
239
+ $asyncStorage: { export: "useAsyncStorage", module: "@fluixi/core/storage", returns: "plain" },
240
+ $persisted: { export: "persisted", module: "@fluixi/core/storage", returns: "signal-handle" },
241
+ // An environment variable, public or server-only; the browser build refuses a secret.
242
+ $env: { export: "env", module: "@fluixi/start/env", returns: "plain" }
227
243
  };
228
244
  var INTRINSIC_CALL = new RegExp(`\\$(?:${Object.keys(INTRINSICS).map((n) => n.slice(1)).join("|")})\\s*\\(`);
229
245
 
@@ -399,13 +415,16 @@ function emitComponent(name, props, children, used, templates, at) {
399
415
  used.add("createComponent");
400
416
  const spreads = props.filter((p) => p.kind === "spread");
401
417
  const normal = props.filter((p) => p.kind !== "spread");
402
- const entries = normal.map(
403
- (p) => `get ${propKey(p.name)}() { return ${componentPropValue(p)}; }`
404
- );
418
+ const bodies = normal.map((p) => [p.name, componentPropValue(p)]);
405
419
  if (children.length > 0) {
406
- entries.push(`get children() { return ${emitChildren(children, used, templates)}; }`);
420
+ bodies.push(["children", emitChildren(children, used, templates)]);
421
+ }
422
+ let obj = `{ ${bodies.map(([name2, body]) => `get ${propKey(name2)}() { return ${body}; }`).join(", ")} }`;
423
+ if (bodies.some(([, body]) => /\bthis\b/.test(body))) {
424
+ const thunks = bodies.map(([name2, body]) => `${JSON.stringify(name2)}: () => ${body}`);
425
+ const getters = bodies.map(([name2]) => `get ${propKey(name2)}() { return _$t[${JSON.stringify(name2)}](); }`);
426
+ obj = `((_$t) => ({ ${getters.join(", ")} }))({ ${thunks.join(", ")} })`;
407
427
  }
408
- let obj = `{ ${entries.join(", ")} }`;
409
428
  if (spreads.length > 0) {
410
429
  used.add("mergeProps");
411
430
  obj = `mergeProps(${spreads.map((s) => s.expr).join(", ")}, ${obj})`;
@@ -487,7 +506,8 @@ function emitNode(node, used, templates) {
487
506
  } else {
488
507
  used.add("spread");
489
508
  const svgFlag = node.svg ? ", isSVG: true" : "";
490
- const customFlag = node.tag.includes("-") ? ", isCustomElement: true" : "";
509
+ const accessors = node.props.filter((p) => p.kind !== "spread" && p.expr !== void 0 && p.reactive && p.name !== "ref").map((p) => p.name);
510
+ const customFlag = node.tag.includes("-") ? `, isCustomElement: true, accessors: ${JSON.stringify(accessors)}` : "";
491
511
  parts.push(
492
512
  `spread({ element: ${el}, props: ${emitProps(node.props, null, used)}${svgFlag}${customFlag} });`
493
513
  );
@@ -616,6 +636,7 @@ var DELEGATED = /* @__PURE__ */ new Set([
616
636
  function escapeTemplate(s) {
617
637
  return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
618
638
  }
639
+ var CONTAINER = "container";
619
640
  function capitalize(s) {
620
641
  return s.charAt(0).toUpperCase() + s.slice(1);
621
642
  }
@@ -670,10 +691,6 @@ var Lowerer = class {
670
691
  if (hasElse) i = elseIdx;
671
692
  continue;
672
693
  }
673
- if (child.attributes.some((a) => a.kind === "EachDirective")) {
674
- out.push(this.lowerEach(child));
675
- continue;
676
- }
677
694
  }
678
695
  const node = this.lowerNode(child);
679
696
  if (node) out.push(node);
@@ -715,13 +732,16 @@ var Lowerer = class {
715
732
  node.props.unshift({ name: "component", kind: "attr", expr: `() => ${paren(this.hole(isAttr.value.hole).code, "arrowBody")}` });
716
733
  return node;
717
734
  }
735
+ const each = this.lowerEach(el);
736
+ const children = each ? [each] : this.lowerChildren(el.children);
737
+ if (el.tag === CONTAINER) return children.length === 1 ? children[0] : { kind: "fragment", children };
718
738
  const at = this.tagAt(el);
719
739
  return {
720
740
  kind: "element",
721
741
  tag: el.tag,
722
742
  svg: el.namespace === "svg",
723
743
  props: this.lowerAttributes(el.attributes),
724
- children: this.lowerChildren(el.children),
744
+ children,
725
745
  static: false,
726
746
  ...at ? { at } : {}
727
747
  };
@@ -793,10 +813,14 @@ var Lowerer = class {
793
813
  const at = this.tagAt(el);
794
814
  return { kind: "component", name: "Show", props, children: [child], ...at ? { at } : {}, ...cond.at ? { bindAt: cond.at } : {} };
795
815
  }
796
- /** `<el each=${items} key?>${item => …}</el>` → `<For each>{item => <el>…</el>}</For>`. */
816
+ /**
817
+ * `<el each=${items} key?>${item => …}</el>` → `<el><For each>{item => …}</For></el>`: the
818
+ * content is repeated per item. Content that is not a `${item => …}` arrow is repeated as
819
+ * it is. Null when the element has no `each`.
820
+ */
797
821
  lowerEach(el) {
798
822
  const eachDir = el.attributes.find((a) => a.kind === "EachDirective");
799
- if (!eachDir || eachDir.kind !== "EachDirective") return this.lowerNode(el);
823
+ if (!eachDir || eachDir.kind !== "EachDirective") return null;
800
824
  const each = this.hole(eachDir.hole);
801
825
  const props = [{ name: "each", kind: "attr", expr: each.code, reactive: each.reactive, ...each.at ? { at: each.at } : {} }];
802
826
  if (eachDir.key) {
@@ -806,12 +830,12 @@ var Lowerer = class {
806
830
  const arrow = this.itemArrow(el);
807
831
  let childrenCode;
808
832
  if (arrow) {
809
- const bodyIR = { kind: "expr", code: arrow.body, reactive: arrow.bodyReactive };
810
- const repeated = this.rebuildWithChildren(el, [bodyIR]);
811
- childrenCode = `(${arrow.params.join(", ")}) => ${paren(this.emit(repeated), "arrowBody")}`;
833
+ const body = arrow.bodyReactive ? `() => ${paren(arrow.body, "arrowBody")}` : paren(arrow.body, "arrowBody");
834
+ childrenCode = `(${arrow.params.join(", ")}) => ${body}`;
812
835
  } else {
813
- const repeated = this.stripAndLower(el, (a) => a.kind === "EachDirective");
814
- childrenCode = `() => ${paren(this.emit(repeated), "arrowBody")}`;
836
+ const content = this.lowerChildren(el.children);
837
+ const ir = content.length === 1 ? content[0] : { kind: "fragment", children: content };
838
+ childrenCode = `() => ${paren(this.emit(ir), "arrowBody")}`;
815
839
  }
816
840
  const children = [{ kind: "expr", code: childrenCode, reactive: false }];
817
841
  const at = this.tagAt(el);
@@ -828,25 +852,6 @@ var Lowerer = class {
828
852
  const clone = { ...el, attributes: el.attributes.filter((a) => !drop(a)) };
829
853
  return clone.kind === "Element" ? this.lowerElement(clone) : this.lowerComponent(clone);
830
854
  }
831
- /** Same element (minus each/key) but with explicit IR children. */
832
- rebuildWithChildren(el, children) {
833
- const props = this.lowerAttributes(
834
- el.attributes.filter((a) => a.kind !== "EachDirective")
835
- );
836
- if (el.kind === "Element") {
837
- const at = this.tagAt(el);
838
- return {
839
- kind: "element",
840
- tag: el.tag,
841
- svg: el.namespace === "svg",
842
- props,
843
- children,
844
- static: false,
845
- ...at ? { at } : {}
846
- };
847
- }
848
- return { kind: "component", name: el.tag, props, children };
849
- }
850
855
  // --- attributes ---------------------------------------------------------
851
856
  lowerAttributes(attrs) {
852
857
  const props = [];
@@ -1258,6 +1263,22 @@ var DELEGATED2 = /* @__PURE__ */ new Set([
1258
1263
  "mouseup"
1259
1264
  ]);
1260
1265
  var isReactive = (node) => isReactiveShape(shapeOf(node));
1266
+ var CONTAINER2 = "container";
1267
+ function directive(node, name) {
1268
+ if (node.type !== "JSXElement") return void 0;
1269
+ const opening = node.openingElement;
1270
+ if (opening.name.type !== "JSXIdentifier" || opening.name.name[0] !== opening.name.name[0].toLowerCase()) return void 0;
1271
+ return opening.attributes.find((a) => a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === name);
1272
+ }
1273
+ function directiveExpr(attr) {
1274
+ const v = attr.value;
1275
+ return v?.type === "JSXExpressionContainer" && v.expression?.type !== "JSXEmptyExpression" ? v.expression : void 0;
1276
+ }
1277
+ function emitIR(ir, ctx) {
1278
+ const { code, imports, templates } = imperativeBackend.emit(ir, {});
1279
+ for (const s of imports) ctx.used.add(s);
1280
+ return inlineTemplates(code, templates);
1281
+ }
1261
1282
  var posOf = (node, ctx) => {
1262
1283
  if (!ctx.sourceFile) return void 0;
1263
1284
  const at = node.loc?.start;
@@ -1366,7 +1387,16 @@ function buildProps(attrs, ctx) {
1366
1387
  }
1367
1388
  function buildChildren(children, ctx) {
1368
1389
  const out = [];
1369
- for (const child of children) {
1390
+ for (let i = 0; i < children.length; i++) {
1391
+ const child = children[i];
1392
+ if (directive(child, "if")) {
1393
+ let next = i + 1;
1394
+ while (next < children.length && children[next].type === "JSXText" && !cleanTemplateText(children[next].value)) next++;
1395
+ const fallback = directive(children[next], "else") ? children[next] : null;
1396
+ out.push(buildIf(child, fallback, ctx));
1397
+ if (fallback) i = next;
1398
+ continue;
1399
+ }
1370
1400
  if (child.type === "JSXText") {
1371
1401
  const text = cleanTemplateText(child.value);
1372
1402
  if (text) out.push({ kind: "text", value: text });
@@ -1403,19 +1433,68 @@ function readLoad(attrs) {
1403
1433
  }
1404
1434
  return void 0;
1405
1435
  }
1436
+ function buildIf(node, fallback, ctx) {
1437
+ const cond = directiveExpr(directive(node, "if"));
1438
+ const props = [
1439
+ cond ? { name: "when", kind: "attr", expr: ctx.code(cond), reactive: isReactive(cond), at: posOf(cond, ctx) } : { name: "when", kind: "attr", literal: true }
1440
+ ];
1441
+ if (fallback) {
1442
+ props.push({ name: "fallback", kind: "attr", expr: emitIR(buildElement(fallback, ctx, ["else"]), ctx), jsxElement: true });
1443
+ }
1444
+ return { kind: "component", name: "Show", props, children: [buildElement(node, ctx, ["if"])] };
1445
+ }
1446
+ function buildEach(node, ctx) {
1447
+ const attr = directive(node, "each");
1448
+ if (!attr) return null;
1449
+ const list = directiveExpr(attr);
1450
+ const props = [
1451
+ list ? { name: "each", kind: "attr", expr: ctx.code(list), reactive: isReactive(list), at: posOf(list, ctx) } : { name: "each", kind: "attr", expr: "[]" }
1452
+ ];
1453
+ const key = directive(node, "key");
1454
+ if (key) {
1455
+ const keyExpr = directiveExpr(key);
1456
+ const by = isString(key.value) ? `(item) => item[${JSON.stringify(literalOf(key.value))}]` : keyExpr ? ctx.code(keyExpr) : void 0;
1457
+ if (by) props.push({ name: "by", kind: "attr", expr: by });
1458
+ }
1459
+ const content = node.children.filter((c) => !(c.type === "JSXText" && !cleanTemplateText(c.value)));
1460
+ const only = content.length === 1 && content[0].type === "JSXExpressionContainer" ? content[0].expression : null;
1461
+ let render;
1462
+ if (only && (only.type === "ArrowFunctionExpression" || only.type === "FunctionExpression")) {
1463
+ const fn = ctx.code(only);
1464
+ const live = only.type === "ArrowFunctionExpression" && only.body.type !== "BlockStatement" && isReactive(only.body);
1465
+ render = live ? `(...args) => () => (${fn})(...args)` : fn;
1466
+ } else {
1467
+ const built = buildChildren(node.children, ctx);
1468
+ const ir = built.length === 1 ? built[0] : { kind: "fragment", children: built };
1469
+ render = `() => ${paren(emitIR(ir, ctx), "arrowBody")}`;
1470
+ }
1471
+ return { kind: "component", name: "For", props, children: [{ kind: "expr", code: render, reactive: false }] };
1472
+ }
1473
+ function buildElement(node, ctx, handled) {
1474
+ const skip = new Set(handled);
1475
+ const directives = /* @__PURE__ */ new Set(["if", "else", "each", ...directive(node, "each") ? ["key"] : []]);
1476
+ const attributes = node.openingElement.attributes.filter(
1477
+ (a) => !(a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && (skip.has(a.name.name) || directives.has(a.name.name)))
1478
+ );
1479
+ const { tag } = tagInfo(node.openingElement.name, ctx);
1480
+ const each = buildEach(node, ctx);
1481
+ const children = each ? [each] : buildChildren(node.children, ctx);
1482
+ if (tag === CONTAINER2) return children.length === 1 ? children[0] : { kind: "fragment", children };
1483
+ const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
1484
+ return { kind: "element", tag, svg: SVG_TAGS.has(tag), props: buildProps(attributes, ctx), children, static: false, ...at ? { at } : {} };
1485
+ }
1406
1486
  function buildNode(node, ctx) {
1407
1487
  if (node.type === "JSXFragment") {
1408
1488
  return { kind: "fragment", children: buildChildren(node.children, ctx) };
1409
1489
  }
1490
+ if (directive(node, "if")) return buildIf(node, null, ctx);
1410
1491
  const { tag, component } = tagInfo(node.openingElement.name, ctx);
1492
+ if (!component) return buildElement(node, ctx, []);
1411
1493
  const props = buildProps(node.openingElement.attributes, ctx);
1412
1494
  const children = buildChildren(node.children, ctx);
1413
1495
  const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
1414
- if (component) {
1415
- const load = readLoad(node.openingElement.attributes);
1416
- return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
1417
- }
1418
- return { kind: "element", tag, svg: SVG_TAGS.has(tag), props, children, static: false, ...at ? { at } : {} };
1496
+ const load = readLoad(node.openingElement.attributes);
1497
+ return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
1419
1498
  }
1420
1499
  function buildJsxIR(node, ctx) {
1421
1500
  const ir = buildNode(node, ctx);