@fluixi/compiler 1.0.0-alpha.92 → 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 (68) hide show
  1. package/dist/analyze/intrinsics.cjs +18 -0
  2. package/dist/analyze/intrinsics.d.ts.map +1 -1
  3. package/dist/analyze/intrinsics.js +18 -0
  4. package/dist/analyze/intrinsics.mjs +18 -0
  5. package/dist/analyze/reactive-bindings.cjs +18 -0
  6. package/dist/analyze/reactive-bindings.mjs +18 -0
  7. package/dist/analyze/static-graph.cjs +22 -4
  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 +22 -4
  11. package/dist/{babel-GI375YZ2.mjs → babel-RJHJQVEM.mjs} +4 -4
  12. package/dist/{chunk-QZ4CJPYP.mjs → chunk-56QSOVVL.mjs} +1 -1
  13. package/dist/{chunk-XJ5H2MXY.mjs → chunk-OD53TMER.mjs} +19 -1
  14. package/dist/{chunk-DWNWN5RH.mjs → chunk-R7PPFBCL.mjs} +1 -1
  15. package/dist/{chunk-VOUI6XPV.mjs → chunk-TJIH7TLX.mjs} +1 -1
  16. package/dist/{chunk-VC6X44FS.mjs → chunk-U7HCFLZ2.mjs} +153 -48
  17. package/dist/codegen/backends/imperative.cjs +24 -6
  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 +24 -6
  21. package/dist/frontend/babel/build-ir-lit.cjs +120 -43
  22. package/dist/frontend/babel/build-ir-lit.mjs +120 -43
  23. package/dist/frontend/babel/build-ir.cjs +120 -43
  24. package/dist/frontend/babel/build-ir.mjs +120 -43
  25. package/dist/frontend/babel/index.cjs +121 -44
  26. package/dist/frontend/babel/index.mjs +121 -44
  27. package/dist/frontend/babel/lower-template.cjs +120 -43
  28. package/dist/frontend/babel/lower-template.mjs +120 -43
  29. package/dist/frontend/babel/plugin.cjs +120 -43
  30. package/dist/frontend/babel/plugin.mjs +120 -43
  31. package/dist/index.cjs +41 -38
  32. package/dist/index.mjs +41 -38
  33. package/dist/integrations.cjs +176 -52
  34. package/dist/integrations.mjs +8 -8
  35. package/dist/lower/compile-template.cjs +41 -38
  36. package/dist/lower/compile-template.mjs +41 -38
  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 +40 -37
  42. package/dist/lower/template.d.ts.map +1 -1
  43. package/dist/lower/template.js +22 -30
  44. package/dist/lower/template.mjs +40 -37
  45. package/dist/resolve/builtins.cjs +1 -1
  46. package/dist/resolve/builtins.mjs +1 -1
  47. package/dist/resolve/component-globals.cjs +14 -0
  48. package/dist/resolve/component-globals.mjs +14 -0
  49. package/dist/resolve/write-globals.cjs +14 -0
  50. package/dist/resolve/write-globals.mjs +14 -0
  51. package/dist/{server-fns-BBZRZUPP.mjs → server-fns-ZFRDIQ2X.mjs} +4 -4
  52. package/dist/{server-functions-VEGLIFCE.mjs → server-functions-CTDXX67X.mjs} +3 -3
  53. package/dist/transform/index.cjs +170 -47
  54. package/dist/transform/index.mjs +170 -47
  55. package/dist/transform/intrinsics.cjs +18 -0
  56. package/dist/transform/intrinsics.mjs +18 -0
  57. package/dist/transform/source-locations.cjs +64 -3
  58. package/dist/transform/source-locations.d.cts +7 -2
  59. package/dist/transform/source-locations.d.ts +7 -2
  60. package/dist/transform/source-locations.d.ts.map +1 -1
  61. package/dist/transform/source-locations.js +68 -3
  62. package/dist/transform/source-locations.mjs +64 -3
  63. package/dist/transform/templates.cjs +169 -46
  64. package/dist/transform/templates.js +2 -2
  65. package/dist/transform/templates.mjs +169 -46
  66. package/dist/{transform-UWZ7ZGJ2.mjs → transform-QWWQDCXE.mjs} +7 -7
  67. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  68. 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.
@@ -310,6 +311,19 @@ var INTRINSICS = {
310
311
  // dependency analysis must not guess at.
311
312
  $untrack: { export: "untrack", module: REACTIVE_MODULE, returns: "plain" },
312
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" },
313
327
  // An environment variable, public or server-only; the browser build refuses a secret.
314
328
  $env: { export: "env", module: "@fluixi/start/env", returns: "plain" }
315
329
  };
