@barefootjs/jsx 0.27.0 → 0.28.0

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 (50) hide show
  1. package/dist/index.js +620 -26
  2. package/dist/ir-to-client-js/control-flow/plan/branch-loop.d.ts +6 -0
  3. package/dist/ir-to-client-js/control-flow/plan/branch-loop.d.ts.map +1 -1
  4. package/dist/ir-to-client-js/control-flow/plan/build-branch-loop.d.ts +2 -1
  5. package/dist/ir-to-client-js/control-flow/plan/build-branch-loop.d.ts.map +1 -1
  6. package/dist/ir-to-client-js/control-flow/plan/build-insert.d.ts +7 -0
  7. package/dist/ir-to-client-js/control-flow/plan/build-insert.d.ts.map +1 -1
  8. package/dist/ir-to-client-js/control-flow/plan/build-lazy-row.d.ts +107 -0
  9. package/dist/ir-to-client-js/control-flow/plan/build-lazy-row.d.ts.map +1 -0
  10. package/dist/ir-to-client-js/control-flow/plan/build-loop.d.ts +9 -1
  11. package/dist/ir-to-client-js/control-flow/plan/build-loop.d.ts.map +1 -1
  12. package/dist/ir-to-client-js/control-flow/plan/lazy-row-eligibility.d.ts +203 -0
  13. package/dist/ir-to-client-js/control-flow/plan/lazy-row-eligibility.d.ts.map +1 -0
  14. package/dist/ir-to-client-js/control-flow/plan/loop.d.ts +10 -0
  15. package/dist/ir-to-client-js/control-flow/plan/loop.d.ts.map +1 -1
  16. package/dist/ir-to-client-js/control-flow/stringify/branch-loop.d.ts.map +1 -1
  17. package/dist/ir-to-client-js/control-flow/stringify/lazy-row.d.ts +98 -0
  18. package/dist/ir-to-client-js/control-flow/stringify/lazy-row.d.ts.map +1 -0
  19. package/dist/ir-to-client-js/control-flow/stringify/loop-child-arm.d.ts +5 -0
  20. package/dist/ir-to-client-js/control-flow/stringify/loop-child-arm.d.ts.map +1 -1
  21. package/dist/ir-to-client-js/control-flow/stringify/loop.d.ts.map +1 -1
  22. package/dist/ir-to-client-js/control-flow.d.ts.map +1 -1
  23. package/dist/ir-to-client-js/imports.d.ts +2 -2
  24. package/dist/ir-to-client-js/imports.d.ts.map +1 -1
  25. package/package.json +2 -2
  26. package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +162 -36
  27. package/src/__tests__/client-js-generation.test.ts +4 -1
  28. package/src/__tests__/composite-branch-loop.test.ts +12 -3
  29. package/src/__tests__/conditional-mapArray-key.test.ts +7 -1
  30. package/src/__tests__/create-selector.test.ts +21 -7
  31. package/src/__tests__/lazy-row-eligibility.test.ts +692 -0
  32. package/src/__tests__/loop-branch-bare-expression-reactive-text.test.ts +98 -0
  33. package/src/__tests__/loop-fallback-wrap.test.ts +31 -12
  34. package/src/__tests__/loop-hoisted-template.test.ts +10 -6
  35. package/src/__tests__/static-loop-csr-materialize.test.ts +6 -1
  36. package/src/ir-to-client-js/collect-elements.ts +48 -13
  37. package/src/ir-to-client-js/control-flow/plan/branch-loop.ts +6 -0
  38. package/src/ir-to-client-js/control-flow/plan/build-branch-loop.ts +35 -12
  39. package/src/ir-to-client-js/control-flow/plan/build-insert.ts +9 -2
  40. package/src/ir-to-client-js/control-flow/plan/build-lazy-row.ts +305 -0
  41. package/src/ir-to-client-js/control-flow/plan/build-loop.ts +44 -12
  42. package/src/ir-to-client-js/control-flow/plan/lazy-row-eligibility.ts +440 -0
  43. package/src/ir-to-client-js/control-flow/plan/loop.ts +10 -0
  44. package/src/ir-to-client-js/control-flow/stringify/branch-loop.ts +22 -0
  45. package/src/ir-to-client-js/control-flow/stringify/lazy-row.ts +478 -0
  46. package/src/ir-to-client-js/control-flow/stringify/loop-child-arm.ts +6 -1
  47. package/src/ir-to-client-js/control-flow/stringify/loop.ts +23 -0
  48. package/src/ir-to-client-js/control-flow.ts +7 -2
  49. package/src/ir-to-client-js/imports.ts +8 -2
  50. package/src/jsx-to-ir.ts +18 -1
