@fluixi/compiler 1.0.0-alpha.105 → 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-USPHNEJF.mjs → babel-HJY4RVAV.mjs} +33 -20
  4. package/dist/{chunk-OQYQ7UTD.mjs → chunk-4XEOMQ57.mjs} +1 -1
  5. package/dist/{chunk-EK4OMGTU.mjs → chunk-JUFI6IR3.mjs} +1 -1
  6. package/dist/{chunk-5GAYDAAC.mjs → chunk-LCQHL2VQ.mjs} +132 -10
  7. package/dist/{chunk-XBAQME2V.mjs → chunk-LGUZRHYN.mjs} +2 -2
  8. package/dist/{chunk-O6OBEV4V.mjs → chunk-TDN5MYWI.mjs} +1 -1
  9. package/dist/{chunk-EQIIWIEU.mjs → chunk-W7DYOMXJ.mjs} +2 -2
  10. package/dist/{chunk-FMIIK5UM.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-7Y25MR3B.mjs → server-fns-WZJK4BJE.mjs} +5 -5
  62. package/dist/{server-functions-43CFKXY3.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-YUDW7K4J.mjs → transform-NO46NFQH.mjs} +11 -14
  70. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  71. package/package.json +4 -4
@@ -178,6 +178,65 @@ function emitComponent(name, props, children, used, templates, at) {
178
178
  // Without one there is nothing to wrap, and a production build never has one.
179
179
  return `createMemo(${paren(`${mark}() => createComponent(${name}, ${obj})`, 'value')})`;
180
180
  }
181
+ /** The attribute a binding writes: a forced name stripped, a plain name in its HTML spelling. */
182
+ function bindingTarget(binding) {
183
+ const name = binding.prop.name;
184
+ if (binding.kind === 'boolAttr')
185
+ return name.slice('bool:'.length);
186
+ if (name.startsWith('prop:'))
187
+ return name.slice('prop:'.length);
188
+ if (name.startsWith('attr:'))
189
+ return name.slice('attr:'.length);
190
+ return htmlAttrName(name);
191
+ }
192
+ /**
193
+ * One attribute bound on a walked template node, through the same helper
194
+ * `spread` applies for that prop. A binding replaces the generic walk over
195
+ * every prop: what it does is decided here, once, instead of per element
196
+ * at runtime.
197
+ */
198
+ function bindingStatement(binding, used) {
199
+ const { ref, prop, kind } = binding;
200
+ const value = propValue(prop);
201
+ switch (kind) {
202
+ case 'class':
203
+ used.add('setClassName');
204
+ return `setClassName(${ref}, ${value});`;
205
+ case 'classList':
206
+ used.add('setClassList');
207
+ return `setClassList(${ref}, ${value}, []);`;
208
+ case 'classListDynamic':
209
+ used.add('setDynamicClassList');
210
+ return `setDynamicClassList(${ref}, ${value});`;
211
+ case 'style':
212
+ used.add('setStyle');
213
+ return `setStyle(${ref}, ${value}, []);`;
214
+ case 'styleDynamic':
215
+ used.add('setDynamicStyle');
216
+ return `setDynamicStyle(${ref}, ${value});`;
217
+ case 'attr':
218
+ used.add('setDynamicAttribute');
219
+ return `setDynamicAttribute(${ref}, ${JSON.stringify(bindingTarget(binding))}, ${value});`;
220
+ case 'property':
221
+ used.add('setDynamicProperty');
222
+ return `setDynamicProperty(${ref}, ${JSON.stringify(bindingTarget(binding))}, ${value});`;
223
+ case 'boolAttr':
224
+ used.add('setDynamicBoolAttribute');
225
+ return `setDynamicBoolAttribute(${ref}, ${JSON.stringify(bindingTarget(binding))}, ${value});`;
226
+ case 'event':
227
+ used.add('addDelegatedEventListener');
228
+ return `addDelegatedEventListener(${ref}, ${JSON.stringify(prop.event.name)}, ${value});`;
229
+ case 'nativeEvent':
230
+ used.add('addNativeEventListener');
231
+ return `addNativeEventListener(${ref}, ${JSON.stringify(prop.name.slice(3))}, ${value});`;
232
+ case 'ref':
233
+ used.add('handleRef');
234
+ return `handleRef(${value}, ${ref});`;
235
+ case 'use':
236
+ used.add('applyUse');
237
+ return `applyUse(${ref}, ${value});`;
238
+ }
239
+ }
181
240
  /** `el.setAttribute("class", "card")` for a compile-time constant. */