@@ -487,13 +501,16 @@ function emitComponent(name, props, children, used, templates, at) {
487
501
  used.add("createComponent");
488
502
  const spreads = props.filter((p) => p.kind === "spread");
489
503
  const normal = props.filter((p) => p.kind !== "spread");
490
- const entries = normal.map(
491
- (p) => `get ${propKey(p.name)}() { return ${componentPropValue(p)}; }`
492
- );
504
+ const bodies = normal.map((p) => [p.name, componentPropValue(p)]);
493
505
  if (children.length > 0) {
494
- 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(", ")} })`;
495
513
  }
496
- let obj = `{ ${entries.join(", ")} }`;
497
514
  if (spreads.length > 0) {
498
515
  used.add("mergeProps");
499
516
  obj = `mergeProps(${spreads.map((s) => s.expr).join(", ")}, ${obj})`;
@@ -575,7 +592,8 @@ function emitNode(node, used, templates) {
575
592
  } else {
576
593
  used.add("spread");
577
594
  const svgFlag = node.svg ? ", isSVG: true" : "";
578
- 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)}` : "";
579
597
  parts.push(
580
598
  `spread({ element: ${el}, props: ${emitProps(node.props, null, used)}${svgFlag}${customFlag} });`
581
599
  );
@@ -646,6 +664,7 @@ var DELEGATED = /* @__PURE__ */ new Set([
646
664
  function escapeTemplate(s) {
647
665
  return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
648
666
  }
667
+ var CONTAINER = "container";
649
668
  function capitalize(s) {
650
669
  return s.charAt(0).toUpperCase() + s.slice(1);
651
670
  }
@@ -700,10 +719,6 @@ var Lowerer = class {
700
719
  if (hasElse) i = elseIdx;
701
720
  continue;
702
721
  }
703
- if (child.attributes.some((a) => a.kind === "EachDirective")) {
704
- out.push(this.lowerEach(child));
705
- continue;
706
- }
707
722
  }
708
723
  const node = this.lowerNode(child);
709
724
  if (node) out.push(node);
@@ -745,13 +760,16 @@ var Lowerer = class {
745
760
  node.props.unshift({ name: "component", kind: "attr", expr: `() => ${paren(this.hole(isAttr.value.hole).code, "arrowBody")}` });
746
761
  return node;
747
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 };
748
766
  const at = this.tagAt(el);
749
767
  return {
750
768
  kind: "element",
751
769
  tag: el.tag,
752
770
  svg: el.namespace === "svg",
753
771
  props: this.lowerAttributes(el.attributes),
754
- children: this.lowerChildren(el.children),
772
+ children,
755
773
  static: false,
756
774
  ...at ? { at } : {}
757
775
  };
@@ -823,10 +841,14 @@ var Lowerer = class {
823
841
  const at = this.tagAt(el);
824
842
  return { kind: "component", name: "Show", props, children: [child], ...at ? { at } : {}, ...cond.at ? { bindAt: cond.at } : {} };
825
843
  }
826
- /** `<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
+ */
827
849
  lowerEach(el) {
828
850
  const eachDir = el.attributes.find((a) => a.kind === "EachDirective");
829
- if (!eachDir || eachDir.kind !== "EachDirective") return this.lowerNode(el);
851
+ if (!eachDir || eachDir.kind !== "EachDirective") return null;
830
852
  const each = this.hole(eachDir.hole);
831
853
  const props = [{ name: "each", kind: "attr", expr: each.code, reactive: each.reactive, ...each.at ? { at: each.at } : {} }];
832
854
  if (eachDir.key) {
@@ -836,12 +858,12 @@ var Lowerer = class {
836
858
  const arrow = this.itemArrow(el);
837
859
  let childrenCode;
838
860
  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")}`;
861
+ const body = arrow.bodyReactive ? `() => ${paren(arrow.body, "arrowBody")}` : paren(arrow.body, "arrowBody");
862
+ childrenCode = `(${arrow.params.join(", ")}) => ${body}`;
842
863
  } else {
843
- const repeated = this.stripAndLower(el, (a) => a.kind === "EachDirective");
844
- 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")}`;
845
867
  }
846
868
  const children = [{ kind: "expr", code: childrenCode, reactive: false }];
847
869
  const at = this.tagAt(el);
@@ -858,25 +880,6 @@ var Lowerer = class {
858
880
  const clone = { ...el, attributes: el.attributes.filter((a) => !drop(a)) };
859
881
  return clone.kind === "Element" ? this.lowerElement(clone) : this.lowerComponent(clone);
860
882
  }
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
883
  // --- attributes ---------------------------------------------------------
881
884
  lowerAttributes(attrs) {
882
885
  const props = [];
@@ -1253,6 +1256,22 @@ var DELEGATED2 = /* @__PURE__ */ new Set([
1253
1256
  "mouseup"
1254
1257
  ]);
1255
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
+ }
1256
1275
  var posOf = (node, ctx) => {
1257
1276
  if (!ctx.sourceFile) return void 0;
1258
1277
  const at = node.loc?.start;
@@ -1361,7 +1380,16 @@ function buildProps(attrs, ctx) {
1361
1380
  }
1362
1381
  function buildChildren(children, ctx) {
1363
1382
  const out = [];
1364
- 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
+ }
1365
1393
  if (child.type === "JSXText") {
1366
1394
  const text = cleanTemplateText(child.value);
1367
1395
  if (text) out.push({ kind: "text", value: text });
@@ -1398,19 +1426,68 @@ function readLoad(attrs) {
1398
1426
  }
1399
1427
  return void 0;
1400
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
+ }
1401
1479
  function buildNode(node, ctx) {
1402
1480
  if (node.type === "JSXFragment") {
1403
1481
  return { kind: "fragment", children: buildChildren(node.children, ctx) };
1404
1482
  }
1483
+ if (directive(node, "if")) return buildIf(node, null, ctx);
1405
1484
  const { tag, component } = tagInfo(node.openingElement.name, ctx);
1485
+ if (!component) return buildElement(node, ctx, []);
1406
1486
  const props = buildProps(node.openingElement.attributes, ctx);
1407
1487
  const children = buildChildren(node.children, ctx);
1408
1488
  const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
1409
- if (component) {
1410
- const load = readLoad(node.openingElement.attributes);
1411
- return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
1412
- }
1413
- 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 } : {} };
1414
1491
  }
1415
1492
  function buildJsxIR(node, ctx) {
1416
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.
@@ -278,6 +279,19 @@ var INTRINSICS = {
278
279
  // dependency analysis must not guess at.
279
280
  $untrack: { export: "untrack", module: REACTIVE_MODULE, returns: "plain" },
280
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" },
281
295
  // An environment variable, public or server-only; the browser build refuses a secret.
282
296
  $env: { export: "env", module: "@fluixi/start/env", returns: "plain" }
283
297
  };
@@ -455,13 +469,16 @@ function emitComponent(name, props, children, used, templates, at) {
455
469
  used.add("createComponent");
456
470
  const spreads = props.filter((p) => p.kind === "spread");
457
471
  const normal = props.filter((p) => p.kind !== "spread");
458
- const entries = normal.map(
459
- (p) => `get ${propKey(p.name)}() { return ${componentPropValue(p)}; }`
460
- );
472
+ const bodies = normal.map((p) => [p.name, componentPropValue(p)]);
461
473
  if (children.length > 0) {
462
- 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(", ")} })`;
463
481
  }
464
- let obj = `{ ${entries.join(", ")} }`;
465
482
  if (spreads.length > 0) {
466
483
  used.add("mergeProps");
467
484
  obj = `mergeProps(${spreads.map((s) => s.expr).join(", ")}, ${obj})`;
@@ -543,7 +560,8 @@ function emitNode(node, used, templates) {
543
560
  } else {
544
561
  used.add("spread");
545
562
  const svgFlag = node.svg ? ", isSVG: true" : "";
546
- 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)}` : "";
547
565
  parts.push(
548
566
  `spread({ element: ${el}, props: ${emitProps(node.props, null, used)}${svgFlag}${customFlag} });`
549
567
  );
@@ -614,6 +632,7 @@ var DELEGATED = /* @__PURE__ */ new Set([
614
632
  function escapeTemplate(s) {
615
633
  return s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
616
634
  }
635
+ var CONTAINER = "container";
617
636
  function capitalize(s) {
618
637
  return s.charAt(0).toUpperCase() + s.slice(1);
619
638
  }
@@ -668,10 +687,6 @@ var Lowerer = class {
668
687
  if (hasElse) i = elseIdx;
669
688
  continue;
670
689
  }
671
- if (child.attributes.some((a) => a.kind === "EachDirective")) {
672
- out.push(this.lowerEach(child));
673
- continue;
674
- }
675
690
  }
676
691
  const node = this.lowerNode(child);
677
692
  if (node) out.push(node);
@@ -713,13 +728,16 @@ var Lowerer = class {
713
728
  node.props.unshift({ name: "component", kind: "attr", expr: `() => ${paren(this.hole(isAttr.value.hole).code, "arrowBody")}` });
714
729
  return node;
715
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 };
716
734
  const at = this.tagAt(el);
717
735
  return {
718
736
  kind: "element",
719
737
  tag: el.tag,
720
738
  svg: el.namespace === "svg",
721
739
  props: this.lowerAttributes(el.attributes),
722
- children: this.lowerChildren(el.children),
740
+ children,
723
741
  static: false,
724
742
  ...at ? { at } : {}
725
743
  };
@@ -791,10 +809,14 @@ var Lowerer = class {
791
809
  const at = this.tagAt(el);
792
810
  return { kind: "component", name: "Show", props, children: [child], ...at ? { at } : {}, ...cond.at ? { bindAt: cond.at } : {} };
793
811
  }
794
- /** `<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
+ */
795
817
  lowerEach(el) {
796
818
  const eachDir = el.attributes.find((a) => a.kind === "EachDirective");
797
- if (!eachDir || eachDir.kind !== "EachDirective") return this.lowerNode(el);
819
+ if (!eachDir || eachDir.kind !== "EachDirective") return null;
798
820
  const each = this.hole(eachDir.hole);
799
821
  const props = [{ name: "each", kind: "attr", expr: each.code, reactive: each.reactive, ...each.at ? { at: each.at } : {} }];
800
822
  if (eachDir.key) {
@@ -804,12 +826,12 @@ var Lowerer = class {
804
826
  const arrow = this.itemArrow(el);
805
827
  let childrenCode;
806
828
  if (arrow) {
807
- const bodyIR = { kind: "expr", code: arrow.body, reactive: arrow.bodyReactive };
808
- const repeated = this.rebuildWithChildren(el, [bodyIR]);
809
- 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}`;
810
831
  } else {
811
- const repeated = this.stripAndLower(el, (a) => a.kind === "EachDirective");
812
- 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")}`;
813
835
  }
814
836
  const children = [{ kind: "expr", code: childrenCode, reactive: false }];
815
837
  const at = this.tagAt(el);
@@ -826,25 +848,6 @@ var Lowerer = class {
826
848
  const clone = { ...el, attributes: el.attributes.filter((a) => !drop(a)) };
827
849
  return clone.kind === "Element" ? this.lowerElement(clone) : this.lowerComponent(clone);
828
850
  }
829
- /** Same element (minus each/key) but with explicit IR children. */
830
- rebuildWithChildren(el, children) {
831
- const props = this.lowerAttributes(
832
- el.attributes.filter((a) => a.kind !== "EachDirective")
833
- );
834
- if (el.kind === "Element") {
835
- const at = this.tagAt(el);
836
- return {
837
- kind: "element",
838
- tag: el.tag,
839
- svg: el.namespace === "svg",
840
- props,
841
- children,
842
- static: false,
843
- ...at ? { at } : {}
844
- };
845
- }
846
- return { kind: "component", name: el.tag, props, children };
847
- }
848
851
  // --- attributes ---------------------------------------------------------
849
852
  lowerAttributes(attrs) {
850
853
  const props = [];
@@ -1221,6 +1224,22 @@ var DELEGATED2 = /* @__PURE__ */ new Set([
1221
1224
  "mouseup"
1222
1225
  ]);
1223
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
+ }
1224
1243
  var posOf = (node, ctx) => {
1225
1244
  if (!ctx.sourceFile) return void 0;
1226
1245
  const at = node.loc?.start;
@@ -1329,7 +1348,16 @@ function buildProps(attrs, ctx) {
1329
1348
  }
1330
1349
  function buildChildren(children, ctx) {
1331
1350
  const out = [];
1332
- 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
+ }
1333
1361
  if (child.type === "JSXText") {
1334
1362
  const text = cleanTemplateText(child.value);
1335
1363
  if (text) out.push({ kind: "text", value: text });
@@ -1366,19 +1394,68 @@ function readLoad(attrs) {
1366
1394
  }
1367
1395
  return void 0;
1368
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
+ }
1369
1447
  function buildNode(node, ctx) {
1370
1448
  if (node.type === "JSXFragment") {
1371
1449
  return { kind: "fragment", children: buildChildren(node.children, ctx) };
1372
1450
  }
1451
+ if (directive(node, "if")) return buildIf(node, null, ctx);
1373
1452
  const { tag, component } = tagInfo(node.openingElement.name, ctx);
1453
+ if (!component) return buildElement(node, ctx, []);
1374
1454
  const props = buildProps(node.openingElement.attributes, ctx);
1375
1455
  const children = buildChildren(node.children, ctx);
1376
1456
  const at = ctx.sourceFile && node.loc?.start ? { file: ctx.sourceFile, line: node.loc.start.line, column: node.loc.start.column } : void 0;
1377
- if (component) {
1378
- const load = readLoad(node.openingElement.attributes);
1379
- return { kind: "component", name: tag, props, children, ...load ? { load } : {}, ...at ? { at } : {} };
1380
- }
1381
- 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 } : {} };
1382
1459
  }
1383
1460
  function buildJsxIR(node, ctx) {
1384
1461
  const ir = buildNode(node, ctx);