@fluixi/compiler 1.0.0-alpha.104 → 1.0.0-alpha.106

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 (71) hide show
  1. package/dist/analyze/static-graph.cjs +1 -1
  2. package/dist/analyze/static-graph.mjs +1 -1
  3. package/dist/{babel-5SX36G76.mjs → babel-HJY4RVAV.mjs} +33 -20
  4. package/dist/{chunk-NWF66YTU.mjs → chunk-4XEOMQ57.mjs} +1 -1
  5. package/dist/{chunk-FMQPBKTW.mjs → chunk-JUFI6IR3.mjs} +1 -1
  6. package/dist/{chunk-NUET3BJB.mjs → chunk-LCQHL2VQ.mjs} +132 -10
  7. package/dist/{chunk-2O2YW5ZO.mjs → chunk-LGUZRHYN.mjs} +2 -2
  8. package/dist/{chunk-RBIYCE6M.mjs → chunk-TDN5MYWI.mjs} +1 -1
  9. package/dist/{chunk-ZDZKJQ5N.mjs → chunk-W7DYOMXJ.mjs} +2 -2
  10. package/dist/{chunk-DN33T5GJ.mjs → chunk-WHEDRMNR.mjs} +1 -1
  11. package/dist/codegen/backends/imperative.cjs +130 -8
  12. package/dist/codegen/backends/imperative.d.ts.map +1 -1
  13. package/dist/codegen/backends/imperative.js +62 -0
  14. package/dist/codegen/backends/imperative.mjs +130 -8
  15. package/dist/codegen/partial-template.cjs +80 -8
  16. package/dist/codegen/partial-template.d.cts +21 -4
  17. package/dist/codegen/partial-template.d.ts +21 -4
  18. package/dist/codegen/partial-template.d.ts.map +1 -1
  19. package/dist/codegen/partial-template.js +0 -0
  20. package/dist/codegen/partial-template.mjs +80 -8
  21. package/dist/codegen/serialize-static.cjs +1 -0
  22. package/dist/codegen/serialize-static.d.cts +8 -0
  23. package/dist/codegen/serialize-static.d.ts +8 -0
  24. package/dist/codegen/serialize-static.d.ts.map +1 -1
  25. package/dist/codegen/serialize-static.js +1 -1
  26. package/dist/codegen/serialize-static.mjs +1 -0
  27. package/dist/frontend/babel/build-ir-html.cjs +149 -21
  28. package/dist/frontend/babel/build-ir-html.d.cts +3 -1
  29. package/dist/frontend/babel/build-ir-html.d.ts +3 -1
  30. package/dist/frontend/babel/build-ir-html.d.ts.map +1 -1
  31. package/dist/frontend/babel/build-ir-html.mjs +149 -21
  32. package/dist/frontend/babel/build-ir.cjs +149 -21
  33. package/dist/frontend/babel/build-ir.d.cts +11 -2
  34. package/dist/frontend/babel/build-ir.d.ts +11 -2
  35. package/dist/frontend/babel/build-ir.d.ts.map +1 -1
  36. package/dist/frontend/babel/build-ir.js +15 -9
  37. package/dist/frontend/babel/build-ir.mjs +149 -21
  38. package/dist/frontend/babel/index.cjs +160 -25
  39. package/dist/frontend/babel/index.mjs +160 -25
  40. package/dist/frontend/babel/lower-template.cjs +149 -21
  41. package/dist/frontend/babel/lower-template.d.cts +2 -1
  42. package/dist/frontend/babel/lower-template.d.ts +2 -1
  43. package/dist/frontend/babel/lower-template.d.ts.map +1 -1
  44. package/dist/frontend/babel/lower-template.js +4 -4
  45. package/dist/frontend/babel/lower-template.mjs +149 -21
  46. package/dist/frontend/babel/plugin.cjs +158 -23
  47. package/dist/frontend/babel/plugin.js +9 -2
  48. package/dist/frontend/babel/plugin.mjs +158 -23
  49. package/dist/index.cjs +131 -9
  50. package/dist/index.mjs +131 -9
  51. package/dist/integrations.cjs +165 -33
  52. package/dist/integrations.mjs +11 -11
  53. package/dist/lower/compile-template.cjs +131 -9
  54. package/dist/lower/compile-template.mjs +131 -9
  55. package/dist/lower/jsx.cjs +130 -8
  56. package/dist/lower/jsx.mjs +130 -8
  57. package/dist/lower/template.cjs +130 -8
  58. package/dist/lower/template.mjs +130 -8
  59. package/dist/resolve/builtins.cjs +1 -1
  60. package/dist/resolve/builtins.mjs +1 -1
  61. package/dist/{server-fns-3Z3ZKJLB.mjs → server-fns-WZJK4BJE.mjs} +5 -5
  62. package/dist/{server-functions-DB7AHWE6.mjs → server-functions-4KTFFEO3.mjs} +3 -3
  63. package/dist/transform/index.cjs +135 -16
  64. package/dist/transform/index.mjs +135 -16
  65. package/dist/transform/templates.cjs +134 -15
  66. package/dist/transform/templates.d.ts.map +1 -1
  67. package/dist/transform/templates.js +4 -8
  68. package/dist/transform/templates.mjs +134 -15
  69. package/dist/{transform-VMK2URDB.mjs → transform-NO46NFQH.mjs} +11 -14
  70. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  71. package/package.json +4 -4
