@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
@@ -302,6 +302,7 @@ var INTRINSICS = {
302
302
  $effect: { export: "effect", module: REACTIVE_MODULE, returns: "effect" },
303
303
  $store: { export: "store", module: "@fluixi/reactive/store", returns: "store-handle" },
304
304
  $resource: { export: "resource", module: REACTIVE_MODULE, returns: "resource-handle" },
305
+ $cachedResource: { export: "cachedResource", module: "@fluixi/reactive/cached-resource", returns: "resource-handle" },
305
306
  // No short runtime form for these yet, so the intrinsic stands for the `create*`
306
307
  // name. Still worth having: no import to write, and when a shorter form is
307
308
  // designed, the change is this line rather than a compiler pass.
@@ -311,7 +312,22 @@ var INTRINSICS = {
311
312
  // does. `untrack` suppresses dependency collection, which is exactly the kind of thing
312
313
  // dependency analysis must not guess at.
313
314
  $untrack: { export: "untrack", module: REACTIVE_MODULE, returns: "plain" },
314
- $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" }
315
+ $untrackStore: { export: "untrackStore", module: "@fluixi/reactive/store", returns: "plain" },
316
+ // Scopes, scheduling and lifecycle: no value to classify, but the same authoring promise,
317
+ // no import to write. `$cleanup` registers a callback on the current owner (`onCleanup`).
318
+ $batch: { export: "batch", module: REACTIVE_MODULE, returns: "plain" },
319
+ $cleanup: { export: "onCleanup", module: REACTIVE_MODULE, returns: "plain" },
320
+ $root: { export: "root", module: REACTIVE_MODULE, returns: "plain" },
321
+ $context: { export: "context", module: REACTIVE_MODULE, returns: "plain" },
322
+ $renderEffect: { export: "renderEffect", module: REACTIVE_MODULE, returns: "effect" },
323
+ $computed: { export: "computed", module: REACTIVE_MODULE, returns: "effect" },
324
+ // Storage for where the code runs, and a signal kept in it. `$persisted` is a signal
325
+ // handle as far as reads go: the same accessor with `set` on it.
326
+ $storage: { export: "useStorage", module: "@fluixi/core/storage", returns: "plain" },
327
+ $asyncStorage: { export: "useAsyncStorage", module: "@fluixi/core/storage", returns: "plain" },
328
+ $persisted: { export: "persisted", module: "@fluixi/core/storage", returns: "signal-handle" },
329
+ // An environment variable, public or server-only; the browser build refuses a secret.
330
+ $env: { export: "env", module: "@fluixi/start/env", returns: "plain" }
315
331
  };
316
332
  var INTRINSIC_CALL = new RegExp(`\\$(?:${Object.keys(INTRINSICS).map((n) => n.slice(1)).join("|")})\\s*\\(`);
317
333
 
@@ -487,13 +503,16 @@ function emitComponent(name, props, children, used, templates, at) {
487
503
  used.add("createComponent");
488
504
  const spreads = props.filter((p) => p.kind === "spread");
489
505
  const normal = props.filter((p) => p.kind !== "spread");
490
- const entries = normal.map(
491
- (p) => `get ${propKey(p.name)}() { return ${componentPropValue(p)}; }`
492
- );
506
+ const bodies = normal.map((p) => [p.name, componentPropValue(p)]);
493
507
  if (children.length > 0) {
494
- entries.push(`get children() { return ${emitChildren(children, used, templates)}; }`);
508
+ bodies.push(["children", emitChildren(children, used, templates)]);
509
+ }
510
+ let obj = `{ ${bodies.map(([name2, body]) => `get ${propKey(name2)}() { return ${body}; }`).join(", ")} }`;
511
+ if (bodies.some(([, body]) => /\bthis\b/.test(body))) {
512
+ const thunks = bodies.map(([name2, body]) => `${JSON.stringify(name2)}: () => ${body}`);
513
+ const getters = bodies.map(([name2]) => `get ${propKey(name2)}() { return _$t[${JSON.stringify(name2)}](); }`);
514
+ obj = `((_$t) => ({ ${getters.join(", ")} }))({ ${thunks.join(", ")} })`;
495
515
  }
496
- let obj = `{ ${entries.join(", ")} }`;
497
516
  if (spreads.length > 0) {
498
517
  used.add("mergeProps");
499
518
  obj = `mergeProps(${spreads.map((s) => s.expr).join(", ")}, ${obj})`;
@@ -575,7 +594,8 @@ function emitNode(node, used, templates) {
575
594
  } else {
576
595
  used.add("spread");
577
596
  const svgFlag = node.svg ? ", isSVG: true" : "";
578
- const customFlag = node.tag.includes("-") ? ", isCustomElement: true" : "";
597
+ const accessors = node.props.filter((p) => p.kind !== "spread" && p.expr !== void 0 && p.reactive && p.name !== "ref").map((p) => p.name);
598
+ const customFlag = node.tag.includes("-") ? `, isCustomElement: true, accessors: ${JSON.stringify(accessors)}` : "";
579
599
  parts.push(
580
600
  `spread({ element: ${el}, props: ${emitProps(node.props, null, used)}${svgFlag}${customFlag} });`
581
601
  );
@@ -646,6 +666,7 @@ var DELEGATED = /* @__PURE__ */ new Set([
646
666
  function escapeTemplate(s) {
647
667
  return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
648
668
  }
669
+ var CONTAINER = "container";
649
670
  function capitalize(s) {
650
671
  return s.charAt(0).toUpperCase() + s.slice(1);
651
672
  }
@@ -700,10 +721,6 @@ var Lowerer = class {
700
721
  if (hasElse) i = elseIdx;
701
722
  continue;
702
723
  }
703
- if (child.attributes.some((a) => a.kind === "EachDirective")) {
704
- out.push(this.lowerEach(child));
705
- continue;
706
- }
707
724
  }
708
725
  const node = this.lowerNode(child);
709
726
  if (node) out.push(node);
@@ -745,13 +762,16 @@ var Lowerer = class {
745
762
  node.props.unshift({ name: "component", kind: "attr", expr: `() => ${paren(this.hole(isAttr.value.hole).code, "arrowBody")}` });
746
763
  return node;
747
764
  }
765
+ const each = this.lowerEach(el);
766
+ const children = each ? [each] : this.lowerChildren(el.children);
767
+ if (el.tag === CONTAINER) return children.length === 1 ? children[0] : { kind: "fragment", children };
748
768
  const at = this.tagAt(el);
749
769
  return {
750
770
  kind: "element",
751
771
  tag: el.tag,
752
772
  svg: el.namespace === "svg",
753
773
  props: this.lowerAttributes(el.attributes),
754
- children: this.lowerChildren(el.children),
774
+ children,
755
775
  static: false,
756
776
  ...at ? { at } : {}
757
777
  };
@@ -823,10 +843,14 @@ var Lowerer = class {
823
843
  const at = this.tagAt(el);
824
844
  return { kind: "component", name: "Show", props, children: [child], ...at ? { at } : {}, ...cond.at ? { bindAt: cond.at } : {} };
825
845
  }
826
- /** `<el each=${items} key?>${item => …}</el>` → `<For each>{item => <el>…</el>}</For>`. */
846
+ /**
847
+ * `<el each=${items} key?>${item => …}</el>` → `<el><For each>{item => …}</For></el>`: the
848
+ * content is repeated per item. Content that is not a `${item => …}` arrow is repeated as
849
+ * it is. Null when the element has no `each`.
850
+ */
827
851
  lowerEach(el) {
828
852
  const eachDir = el.attributes.find((a) => a.kind === "EachDirective");
829
- if (!eachDir || eachDir.kind !== "EachDirective") return this.lowerNode(el);
853
+ if (!eachDir || eachDir.kind !== "EachDirective") return null;
830
854
  const each = this.hole(eachDir.hole);
831
855
  const props = [{ name: "each", kind: "attr", expr: each.code, reactive: each.reactive, ...each.at ? { at: each.at } : {} }];
832
856
  if (eachDir.key) {
@@ -836,12 +860,12 @@ var Lowerer = class {
836
860
  const arrow = this.itemArrow(el);
837
861
  let childrenCode;
838
862
  if (arrow) {
839
- const bodyIR = { kind: "expr", code: arrow.body, reactive: arrow.bodyReactive };
840
- const repeated = this.rebuildWithChildren(el, [bodyIR]);
841
- childrenCode = `(${arrow.params.join(", ")}) => ${paren(this.emit(repeated), "arrowBody")}`;
863
+ const body = arrow.bodyReactive ? `() => ${paren(arrow.body, "arrowBody")}` : paren(arrow.body, "arrowBody");
864
+ childrenCode = `(${arrow.params.join(", ")}) => ${body}`;
842
865
  } else {
843
- const repeated = this.stripAndLower(el, (a) => a.kind === "EachDirective");
844
- childrenCode = `() => ${paren(this.emit(repeated), "arrowBody")}`;
866
+ const content = this.lowerChildren(el.children);
867
+ const ir = content.length === 1 ? content[0] : { kind: "fragment", children: content };
868
+ childrenCode = `() => ${paren(this.emit(ir), "arrowBody")}`;
845
869
  }
846
870
  const children = [{ kind: "expr", code: childrenCode, reactive: false }];
847
871
  const at = this.tagAt(el);
@@ -858,25 +882,6 @@ var Lowerer = class {
858
882
  const clone = { ...el, attributes: el.attributes.filter((a) => !drop(a)) };
859
883
  return clone.kind === "Element" ? this.lowerElement(clone) : this.lowerComponent(clone);
860
884
  }
861
- /** Same element (minus each/key) but with explicit IR children. */
862
- rebuildWithChildren(el, children) {
863
- const props = this.lowerAttributes(
864
- el.attributes.filter((a) => a.kind !== "EachDirective")
865
- );
866
- if (el.kind === "Element") {
867
- const at = this.tagAt(el);
868
- return {
869
- kind: "element",
870
- tag: el.tag,
871
- svg: el.namespace === "svg",
872
- props,
873
- children,
874
- static: false,
875
- ...at ? { at } : {}
876
- };
877
- }
878
- return { kind: "component", name: el.tag, props, children };
879
- }
880
885
  // --- attributes ---------------------------------------------------------
881
886
  lowerAttributes(attrs) {
882
887
  const props = [];
@@ -1246,6 +1251,22 @@ var DELEGATED2 = /* @__PURE__ */ new Set([
1246
1251
  "mouseup"
1247
1252
  ]);
1248
1253
  var isReactive = (node) => isReactiveShape(shapeOf(node));
1254
+ var CONTAINER2 = "container";
1255
+ function directive(node, name) {
1256
+ if (node.type !== "JSXElement") return void 0;
1257
+ const opening = node.openingElement;
1258
+ if (opening.name.type !== "JSXIdentifier" || opening.name.name[0] !== opening.name.name[0].toLowerCase()) return void 0;
1259
+ return opening.attributes.find((a) => a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === name);
1260
+ }
1261
+ function directiveExpr(attr) {
1262
+ const v = attr.value;
1263
+ return v?.type === "JSXExpressionContainer" && v.expression?.type !== "JSXEmptyExpression" ? v.expression : void 0;
1264
+ }
1265
+ function emitIR(ir, ctx) {
1266
+ const { code, imports, templates } = imperativeBackend.emit(ir, {});
1267
+ for (const s of imports) ctx.used.add(s);
1268
+ return inlineTemplates(code, templates);
1269
+ }
1249
1270
  var posOf = (node, ctx) => {
1250
1271
  if (!ctx.sourceFile) return void 0;
1251
1272
  const at = node.loc?.start;
@@ -1354,7 +1375,16 @@ function buildProps(attrs, ctx) {
1354
1375
  }
1355
1376
  function buildChildren(children, ctx) {
1356
1377
  const out = [];
1357
- for (const child of children) {
1378
+ for (let i = 0; i < children.length; i++) {
1379
+ const child = children[i];
1380
+ if (directive(child, "if")) {
1381
+ let next = i + 1;
1382
+ while (next < children.length && children[next].type === "JSXText" && !cleanTemplateText(children[next].value)) next++;
1383
+ const fallback = directive(children[next], "else") ? children[next] : null;
1384
+ out.push(buildIf(child, fallback, ctx));
1385
+ if (fallback) i = next;
1386
+ continue;
1387
+ }
1358
1388
  if (child.type === "JSXText") {
1359
1389
  const text = cleanTemplateText(child.value);
1360
1390
  if (text) out.push({ kind: "text", value: text });
@@ -1391,19 +1421,68 @@ function readLoad(attrs) {
1391
1421
  }
1392
1422
  return void 0;
1393
1423
  }
1424
+ function buildIf(node, fallback, ctx) {
1425
+ const cond = directiveExpr(directive(node, "if"));
1426
+ const props = [
1427
+ cond ? { name: "when", kind: "attr", expr: ctx.code(cond), reactive: isReactive(cond), at: posOf(cond, ctx) } : { name: "when", kind: "attr", literal: true }
1428
+ ];
1429
+ if (fallback) {
1430
+ props.push({ name: "fallback", kind: "attr", expr: emitIR(buildElement(fallback, ctx, ["else"]), ctx), jsxElement: true });
1431
+ }
1432
+ return { kind: "component", name: "Show", props, children: [buildElement(node, ctx, ["if"])] };
1433
+ }
1434
+ function buildEach(node, ctx) {
1435
+ const attr = directive(node, "each");
1436
+ if (!attr) return null;
1437
+ const list = directiveExpr(attr);
1438
+ const props = [
1439
+ list ? { name: "each", kind: "attr", expr: ctx.code(list), reactive: isReactive(list), at: posOf(list, ctx) } : { name: "each", kind: "attr", expr: "[]" }
1440
+ ];
1441
+ const key = directive(node, "key");
1442
+ if (key) {
1443
+ const keyExpr = directiveExpr(key);
1444
+ const by = isString(key.value) ? `(item) => item[${JSON.stringify(literalOf(key.value))}]` : keyExpr ? ctx.code(keyExpr) : void 0;
1445
+ if (by) props.push({ name: "by", kind: "attr", expr: by });
1446
+ }
1447
+ const content = node.children.filter((c) => !(c.type === "JSXText" && !cleanTemplateText(c.value)));
1448
+ const only = content.length === 1 && content[0].type === "JSXExpressionContainer" ? content[0].expression : null;
1449
+ let render;
1450
+ if (only && (only.type === "ArrowFunctionExpression" || only.type === "FunctionExpression")) {
1451
+ const fn = ctx.code(only);
1452
+ const live = only.type === "ArrowFunctionExpression" && only.body.type !== "BlockStatement" && isReactive(only.body);
1453
+ render = live ? `(...args) => () => (${fn})(...args)` : fn;
1454
+ } else {
1455
+ const built = buildChildren(node.children, ctx);
1456
+ const ir = built.length === 1 ? built[0] : { kind: "fragment", children: built };
1457
+ render = `() => ${paren(emitIR(ir, ctx), "arrowBody")}`;
1458
+ }
1459
+ return { kind: "component", name: "For", props, children: [{ kind: "expr", code: render, reactive: false }] };
1460
+ }
1461
+ function buildElement(node, ctx, handled) {
1462
+ const skip = new Set(handled);
1463
+ const directives = /* @__PURE__ */ new Set(["if", "else", "each", ...directive(node, "each") ? ["key"] : []]);
1464
+ const attributes = node.openingElement.attributes.filter(
1465
+ (a) => !(a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && (skip.has(a.name.name) || directives.has(a.name.name)))
1466
+ );
1467
+ const { tag } = tagInfo(node.openingElement.name, ctx);
1468
+ const each = buildEach(node, ctx);
1469
+ const children = each ? [each] : buildChildren(node.children, ctx);
1470
+ if (tag === CONTAINER2) return children.length === 1 ? children[0] : { kind: "fragment", children };
1471
+ const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
1472
+ return { kind: "element", tag, svg: SVG_TAGS.has(tag), props: buildProps(attributes, ctx), children, static: false, ...at ? { at } : {} };
1473
+ }
1394
1474
  function buildNode(node, ctx) {
1395
1475
  if (node.type === "JSXFragment") {
1396
1476
  return { kind: "fragment", children: buildChildren(node.children, ctx) };
1397
1477
  }
1478
+ if (directive(node, "if")) return buildIf(node, null, ctx);
1398
1479
  const { tag, component } = tagInfo(node.openingElement.name, ctx);
1480
+ if (!component) return buildElement(node, ctx, []);
1399
1481
  const props = buildProps(node.openingElement.attributes, ctx);
1400
1482
  const children = buildChildren(node.children, ctx);
1401
1483
  const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
1402
- if (component) {
1403
- const load = readLoad(node.openingElement.attributes);
1404
- return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
1405
- }
1406
- return { kind: "element", tag, svg: SVG_TAGS.has(tag), props, children, static: false, ...at ? { at } : {} };
1484
+ const load = readLoad(node.openingElement.attributes);
1485
+ return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
1407
1486
  }
1408
1487
  function buildJsxIR(node, ctx) {
1409
1488
  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 = [];
@@ -1212,6 +1217,22 @@ var DELEGATED2 = /* @__PURE__ */ new Set([
1212
1217
  "mouseup"
1213
1218
  ]);
1214
1219
  var isReactive = (node) => isReactiveShape(shapeOf(node));
1220
+ var CONTAINER2 = "container";
1221
+ function directive(node, name) {
1222
+ if (node.type !== "JSXElement") return void 0;
1223
+ const opening = node.openingElement;
1224
+ if (opening.name.type !== "JSXIdentifier" || opening.name.name[0] !== opening.name.name[0].toLowerCase()) return void 0;
1225
+ return opening.attributes.find((a) => a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === name);
1226
+ }
1227
+ function directiveExpr(attr) {
1228
+ const v = attr.value;
1229
+ return v?.type === "JSXExpressionContainer" && v.expression?.type !== "JSXEmptyExpression" ? v.expression : void 0;
1230
+ }
1231
+ function emitIR(ir, ctx) {
1232
+ const { code, imports, templates } = imperativeBackend.emit(ir, {});
1233
+ for (const s of imports) ctx.used.add(s);
1234
+ return inlineTemplates(code, templates);
1235
+ }
1215
1236
  var posOf = (node, ctx) => {
1216
1237
  if (!ctx.sourceFile) return void 0;
1217
1238
  const at = node.loc?.start;
@@ -1320,7 +1341,16 @@ function buildProps(attrs, ctx) {
1320
1341
  }
1321
1342
  function buildChildren(children, ctx) {
1322
1343
  const out = [];
1323
- for (const child of children) {
1344
+ for (let i = 0; i < children.length; i++) {
1345
+ const child = children[i];
1346
+ if (directive(child, "if")) {
1347
+ let next = i + 1;
1348
+ while (next < children.length && children[next].type === "JSXText" && !cleanTemplateText(children[next].value)) next++;
1349
+ const fallback = directive(children[next], "else") ? children[next] : null;
1350
+ out.push(buildIf(child, fallback, ctx));
1351
+ if (fallback) i = next;
1352
+ continue;
1353
+ }
1324
1354
  if (child.type === "JSXText") {
1325
1355
  const text = cleanTemplateText(child.value);
1326
1356
  if (text) out.push({ kind: "text", value: text });
@@ -1357,19 +1387,68 @@ function readLoad(attrs) {
1357
1387
  }
1358
1388
  return void 0;
1359
1389
  }
1390
+ function buildIf(node, fallback, ctx) {
1391
+ const cond = directiveExpr(directive(node, "if"));
1392
+ const props = [
1393
+ cond ? { name: "when", kind: "attr", expr: ctx.code(cond), reactive: isReactive(cond), at: posOf(cond, ctx) } : { name: "when", kind: "attr", literal: true }
1394
+ ];
1395
+ if (fallback) {
1396
+ props.push({ name: "fallback", kind: "attr", expr: emitIR(buildElement(fallback, ctx, ["else"]), ctx), jsxElement: true });
1397
+ }
1398
+ return { kind: "component", name: "Show", props, children: [buildElement(node, ctx, ["if"])] };
1399
+ }
1400
+ function buildEach(node, ctx) {
1401
+ const attr = directive(node, "each");
1402
+ if (!attr) return null;
1403
+ const list = directiveExpr(attr);
1404
+ const props = [
1405
+ list ? { name: "each", kind: "attr", expr: ctx.code(list), reactive: isReactive(list), at: posOf(list, ctx) } : { name: "each", kind: "attr", expr: "[]" }
1406
+ ];
1407
+ const key = directive(node, "key");
1408
+ if (key) {
1409
+ const keyExpr = directiveExpr(key);
1410
+ const by = isString(key.value) ? `(item) => item[${JSON.stringify(literalOf(key.value))}]` : keyExpr ? ctx.code(keyExpr) : void 0;
1411
+ if (by) props.push({ name: "by", kind: "attr", expr: by });
1412
+ }
1413
+ const content = node.children.filter((c) => !(c.type === "JSXText" && !cleanTemplateText(c.value)));
1414
+ const only = content.length === 1 && content[0].type === "JSXExpressionContainer" ? content[0].expression : null;
1415
+ let render;
1416
+ if (only && (only.type === "ArrowFunctionExpression" || only.type === "FunctionExpression")) {
1417
+ const fn = ctx.code(only);
1418
+ const live = only.type === "ArrowFunctionExpression" && only.body.type !== "BlockStatement" && isReactive(only.body);
1419
+ render = live ? `(...args) => () => (${fn})(...args)` : fn;
1420
+ } else {
1421
+ const built = buildChildren(node.children, ctx);
1422
+ const ir = built.length === 1 ? built[0] : { kind: "fragment", children: built };
1423
+ render = `() => ${paren(emitIR(ir, ctx), "arrowBody")}`;
1424
+ }
1425
+ return { kind: "component", name: "For", props, children: [{ kind: "expr", code: render, reactive: false }] };
1426
+ }
1427
+ function buildElement(node, ctx, handled) {
1428
+ const skip = new Set(handled);
1429
+ const directives = /* @__PURE__ */ new Set(["if", "else", "each", ...directive(node, "each") ? ["key"] : []]);
1430
+ const attributes = node.openingElement.attributes.filter(
1431
+ (a) => !(a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && (skip.has(a.name.name) || directives.has(a.name.name)))
1432
+ );
1433
+ const { tag } = tagInfo(node.openingElement.name, ctx);
1434
+ const each = buildEach(node, ctx);
1435
+ const children = each ? [each] : buildChildren(node.children, ctx);
1436
+ if (tag === CONTAINER2) return children.length === 1 ? children[0] : { kind: "fragment", children };
1437
+ const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
1438
+ return { kind: "element", tag, svg: SVG_TAGS.has(tag), props: buildProps(attributes, ctx), children, static: false, ...at ? { at } : {} };
1439
+ }
1360
1440
  function buildNode(node, ctx) {
1361
1441
  if (node.type === "JSXFragment") {
1362
1442
  return { kind: "fragment", children: buildChildren(node.children, ctx) };
1363
1443
  }
1444
+ if (directive(node, "if")) return buildIf(node, null, ctx);
1364
1445
  const { tag, component } = tagInfo(node.openingElement.name, ctx);
1446
+ if (!component) return buildElement(node, ctx, []);
1365
1447
  const props = buildProps(node.openingElement.attributes, ctx);
1366
1448
  const children = buildChildren(node.children, ctx);
1367
1449
  const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
1368
- if (component) {
1369
- const load = readLoad(node.openingElement.attributes);
1370
- return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
1371
- }
1372
- return { kind: "element", tag, svg: SVG_TAGS.has(tag), props, children, static: false, ...at ? { at } : {} };
1450
+ const load = readLoad(node.openingElement.attributes);
1451
+ return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
1373
1452
  }
1374
1453
  function buildJsxIR(node, ctx) {
1375
1454
  const ir = buildNode(node, ctx);