182
241
  function staticAttrAssignment(el, prop) {
183
242
  const name = htmlAttrName(prop.name);
@@ -253,6 +312,9 @@ function emitNode(node, used, templates) {
253
312
  // complete before any content goes in.
254
313
  for (const step of plan.steps)
255
314
  lines.push(`const ${step.ref} = ${step.expr};`);
315
+ // Attributes next, as spread applies props before children are inserted.
316
+ for (const binding of plan.bindings)
317
+ lines.push(bindingStatement(binding, used));
256
318
  for (const hole of plan.holes) {
257
319
  // The fourth argument is what the hole already holds, nothing on a fresh
258
320
  // clone, the server's content when hydrating. `insert` reconciles against it,
@@ -104,10 +104,64 @@ function isHole(node) {
104
104
  function isTemplateChild(node) {
105
105
  return node.kind === "text" || node.kind === "element" && isTemplateElement(node);
106
106
  }
107
+ var PROPERTY_BINDS = /* @__PURE__ */ new Set(["indeterminate"]);
108
+ function disposition(prop) {
109
+ if (prop.kind === "spread") return null;
110
+ if (prop.name === "ref") return "ref";
111
+ if (prop.name === "use") return "use";
112
+ if (prop.kind === "event") return prop.event ? "event" : null;
113
+ if (prop.kind === "prop") return "property";
114
+ if (prop.kind !== "attr") return null;
115
+ const name = prop.name;
116
+ if (name.startsWith("on:")) return "nativeEvent";
117
+ if (name.startsWith("prop:")) return "property";
118
+ if (name.startsWith("attr:")) return "attr";
119
+ if (name.startsWith("bool:")) return "boolAttr";
120
+ if (name === "className" || name === "class") {
121
+ return isPlainLiteralAttr(prop) ? "serialize" : "class";
122
+ }
123
+ if (name === "classList") {
124
+ if (isPlainLiteralAttr(prop)) return "serialize";
125
+ return prop.reactive ? "classListDynamic" : "classList";
126
+ }
127
+ if (name === "style") {
128
+ if (isPlainLiteralAttr(prop)) return "serialize";
129
+ return prop.reactive ? "styleDynamic" : "style";
130
+ }
131
+ if (name.includes(":")) return null;
132
+ if (PROPERTY_ONLY.has(name)) return null;
133
+ if (PROPERTY_BINDS.has(name)) return "property";
134
+ if (prop.expr === void 0) return isPlainLiteralAttr(prop) ? "serialize" : null;
135
+ return "attr";
136
+ }
137
+ function effectiveProps(el) {
138
+ const order = [];
139
+ const byName = /* @__PURE__ */ new Map();
140
+ for (const prop of el.props) {
141
+ if (!byName.has(prop.name)) order.push(prop.name);
142
+ byName.set(prop.name, prop);
143
+ }
144
+ return order.map((name) => byName.get(name));
145
+ }
146
+ function propPlan(el) {
147
+ const custom = el.tag.includes("-");
148
+ const plan = { serialize: [], bindings: [] };
149
+ for (const prop of effectiveProps(el)) {
150
+ const kind = disposition(prop);
151
+ if (kind === null) return null;
152
+ if (kind === "serialize") {
153
+ plan.serialize.push(prop);
154
+ continue;
155
+ }
156
+ if (custom) return null;
157
+ plan.bindings.push({ prop, kind });
158
+ }
159
+ return plan;
160
+ }
107
161
  function isTemplateElement(node) {
108
162
  if (node.svg) return false;
109
163
  if (isRawTextElement(node.tag)) return false;
110
- if (!node.props.every((p) => isPlainLiteralAttr(p))) return false;
164
+ if (!propPlan(node)) return false;
111
165
  return node.children.every((c) => isTemplateChild(c) || isHole(c));
112
166
  }
113
167
  function containsHole(node) {
@@ -115,6 +169,15 @@ function containsHole(node) {
115
169
  const children = node.children;
116
170
  return children ? children.some(containsHole) : false;
117
171
  }
172
+ function containsBinding(node) {
173
+ if (node.kind !== "element") return false;
174
+ const plan = propPlan(node);
175
+ if (plan && plan.bindings.length > 0) return true;
176
+ return node.children.some(containsBinding);
177
+ }
178
+ function interesting(node) {
179
+ return containsHole(node) || containsBinding(node);
180
+ }
118
181
  function addressable(el) {
119
182
  for (let i = 0; i < el.children.length; i++) {
120
183
  const child = el.children[i];
@@ -128,9 +191,10 @@ function addressable(el) {
128
191
  function planPartialTemplate(root) {
129
192
  if (!isTemplateElement(root)) return null;
130
193
  if (!addressable(root)) return null;
131
- if (!root.children.some(containsHole)) return null;
194
+ if (!interesting(root)) return null;
132
195
  const steps = [];
133
196
  const holes = [];
197
+ const bindings = [];
134
198
  let refCount = 0;
135
199
  const declare = (expr) => {
136
200
  const ref = `_n$${refCount++}`;
@@ -138,11 +202,17 @@ function planPartialTemplate(root) {
138
202
  return ref;
139
203
  };
140
204
  walk(root, "_el$");
141
- return { html: serializeWithMarkers(root), tag: root.tag, steps, holes };
205
+ return { html: serializeWithMarkers(root), tag: root.tag, steps, holes, bindings };
142
206
  function walk(el, elementRef) {
207
+ const plan = propPlan(el);
208
+ if (plan) {
209
+ for (const binding of plan.bindings) {
210
+ bindings.push({ ref: elementRef, prop: binding.prop, kind: binding.kind });
211
+ }
212
+ }
143
213
  let last = -1;
144
214
  el.children.forEach((child, i) => {
145
- if (containsHole(child)) last = i;
215
+ if (interesting(child)) last = i;
146
216
  });
147
217
  let previous = null;
148
218
  for (let i = 0; i <= last; i++) {
@@ -156,19 +226,21 @@ function planPartialTemplate(root) {
156
226
  continue;
157
227
  }
158
228
  const ref = declare(expr);
159
- if (child.kind === "element" && containsHole(child)) walk(child, ref);
229
+ if (child.kind === "element" && interesting(child)) walk(child, ref);
160
230
  previous = ref;
161
231
  }
162
232
  }
163
233
  }
164
234
  function serializeWithMarkers(el) {
165
- return serializeStatic(withSentinels(el)).split(SENTINEL).join(HOLE_START + HOLE_END);
235
+ return serializeStatic(templateTree(el)).split(SENTINEL).join(HOLE_START + HOLE_END);
166
236
  }
167
- function withSentinels(el) {
237
+ function templateTree(el) {
238
+ const plan = propPlan(el);
168
239
  return {
169
240
  ...el,
241
+ props: plan ? plan.serialize : [],
170
242
  children: el.children.map(
171
- (child) => isHole(child) ? { kind: "text", value: SENTINEL } : child.kind === "element" ? withSentinels(child) : child
243
+ (child) => isHole(child) ? { kind: "text", value: SENTINEL } : child.kind === "element" ? templateTree(child) : child
172
244
  )
173
245
  };
174
246
  }
@@ -441,6 +513,55 @@ function emitComponent(name, props, children, used, templates, at) {
441
513
  const mark = at ? `globalThis.${SOURCE_MARKER}?.(${JSON.stringify(at.file)},${at.line},${at.column},${JSON.stringify(name)}), ` : "";
442
514
  return `createMemo(${paren(`${mark}() => createComponent(${name}, ${obj})`, "value")})`;
443
515
  }
516
+ function bindingTarget(binding) {
517
+ const name = binding.prop.name;
518
+ if (binding.kind === "boolAttr") return name.slice("bool:".length);
519
+ if (name.startsWith("prop:")) return name.slice("prop:".length);
520
+ if (name.startsWith("attr:")) return name.slice("attr:".length);
521
+ return htmlAttrName(name);
522
+ }
523
+ function bindingStatement(binding, used) {
524
+ const { ref, prop, kind } = binding;
525
+ const value = propValue(prop);
526
+ switch (kind) {
527
+ case "class":
528
+ used.add("setClassName");
529
+ return `setClassName(${ref}, ${value});`;
530
+ case "classList":
531
+ used.add("setClassList");
532
+ return `setClassList(${ref}, ${value}, []);`;
533
+ case "classListDynamic":
534
+ used.add("setDynamicClassList");
535
+ return `setDynamicClassList(${ref}, ${value});`;
536
+ case "style":
537
+ used.add("setStyle");
538
+ return `setStyle(${ref}, ${value}, []);`;
539
+ case "styleDynamic":
540
+ used.add("setDynamicStyle");
541
+ return `setDynamicStyle(${ref}, ${value});`;
542
+ case "attr":
543
+ used.add("setDynamicAttribute");
544
+ return `setDynamicAttribute(${ref}, ${JSON.stringify(bindingTarget(binding))}, ${value});`;
545
+ case "property":
546
+ used.add("setDynamicProperty");
547
+ return `setDynamicProperty(${ref}, ${JSON.stringify(bindingTarget(binding))}, ${value});`;
548
+ case "boolAttr":
549
+ used.add("setDynamicBoolAttribute");
550
+ return `setDynamicBoolAttribute(${ref}, ${JSON.stringify(bindingTarget(binding))}, ${value});`;
551
+ case "event":
552
+ used.add("addDelegatedEventListener");
553
+ return `addDelegatedEventListener(${ref}, ${JSON.stringify(prop.event.name)}, ${value});`;
554
+ case "nativeEvent":
555
+ used.add("addNativeEventListener");
556
+ return `addNativeEventListener(${ref}, ${JSON.stringify(prop.name.slice(3))}, ${value});`;
557
+ case "ref":
558
+ used.add("handleRef");
559
+ return `handleRef(${value}, ${ref});`;
560
+ case "use":
561
+ used.add("applyUse");
562
+ return `applyUse(${ref}, ${value});`;
563
+ }
564
+ }
444
565
  function staticAttrAssignment(el, prop) {
445
566
  const name = htmlAttrName(prop.name);
446
567
  const value = prop.literal;
@@ -491,6 +612,7 @@ function emitNode(node, used, templates) {
491
612
  `const _el$ = templateNode(${id}, ${JSON.stringify(plan.tag)}, true${at ? `, false, ${JSON.stringify(at)}` : ""});`
492
613
  ];
493
614
  for (const step of plan.steps) lines.push(`const ${step.ref} = ${step.expr};`);
615
+ for (const binding of plan.bindings) lines.push(bindingStatement(binding, used));
494
616
  for (const hole of plan.holes) {
495
617
  lines.push(
496
618
  `insert(${hole.parentRef}, holeScope(${hole.startRef}, () => ${paren(emitNode(hole.node, used, templates), "arrowBody")}), ${hole.endRef}, holeContent(${hole.startRef}, ${hole.endRef}));`
@@ -105,10 +105,64 @@ function isHole(node) {
105
105
  function isTemplateChild(node) {
106
106
  return node.kind === "text" || node.kind === "element" && isTemplateElement(node);
107
107
  }
108
+ var PROPERTY_BINDS = /* @__PURE__ */ new Set(["indeterminate"]);
109
+ function disposition(prop) {
110
+ if (prop.kind === "spread") return null;
111
+ if (prop.name === "ref") return "ref";
112
+ if (prop.name === "use") return "use";
113
+ if (prop.kind === "event") return prop.event ? "event" : null;
114
+ if (prop.kind === "prop") return "property";
115
+ if (prop.kind !== "attr") return null;
116
+ const name = prop.name;
117
+ if (name.startsWith("on:")) return "nativeEvent";
118
+ if (name.startsWith("prop:")) return "property";
119
+ if (name.startsWith("attr:")) return "attr";
120
+ if (name.startsWith("bool:")) return "boolAttr";
121
+ if (name === "className" || name === "class") {
122
+ return isPlainLiteralAttr(prop) ? "serialize" : "class";
123
+ }
124
+ if (name === "classList") {
125
+ if (isPlainLiteralAttr(prop)) return "serialize";
126
+ return prop.reactive ? "classListDynamic" : "classList";
127
+ }
128
+ if (name === "style") {
129
+ if (isPlainLiteralAttr(prop)) return "serialize";
130
+ return prop.reactive ? "styleDynamic" : "style";
131
+ }
132
+ if (name.includes(":")) return null;
133
+ if (PROPERTY_ONLY.has(name)) return null;
134
+ if (PROPERTY_BINDS.has(name)) return "property";
135
+ if (prop.expr === void 0) return isPlainLiteralAttr(prop) ? "serialize" : null;
136
+ return "attr";
137
+ }
138
+ function effectiveProps(el) {
139
+ const order = [];
140
+ const byName = /* @__PURE__ */ new Map();
141
+ for (const prop of el.props) {
142
+ if (!byName.has(prop.name)) order.push(prop.name);
143
+ byName.set(prop.name, prop);
144
+ }
145
+ return order.map((name) => byName.get(name));
146
+ }
147
+ function propPlan(el) {
148
+ const custom = el.tag.includes("-");
149
+ const plan = { serialize: [], bindings: [] };
150
+ for (const prop of effectiveProps(el)) {
151
+ const kind = disposition(prop);
152
+ if (kind === null) return null;
153
+ if (kind === "serialize") {
154
+ plan.serialize.push(prop);
155
+ continue;
156
+ }
157
+ if (custom) return null;
158
+ plan.bindings.push({ prop, kind });
159
+ }
160
+ return plan;
161
+ }
108
162
  function isTemplateElement(node) {
109
163
  if (node.svg) return false;
110
164
  if (isRawTextElement(node.tag)) return false;
111
- if (!node.props.every((p) => isPlainLiteralAttr(p))) return false;
165
+ if (!propPlan(node)) return false;
112
166
  return node.children.every((c) => isTemplateChild(c) || isHole(c));
113
167
  }
114
168
  function containsHole(node) {
@@ -116,6 +170,15 @@ function containsHole(node) {
116
170
  const children = node.children;
117
171
  return children ? children.some(containsHole) : false;
118
172
  }
173
+ function containsBinding(node) {
174
+ if (node.kind !== "element") return false;
175
+ const plan = propPlan(node);
176
+ if (plan && plan.bindings.length > 0) return true;
177
+ return node.children.some(containsBinding);
178
+ }
179
+ function interesting(node) {
180
+ return containsHole(node) || containsBinding(node);
181
+ }
119
182
  function addressable(el) {
120
183
  for (let i = 0; i < el.children.length; i++) {
121
184
  const child = el.children[i];
@@ -129,9 +192,10 @@ function addressable(el) {
129
192
  function planPartialTemplate(root) {
130
193
  if (!isTemplateElement(root)) return null;
131
194
  if (!addressable(root)) return null;
132
- if (!root.children.some(containsHole)) return null;
195
+ if (!interesting(root)) return null;
133
196
  const steps = [];
134
197
  const holes = [];
198
+ const bindings = [];
135
199
  let refCount = 0;
136
200
  const declare = (expr) => {
137
201
  const ref = `_n$${refCount++}`;
@@ -139,11 +203,17 @@ function planPartialTemplate(root) {
139
203
  return ref;
140
204
  };
141
205
  walk(root, "_el$");
142
- return { html: serializeWithMarkers(root), tag: root.tag, steps, holes };
206
+ return { html: serializeWithMarkers(root), tag: root.tag, steps, holes, bindings };
143
207
  function walk(el, elementRef) {
208
+ const plan = propPlan(el);
209
+ if (plan) {
210
+ for (const binding of plan.bindings) {
211
+ bindings.push({ ref: elementRef, prop: binding.prop, kind: binding.kind });
212
+ }
213
+ }
144
214
  let last = -1;
145
215
  el.children.forEach((child, i) => {
146
- if (containsHole(child)) last = i;
216
+ if (interesting(child)) last = i;
147
217
  });
148
218
  let previous = null;
149
219
  for (let i = 0; i <= last; i++) {
@@ -157,19 +227,21 @@ function planPartialTemplate(root) {
157
227
  continue;
158
228
  }
159
229
  const ref = declare(expr);
160
- if (child.kind === "element" && containsHole(child)) walk(child, ref);
230
+ if (child.kind === "element" && interesting(child)) walk(child, ref);
161
231
  previous = ref;
162
232
  }
163
233
  }
164
234
  }
165
235
  function serializeWithMarkers(el) {
166
- return serializeStatic(withSentinels(el)).split(SENTINEL).join(HOLE_START + HOLE_END);
236
+ return serializeStatic(templateTree(el)).split(SENTINEL).join(HOLE_START + HOLE_END);
167
237
  }
168
- function withSentinels(el) {
238
+ function templateTree(el) {
239
+ const plan = propPlan(el);
169
240
  return {
170
241
  ...el,
242
+ props: plan ? plan.serialize : [],
171
243
  children: el.children.map(
172
- (child) => isHole(child) ? { kind: "text", value: SENTINEL } : child.kind === "element" ? withSentinels(child) : child
244
+ (child) => isHole(child) ? { kind: "text", value: SENTINEL } : child.kind === "element" ? templateTree(child) : child
173
245
  )
174
246
  };
175
247
  }
@@ -1,12 +1,19 @@
1
1
  /**
2
2
  * Cloning a subtree whose *structure* is static but which contains dynamic holes.
3
3
  *
4
- * The fully-static case is easy: serialize, clone, done. The common case is not static ,
4
+ * The fully-static case is easy: serialize, clone, done. The common case is not static,
5
5
  * a card wrapper with one reactive value inside: and a single hole used to send the
6
6
  * whole subtree back to being built an element at a time. This plans the middle ground:
7
7
  * the structure becomes one template, and each hole is filled through a reference into
8
8
  * the cloned tree.
9
9
  *
10
+ * An element with dynamic attributes takes the same middle ground. Its static
11
+ * attributes serialize with the structure, and each attribute that can be bound one
12
+ * by one becomes a binding on the walked node, through the same setters `spread`
13
+ * applies internally. What cannot be bound that way (a real spread, a property-only
14
+ * name, a custom element's props, an SVG subtree) keeps the element out of the
15
+ * template entirely, so the imperative path builds it exactly as before.
16
+ *
10
17
  * Three things make it correct in all three environments, fresh client render, server
11
18
  * render, and hydration:
12
19
  *
@@ -21,7 +28,7 @@
21
28
  * - **Every reference is read before any insertion**, since inserting shifts what
22
29
  * follows.
23
30
  */
24
- import type { IRElement, IRNode } from '../ir/nodes.cjs';
31
+ import type { IRElement, IRNode, IRProp } from '../ir/nodes.cjs';
25
32
  /**
26
33
  * The comments bracketing a hole.
27
34
  *
@@ -45,17 +52,27 @@ export interface TemplateHole {
45
52
  endRef: string;
46
53
  node: IRNode;
47
54
  }
55
+ /** How one attribute is bound on a walked node: the runtime helper family it uses. */
56
+ export type BindingKind = 'class' | 'classList' | 'classListDynamic' | 'style' | 'styleDynamic' | 'attr' | 'property' | 'boolAttr' | 'event' | 'nativeEvent' | 'ref' | 'use';
57
+ /** One attribute bound at runtime on the element `ref` resolves to. */
58
+ export interface TemplateBinding {
59
+ ref: string;
60
+ prop: IRProp;
61
+ kind: BindingKind;
62
+ }
48
63
  export interface PartialTemplate {
49
64
  html: string;
50
65
  tag: string;
51
66
  /** Declarations to emit in order, before the inserts. */
52
67
  steps: WalkStep[];
53
68
  holes: TemplateHole[];
69
+ /** Attribute bindings to emit after the walk, before the inserts. */
70
+ bindings: TemplateBinding[];
54
71
  }
55
72
  /**
56
73
  * Plan a partial template for `root`, or null when it does not qualify.
57
74
  *
58
- * Returns null for a subtree with no holes: that is the fully-static case, which has a
59
- * cheaper path of its own and must not be routed through this one.
75
+ * Returns null for a subtree with no holes and no bindings: that is the fully-static
76
+ * case, which has a cheaper path of its own and must not be routed through this one.
60
77
  */
61
78
  export declare function planPartialTemplate(root: IRElement): PartialTemplate | null;
@@ -1,12 +1,19 @@
1
1
  /**
2
2
  * Cloning a subtree whose *structure* is static but which contains dynamic holes.
3
3
  *
4
- * The fully-static case is easy: serialize, clone, done. The common case is not static ,
4
+ * The fully-static case is easy: serialize, clone, done. The common case is not static,
5
5
  * a card wrapper with one reactive value inside: and a single hole used to send the
6
6
  * whole subtree back to being built an element at a time. This plans the middle ground:
7
7
  * the structure becomes one template, and each hole is filled through a reference into
8
8
  * the cloned tree.
9
9
  *
10
+ * An element with dynamic attributes takes the same middle ground. Its static
11
+ * attributes serialize with the structure, and each attribute that can be bound one
12
+ * by one becomes a binding on the walked node, through the same setters `spread`
13
+ * applies internally. What cannot be bound that way (a real spread, a property-only
14
+ * name, a custom element's props, an SVG subtree) keeps the element out of the
15
+ * template entirely, so the imperative path builds it exactly as before.
16
+ *
10
17
  * Three things make it correct in all three environments, fresh client render, server
11
18
  * render, and hydration:
12
19
  *
@@ -21,7 +28,7 @@
21
28
  * - **Every reference is read before any insertion**, since inserting shifts what
22
29
  * follows.
23
30
  */
24
- import type { IRElement, IRNode } from '../ir/nodes.js';
31
+ import type { IRElement, IRNode, IRProp } from '../ir/nodes.js';
25
32
  /**
26
33
  * The comments bracketing a hole.
27
34
  *
@@ -45,18 +52,28 @@ export interface TemplateHole {
45
52
  endRef: string;
46
53
  node: IRNode;
47
54
  }
55
+ /** How one attribute is bound on a walked node: the runtime helper family it uses. */
56
+ export type BindingKind = 'class' | 'classList' | 'classListDynamic' | 'style' | 'styleDynamic' | 'attr' | 'property' | 'boolAttr' | 'event' | 'nativeEvent' | 'ref' | 'use';
57
+ /** One attribute bound at runtime on the element `ref` resolves to. */
58
+ export interface TemplateBinding {
59
+ ref: string;
60
+ prop: IRProp;
61
+ kind: BindingKind;
62
+ }
48
63
  export interface PartialTemplate {
49
64
  html: string;
50
65
  tag: string;
51
66
  /** Declarations to emit in order, before the inserts. */
52
67
  steps: WalkStep[];
53
68
  holes: TemplateHole[];
69
+ /** Attribute bindings to emit after the walk, before the inserts. */
70
+ bindings: TemplateBinding[];
54
71
  }
55
72
  /**
56
73
  * Plan a partial template for `root`, or null when it does not qualify.
57
74
  *
58
- * Returns null for a subtree with no holes: that is the fully-static case, which has a
59
- * cheaper path of its own and must not be routed through this one.
75
+ * Returns null for a subtree with no holes and no bindings: that is the fully-static
76
+ * case, which has a cheaper path of its own and must not be routed through this one.
60
77
  */
61
78
  export declare function planPartialTemplate(root: IRElement): PartialTemplate | null;
62
79
  //# sourceMappingURL=partial-template.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"partial-template.d.ts","sourceRoot":"","sources":["../../src/codegen/partial-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAU,MAAM,gBAAgB,CAAC;AAGhE;;;;;GAKG;AACH,eAAO,MAAM,UAAU,cAAc,CAAC;AACtC,eAAO,MAAM,QAAQ,eAAe,CAAC;AAErC,qEAAqE;AACrE,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED,kEAAkE;AAClE,MAAM,WAAW,YAAY;IAC3B,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,uFAAuF;IACvF,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,yDAAyD;IACzD,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAkDD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,GAAG,eAAe,GAAG,IAAI,CA8C3E"}
1
+ {"version":3,"file":"partial-template.d.ts","sourceRoot":"","sources":["../../src/codegen/partial-template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAGhE;;;;;GAKG;AACH,eAAO,MAAM,UAAU,cAAc,CAAC;AACtC,eAAO,MAAM,QAAQ,eAAe,CAAC;AAErC,qEAAqE;AACrE,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED,kEAAkE;AAClE,MAAM,WAAW,YAAY;IAC3B,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,uFAAuF;IACvF,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED,sFAAsF;AACtF,MAAM,MAAM,WAAW,GACnB,OAAO,GACP,WAAW,GACX,kBAAkB,GAClB,OAAO,GACP,cAAc,GACd,MAAM,GACN,UAAU,GACV,UAAU,GACV,OAAO,GACP,aAAa,GACb,KAAK,GACL,KAAK,CAAC;AAEV,uEAAuE;AACvE,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,WAAW,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,yDAAyD;IACzD,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,qEAAqE;IACrE,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B;AAmJD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,GAAG,eAAe,GAAG,IAAI,CAqD3E"}
Binary file