@@ -1,4 +1,4 @@
1
- /*! @fluixi/compiler v1.0.0-alpha.104 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
1
+ /*! @fluixi/compiler v1.0.0-alpha.106 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
2
  "use strict";
3
3
  var __create = Object.create;
4
4
  var __defProp = Object.defineProperty;
@@ -370,10 +370,64 @@ function isHole(node) {
370
370
  function isTemplateChild(node) {
371
371
  return node.kind === "text" || node.kind === "element" && isTemplateElement(node);
372
372
  }
373
+ var PROPERTY_BINDS = /* @__PURE__ */ new Set(["indeterminate"]);
374
+ function disposition(prop) {
375
+ if (prop.kind === "spread") return null;
376
+ if (prop.name === "ref") return "ref";
377
+ if (prop.name === "use") return "use";
378
+ if (prop.kind === "event") return prop.event ? "event" : null;
379
+ if (prop.kind === "prop") return "property";
380
+ if (prop.kind !== "attr") return null;
381
+ const name = prop.name;
382
+ if (name.startsWith("on:")) return "nativeEvent";
383
+ if (name.startsWith("prop:")) return "property";
384
+ if (name.startsWith("attr:")) return "attr";
385
+ if (name.startsWith("bool:")) return "boolAttr";
386
+ if (name === "className" || name === "class") {
387
+ return isPlainLiteralAttr(prop) ? "serialize" : "class";
388
+ }
389
+ if (name === "classList") {
390
+ if (isPlainLiteralAttr(prop)) return "serialize";
391
+ return prop.reactive ? "classListDynamic" : "classList";
392
+ }
393
+ if (name === "style") {
394
+ if (isPlainLiteralAttr(prop)) return "serialize";
395
+ return prop.reactive ? "styleDynamic" : "style";
396
+ }
397
+ if (name.includes(":")) return null;
398
+ if (PROPERTY_ONLY.has(name)) return null;
399
+ if (PROPERTY_BINDS.has(name)) return "property";
400
+ if (prop.expr === void 0) return isPlainLiteralAttr(prop) ? "serialize" : null;
401
+ return "attr";
402
+ }
403
+ function effectiveProps(el) {
404
+ const order = [];
405
+ const byName = /* @__PURE__ */ new Map();
406
+ for (const prop of el.props) {
407
+ if (!byName.has(prop.name)) order.push(prop.name);
408
+ byName.set(prop.name, prop);
409
+ }
410
+ return order.map((name) => byName.get(name));
411
+ }
412
+ function propPlan(el) {
413
+ const custom = el.tag.includes("-");
414
+ const plan = { serialize: [], bindings: [] };
415
+ for (const prop of effectiveProps(el)) {
416
+ const kind = disposition(prop);
417
+ if (kind === null) return null;
418
+ if (kind === "serialize") {
419
+ plan.serialize.push(prop);
420
+ continue;
421
+ }
422
+ if (custom) return null;
423
+ plan.bindings.push({ prop, kind });
424
+ }
425
+ return plan;
426
+ }
373
427
  function isTemplateElement(node) {
374
428
  if (node.svg) return false;
375
429
  if (isRawTextElement(node.tag)) return false;
376
- if (!node.props.every((p) => isPlainLiteralAttr(p))) return false;
430
+ if (!propPlan(node)) return false;
377
431
  return node.children.every((c) => isTemplateChild(c) || isHole(c));
378
432
  }
