@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
@@ -300,6 +300,7 @@ var INTRINSICS = {
300
300
  $effect: { export: "effect", module: REACTIVE_MODULE, returns: "effect" },
301
301
  $store: { export: "store", module: "@fluixi/reactive/store", returns: "store-handle" },
302
302
  $resource: { export: "resource", module: REACTIVE_MODULE, returns: "resource-handle" },
303
+ $cachedResource: { export: "cachedResource", module: "@fluixi/reactive/cached-resource", returns: "resource-handle" },
303
304
  // No short runtime form for these yet, so the intrinsic stands for the `create*`
304
305
  // name. Still worth having: no import to write, and when a shorter form is
305
306
  // designed, the change is this line rather than a compiler pass.
@@ -309,7 +310,22 @@ var INTRINSICS = {
309
310
  // does. `untrack` suppresses dependency collection, which is exactly the kind of thing
310
311
  // dependency analysis must not guess at.
311
312
  $untrack: { export: "untrack", module: REACTIVE_MODULE, returns: "plain" },
312
- $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" }
313
+ $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" },
314
+ // Scopes, scheduling and lifecycle: no value to classify, but the same authoring promise,
315
+ // no import to write. `$cleanup` registers a callback on the current owner (`onCleanup`).
316
+ $batch: { export: "batch", module: REACTIVE_MODULE, returns: "plain" },
317
+ $cleanup: { export: "onCleanup", module: REACTIVE_MODULE, returns: "plain" },
318
+ $root: { export: "root", module: REACTIVE_MODULE, returns: "plain" },
319
+ $context: { export: "context", module: REACTIVE_MODULE, returns: "plain" },
320
+ $renderEffect: { export: "renderEffect", module: REACTIVE_MODULE, returns: "effect" },
321
+ $computed: { export: "computed", module: REACTIVE_MODULE, returns: "effect" },
322
+ // Storage for where the code runs, and a signal kept in it. `$persisted` is a signal
323
+ // handle as far as reads go: the same accessor with `set` on it.
324
+ $storage: { export: "useStorage", module: "@fluixi/core/storage", returns: "plain" },
325
+ $asyncStorage: { export: "useAsyncStorage", module: "@fluixi/core/storage", returns: "plain" },
326
+ $persisted: { export: "persisted", module: "@fluixi/core/storage", returns: "signal-handle" },
327
+ // An environment variable, public or server-only; the browser build refuses a secret.
328
+ $env: { export: "env", module: "@fluixi/start/env", returns: "plain" }
313
329
  };
314
330
  var INTRINSIC_CALL = new RegExp(`\\$(?:${Object.keys(INTRINSICS).map((n) => n.slice(1)).join("|")})\\s*\\(`);
315
331
 
@@ -485,13 +501,16 @@ function emitComponent(name, props, children, used, templates, at) {
485
501
  used.add("createComponent");
486
502
  const spreads = props.filter((p) => p.kind === "spread");
487
503
  const normal = props.filter((p) => p.kind !== "spread");
488
- const entries = normal.map(
489
- (p) => `get ${propKey(p.name)}() { return ${componentPropValue(p)}; }`
490
- );
504
+ const bodies = normal.map((p) => [p.name, componentPropValue(p)]);
491
505
  if (children.length > 0) {
492
- entries.push(`get children() { return ${emitChildren(children, used, templates)}; }`);
506
+ bodies.push(["children", emitChildren(children, used, templates)]);
507
+ }
508
+ let obj = `{ ${bodies.map(([name2, body]) => `get ${propKey(name2)}() { return ${body}; }`).join(", ")} }`;
509
+ if (bodies.some(([, body]) => /\bthis\b/.test(body))) {
510
+ const thunks = bodies.map(([name2, body]) => `${JSON.stringify(name2)}: () => ${body}`);
511
+ const getters = bodies.map(([name2]) => `get ${propKey(name2)}() { return _$t[${JSON.stringify(name2)}](); }`);
512
+ obj = `((_$t) => ({ ${getters.join(", ")} }))({ ${thunks.join(", ")} })`;
493
513
  }
494
- let obj = `{ ${entries.join(", ")} }`;
495
514
  if (spreads.length > 0) {
496
515
  used.add("mergeProps");
497
516
  obj = `mergeProps(${spreads.map((s) => s.expr).join(", ")}, ${obj})`;
@@ -573,7 +592,8 @@ function emitNode(node, used, templates) {
573
592
  } else {
574
593
  used.add("spread");
575
594
  const svgFlag = node.svg ? ", isSVG: true" : "";
576
- const customFlag = node.tag.includes("-") ? ", isCustomElement: true" : "";
595
+ const accessors = node.props.filter((p) => p.kind !== "spread" && p.expr !== void 0 && p.reactive && p.name !== "ref").map((p) => p.name);
596
+ const customFlag = node.tag.includes("-") ? `, isCustomElement: true, accessors: ${JSON.stringify(accessors)}` : "";
577
597
  parts.push(
578
598
  `spread({ element: ${el}, props: ${emitProps(node.props, null, used)}${svgFlag}${customFlag} });`
579
599
  );
@@ -644,6 +664,7 @@ var DELEGATED = /* @__PURE__ */ new Set([
644
664
  function escapeTemplate(s) {
645
665
  return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
646
666
  }
667
+ var CONTAINER = "container";
647
668
  function capitalize(s) {
648
669
  return s.charAt(0).toUpperCase() + s.slice(1);
649
670
  }
@@ -698,10 +719,6 @@ var Lowerer = class {
698
719
  if (hasElse) i = elseIdx;
699
720
  continue;
700
721
  }
701
- if (child.attributes.some((a) => a.kind === "EachDirective")) {
702
- out.push(this.lowerEach(child));
703
- continue;
704
- }
705
722
  }
706
723
  const node = this.lowerNode(child);
707
724
  if (node) out.push(node);
@@ -743,13 +760,16 @@ var Lowerer = class {
743
760
  node.props.unshift({ name: "component", kind: "attr", expr: `() => ${paren(this.hole(isAttr.value.hole).code, "arrowBody")}` });
744
761
  return node;
745
762
  }
763
+ const each = this.lowerEach(el);
764
+ const children = each ? [each] : this.lowerChildren(el.children);
765
+ if (el.tag === CONTAINER) return children.length === 1 ? children[0] : { kind: "fragment", children };
746
766
  const at = this.tagAt(el);
747
767
  return {
748
768
  kind: "element",
749
769
  tag: el.tag,
750
770
  svg: el.namespace === "svg",
751
771
  props: this.lowerAttributes(el.attributes),
752
- children: this.lowerChildren(el.children),
772
+ children,
753
773
  static: false,
754
774
  ...at ? { at } : {}
755
775
  };
@@ -821,10 +841,14 @@ var Lowerer = class {
821
841
  const at = this.tagAt(el);
822
842
  return { kind: "component", name: "Show", props, children: [child], ...at ? { at } : {}, ...cond.at ? { bindAt: cond.at } : {} };
823
843
  }
824
- /** `<el each=${items} key?>${item => …}</el>` → `<For each>{item => <el>…</el>}</For>`. */
844
+ /**
845
+ * `<el each=${items} key?>${item => …}</el>` → `<el><For each>{item => …}</For></el>`: the
846
+ * content is repeated per item. Content that is not a `${item => …}` arrow is repeated as
847
+ * it is. Null when the element has no `each`.
848
+ */
825
849
  lowerEach(el) {
826
850
  const eachDir = el.attributes.find((a) => a.kind === "EachDirective");
827
- if (!eachDir || eachDir.kind !== "EachDirective") return this.lowerNode(el);
851
+ if (!eachDir || eachDir.kind !== "EachDirective") return null;
828
852
  const each = this.hole(eachDir.hole);
829
853
  const props = [{ name: "each", kind: "attr", expr: each.code, reactive: each.reactive, ...each.at ? { at: each.at } : {} }];
830
854
  if (eachDir.key) {
@@ -834,12 +858,12 @@ var Lowerer = class {
834
858
  const arrow = this.itemArrow(el);
835
859
  let childrenCode;
836
860
  if (arrow) {
837
- const bodyIR = { kind: "expr", code: arrow.body, reactive: arrow.bodyReactive };
838
- const repeated = this.rebuildWithChildren(el, [bodyIR]);
839
- childrenCode = `(${arrow.params.join(", ")}) => ${paren(this.emit(repeated), "arrowBody")}`;
861
+ const body = arrow.bodyReactive ? `() => ${paren(arrow.body, "arrowBody")}` : paren(arrow.body, "arrowBody");
862
+ childrenCode = `(${arrow.params.join(", ")}) => ${body}`;
840
863
  } else {
841
- const repeated = this.stripAndLower(el, (a) => a.kind === "EachDirective");
842
- childrenCode = `() => ${paren(this.emit(repeated), "arrowBody")}`;
864
+ const content = this.lowerChildren(el.children);
865
+ const ir = content.length === 1 ? content[0] : { kind: "fragment", children: content };
866
+ childrenCode = `() => ${paren(this.emit(ir), "arrowBody")}`;
843
867
  }
844
868
  const children = [{ kind: "expr", code: childrenCode, reactive: false }];
845
869
  const at = this.tagAt(el);
@@ -856,25 +880,6 @@ var Lowerer = class {
856
880
  const clone = { ...el, attributes: el.attributes.filter((a) => !drop(a)) };
857
881
  return clone.kind === "Element" ? this.lowerElement(clone) : this.lowerComponent(clone);
858
882
  }
859
- /** Same element (minus each/key) but with explicit IR children. */
860
- rebuildWithChildren(el, children) {
861
- const props = this.lowerAttributes(
862
- el.attributes.filter((a) => a.kind !== "EachDirective")
863
- );
864
- if (el.kind === "Element") {
865
- const at = this.tagAt(el);
866
- return {
867
- kind: "element",
868
- tag: el.tag,
869
- svg: el.namespace === "svg",
870
- props,
871
- children,
872
- static: false,
873
- ...at ? { at } : {}
874
- };
875
- }
876
- return { kind: "component", name: el.tag, props, children };
877
- }
878
883
  // --- attributes ---------------------------------------------------------
879
884
  lowerAttributes(attrs) {
880
885
  const props = [];
@@ -1251,6 +1256,22 @@ var DELEGATED2 = /* @__PURE__ */ new Set([
1251
1256
  "mouseup"
1252
1257
  ]);
1253
1258
  var isReactive = (node) => isReactiveShape(shapeOf(node));
1259
+ var CONTAINER2 = "container";
1260
+ function directive(node, name) {
1261
+ if (node.type !== "JSXElement") return void 0;
1262
+ const opening = node.openingElement;
1263
+ if (opening.name.type !== "JSXIdentifier" || opening.name.name[0] !== opening.name.name[0].toLowerCase()) return void 0;
1264
+ return opening.attributes.find((a) => a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === name);
1265
+ }
1266
+ function directiveExpr(attr) {
1267
+ const v = attr.value;
1268
+ return v?.type === "JSXExpressionContainer" && v.expression?.type !== "JSXEmptyExpression" ? v.expression : void 0;
1269
+ }
1270
+ function emitIR(ir, ctx) {
1271
+ const { code, imports, templates } = imperativeBackend.emit(ir, {});
1272
+ for (const s of imports) ctx.used.add(s);
1273
+ return inlineTemplates(code, templates);
1274
+ }
1254
1275
  var posOf = (node, ctx) => {
1255
1276
  if (!ctx.sourceFile) return void 0;
1256
1277
  const at = node.loc?.start;
@@ -1359,7 +1380,16 @@ function buildProps(attrs, ctx) {
1359
1380
  }
1360
1381
  function buildChildren(children, ctx) {
1361
1382
  const out = [];
1362
- for (const child of children) {
1383
+ for (let i = 0; i < children.length; i++) {
1384
+ const child = children[i];
1385
+ if (directive(child, "if")) {
1386
+ let next = i + 1;
1387
+ while (next < children.length && children[next].type === "JSXText" && !cleanTemplateText(children[next].value)) next++;
1388
+ const fallback = directive(children[next], "else") ? children[next] : null;
1389
+ out.push(buildIf(child, fallback, ctx));
1390
+ if (fallback) i = next;
1391
+ continue;
1392
+ }
1363
1393
  if (child.type === "JSXText") {
1364
1394
  const text = cleanTemplateText(child.value);
1365
1395
  if (text) out.push({ kind: "text", value: text });
@@ -1396,19 +1426,68 @@ function readLoad(attrs) {
1396
1426
  }
1397
1427
  return void 0;
1398
1428
  }
1429
+ function buildIf(node, fallback, ctx) {
1430
+ const cond = directiveExpr(directive(node, "if"));
1431
+ const props = [
1432
+ cond ? { name: "when", kind: "attr", expr: ctx.code(cond), reactive: isReactive(cond), at: posOf(cond, ctx) } : { name: "when", kind: "attr", literal: true }
1433
+ ];
1434
+ if (fallback) {
1435
+ props.push({ name: "fallback", kind: "attr", expr: emitIR(buildElement(fallback, ctx, ["else"]), ctx), jsxElement: true });
1436
+ }
1437
+ return { kind: "component", name: "Show", props, children: [buildElement(node, ctx, ["if"])] };
1438
+ }
1439
+ function buildEach(node, ctx) {
1440
+ const attr = directive(node, "each");
1441
+ if (!attr) return null;
1442
+ const list = directiveExpr(attr);
1443
+ const props = [
1444
+ list ? { name: "each", kind: "attr", expr: ctx.code(list), reactive: isReactive(list), at: posOf(list, ctx) } : { name: "each", kind: "attr", expr: "[]" }
1445
+ ];
1446
+ const key = directive(node, "key");
1447
+ if (key) {
1448
+ const keyExpr = directiveExpr(key);
1449
+ const by = isString(key.value) ? `(item) => item[${JSON.stringify(literalOf(key.value))}]` : keyExpr ? ctx.code(keyExpr) : void 0;
1450
+ if (by) props.push({ name: "by", kind: "attr", expr: by });
1451
+ }
1452
+ const content = node.children.filter((c) => !(c.type === "JSXText" && !cleanTemplateText(c.value)));
1453
+ const only = content.length === 1 && content[0].type === "JSXExpressionContainer" ? content[0].expression : null;
1454
+ let render;
1455
+ if (only && (only.type === "ArrowFunctionExpression" || only.type === "FunctionExpression")) {
1456
+ const fn = ctx.code(only);
1457
+ const live = only.type === "ArrowFunctionExpression" && only.body.type !== "BlockStatement" && isReactive(only.body);
1458
+ render = live ? `(...args) => () => (${fn})(...args)` : fn;
1459
+ } else {
1460
+ const built = buildChildren(node.children, ctx);
1461
+ const ir = built.length === 1 ? built[0] : { kind: "fragment", children: built };
1462
+ render = `() => ${paren(emitIR(ir, ctx), "arrowBody")}`;
1463
+ }
1464
+ return { kind: "component", name: "For", props, children: [{ kind: "expr", code: render, reactive: false }] };
1465
+ }
1466
+ function buildElement(node, ctx, handled) {
1467
+ const skip = new Set(handled);
1468
+ const directives = /* @__PURE__ */ new Set(["if", "else", "each", ...directive(node, "each") ? ["key"] : []]);
1469
+ const attributes = node.openingElement.attributes.filter(
1470
+ (a) => !(a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && (skip.has(a.name.name) || directives.has(a.name.name)))
1471
+ );
1472
+ const { tag } = tagInfo(node.openingElement.name, ctx);
1473
+ const each = buildEach(node, ctx);
1474
+ const children = each ? [each] : buildChildren(node.children, ctx);
1475
+ if (tag === CONTAINER2) return children.length === 1 ? children[0] : { kind: "fragment", children };
1476
+ const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
1477
+ return { kind: "element", tag, svg: SVG_TAGS.has(tag), props: buildProps(attributes, ctx), children, static: false, ...at ? { at } : {} };
1478
+ }
1399
1479
  function buildNode(node, ctx) {
1400
1480
  if (node.type === "JSXFragment") {
1401
1481
  return { kind: "fragment", children: buildChildren(node.children, ctx) };
1402
1482
  }
1483
+ if (directive(node, "if")) return buildIf(node, null, ctx);
1403
1484
  const { tag, component } = tagInfo(node.openingElement.name, ctx);
1485
+ if (!component) return buildElement(node, ctx, []);
1404
1486
  const props = buildProps(node.openingElement.attributes, ctx);
1405
1487
  const children = buildChildren(node.children, ctx);
1406
1488
  const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
1407
- if (component) {
1408
- const load = readLoad(node.openingElement.attributes);
1409
- return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
1410
- }
1411
- return { kind: "element", tag, svg: SVG_TAGS.has(tag), props, children, static: false, ...at ? { at } : {} };
1489
+ const load = readLoad(node.openingElement.attributes);
1490
+ return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
1412
1491
  }
1413
1492
  function buildJsxIR(node, ctx) {
1414
1493
  const ir = buildNode(node, ctx);
@@ -268,6 +268,7 @@ var INTRINSICS = {
268
268
  $effect: { export: "effect", module: REACTIVE_MODULE, returns: "effect" },
269
269
  $store: { export: "store", module: "@fluixi/reactive/store", returns: "store-handle" },
270
270
  $resource: { export: "resource", module: REACTIVE_MODULE, returns: "resource-handle" },
271
+ $cachedResource: { export: "cachedResource", module: "@fluixi/reactive/cached-resource", returns: "resource-handle" },
271
272
  // No short runtime form for these yet, so the intrinsic stands for the `create*`
272
273
  // name. Still worth having: no import to write, and when a shorter form is
273
274
  // designed, the change is this line rather than a compiler pass.
@@ -277,7 +278,22 @@ var INTRINSICS = {
277
278
  // does. `untrack` suppresses dependency collection, which is exactly the kind of thing
278
279
  // dependency analysis must not guess at.
279
280
  $untrack: { export: "untrack", module: REACTIVE_MODULE, returns: "plain" },
280
- $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" }
281
+ $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" },
282
+ // Scopes, scheduling and lifecycle: no value to classify, but the same authoring promise,
283
+ // no import to write. `$cleanup` registers a callback on the current owner (`onCleanup`).
284
+ $batch: { export: "batch", module: REACTIVE_MODULE, returns: "plain" },
285
+ $cleanup: { export: "onCleanup", module: REACTIVE_MODULE, returns: "plain" },
286
+ $root: { export: "root", module: REACTIVE_MODULE, returns: "plain" },
287
+ $context: { export: "context", module: REACTIVE_MODULE, returns: "plain" },
288
+ $renderEffect: { export: "renderEffect", module: REACTIVE_MODULE, returns: "effect" },
289
+ $computed: { export: "computed", module: REACTIVE_MODULE, returns: "effect" },
290
+ // Storage for where the code runs, and a signal kept in it. `$persisted` is a signal
291
+ // handle as far as reads go: the same accessor with `set` on it.
292
+ $storage: { export: "useStorage", module: "@fluixi/core/storage", returns: "plain" },
293
+ $asyncStorage: { export: "useAsyncStorage", module: "@fluixi/core/storage", returns: "plain" },
294
+ $persisted: { export: "persisted", module: "@fluixi/core/storage", returns: "signal-handle" },
295
+ // An environment variable, public or server-only; the browser build refuses a secret.
296
+ $env: { export: "env", module: "@fluixi/start/env", returns: "plain" }
281
297
  };
282
298
  var INTRINSIC_CALL = new RegExp(`\\$(?:${Object.keys(INTRINSICS).map((n) => n.slice(1)).join("|")})\\s*\\(`);
283
299
 
@@ -453,13 +469,16 @@ function emitComponent(name, props, children, used, templates, at) {
453
469
  used.add("createComponent");
454
470
  const spreads = props.filter((p) => p.kind === "spread");
455
471
  const normal = props.filter((p) => p.kind !== "spread");
456
- const entries = normal.map(
457
- (p) => `get ${propKey(p.name)}() { return ${componentPropValue(p)}; }`
458
- );
472
+ const bodies = normal.map((p) => [p.name, componentPropValue(p)]);
459
473
  if (children.length > 0) {
460
- entries.push(`get children() { return ${emitChildren(children, used, templates)}; }`);
474
+ bodies.push(["children", emitChildren(children, used, templates)]);
475
+ }
476
+ let obj = `{ ${bodies.map(([name2, body]) => `get ${propKey(name2)}() { return ${body}; }`).join(", ")} }`;
477
+ if (bodies.some(([, body]) => /\bthis\b/.test(body))) {
478
+ const thunks = bodies.map(([name2, body]) => `${JSON.stringify(name2)}: () => ${body}`);
479
+ const getters = bodies.map(([name2]) => `get ${propKey(name2)}() { return _$t[${JSON.stringify(name2)}](); }`);
480
+ obj = `((_$t) => ({ ${getters.join(", ")} }))({ ${thunks.join(", ")} })`;
461
481
  }
462
- let obj = `{ ${entries.join(", ")} }`;
463
482
  if (spreads.length > 0) {
464
483
  used.add("mergeProps");
465
484
  obj = `mergeProps(${spreads.map((s) => s.expr).join(", ")}, ${obj})`;
@@ -541,7 +560,8 @@ function emitNode(node, used, templates) {
541
560
  } else {
542
561
  used.add("spread");
543
562
  const svgFlag = node.svg ? ", isSVG: true" : "";
544
- const customFlag = node.tag.includes("-") ? ", isCustomElement: true" : "";
563
+ const accessors = node.props.filter((p) => p.kind !== "spread" && p.expr !== void 0 && p.reactive && p.name !== "ref").map((p) => p.name);
564
+ const customFlag = node.tag.includes("-") ? `, isCustomElement: true, accessors: ${JSON.stringify(accessors)}` : "";
545
565
  parts.push(
546
566
  `spread({ element: ${el}, props: ${emitProps(node.props, null, used)}${svgFlag}${customFlag} });`
547
567
  );
@@ -612,6 +632,7 @@ var DELEGATED = /* @__PURE__ */ new Set([
612
632
  function escapeTemplate(s) {
613
633
  return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
614
634
  }
635
+ var CONTAINER = "container";
615
636
  function capitalize(s) {
616
637
  return s.charAt(0).toUpperCase() + s.slice(1);
617
638
  }
@@ -666,10 +687,6 @@ var Lowerer = class {
666
687
  if (hasElse) i = elseIdx;
667
688
  continue;
668
689
  }
669
- if (child.attributes.some((a) => a.kind === "EachDirective")) {
670
- out.push(this.lowerEach(child));
671
- continue;
672
- }
673
690
  }
674
691
  const node = this.lowerNode(child);
675
692
  if (node) out.push(node);
@@ -711,13 +728,16 @@ var Lowerer = class {
711
728
  node.props.unshift({ name: "component", kind: "attr", expr: `() => ${paren(this.hole(isAttr.value.hole).code, "arrowBody")}` });
712
729
  return node;
713
730
  }
731
+ const each = this.lowerEach(el);
732
+ const children = each ? [each] : this.lowerChildren(el.children);
733
+ if (el.tag === CONTAINER) return children.length === 1 ? children[0] : { kind: "fragment", children };
714
734
  const at = this.tagAt(el);
715
735
  return {
716
736
  kind: "element",
717
737
  tag: el.tag,
718
738
  svg: el.namespace === "svg",
719
739
  props: this.lowerAttributes(el.attributes),
720
- children: this.lowerChildren(el.children),
740
+ children,
721
741
  static: false,
722
742
  ...at ? { at } : {}
723
743
  };
@@ -789,10 +809,14 @@ var Lowerer = class {
789
809
  const at = this.tagAt(el);
790
810
  return { kind: "component", name: "Show", props, children: [child], ...at ? { at } : {}, ...cond.at ? { bindAt: cond.at } : {} };
791
811
  }
792
- /** `<el each=${items} key?>${item => …}</el>` → `<For each>{item => <el>…</el>}</For>`. */
812
+ /**
813
+ * `<el each=${items} key?>${item => …}</el>` → `<el><For each>{item => …}</For></el>`: the
814
+ * content is repeated per item. Content that is not a `${item => …}` arrow is repeated as
815
+ * it is. Null when the element has no `each`.
816
+ */
793
817
  lowerEach(el) {
794
818
  const eachDir = el.attributes.find((a) => a.kind === "EachDirective");
795
- if (!eachDir || eachDir.kind !== "EachDirective") return this.lowerNode(el);
819
+ if (!eachDir || eachDir.kind !== "EachDirective") return null;
796
820
  const each = this.hole(eachDir.hole);
797
821
  const props = [{ name: "each", kind: "attr", expr: each.code, reactive: each.reactive, ...each.at ? { at: each.at } : {} }];
798
822
  if (eachDir.key) {
@@ -802,12 +826,12 @@ var Lowerer = class {
802
826
  const arrow = this.itemArrow(el);
803
827
  let childrenCode;
804
828
  if (arrow) {
805
- const bodyIR = { kind: "expr", code: arrow.body, reactive: arrow.bodyReactive };
806
- const repeated = this.rebuildWithChildren(el, [bodyIR]);
807
- childrenCode = `(${arrow.params.join(", ")}) => ${paren(this.emit(repeated), "arrowBody")}`;
829
+ const body = arrow.bodyReactive ? `() => ${paren(arrow.body, "arrowBody")}` : paren(arrow.body, "arrowBody");
830
+ childrenCode = `(${arrow.params.join(", ")}) => ${body}`;
808
831
  } else {
809
- const repeated = this.stripAndLower(el, (a) => a.kind === "EachDirective");
810
- childrenCode = `() => ${paren(this.emit(repeated), "arrowBody")}`;
832
+ const content = this.lowerChildren(el.children);
833
+ const ir = content.length === 1 ? content[0] : { kind: "fragment", children: content };
834
+ childrenCode = `() => ${paren(this.emit(ir), "arrowBody")}`;
811
835
  }
812
836
  const children = [{ kind: "expr", code: childrenCode, reactive: false }];
813
837
  const at = this.tagAt(el);
@@ -824,25 +848,6 @@ var Lowerer = class {
824
848
  const clone = { ...el, attributes: el.attributes.filter((a) => !drop(a)) };
825
849
  return clone.kind === "Element" ? this.lowerElement(clone) : this.lowerComponent(clone);
826
850
  }
827
- /** Same element (minus each/key) but with explicit IR children. */
828
- rebuildWithChildren(el, children) {
829
- const props = this.lowerAttributes(
830
- el.attributes.filter((a) => a.kind !== "EachDirective")
831
- );
832
- if (el.kind === "Element") {
833
- const at = this.tagAt(el);
834
- return {
835
- kind: "element",
836
- tag: el.tag,
837
- svg: el.namespace === "svg",
838
- props,
839
- children,
840
- static: false,
841
- ...at ? { at } : {}
842
- };
843
- }
844
- return { kind: "component", name: el.tag, props, children };
845
- }
846
851
  // --- attributes ---------------------------------------------------------
847
852
  lowerAttributes(attrs) {
848
853
  const props = [];
@@ -1219,6 +1224,22 @@ var DELEGATED2 = /* @__PURE__ */ new Set([
1219
1224
  "mouseup"
1220
1225
  ]);
1221
1226
  var isReactive = (node) => isReactiveShape(shapeOf(node));
1227
+ var CONTAINER2 = "container";
1228
+ function directive(node, name) {
1229
+ if (node.type !== "JSXElement") return void 0;
1230
+ const opening = node.openingElement;
1231
+ if (opening.name.type !== "JSXIdentifier" || opening.name.name[0] !== opening.name.name[0].toLowerCase()) return void 0;
1232
+ return opening.attributes.find((a) => a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === name);
1233
+ }
1234
+ function directiveExpr(attr) {
1235
+ const v = attr.value;
1236
+ return v?.type === "JSXExpressionContainer" && v.expression?.type !== "JSXEmptyExpression" ? v.expression : void 0;
1237
+ }
1238
+ function emitIR(ir, ctx) {
1239
+ const { code, imports, templates } = imperativeBackend.emit(ir, {});
1240
+ for (const s of imports) ctx.used.add(s);
1241
+ return inlineTemplates(code, templates);
1242
+ }
1222
1243
  var posOf = (node, ctx) => {
1223
1244
  if (!ctx.sourceFile) return void 0;
1224
1245
  const at = node.loc?.start;
@@ -1327,7 +1348,16 @@ function buildProps(attrs, ctx) {
1327
1348
  }
1328
1349
  function buildChildren(children, ctx) {
1329
1350
  const out = [];
1330
- for (const child of children) {
1351
+ for (let i = 0; i < children.length; i++) {
1352
+ const child = children[i];
1353
+ if (directive(child, "if")) {
1354
+ let next = i + 1;
1355
+ while (next < children.length && children[next].type === "JSXText" && !cleanTemplateText(children[next].value)) next++;
1356
+ const fallback = directive(children[next], "else") ? children[next] : null;
1357
+ out.push(buildIf(child, fallback, ctx));
1358
+ if (fallback) i = next;
1359
+ continue;
1360
+ }
1331
1361
  if (child.type === "JSXText") {
1332
1362
  const text = cleanTemplateText(child.value);
1333
1363
  if (text) out.push({ kind: "text", value: text });
@@ -1364,19 +1394,68 @@ function readLoad(attrs) {
1364
1394
  }
1365
1395
  return void 0;
1366
1396
  }
1397
+ function buildIf(node, fallback, ctx) {
1398
+ const cond = directiveExpr(directive(node, "if"));
1399
+ const props = [
1400
+ cond ? { name: "when", kind: "attr", expr: ctx.code(cond), reactive: isReactive(cond), at: posOf(cond, ctx) } : { name: "when", kind: "attr", literal: true }
1401
+ ];
1402
+ if (fallback) {
1403
+ props.push({ name: "fallback", kind: "attr", expr: emitIR(buildElement(fallback, ctx, ["else"]), ctx), jsxElement: true });
1404
+ }
1405
+ return { kind: "component", name: "Show", props, children: [buildElement(node, ctx, ["if"])] };
1406
+ }
1407
+ function buildEach(node, ctx) {
1408
+ const attr = directive(node, "each");
1409
+ if (!attr) return null;
1410
+ const list = directiveExpr(attr);
1411
+ const props = [
1412
+ list ? { name: "each", kind: "attr", expr: ctx.code(list), reactive: isReactive(list), at: posOf(list, ctx) } : { name: "each", kind: "attr", expr: "[]" }
1413
+ ];
1414
+ const key = directive(node, "key");
1415
+ if (key) {
1416
+ const keyExpr = directiveExpr(key);
1417
+ const by = isString(key.value) ? `(item) => item[${JSON.stringify(literalOf(key.value))}]` : keyExpr ? ctx.code(keyExpr) : void 0;
1418
+ if (by) props.push({ name: "by", kind: "attr", expr: by });
1419
+ }
1420
+ const content = node.children.filter((c) => !(c.type === "JSXText" && !cleanTemplateText(c.value)));
1421
+ const only = content.length === 1 && content[0].type === "JSXExpressionContainer" ? content[0].expression : null;
1422
+ let render;
1423
+ if (only && (only.type === "ArrowFunctionExpression" || only.type === "FunctionExpression")) {
1424
+ const fn = ctx.code(only);
1425
+ const live = only.type === "ArrowFunctionExpression" && only.body.type !== "BlockStatement" && isReactive(only.body);
1426
+ render = live ? `(...args) => () => (${fn})(...args)` : fn;
1427
+ } else {
1428
+ const built = buildChildren(node.children, ctx);
1429
+ const ir = built.length === 1 ? built[0] : { kind: "fragment", children: built };
1430
+ render = `() => ${paren(emitIR(ir, ctx), "arrowBody")}`;
1431
+ }
1432
+ return { kind: "component", name: "For", props, children: [{ kind: "expr", code: render, reactive: false }] };
1433
+ }
1434
+ function buildElement(node, ctx, handled) {
1435
+ const skip = new Set(handled);
1436
+ const directives = /* @__PURE__ */ new Set(["if", "else", "each", ...directive(node, "each") ? ["key"] : []]);
1437
+ const attributes = node.openingElement.attributes.filter(
1438
+ (a) => !(a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && (skip.has(a.name.name) || directives.has(a.name.name)))
1439
+ );
1440
+ const { tag } = tagInfo(node.openingElement.name, ctx);
1441
+ const each = buildEach(node, ctx);
1442
+ const children = each ? [each] : buildChildren(node.children, ctx);
1443
+ if (tag === CONTAINER2) return children.length === 1 ? children[0] : { kind: "fragment", children };
1444
+ const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
1445
+ return { kind: "element", tag, svg: SVG_TAGS.has(tag), props: buildProps(attributes, ctx), children, static: false, ...at ? { at } : {} };
1446
+ }
1367
1447
  function buildNode(node, ctx) {
1368
1448
  if (node.type === "JSXFragment") {
1369
1449
  return { kind: "fragment", children: buildChildren(node.children, ctx) };
1370
1450
  }
1451
+ if (directive(node, "if")) return buildIf(node, null, ctx);
1371
1452
  const { tag, component } = tagInfo(node.openingElement.name, ctx);
1453
+ if (!component) return buildElement(node, ctx, []);
1372
1454
  const props = buildProps(node.openingElement.attributes, ctx);
1373
1455
  const children = buildChildren(node.children, ctx);
1374
1456
  const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
1375
- if (component) {
1376
- const load = readLoad(node.openingElement.attributes);
1377
- return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
1378
- }
1379
- return { kind: "element", tag, svg: SVG_TAGS.has(tag), props, children, static: false, ...at ? { at } : {} };
1457
+ const load = readLoad(node.openingElement.attributes);
1458
+ return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
1380
1459
  }
1381
1460
  function buildJsxIR(node, ctx) {
1382
1461
  const ir = buildNode(node, ctx);