@@ -0,0 +1,305 @@
1
+ /**
2
+ * Build the compiler-side `LazyRowPlan` payload (`spec/slot-unification.md`
3
+ * §9, L3) for one plain loop row — or `null` when the row is ineligible and
4
+ * must keep today's eager `mapArray` emission.
5
+ *
6
+ * Everything decided here is BUILD-TIME data: per-binding item/outer
7
+ * classification (`classifyLazyBinding`), ref-array and dedup-slot layout,
8
+ * and the outer-signal prime list. The stringifier
9
+ * (`stringify/lazy-row.ts`) is a deterministic walk over this plan — it
10
+ * makes no classification decision of its own.
11
+ *
12
+ * Free identifiers are never obtained by regex (repo rule). Texts carry
13
+ * `LoopChildReactiveText.freeIdentifiers` from the analyzer; attrs have no
14
+ * such field, so their expression is run through `parseExpression` +
15
+ * `freeIdentifiers` (`expression-parser.ts`) — both of which fail SAFE, the
16
+ * latter returning `null` on an `unsupported` node, which
17
+ * `classifyLazyBinding` turns into "reads both, and opaque".
18
+ */
19
+
20
+ import { freeIdentifiers, parseExpression } from '../../../expression-parser.ts'
21
+ import { pickAttrMeta, type AttrMeta } from '../../../types.ts'
22
+ import { extractFreeIdentifiersFromText } from '../../csr-substitute.ts'
23
+ import { wrapLoopParamAsAccessor, PROPS_PARAM } from '../../utils.ts'
24
+ import type { BranchLoop, ClientJsContext, TopLevelLoop } from '../../types.ts'
25
+ import {
26
+ classifyLazyBinding,
27
+ lazyRowEligibility,
28
+ type ClassifiedLazyBinding,
29
+ type LazyRowEligibility,
30
+ type LazyRowScopeInfo,
31
+ type LazyRowShapeFacts,
32
+ } from './lazy-row-eligibility.ts'
33
+
34
+ /** One reactive attribute of a lazy row. */
35
+ export interface LazyRowAttrBinding {
36
+ slotId: string
37
+ attrName: string
38
+ /** Already wrapped via `wrapLoopParamAsAccessor` — reads `<param>()`. */
39
+ wrappedExpression: string
40
+ meta: AttrMeta
41
+ /** Index into `entry.refs` holding this slot's element. */
42
+ refIndex: number
43
+ /** Index into `entry.last` holding this binding's dedup value. */
44
+ ordinal: number
45
+ readsItem: boolean
46
+ readsOuter: boolean
47
+ }
48
+
49
+ /** One reactive text (content slot) of a lazy row. */
50
+ export interface LazyRowTextBinding {
51
+ slotId: string
52
+ wrappedExpression: string
53
+ ordinal: number
54
+ readsItem: boolean
55
+ /**
56
+ * Reads at least one reactive outer name — emitted into `applyOuter` with
57
+ * §9.3(1) read-compare-write seeding, which needs the RW claim door
58
+ * (`LazyRowPlanData.textNeedsRead`).
59
+ */
60
+ readsOuter: boolean
61
+ }
62
+
63
+ export interface LazyRowPlanData {
64
+ /** Distinct attr slot ids, in declaration order — `entry.refs[0..n-1]`. */
65
+ attrSlotIds: readonly string[]
66
+ attrs: readonly LazyRowAttrBinding[]
67
+ texts: readonly LazyRowTextBinding[]
68
+ /** Index into `entry.refs` holding the claimed-slot door; -1 when no texts. */
69
+ writerIndex: number
70
+ /**
71
+ * PER-LOOP door selection (§9.3(1) content seeding): true when at least
72
+ * one text binding is outer-involving and therefore needs `read(id)` to
73
+ * seed by comparison. The emitter then claims through `lazyClaimSlots`
74
+ * (the `{ write, read }` door) and every text write becomes
75
+ * `__r[w].write(...)`; false keeps the single-closure `lazySlots` writer
76
+ * and the bare `__r[w](...)` call form. One decision for the whole loop —
77
+ * the door is a per-ROW allocation, so it must not be chosen per binding.
78
+ */
79
+ textNeedsRead: boolean
80
+ /** Total `entry.last` slots. */
81
+ lastCount: number
82
+ /**
83
+ * Signal / memo getters read at the top of `applyOuter` so the ONE
84
+ * loop-level effect subscribes even while the entry list is empty. Empty
85
+ * ⇒ no `applyOuter` is emitted at all (and hydration does literally
86
+ * nothing per row).
87
+ */
88
+ outerPrimeGetters: readonly string[]
89
+ /** True when at least one binding is outer-involving. */
90
+ hasOuter: boolean
91
+ }
92
+
93
+ export interface BuildLazyRowArgs {
94
+ loop: TopLevelLoop | BranchLoop
95
+ /** Fully-chained array expression as emitted (`buildChainedArrayExpr`). */
96
+ arrayExpr: string
97
+ indexParam: string
98
+ /** `destructureLoopParam(...).unwrap` — non-empty ⇒ ineligible. */
99
+ paramUnwrap: string
100
+ mapPreambleWrapped: string
101
+ preambleRegionCount: number
102
+ callSite: 'plain' | 'branch-plain'
103
+ flatMapLeafItem: boolean
104
+ anchored: boolean
105
+ scope: LazyRowScopeInfo | undefined
106
+ }
107
+
108
+ /**
109
+ * Returns the lazy row plan when eligible, otherwise `null` plus the
110
+ * decision (exposed for tests / diagnostics via {@link decideLazyRow}).
111
+ */
112
+ export function buildLazyRowPlan(args: BuildLazyRowArgs): LazyRowPlanData | null {
113
+ return decideLazyRow(args).plan
114
+ }
115
+
116
+ export function decideLazyRow(args: BuildLazyRowArgs): {
117
+ plan: LazyRowPlanData | null
118
+ decision: LazyRowEligibility
119
+ } {
120
+ const { loop, scope } = args
121
+ if (!scope) {
122
+ return { plan: null, decision: { eligible: false, reason: 'no component scope info supplied' } }
123
+ }
124
+
125
+ const wrap = (expr: string) => wrapLoopParamAsAccessor(expr, loop.param, loop.paramBindings)
126
+ const rowLocalNames = new Set<string>([loop.param])
127
+ for (const b of loop.paramBindings ?? []) rowLocalNames.add(b.name)
128
+
129
+ // --- classify every binding -------------------------------------------
130
+ const classified: ClassifiedLazyBinding[] = []
131
+ const attrClass = new Map<number, ClassifiedLazyBinding>()
132
+ loop.bindings.reactiveAttrs.forEach((attr, i) => {
133
+ const c = classifyLazyBinding({
134
+ kind: 'attr',
135
+ slotId: attr.childSlotId,
136
+ free: attrFreeIdentifiers(attr.expression),
137
+ rowLocalNames,
138
+ indexParam: args.indexParam,
139
+ scope,
140
+ })
141
+ attrClass.set(i, c)
142
+ classified.push(c)
143
+ })
144
+ const textClass = new Map<number, ClassifiedLazyBinding>()
145
+ loop.bindings.reactiveTexts.forEach((text, i) => {
146
+ const c = classifyLazyBinding({
147
+ kind: 'text',
148
+ slotId: text.slotId,
149
+ free: text.freeIdentifiers ?? null,
150
+ rowLocalNames,
151
+ indexParam: args.indexParam,
152
+ scope,
153
+ })
154
+ textClass.set(i, c)
155
+ classified.push(c)
156
+ })
157
+
158
+ // --- §9.4 gate ---------------------------------------------------------
159
+ const shape: LazyRowShapeFacts = {
160
+ callSite: args.callSite,
161
+ flatMapLeafItem: args.flatMapLeafItem,
162
+ anchored: args.anchored,
163
+ bodyIsMultiRoot: loop.bodyIsMultiRoot ?? false,
164
+ hasExplicitKey: loop.key != null,
165
+ conditionalCount: loop.bindings.conditionals?.length ?? 0,
166
+ childRefCount: loop.bindings.refs?.length ?? 0,
167
+ nestedComponentCount: loop.nestedComponents?.length ?? 0,
168
+ innerLoopCount: loop.innerLoops?.length ?? 0,
169
+ hasChildComponent: 'childComponent' in loop && loop.childComponent != null,
170
+ hasMapPreamble: args.mapPreambleWrapped.length > 0,
171
+ preambleRegionCount: args.preambleRegionCount,
172
+ hasParamUnwrap: args.paramUnwrap.length > 0,
173
+ }
174
+
175
+ const decision = lazyRowEligibility({
176
+ shape,
177
+ bindings: classified,
178
+ arraySourceIdentifiers: loopSourceIdentifiers(loop, args.arrayExpr),
179
+ scope,
180
+ })
181
+ if (!decision.eligible) return { plan: null, decision }
182
+
183
+ // --- layout ------------------------------------------------------------
184
+ const attrSlotIds: string[] = []
185
+ for (const attr of loop.bindings.reactiveAttrs) {
186
+ if (!attrSlotIds.includes(attr.childSlotId)) attrSlotIds.push(attr.childSlotId)
187
+ }
188
+
189
+ let ordinal = 0
190
+ const attrs: LazyRowAttrBinding[] = loop.bindings.reactiveAttrs.map((attr, i) => {
191
+ const c = attrClass.get(i)!
192
+ return {
193
+ slotId: attr.childSlotId,
194
+ attrName: attr.attrName,
195
+ wrappedExpression: wrap(attr.expression),
196
+ meta: pickAttrMeta(attr),
197
+ refIndex: attrSlotIds.indexOf(attr.childSlotId),
198
+ ordinal: ordinal++,
199
+ readsItem: c.readsItem,
200
+ readsOuter: c.readsOuter,
201
+ }
202
+ })
203
+ const texts: LazyRowTextBinding[] = loop.bindings.reactiveTexts.map((text, i) => {
204
+ const c = textClass.get(i)!
205
+ return {
206
+ slotId: text.slotId,
207
+ wrappedExpression: wrap(text.expression),
208
+ ordinal: ordinal++,
209
+ // A text that classified as NEITHER item- nor outer-driven still gets
210
+ // applied on item change (harmless, dedup-guarded) rather than
211
+ // silently never being written. Anything the classifier did place in
212
+ // a list keeps exactly the classifier's answer.
213
+ readsItem: c.readsItem || !c.readsOuter,
214
+ readsOuter: c.readsOuter,
215
+ }
216
+ })
217
+
218
+ const outerPrimeGetters: string[] = []
219
+ for (const c of classified) {
220
+ for (const name of c.reactiveOuterNames) {
221
+ if (!outerPrimeGetters.includes(name)) outerPrimeGetters.push(name)
222
+ }
223
+ }
224
+
225
+ return {
226
+ plan: {
227
+ attrSlotIds,
228
+ attrs,
229
+ texts,
230
+ writerIndex: texts.length > 0 ? attrSlotIds.length : -1,
231
+ textNeedsRead: texts.some(t => t.readsOuter),
232
+ lastCount: ordinal,
233
+ outerPrimeGetters,
234
+ hasOuter: attrs.some(a => a.readsOuter) || texts.some(t => t.readsOuter),
235
+ },
236
+ decision,
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Project a `ClientJsContext` down to the name facts the §9.4 gate resolves
242
+ * against. Built once per component and threaded into every loop plan
243
+ * builder, so the gate never reaches back into the whole context.
244
+ */
245
+ export function buildLazyRowScopeInfo(ctx: ClientJsContext): LazyRowScopeInfo {
246
+ const signals = new Map<string, { initializerFreeIdentifiers: ReadonlySet<string> | null }>()
247
+ for (const s of ctx.signals) {
248
+ signals.set(s.getter, {
249
+ // `parsed` is the analyzer's structured initializer. Absent (or
250
+ // refused by `freeIdentifiers`) ⇒ unprovable, which the source gate
251
+ // treats as a hard stop rather than an assumption.
252
+ initializerFreeIdentifiers: s.parsed ? freeIdentifiers(s.parsed) : null,
253
+ })
254
+ }
255
+ const memos = new Set(ctx.memos.map(m => m.name))
256
+ const props = new Set<string>([PROPS_PARAM])
257
+ if (ctx.propsObjectName) props.add(ctx.propsObjectName)
258
+ for (const p of ctx.propsParams) props.add(p.name)
259
+ const constants = new Map<string, ReadonlySet<string> | null>()
260
+ for (const c of ctx.localConstants) {
261
+ constants.set(c.name, c.freeIdentifiers ?? null)
262
+ }
263
+ const inert = new Set<string>()
264
+ for (const f of ctx.localFunctions) inert.add(f.name)
265
+ for (const imp of ctx.imports) {
266
+ if (imp.isTypeOnly) continue
267
+ for (const spec of imp.specifiers) inert.add(spec.alias || spec.name)
268
+ }
269
+ return { signals, memos, props, constants, inert, profile: ctx.profile }
270
+ }
271
+
272
+ /**
273
+ * Free identifiers of a reactive-attr expression. `LoopChildReactiveAttr`
274
+ * carries none (unlike `LoopChildReactiveText`), so parse it structurally —
275
+ * `freeIdentifiers` returns `null` on an `unsupported` node, which is the
276
+ * fail-safe signal `classifyLazyBinding` expects.
277
+ */
278
+ function attrFreeIdentifiers(expression: string): ReadonlySet<string> | null {
279
+ if (!expression || expression.trim().length === 0) return new Set()
280
+ try {
281
+ return freeIdentifiers(parseExpression(expression))
282
+ } catch {
283
+ return null
284
+ }
285
+ }
286
+
287
+ /**
288
+ * §9.3(2) source names: the IR's pre-computed `arrayFreeIdentifiers` UNIONED
289
+ * with the free identifiers of the fully-chained expression actually emitted
290
+ * — `arrayFreeIdentifiers` describes `loop.array` alone, so a
291
+ * `.filter(t => t.done === showDone())` / `.sort(...)` chain would otherwise
292
+ * smuggle an unvetted dependency past the gate. `extractFreeIdentifiersFromNode`
293
+ * (via `...FromText`) scopes arrow parameters, so the predicate's own param
294
+ * never shows up as a free name. Returns `null` when the IR carried no
295
+ * pre-computed set — the gate refuses rather than assuming empty.
296
+ */
297
+ function loopSourceIdentifiers(
298
+ loop: TopLevelLoop | BranchLoop,
299
+ arrayExpr: string,
300
+ ): ReadonlySet<string> | null {
301
+ if (!loop.arrayFreeIdentifiers) return null
302
+ const names = new Set<string>(loop.arrayFreeIdentifiers)
303
+ for (const name of extractFreeIdentifiersFromText(arrayExpr)) names.add(name)
304
+ return names
305
+ }
@@ -38,6 +38,8 @@ import {
38
38
  buildPreambleRegionPlans,
39
39
  } from '../shared.ts'
40
40
  import { buildLoopReactiveEffectsPlan } from './build-reactive-effects.ts'
41
+ import { buildLazyRowPlan } from './build-lazy-row.ts'
42
+ import type { LazyRowScopeInfo } from './lazy-row-eligibility.ts'
41
43
  import { buildComponentLoopPlan } from './build-component-loop.ts'
42
44
  import { buildTopLevelCompositePlan } from './build-composite-loop.ts'
43
45
  import { renderPreamble, irToHtmlTemplate } from '../../html-template.ts'
@@ -54,6 +56,13 @@ export interface BuildLoopPlanOptions {
54
56
  unsafeLocalNames: Set<string>
55
57
  /** Owning component name when compiling in profile mode (#1690) — else undefined. */
56
58
  profileComponentName?: string
59
+ /**
60
+ * Component-scope name facts for the lazy row graph gate
61
+ * (`spec/slot-unification.md` §9.4, `buildLazyRowScopeInfo`). Omitted →
62
+ * no loop is lazy-eligible, i.e. today's emission everywhere. Optional so
63
+ * hand-built plan fixtures keep type-checking.
64
+ */
65
+ lazyScope?: LazyRowScopeInfo
57
66
  }
58
67
 
59
68
  /**
@@ -68,7 +77,7 @@ export function buildLoopPlan(elem: TopLevelLoop, opts: BuildLoopPlanOptions): L
68
77
  // array's per-item conditional still toggles reactively instead of freezing
69
78
  // in the SSR-time `forEach` (which has no conditional handling at all).
70
79
  if (elem.bodyIsItemConditional) {
71
- return buildPlainLoopPlan(elem, opts.profileComponentName)
80
+ return buildPlainLoopPlan(elem, opts.profileComponentName, opts.lazyScope)
72
81
  }
73
82
  if (elem.isStaticArray) {
74
83
  return buildStaticLoopPlan(elem, opts.unsafeLocalNames, opts.profileComponentName)
@@ -81,11 +90,15 @@ export function buildLoopPlan(elem: TopLevelLoop, opts: BuildLoopPlanOptions): L
81
90
  if (elem.childComponent) {
82
91
  return buildComponentLoopPlan(elem, opts.profileComponentName)
83
92
  }
84
- return buildPlainLoopPlan(elem, opts.profileComponentName)
93
+ return buildPlainLoopPlan(elem, opts.profileComponentName, opts.lazyScope)
85
94
  }
86
95
 
87
96
  /** @internal — prefer `buildLoopPlan`. Exported for the branch-loop wrapper only. */
88
- export function buildPlainLoopPlan(elem: TopLevelLoop, profileComponentName?: string): PlainLoopPlan {
97
+ export function buildPlainLoopPlan(
98
+ elem: TopLevelLoop,
99
+ profileComponentName?: string,
100
+ lazyScope?: LazyRowScopeInfo,
101
+ ): PlainLoopPlan {
89
102
  const wrap = (expr: string) => wrapLoopParamAsAccessor(expr, elem.param, elem.paramBindings)
90
103
  const { head: paramHead, unwrap: paramUnwrap } = destructureLoopParam(elem.param, elem.paramBindings)
91
104
  const hasReactive = elem.bindings.reactiveAttrs.length > 0
@@ -126,32 +139,51 @@ export function buildPlainLoopPlan(elem: TopLevelLoop, profileComponentName?: st
126
139
  }
127
140
  }
128
141
 
142
+ const arrayExpr = buildChainedArrayExpr(elem)
143
+ const indexParam = elem.index || '__idx'
144
+ const mapPreambleWrapped = elem.preamble
145
+ ? renderPreamble(elem.preamble, {
146
+ transformJs: wrap,
147
+ renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, [{ param: elem.param, bindings: elem.paramBindings }], undefined, true),
148
+ })
149
+ : ''
150
+ const preambleRegions = buildPreambleRegionPlans(elem.preambleRegions, elem.param, elem.paramBindings)
151
+
129
152
  return {
130
153
  kind: 'plain',
131
154
  rowConstruction: 'string-template',
132
155
  containerVar: `_${varSlotId(elem.slotId)}`,
133
156
  markerId: elem.markerId,
134
157
  profileLoopId: profileComponentName ? `${profileComponentName}#binding:${elem.slotId}` : undefined,
135
- arrayExpr: buildChainedArrayExpr(elem),
158
+ arrayExpr,
136
159
  keyFn: loopKeyFn(elem),
137
160
  paramHead,
138
161
  paramUnwrap,
139
- indexParam: elem.index || '__idx',
162
+ indexParam,
163
+ // Lazy row graph (§9, L3). `null` for every ineligible loop, which then
164
+ // keeps the eager emission below byte-for-byte.
165
+ lazyRow: buildLazyRowPlan({
166
+ loop: elem,
167
+ arrayExpr,
168
+ indexParam,
169
+ paramUnwrap,
170
+ mapPreambleWrapped,
171
+ preambleRegionCount: preambleRegions.length,
172
+ callSite: 'plain',
173
+ flatMapLeafItem: false,
174
+ anchored: elem.bodyIsItemConditional ?? false,
175
+ scope: lazyScope,
176
+ }) ?? undefined,
140
177
  // Stage 3 / D4 — js segments get the loop-param accessor wrap; jsx leaves
141
178
  // render as HTML-string templates under this loop's param context so a
142
179
  // leaf that reads the item (`r`) becomes `r()`.
143
- mapPreambleWrapped: elem.preamble
144
- ? renderPreamble(elem.preamble, {
145
- transformJs: wrap,
146
- renderLeaf: (ir) => irToHtmlTemplate(ir, undefined, 1, [{ param: elem.param, bindings: elem.paramBindings }], undefined, true),
147
- })
148
- : '',
180
+ mapPreambleWrapped,
149
181
  template: elem.template,
150
182
  skeletonTemplate: elem.skeletonTemplate,
151
183
  skeletonPaths: elem.skeletonPaths,
152
184
  reactiveEffects: hasReactive ? buildLoopReactiveEffectsPlan(elem, profileComponentName) : null,
153
185
  childRefs: buildChildRefBindings(elem.bindings.refs, elem.param, elem.paramBindings),
154
- preambleRegions: buildPreambleRegionPlans(elem.preambleRegions, elem.param, elem.paramBindings),
186
+ preambleRegions,
155
187
  bodyIsMultiRoot: elem.bodyIsMultiRoot ?? false,
156
188
  anchored: elem.bodyIsItemConditional ?? false,
157
189
  // Fall back to the iteration index when the loop has no key. A whole-item