379
433
  function containsHole(node) {
@@ -381,6 +435,15 @@ function containsHole(node) {
381
435
  const children = node.children;
382
436
  return children ? children.some(containsHole) : false;
383
437
  }
438
+ function containsBinding(node) {
439
+ if (node.kind !== "element") return false;
440
+ const plan = propPlan(node);
441
+ if (plan && plan.bindings.length > 0) return true;
442
+ return node.children.some(containsBinding);
443
+ }
444
+ function interesting(node) {
445
+ return containsHole(node) || containsBinding(node);
446
+ }
384
447
  function addressable(el) {
385
448
  for (let i = 0; i < el.children.length; i++) {
386
449
  const child = el.children[i];
@@ -394,9 +457,10 @@ function addressable(el) {
394
457
  function planPartialTemplate(root) {
395
458
  if (!isTemplateElement(root)) return null;
396
459
  if (!addressable(root)) return null;
397
- if (!root.children.some(containsHole)) return null;
460
+ if (!interesting(root)) return null;
398
461
  const steps = [];
399
462
  const holes = [];
463
+ const bindings = [];
400
464
  let refCount = 0;
401
465
  const declare2 = (expr) => {
402
466
  const ref = `_n$${refCount++}`;
@@ -404,11 +468,17 @@ function planPartialTemplate(root) {
404
468
  return ref;
405
469
  };
406
470
  walk2(root, "_el$");
407
- return { html: serializeWithMarkers(root), tag: root.tag, steps, holes };
471
+ return { html: serializeWithMarkers(root), tag: root.tag, steps, holes, bindings };
408
472
  function walk2(el, elementRef) {
473
+ const plan = propPlan(el);
474
+ if (plan) {
475
+ for (const binding of plan.bindings) {
476
+ bindings.push({ ref: elementRef, prop: binding.prop, kind: binding.kind });
477
+ }
478
+ }
409
479
  let last = -1;
410
480
  el.children.forEach((child, i) => {
411
- if (containsHole(child)) last = i;
481
+ if (interesting(child)) last = i;
412
482
  });
413
483
  let previous = null;
414
484
  for (let i = 0; i <= last; i++) {
@@ -422,19 +492,21 @@ function planPartialTemplate(root) {
422
492
  continue;
423
493
  }
424
494
  const ref = declare2(expr);
425
- if (child.kind === "element" && containsHole(child)) walk2(child, ref);
495
+ if (child.kind === "element" && interesting(child)) walk2(child, ref);
426
496
  previous = ref;
427
497
  }
428
498
  }
429
499
  }
430
500
  function serializeWithMarkers(el) {
431
- return serializeStatic(withSentinels(el)).split(SENTINEL).join(HOLE_START + HOLE_END);
501
+ return serializeStatic(templateTree(el)).split(SENTINEL).join(HOLE_START + HOLE_END);
432
502
  }
433
- function withSentinels(el) {
503
+ function templateTree(el) {
504
+ const plan = propPlan(el);
434
505
  return {
435
506
  ...el,
507
+ props: plan ? plan.serialize : [],
436
508
  children: el.children.map(
437
- (child) => isHole(child) ? { kind: "text", value: SENTINEL } : child.kind === "element" ? withSentinels(child) : child
509
+ (child) => isHole(child) ? { kind: "text", value: SENTINEL } : child.kind === "element" ? templateTree(child) : child
438
510
  )
439
511
  };
440
512
  }
@@ -707,6 +779,55 @@ function emitComponent(name, props, children, used, templates, at) {
707
779
  const mark = at ? `globalThis.${SOURCE_MARKER}?.(${JSON.stringify(at.file)},${at.line},${at.column},${JSON.stringify(name)}), ` : "";
708
780
  return `createMemo(${paren(`${mark}() => createComponent(${name}, ${obj})`, "value")})`;
709
781
  }
782
+ function bindingTarget(binding) {
783
+ const name = binding.prop.name;
784
+ if (binding.kind === "boolAttr") return name.slice("bool:".length);
785
+ if (name.startsWith("prop:")) return name.slice("prop:".length);
786
+ if (name.startsWith("attr:")) return name.slice("attr:".length);
787
+ return htmlAttrName(name);
788
+ }
789
+ function bindingStatement(binding, used) {
790
+ const { ref, prop, kind } = binding;
791
+ const value = propValue(prop);
792
+ switch (kind) {
793
+ case "class":
794
+ used.add("setClassName");
795
+ return `setClassName(${ref}, ${value});`;
796
+ case "classList":
797
+ used.add("setClassList");
798
+ return `setClassList(${ref}, ${value}, []);`;
799
+ case "classListDynamic":
800
+ used.add("setDynamicClassList");
801
+ return `setDynamicClassList(${ref}, ${value});`;
802
+ case "style":
803
+ used.add("setStyle");
804
+ return `setStyle(${ref}, ${value}, []);`;
805
+ case "styleDynamic":
806
+ used.add("setDynamicStyle");
807
+ return `setDynamicStyle(${ref}, ${value});`;
808
+ case "attr":
809
+ used.add("setDynamicAttribute");
810
+ return `setDynamicAttribute(${ref}, ${JSON.stringify(bindingTarget(binding))}, ${value});`;
811
+ case "property":
812
+ used.add("setDynamicProperty");
813
+ return `setDynamicProperty(${ref}, ${JSON.stringify(bindingTarget(binding))}, ${value});`;
814
+ case "boolAttr":
815
+ used.add("setDynamicBoolAttribute");
816
+ return `setDynamicBoolAttribute(${ref}, ${JSON.stringify(bindingTarget(binding))}, ${value});`;
817
+ case "event":
818
+ used.add("addDelegatedEventListener");
819
+ return `addDelegatedEventListener(${ref}, ${JSON.stringify(prop.event.name)}, ${value});`;
820
+ case "nativeEvent":
821
+ used.add("addNativeEventListener");
822
+ return `addNativeEventListener(${ref}, ${JSON.stringify(prop.name.slice(3))}, ${value});`;
823
+ case "ref":
824
+ used.add("handleRef");
825
+ return `handleRef(${value}, ${ref});`;
826
+ case "use":
827
+ used.add("applyUse");
828
+ return `applyUse(${ref}, ${value});`;
829
+ }
830
+ }
710
831
  function staticAttrAssignment(el, prop) {
711
832
  const name = htmlAttrName(prop.name);
712
833
  const value = prop.literal;
@@ -757,6 +878,7 @@ function emitNode(node, used, templates) {
757
878
  `const _el$ = templateNode(${id}, ${JSON.stringify(plan.tag)}, true${at ? `, false, ${JSON.stringify(at)}` : ""});`
758
879
  ];
759
880
  for (const step of plan.steps) lines.push(`const ${step.ref} = ${step.expr};`);
881
+ for (const binding of plan.bindings) lines.push(bindingStatement(binding, used));
760
882
  for (const hole of plan.holes) {
761
883
  lines.push(
762
884
  `insert(${hole.parentRef}, holeScope(${hole.startRef}, () => ${paren(emitNode(hole.node, used, templates), "arrowBody")}), ${hole.endRef}, holeContent(${hole.startRef}, ${hole.endRef}));`
@@ -1558,16 +1680,16 @@ var generate = import_generator.default.default ?? import_generator.default;
1558
1680
  function gen(node) {
1559
1681
  return generate(node, { concise: true }).code;
1560
1682
  }
1561
- function bindHole(expr, used, onNested) {
1683
+ function bindHole(expr, used, onNested, emit) {
1562
1684
  const hole = {
1563
- code: genExpr(expr, used, onNested),
1685
+ code: genExpr(expr, used, onNested, emit),
1564
1686
  reactive: isReactiveExpr(expr)
1565
1687
  };
1566
1688
  if (import_core.types.isArrowFunctionExpression(expr) && !import_core.types.isBlockStatement(expr.body)) {
1567
1689
  const body = expr.body;
1568
1690
  hole.arrow = {
1569
1691
  params: expr.params.map((p) => gen(p)),
1570
- body: genExpr(body, used, onNested),
1692
+ body: genExpr(body, used, onNested, emit),
1571
1693
  bodyReactive: isReactiveExpr(body)
1572
1694
  };
1573
1695
  }
@@ -1576,7 +1698,7 @@ function bindHole(expr, used, onNested) {
1576
1698
  }
1577
1699
  function lowerHtmlTemplate(node, used, opts = {}) {
1578
1700
  const quasi = node.quasi;
1579
- const holes = quasi.expressions.map((e) => bindHole(e, used, opts.onNested));
1701
+ const holes = quasi.expressions.map((e) => bindHole(e, used, opts.onNested, opts.emit));
1580
1702
  return lowerTemplate(buildPieces(quasi), holes, used, opts);
1581
1703
  }
1582
1704
  function buildPieces(quasi) {
@@ -2064,7 +2186,7 @@ var generate2 = import_generator2.default.default ?? import_generator2.default;
2064
2186
  function gen2(node) {
2065
2187
  return generate2(node, { concise: true }).code;
2066
2188
  }
2067
- function transformNestedJSX(node, used, onNested) {
2189
+ function transformNestedJSX(node, used, onNested, emit) {
2068
2190
  const clone = import_core2.types.cloneNode(node, true);
2069
2191
  const file = import_core2.types.file(import_core2.types.program([import_core2.types.expressionStatement(clone)]));
2070
2192
  let changed = false;
@@ -2077,9 +2199,12 @@ function transformNestedJSX(node, used, onNested) {
2077
2199
  (0, import_core2.traverse)(file, {
2078
2200
  "JSXElement|JSXFragment"(path) {
2079
2201
  const jsx = path.node;
2080
- const nestedIR = buildIR(jsx, used, onNested);
2202
+ const nestedIR = buildIR(jsx, used, onNested, emit);
2081
2203
  onNested?.(nestedIR);
2082
- const { code, imports, templates } = imperativeBackend.emit(nestedIR, {});
2204
+ const { code, imports, templates } = imperativeBackend.emit(nestedIR, {
2205
+ templateClone: emit?.templateClone,
2206
+ partialTemplates: emit?.partialTemplates ?? true
2207
+ });
2083
2208
  emitInto(path, inlineTemplates(code, templates), imports);
2084
2209
  },
2085
2210
  // Nested html templates compose: ${cond ? html`...` : html`...`},
@@ -2087,26 +2212,29 @@ function transformNestedJSX(node, used, onNested) {
2087
2212
  TaggedTemplateExpression(path) {
2088
2213
  const tag = path.node.tag;
2089
2214
  if (!import_core2.types.isIdentifier(tag) || tag.name !== "html" && tag.name !== "svg") return;
2090
- const ir = buildIRFromHtml(path.node, used, { svg: tag.name === "svg", onNested });
2215
+ const ir = buildIRFromHtml(path.node, used, { svg: tag.name === "svg", onNested, emit });
2091
2216
  onNested?.(ir);
2092
- const { code, imports, templates } = imperativeBackend.emit(ir, {});
2217
+ const { code, imports, templates } = imperativeBackend.emit(ir, {
2218
+ templateClone: emit?.templateClone,
2219
+ partialTemplates: emit?.partialTemplates ?? true
2220
+ });
2093
2221
  emitInto(path, inlineTemplates(code, templates), imports);
2094
2222
  }
2095
2223
  });
2096
2224
  return changed ? file.program.body[0].expression : node;
2097
2225
  }
2098
- function genExpr(node, used, onNested) {
2099
- return gen2(transformNestedJSX(node, used, onNested));
2226
+ function genExpr(node, used, onNested, emit) {
2227
+ return gen2(transformNestedJSX(node, used, onNested, emit));
2100
2228
  }
2101
2229
  function isReactiveExpr(node) {
2102
2230
  return isReactiveShape(shapeOf(node));
2103
2231
  }
2104
- function buildIR(node, used = /* @__PURE__ */ new Set(), onNested) {
2232
+ function buildIR(node, used = /* @__PURE__ */ new Set(), onNested, emit) {
2105
2233
  const ir = buildJsxIR(node, {
2106
2234
  code: (n) => (
2107
2235
  // A member-expression tag (`<ns.Foo/>`) is not an Expression, so it cannot go
2108
2236
  // through the nested-JSX compile: it only ever needs printing.
2109
- n.type === "JSXMemberExpression" ? gen2(n) : genExpr(n, used, onNested)
2237
+ n.type === "JSXMemberExpression" ? gen2(n) : genExpr(n, used, onNested, emit)
2110
2238
  ),
2111
2239
  used
2112
2240
  });
@@ -2441,7 +2569,10 @@ function adoptTemplates(code, templates, state) {
2441
2569
  }
2442
2570
  function emitViaIR(node, state) {
2443
2571
  const used = /* @__PURE__ */ new Set();
2444
- const ir = buildIR(node, used, nestedHook(state));
2572
+ const ir = buildIR(node, used, nestedHook(state), {
2573
+ partialTemplates: state.partialTemplates,
2574
+ templateClone: state.templateClone
2575
+ });
2445
2576
  resolveComponentSources(ir, {
2446
2577
  resolver: state.componentResolver,
2447
2578
  modules: {
@@ -2490,7 +2621,11 @@ function dedupeImports(imports) {
2490
2621
  function emitHtmlViaIR(node, state) {
2491
2622
  const used = /* @__PURE__ */ new Set();
2492
2623
  const svg = import_core3.types.isIdentifier(node.tag) && node.tag.name === "svg";
2493
- const ir = buildIRFromHtml(node, used, { svg, onNested: nestedHook(state) });
2624
+ const ir = buildIRFromHtml(node, used, {
2625
+ svg,
2626
+ onNested: nestedHook(state),
2627
+ emit: { partialTemplates: state.partialTemplates, templateClone: state.templateClone }
2628
+ });
2494
2629
  resolveComponentSources(ir, {
2495
2630
  resolver: state.componentResolver,
2496
2631
  modules: {
@@ -2579,7 +2714,7 @@ function createPreset(options = {}) {
2579
2714
  {
2580
2715
  runtime: "automatic",
2581
2716
  importSource: "@fluixi/jsx",
2582
- development: false,
2717
+ development: process.env.NODE_ENV === "development",
2583
2718
  detectReactivity: true,
2584
2719
  useHtmlTag: true,
2585
2720
  hoistStatics: true,
@@ -1,4 +1,4 @@
1
- /*! @fluixi/compiler v1.0.0-alpha.104 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
1
+ /*! @fluixi/compiler v1.0.0-alpha.106 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
2
 
3
3
  // src/frontend/babel/plugin.ts
4
4
  import { declare } from "@babel/helper-plugin-utils";
@@ -331,10 +331,64 @@ function isHole(node) {
331
331
  function isTemplateChild(node) {
332
332
  return node.kind === "text" || node.kind === "element" && isTemplateElement(node);
333
333
  }
334
+ var PROPERTY_BINDS = /* @__PURE__ */ new Set(["indeterminate"]);
335
+ function disposition(prop) {
336
+ if (prop.kind === "spread") return null;
337
+ if (prop.name === "ref") return "ref";
338
+ if (prop.name === "use") return "use";
339
+ if (prop.kind === "event") return prop.event ? "event" : null;
340
+ if (prop.kind === "prop") return "property";
341
+ if (prop.kind !== "attr") return null;
342
+ const name = prop.name;
343
+ if (name.startsWith("on:")) return "nativeEvent";
344
+ if (name.startsWith("prop:")) return "property";
345
+ if (name.startsWith("attr:")) return "attr";
346
+ if (name.startsWith("bool:")) return "boolAttr";
347
+ if (name === "className" || name === "class") {
348
+ return isPlainLiteralAttr(prop) ? "serialize" : "class";
349
+ }
350
+ if (name === "classList") {
351
+ if (isPlainLiteralAttr(prop)) return "serialize";
352
+ return prop.reactive ? "classListDynamic" : "classList";
353
+ }
354
+ if (name === "style") {
355
+ if (isPlainLiteralAttr(prop)) return "serialize";
356
+ return prop.reactive ? "styleDynamic" : "style";
357
+ }
358
+ if (name.includes(":")) return null;
359
+ if (PROPERTY_ONLY.has(name)) return null;
360
+ if (PROPERTY_BINDS.has(name)) return "property";
361
+ if (prop.expr === void 0) return isPlainLiteralAttr(prop) ? "serialize" : null;
362
+ return "attr";
363
+ }
364
+ function effectiveProps(el) {
365
+ const order = [];
366
+ const byName = /* @__PURE__ */ new Map();
367
+ for (const prop of el.props) {
368
+ if (!byName.has(prop.name)) order.push(prop.name);
369
+ byName.set(prop.name, prop);
370
+ }
371
+ return order.map((name) => byName.get(name));
372
+ }
373
+ function propPlan(el) {
374
+ const custom = el.tag.includes("-");
375
+ const plan = { serialize: [], bindings: [] };
376
+ for (const prop of effectiveProps(el)) {
377
+ const kind = disposition(prop);
378
+ if (kind === null) return null;
379
+ if (kind === "serialize") {
380
+ plan.serialize.push(prop);
381
+ continue;
382
+ }
383
+ if (custom) return null;
384
+ plan.bindings.push({ prop, kind });
385
+ }
386
+ return plan;
387
+ }
334
388
  function isTemplateElement(node) {
335
389
  if (node.svg) return false;
336
390
  if (isRawTextElement(node.tag)) return false;
337
- if (!node.props.every((p) => isPlainLiteralAttr(p))) return false;
391
+ if (!propPlan(node)) return false;
338
392
  return node.children.every((c) => isTemplateChild(c) || isHole(c));
339
393
  }
340
394
  function containsHole(node) {
@@ -342,6 +396,15 @@ function containsHole(node) {
342
396
  const children = node.children;
343
397
  return children ? children.some(containsHole) : false;
344
398
  }
399
+ function containsBinding(node) {
400
+ if (node.kind !== "element") return false;
401
+ const plan = propPlan(node);
402
+ if (plan && plan.bindings.length > 0) return true;
403
+ return node.children.some(containsBinding);
404
+ }
405
+ function interesting(node) {
406
+ return containsHole(node) || containsBinding(node);
407
+ }
345
408
  function addressable(el) {
346
409
  for (let i = 0; i < el.children.length; i++) {
347
410
  const child = el.children[i];
@@ -355,9 +418,10 @@ function addressable(el) {
355
418
  function planPartialTemplate(root) {
356
419
  if (!isTemplateElement(root)) return null;
357
420
  if (!addressable(root)) return null;
358
- if (!root.children.some(containsHole)) return null;
421
+ if (!interesting(root)) return null;
359
422
  const steps = [];
360
423
  const holes = [];
424
+ const bindings = [];
361
425
  let refCount = 0;
362
426
  const declare2 = (expr) => {
363
427
  const ref = `_n$${refCount++}`;
@@ -365,11 +429,17 @@ function planPartialTemplate(root) {
365
429
  return ref;
366
430
  };
367
431
  walk2(root, "_el$");
368
- return { html: serializeWithMarkers(root), tag: root.tag, steps, holes };
432
+ return { html: serializeWithMarkers(root), tag: root.tag, steps, holes, bindings };
369
433
  function walk2(el, elementRef) {
434
+ const plan = propPlan(el);
435
+ if (plan) {
436
+ for (const binding of plan.bindings) {
437
+ bindings.push({ ref: elementRef, prop: binding.prop, kind: binding.kind });
438
+ }
439
+ }
370
440
  let last = -1;
371
441
  el.children.forEach((child, i) => {
372
- if (containsHole(child)) last = i;
442
+ if (interesting(child)) last = i;
373
443
  });
374
444
  let previous = null;
375
445
  for (let i = 0; i <= last; i++) {
@@ -383,19 +453,21 @@ function planPartialTemplate(root) {
383
453
  continue;
384
454
  }
385
455
  const ref = declare2(expr);
386
- if (child.kind === "element" && containsHole(child)) walk2(child, ref);
456
+ if (child.kind === "element" && interesting(child)) walk2(child, ref);
387
457
  previous = ref;
388
458
  }
389
459
  }
390
460
  }
391
461
  function serializeWithMarkers(el) {
392
- return serializeStatic(withSentinels(el)).split(SENTINEL).join(HOLE_START + HOLE_END);
462
+ return serializeStatic(templateTree(el)).split(SENTINEL).join(HOLE_START + HOLE_END);
393
463
  }
394
- function withSentinels(el) {
464
+ function templateTree(el) {
465
+ const plan = propPlan(el);
395
466
  return {
396
467
  ...el,
468
+ props: plan ? plan.serialize : [],
397
469
  children: el.children.map(
398
- (child) => isHole(child) ? { kind: "text", value: SENTINEL } : child.kind === "element" ? withSentinels(child) : child
470
+ (child) => isHole(child) ? { kind: "text", value: SENTINEL } : child.kind === "element" ? templateTree(child) : child
399
471
  )
400
472
  };
401
473
  }
@@ -668,6 +740,55 @@ function emitComponent(name, props, children, used, templates, at) {
668
740
  const mark = at ? `globalThis.${SOURCE_MARKER}?.(${JSON.stringify(at.file)},${at.line},${at.column},${JSON.stringify(name)}), ` : "";
669
741
  return `createMemo(${paren(`${mark}() => createComponent(${name}, ${obj})`, "value")})`;
670
742
  }
743
+ function bindingTarget(binding) {
744
+ const name = binding.prop.name;
745
+ if (binding.kind === "boolAttr") return name.slice("bool:".length);
746
+ if (name.startsWith("prop:")) return name.slice("prop:".length);
747
+ if (name.startsWith("attr:")) return name.slice("attr:".length);
748
+ return htmlAttrName(name);
749
+ }
750
+ function bindingStatement(binding, used) {
751
+ const { ref, prop, kind } = binding;
752
+ const value = propValue(prop);
753
+ switch (kind) {
754
+ case "class":
755
+ used.add("setClassName");
756
+ return `setClassName(${ref}, ${value});`;
757
+ case "classList":
758
+ used.add("setClassList");
759
+ return `setClassList(${ref}, ${value}, []);`;
760
+ case "classListDynamic":
761
+ used.add("setDynamicClassList");
762
+ return `setDynamicClassList(${ref}, ${value});`;
763
+ case "style":
764
+ used.add("setStyle");
765
+ return `setStyle(${ref}, ${value}, []);`;
766
+ case "styleDynamic":
767
+ used.add("setDynamicStyle");
768
+ return `setDynamicStyle(${ref}, ${value});`;
769
+ case "attr":
770
+ used.add("setDynamicAttribute");
771
+ return `setDynamicAttribute(${ref}, ${JSON.stringify(bindingTarget(binding))}, ${value});`;
772
+ case "property":
773
+ used.add("setDynamicProperty");
774
+ return `setDynamicProperty(${ref}, ${JSON.stringify(bindingTarget(binding))}, ${value});`;
775
+ case "boolAttr":
776
+ used.add("setDynamicBoolAttribute");
777
+ return `setDynamicBoolAttribute(${ref}, ${JSON.stringify(bindingTarget(binding))}, ${value});`;
778
+ case "event":
779
+ used.add("addDelegatedEventListener");
780
+ return `addDelegatedEventListener(${ref}, ${JSON.stringify(prop.event.name)}, ${value});`;
781
+ case "nativeEvent":
782
+ used.add("addNativeEventListener");
783
+ return `addNativeEventListener(${ref}, ${JSON.stringify(prop.name.slice(3))}, ${value});`;
784
+ case "ref":
785
+ used.add("handleRef");
786
+ return `handleRef(${value}, ${ref});`;
787
+ case "use":
788
+ used.add("applyUse");
789
+ return `applyUse(${ref}, ${value});`;
790
+ }
791
+ }
671
792
  function staticAttrAssignment(el, prop) {
672
793
  const name = htmlAttrName(prop.name);
673
794
  const value = prop.literal;
@@ -718,6 +839,7 @@ function emitNode(node, used, templates) {
718
839
  `const _el$ = templateNode(${id}, ${JSON.stringify(plan.tag)}, true${at ? `, false, ${JSON.stringify(at)}` : ""});`
719
840
  ];
720
841
  for (const step of plan.steps) lines.push(`const ${step.ref} = ${step.expr};`);
842
+ for (const binding of plan.bindings) lines.push(bindingStatement(binding, used));
721
843
  for (const hole of plan.holes) {
722
844
  lines.push(
723
845
  `insert(${hole.parentRef}, holeScope(${hole.startRef}, () => ${paren(emitNode(hole.node, used, templates), "arrowBody")}), ${hole.endRef}, holeContent(${hole.startRef}, ${hole.endRef}));`
@@ -1521,16 +1643,16 @@ var generate = _generate.default ?? _generate;
1521
1643
  function gen(node) {
1522
1644
  return generate(node, { concise: true }).code;
1523
1645
  }
1524
- function bindHole(expr, used, onNested) {
1646
+ function bindHole(expr, used, onNested, emit) {
1525
1647
  const hole = {
1526
- code: genExpr(expr, used, onNested),
1648
+ code: genExpr(expr, used, onNested, emit),
1527
1649
  reactive: isReactiveExpr(expr)
1528
1650
  };
1529
1651
  if (t.isArrowFunctionExpression(expr) && !t.isBlockStatement(expr.body)) {
1530
1652
  const body = expr.body;
1531
1653
  hole.arrow = {
1532
1654
  params: expr.params.map((p) => gen(p)),
1533
- body: genExpr(body, used, onNested),
1655
+ body: genExpr(body, used, onNested, emit),
1534
1656
  bodyReactive: isReactiveExpr(body)
1535
1657
  };
1536
1658
  }
@@ -1539,7 +1661,7 @@ function bindHole(expr, used, onNested) {
1539
1661
  }
1540
1662
  function lowerHtmlTemplate(node, used, opts = {}) {
1541
1663
  const quasi = node.quasi;
1542
- const holes = quasi.expressions.map((e) => bindHole(e, used, opts.onNested));
1664
+ const holes = quasi.expressions.map((e) => bindHole(e, used, opts.onNested, opts.emit));
1543
1665
  return lowerTemplate(buildPieces(quasi), holes, used, opts);
1544
1666
  }
1545
1667
  function buildPieces(quasi) {
@@ -2027,7 +2149,7 @@ var generate2 = _generate2.default ?? _generate2;
2027
2149
  function gen2(node) {
2028
2150
  return generate2(node, { concise: true }).code;
2029
2151
  }
2030
- function transformNestedJSX(node, used, onNested) {
2152
+ function transformNestedJSX(node, used, onNested, emit) {
2031
2153
  const clone = t2.cloneNode(node, true);
2032
2154
  const file = t2.file(t2.program([t2.expressionStatement(clone)]));
2033
2155
  let changed = false;
@@ -2040,9 +2162,12 @@ function transformNestedJSX(node, used, onNested) {
2040
2162
  traverse(file, {
2041
2163
  "JSXElement|JSXFragment"(path) {
2042
2164
  const jsx = path.node;
2043
- const nestedIR = buildIR(jsx, used, onNested);
2165
+ const nestedIR = buildIR(jsx, used, onNested, emit);
2044
2166
  onNested?.(nestedIR);
2045
- const { code, imports, templates } = imperativeBackend.emit(nestedIR, {});
2167
+ const { code, imports, templates } = imperativeBackend.emit(nestedIR, {
2168
+ templateClone: emit?.templateClone,
2169
+ partialTemplates: emit?.partialTemplates ?? true
2170
+ });
2046
2171
  emitInto(path, inlineTemplates(code, templates), imports);
2047
2172
  },
2048
2173
  // Nested html templates compose: ${cond ? html`...` : html`...`},
@@ -2050,26 +2175,29 @@ function transformNestedJSX(node, used, onNested) {
2050
2175
  TaggedTemplateExpression(path) {
2051
2176
  const tag = path.node.tag;
2052
2177
  if (!t2.isIdentifier(tag) || tag.name !== "html" && tag.name !== "svg") return;
2053
- const ir = buildIRFromHtml(path.node, used, { svg: tag.name === "svg", onNested });
2178
+ const ir = buildIRFromHtml(path.node, used, { svg: tag.name === "svg", onNested, emit });
2054
2179
  onNested?.(ir);
2055
- const { code, imports, templates } = imperativeBackend.emit(ir, {});
2180
+ const { code, imports, templates } = imperativeBackend.emit(ir, {
2181
+ templateClone: emit?.templateClone,
2182
+ partialTemplates: emit?.partialTemplates ?? true
2183
+ });
2056
2184
  emitInto(path, inlineTemplates(code, templates), imports);
2057
2185
  }
2058
2186
  });
2059
2187
  return changed ? file.program.body[0].expression : node;
2060
2188
  }
2061
- function genExpr(node, used, onNested) {
2062
- return gen2(transformNestedJSX(node, used, onNested));
2189
+ function genExpr(node, used, onNested, emit) {
2190
+ return gen2(transformNestedJSX(node, used, onNested, emit));
2063
2191
  }
2064
2192
  function isReactiveExpr(node) {
2065
2193
  return isReactiveShape(shapeOf(node));
2066
2194
  }
2067
- function buildIR(node, used = /* @__PURE__ */ new Set(), onNested) {
2195
+ function buildIR(node, used = /* @__PURE__ */ new Set(), onNested, emit) {
2068
2196
  const ir = buildJsxIR(node, {
2069
2197
  code: (n) => (
2070
2198
  // A member-expression tag (`<ns.Foo/>`) is not an Expression, so it cannot go
2071
2199
  // through the nested-JSX compile: it only ever needs printing.
2072
- n.type === "JSXMemberExpression" ? gen2(n) : genExpr(n, used, onNested)
2200
+ n.type === "JSXMemberExpression" ? gen2(n) : genExpr(n, used, onNested, emit)
2073
2201
  ),
2074
2202
  used
2075
2203
  });
@@ -2404,7 +2532,10 @@ function adoptTemplates(code, templates, state) {
2404
2532
  }
2405
2533
  function emitViaIR(node, state) {
2406
2534
  const used = /* @__PURE__ */ new Set();
2407
- const ir = buildIR(node, used, nestedHook(state));
2535
+ const ir = buildIR(node, used, nestedHook(state), {
2536
+ partialTemplates: state.partialTemplates,
2537
+ templateClone: state.templateClone
2538
+ });
2408
2539
  resolveComponentSources(ir, {
2409
2540
  resolver: state.componentResolver,
2410
2541
  modules: {
@@ -2453,7 +2584,11 @@ function dedupeImports(imports) {
2453
2584
  function emitHtmlViaIR(node, state) {
2454
2585
  const used = /* @__PURE__ */ new Set();
2455
2586
  const svg = t3.isIdentifier(node.tag) && node.tag.name === "svg";
2456
- const ir = buildIRFromHtml(node, used, { svg, onNested: nestedHook(state) });
2587
+ const ir = buildIRFromHtml(node, used, {
2588
+ svg,
2589
+ onNested: nestedHook(state),
2590
+ emit: { partialTemplates: state.partialTemplates, templateClone: state.templateClone }
2591
+ });
2457
2592
  resolveComponentSources(ir, {
2458
2593
  resolver: state.componentResolver,
2459
2594
  modules: {
@@ -2542,7 +2677,7 @@ function createPreset(options = {}) {
2542
2677
  {
2543
2678
  runtime: "automatic",
2544
2679
  importSource: "@fluixi/jsx",
2545
- development: false,
2680
+ development: process.env.NODE_ENV === "development",
2546
2681
  detectReactivity: true,
2547
2682
  useHtmlTag: true,
2548
2683
  hoistStatics: true,