@barefootjs/jsx 0.26.4 → 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.
- package/dist/compiler.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +941 -130
- package/dist/ir-to-client-js/client-only-elision.d.ts +103 -0
- package/dist/ir-to-client-js/client-only-elision.d.ts.map +1 -0
- package/dist/ir-to-client-js/collect-elements.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/branch-loop.d.ts +6 -0
- package/dist/ir-to-client-js/control-flow/plan/branch-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-branch-loop.d.ts +2 -1
- package/dist/ir-to-client-js/control-flow/plan/build-branch-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-insert.d.ts +7 -0
- package/dist/ir-to-client-js/control-flow/plan/build-insert.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-lazy-row.d.ts +107 -0
- package/dist/ir-to-client-js/control-flow/plan/build-lazy-row.d.ts.map +1 -0
- package/dist/ir-to-client-js/control-flow/plan/build-loop.d.ts +9 -1
- package/dist/ir-to-client-js/control-flow/plan/build-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/lazy-row-eligibility.d.ts +203 -0
- package/dist/ir-to-client-js/control-flow/plan/lazy-row-eligibility.d.ts.map +1 -0
- package/dist/ir-to-client-js/control-flow/plan/loop.d.ts +10 -0
- package/dist/ir-to-client-js/control-flow/plan/loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/shared.d.ts +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/branch-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/claim-plan.d.ts +54 -0
- package/dist/ir-to-client-js/control-flow/stringify/claim-plan.d.ts.map +1 -0
- package/dist/ir-to-client-js/control-flow/stringify/inner-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/insert.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/lazy-row.d.ts +98 -0
- package/dist/ir-to-client-js/control-flow/stringify/lazy-row.d.ts.map +1 -0
- package/dist/ir-to-client-js/control-flow/stringify/loop-child-arm.d.ts +5 -0
- package/dist/ir-to-client-js/control-flow/stringify/loop-child-arm.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/loop.d.ts +17 -20
- package/dist/ir-to-client-js/control-flow/stringify/loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/reactive-effects.d.ts +63 -4
- package/dist/ir-to-client-js/control-flow/stringify/reactive-effects.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow.d.ts +1 -1
- package/dist/ir-to-client-js/control-flow.d.ts.map +1 -1
- package/dist/ir-to-client-js/element-refs.d.ts +8 -3
- package/dist/ir-to-client-js/element-refs.d.ts.map +1 -1
- package/dist/ir-to-client-js/emit-reactive.d.ts +8 -1
- package/dist/ir-to-client-js/emit-reactive.d.ts.map +1 -1
- package/dist/ir-to-client-js/html-template.d.ts +24 -0
- package/dist/ir-to-client-js/html-template.d.ts.map +1 -1
- package/dist/ir-to-client-js/imports.d.ts +2 -2
- package/dist/ir-to-client-js/imports.d.ts.map +1 -1
- package/dist/ir-to-client-js/reactivity.d.ts.map +1 -1
- package/dist/ir-to-client-js/types.d.ts +9 -1
- package/dist/ir-to-client-js/types.d.ts.map +1 -1
- package/dist/ir-to-client-js/utils.d.ts +1 -1
- package/dist/types.d.ts +25 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +542 -689
- package/src/__tests__/child-components-in-map.test.ts +4 -4
- package/src/__tests__/client-js-generation.test.ts +23 -12
- package/src/__tests__/composite-branch-loop.test.ts +14 -5
- package/src/__tests__/conditional-mapArray-key.test.ts +7 -1
- package/src/__tests__/create-selector.test.ts +21 -7
- package/src/__tests__/cross-file-client-signal.test.ts +5 -1
- package/src/__tests__/early-return-scope-var-ref.test.ts +10 -11
- package/src/__tests__/inline-jsx-callback.test.ts +6 -4
- package/src/__tests__/ir-jsx-props.test.ts +6 -5
- package/src/__tests__/lazy-row-eligibility.test.ts +692 -0
- package/src/__tests__/loop-branch-bare-expression-reactive-text.test.ts +98 -0
- package/src/__tests__/loop-fallback-wrap.test.ts +34 -16
- package/src/__tests__/loop-hoisted-template.test.ts +10 -6
- package/src/__tests__/module-level-signal.audit.test.ts +7 -2
- package/src/__tests__/nested-loop-conditional.test.ts +9 -7
- package/src/__tests__/nested-loop-reactive-attrs.test.ts +2 -2
- package/src/__tests__/preamble-region-patch.test.ts +18 -12
- package/src/__tests__/profile-cond-binding-ids.test.ts +3 -2
- package/src/__tests__/profile-loop-binding-ids.test.ts +4 -3
- package/src/__tests__/profile-nested-binding-ids.test.ts +11 -6
- package/src/__tests__/reactive-attrs-in-map.test.ts +6 -4
- package/src/__tests__/static-loop-csr-materialize.test.ts +6 -1
- package/src/__tests__/template-closure.test.ts +1 -1
- package/src/compiler.ts +11 -0
- package/src/index.ts +1 -0
- package/src/ir-to-client-js/client-only-elision.ts +273 -0
- package/src/ir-to-client-js/collect-elements.ts +56 -17
- package/src/ir-to-client-js/control-flow/plan/branch-loop.ts +6 -0
- package/src/ir-to-client-js/control-flow/plan/build-branch-loop.ts +35 -12
- package/src/ir-to-client-js/control-flow/plan/build-insert.ts +9 -2
- package/src/ir-to-client-js/control-flow/plan/build-lazy-row.ts +305 -0
- package/src/ir-to-client-js/control-flow/plan/build-loop.ts +44 -12
- package/src/ir-to-client-js/control-flow/plan/lazy-row-eligibility.ts +440 -0
- package/src/ir-to-client-js/control-flow/plan/loop.ts +10 -0
- package/src/ir-to-client-js/control-flow/shared.ts +1 -1
- package/src/ir-to-client-js/control-flow/stringify/branch-loop.ts +34 -4
- package/src/ir-to-client-js/control-flow/stringify/claim-plan.ts +68 -0
- package/src/ir-to-client-js/control-flow/stringify/inner-loop.ts +18 -9
- package/src/ir-to-client-js/control-flow/stringify/insert.ts +17 -11
- package/src/ir-to-client-js/control-flow/stringify/lazy-row.ts +478 -0
- package/src/ir-to-client-js/control-flow/stringify/loop-child-arm.ts +41 -17
- package/src/ir-to-client-js/control-flow/stringify/loop.ts +71 -46
- package/src/ir-to-client-js/control-flow/stringify/reactive-effects.ts +241 -31
- package/src/ir-to-client-js/control-flow.ts +8 -3
- package/src/ir-to-client-js/element-refs.ts +9 -11
- package/src/ir-to-client-js/emit-reactive.ts +50 -14
- package/src/ir-to-client-js/html-template.ts +29 -9
- package/src/ir-to-client-js/imports.ts +13 -4
- package/src/ir-to-client-js/reactivity.ts +3 -2
- package/src/ir-to-client-js/types.ts +10 -2
- package/src/ir-to-client-js/utils.ts +1 -1
- package/src/jsx-to-ir.ts +26 -8
- package/src/types.ts +25 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Stringify LoopPlan variants to source lines.
|
|
3
3
|
*
|
|
4
|
-
* Output shapes
|
|
4
|
+
* Output shapes:
|
|
5
5
|
*
|
|
6
6
|
* PlainLoopPlan, no reactive effects (single-line renderItem):
|
|
7
7
|
* <indent>mapArray(() => <arr>, <container>, <keyFn>, (<head>, <idx>, __existing) =>
|
|
@@ -12,13 +12,18 @@
|
|
|
12
12
|
* <indent> <unwrap?>
|
|
13
13
|
* <indent> <preamble?>
|
|
14
14
|
* <indent> const __el = __existing ?? (() => { ... })()
|
|
15
|
-
* <indent> <reactive effects via stringifyReactiveEffects
|
|
15
|
+
* <indent> <reactive effects + preamble regions via stringifyReactiveEffects —
|
|
16
|
+
* <indent> row-granularity (spec/slot-unification.md §3(c)): attrs, outer
|
|
17
|
+
* <indent> texts, and preamble regions merge into ONE createEffect per row
|
|
18
|
+
* <indent> in normal builds; profile-mode builds keep the legacy
|
|
19
|
+
* <indent> one-effect-per-slot shape (see that module's docstring)>
|
|
16
20
|
* <indent> return __el
|
|
17
21
|
* <indent>})
|
|
18
22
|
*
|
|
19
23
|
* StaticLoopPlan: two parallel forEach blocks (attrs / texts) — see
|
|
20
24
|
* emitStaticLoop. The forEach-duplication noted in observation O-4 is
|
|
21
|
-
* preserved bug-for-bug; PR 5+ collapses them.
|
|
25
|
+
* preserved bug-for-bug; PR 5+ collapses them. Left untouched by the
|
|
26
|
+
* row-granularity pass above — a different (forEach, not mapArray) shape.
|
|
22
27
|
*
|
|
23
28
|
* Indent convention for plain loops: top-level emission uses `' '` (2 sp);
|
|
24
29
|
* passed in via `topIndent`.
|
|
@@ -31,8 +36,9 @@ import { emitTemplateCloneInline, emitLoopItemElementSetup, emitHoistedTemplateD
|
|
|
31
36
|
import { buildSkeletonPathPlan, type SkeletonPathPlan } from './skeleton-paths.ts'
|
|
32
37
|
import { stringifyComponentLoop } from './component-loop.ts'
|
|
33
38
|
import { stringifyCompositeLoop } from './composite-loop.ts'
|
|
39
|
+
import { claimPlanLiteral, claimWriterVarName, type ClaimSlotSpec } from './claim-plan.ts'
|
|
40
|
+
import { stringifyLazyRowLoop } from './lazy-row.ts'
|
|
34
41
|
import type { LoopChildRefBinding, LoopPlan, PlainLoopPlan, StaticLoopPlan } from '../plan/types.ts'
|
|
35
|
-
import type { PreambleRegionPlan } from '../plan/loop.ts'
|
|
36
42
|
|
|
37
43
|
/**
|
|
38
44
|
* Emit `(callback)(__rf)` for each ref on a per-item slot, looking up the
|
|
@@ -66,38 +72,16 @@ export function emitLoopChildRefs(
|
|
|
66
72
|
}
|
|
67
73
|
|
|
68
74
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* Non-empty `regions` forces the caller's multi-line renderItem layout (the
|
|
80
|
-
* effect needs `__el` as a query root), mirroring `emitLoopChildRefs`.
|
|
75
|
+
* Preamble-patched regions (#2389 — `arr.map(t => { const cells = []; ...;
|
|
76
|
+
* return <tr>{cells}<td>{t.name}</td></tr> })`) used to get their own
|
|
77
|
+
* per-region `createEffect` here. Row-granularity effects (perf,
|
|
78
|
+
* spec/slot-unification.md §3(c)/§8) merged that into the SAME single row
|
|
79
|
+
* effect `stringifyReactiveEffects` now emits for attrs/outer texts — see
|
|
80
|
+
* that module's docstring. Both plain-loop-row call sites
|
|
81
|
+
* (`stringifyPlainLoop` below, `stringify/branch-loop.ts`'s `emitPlain`) pass
|
|
82
|
+
* `preambleRegions`/`mapPreambleWrapped` straight into
|
|
83
|
+
* `stringifyReactiveEffects` instead of calling a separate emitter.
|
|
81
84
|
*/
|
|
82
|
-
export function emitPreambleRegionEffects(
|
|
83
|
-
lines: string[],
|
|
84
|
-
regions: readonly PreambleRegionPlan[],
|
|
85
|
-
mapPreambleWrapped: string,
|
|
86
|
-
opts: { indent: string; elVar: string },
|
|
87
|
-
): void {
|
|
88
|
-
if (regions.length === 0) return
|
|
89
|
-
const { indent, elVar } = opts
|
|
90
|
-
for (const region of regions) {
|
|
91
|
-
const v = varSlotId(region.slotId)
|
|
92
|
-
lines.push(`${indent}{ let __last_${v}`)
|
|
93
|
-
lines.push(`${indent}createEffect(() => {`)
|
|
94
|
-
if (mapPreambleWrapped) lines.push(`${indent} ${mapPreambleWrapped}`)
|
|
95
|
-
lines.push(`${indent} const __html_${v} = ${region.valueExpr}`)
|
|
96
|
-
lines.push(`${indent} if (__last_${v} === undefined) { __last_${v} = __html_${v}; return }`)
|
|
97
|
-
lines.push(`${indent} if (__html_${v} !== __last_${v}) { __last_${v} = __html_${v}; patchSlotRange(${elVar}, '${region.slotId}', __html_${v}) }`)
|
|
98
|
-
lines.push(`${indent}}) }`)
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
85
|
|
|
102
86
|
/**
|
|
103
87
|
* Single dispatch over `LoopPlan` (#1253). Narrows on `plan.kind` and
|
|
@@ -199,6 +183,28 @@ export function stringifyPlainLoop(
|
|
|
199
183
|
return
|
|
200
184
|
}
|
|
201
185
|
|
|
186
|
+
// Lazy row graph (spec/slot-unification.md §9, L3): rows carry NO per-row
|
|
187
|
+
// reactive resources. `plan.lazyRow` is set only when
|
|
188
|
+
// `lazyRowEligibility` accepted this loop; every other loop falls through
|
|
189
|
+
// to the eager emission below, byte-for-byte unchanged.
|
|
190
|
+
if (plan.lazyRow) {
|
|
191
|
+
stringifyLazyRowLoop(lines, {
|
|
192
|
+
indent: topIndent,
|
|
193
|
+
containerVar,
|
|
194
|
+
guardContainer: false,
|
|
195
|
+
markerId,
|
|
196
|
+
arrayExpr,
|
|
197
|
+
keyFn,
|
|
198
|
+
paramHead,
|
|
199
|
+
indexParam,
|
|
200
|
+
template,
|
|
201
|
+
skeletonTemplate,
|
|
202
|
+
skeletonPaths: plan.skeletonPaths,
|
|
203
|
+
lazyRow: plan.lazyRow,
|
|
204
|
+
})
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
|
|
202
208
|
// Hoisted shared-template fast path (perf, see `buildLoopSkeletonTemplate`):
|
|
203
209
|
// declare the once-per-loop template BEFORE the `mapArray` call so every
|
|
204
210
|
// row clones from an already-parsed node instead of re-running
|
|
@@ -254,33 +260,49 @@ export function stringifyPlainLoop(
|
|
|
254
260
|
// `reactiveEffects.conditionals` is always empty here and needs no path
|
|
255
261
|
// handling. `__p` is `null` on the hydration branch (`__existing` truthy)
|
|
256
262
|
// since the skeleton's empty markers/omitted attrs don't describe the
|
|
257
|
-
// SSR-rendered tree; every resolution falls back to qsa/$t there.
|
|
263
|
+
// SSR-rendered tree; every resolution falls back to qsa/$t there. Attr/ref
|
|
264
|
+
// slots still resolve through `__p` (unchanged by slot unification A3 —
|
|
265
|
+
// only CONTENT slots migrate to claim plans); text slots below embed the
|
|
266
|
+
// same skeleton path directly into their claim-plan entry, guarded by the
|
|
267
|
+
// same `__existing ? [] : […]` ternary so a hydrated row never tries a
|
|
268
|
+
// path the skeleton doesn't actually describe.
|
|
258
269
|
let pathPlan: SkeletonPathPlan | null = null
|
|
259
270
|
if (hoistedTpl && plan.skeletonPaths) {
|
|
260
271
|
const elementSlotIds = [
|
|
261
272
|
...(reactiveEffects?.attrSlots.map(s => s.slotId) ?? []),
|
|
262
273
|
...childRefs.map(r => r.childSlotId),
|
|
263
274
|
]
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const built = buildSkeletonPathPlan(plan.skeletonPaths, '__el', { elementSlotIds, textSlotIds })
|
|
275
|
+
if (elementSlotIds.length > 0) {
|
|
276
|
+
const built = buildSkeletonPathPlan(plan.skeletonPaths, '__el', { elementSlotIds, textSlotIds: [] })
|
|
267
277
|
if (built.arrayElems.length > 0) {
|
|
268
278
|
pathPlan = built
|
|
269
279
|
lines.push(`${bodyIndent}const __p = __existing ? null : [${built.arrayElems.join(', ')}]`)
|
|
270
280
|
}
|
|
271
281
|
}
|
|
272
282
|
}
|
|
273
|
-
|
|
283
|
+
const textClaimPathExprs = new Map<string, string>()
|
|
284
|
+
if (hoistedTpl && plan.skeletonPaths) {
|
|
285
|
+
for (const text of reactiveEffects?.outerTexts ?? []) {
|
|
286
|
+
const path = plan.skeletonPaths.textMarkerPaths.get(text.slotId)
|
|
287
|
+
if (path) textClaimPathExprs.set(text.slotId, `__existing ? [] : [${path.join(', ')}]`)
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (reactiveEffects !== null || preambleRegions.length > 0) {
|
|
291
|
+
// Row-granularity effects (§3(c)): attrs, outer texts, AND preamble
|
|
292
|
+
// regions all merge into the SAME single row `createEffect` — pass
|
|
293
|
+
// `preambleRegions`/`mapPreambleWrapped` straight through instead of a
|
|
294
|
+
// separate emitter call (see `stringifyReactiveEffects`'s docstring).
|
|
274
295
|
stringifyReactiveEffects(lines, reactiveEffects, {
|
|
275
296
|
indent: bodyIndent,
|
|
276
297
|
elVar: '__el',
|
|
277
298
|
bodyIsMultiRoot,
|
|
278
299
|
elementIndexBySlot: pathPlan?.elementIndexBySlot,
|
|
279
|
-
|
|
300
|
+
textClaimPathExprs,
|
|
301
|
+
preambleRegions,
|
|
302
|
+
mapPreambleWrapped,
|
|
280
303
|
})
|
|
281
304
|
}
|
|
282
305
|
emitLoopChildRefs(lines, childRefs, { indent: bodyIndent, elVar: '__el', bodyIsMultiRoot, elementIndexBySlot: pathPlan?.elementIndexBySlot })
|
|
283
|
-
emitPreambleRegionEffects(lines, preambleRegions, mapPreambleWrapped, { indent: bodyIndent, elVar: '__el' })
|
|
284
306
|
lines.push(`${bodyIndent}return __el`)
|
|
285
307
|
lines.push(`${topIndent}}, '${markerId}'${loopBfId})`)
|
|
286
308
|
}
|
|
@@ -433,10 +455,13 @@ export function stringifyStaticLoop(lines: string[], plan: StaticLoopPlan): void
|
|
|
433
455
|
}
|
|
434
456
|
lines.push(` }`)
|
|
435
457
|
}
|
|
436
|
-
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
lines.push(`
|
|
458
|
+
if (texts.length > 0) {
|
|
459
|
+
const slots: ClaimSlotSpec[] = texts.map(t => ({ id: t.slotId, kind: 'text', path: [] }))
|
|
460
|
+
const writer = claimWriterVarName(slots, varSlotId)
|
|
461
|
+
lines.push(` const ${writer} = lazySlots(__iterEl, ${claimPlanLiteral(slots)})`)
|
|
462
|
+
for (const text of texts) {
|
|
463
|
+
lines.push(` createEffect(() => { ${writer}('${text.slotId}', String(${text.expression})) }${profileBindingId(pc, text.slotId)})`)
|
|
464
|
+
}
|
|
440
465
|
}
|
|
441
466
|
// Ref callbacks fire on every forEach iteration — initial mount and any
|
|
442
467
|
// future array-change-driven re-iteration (#1244). For static arrays the
|
|
@@ -6,24 +6,58 @@
|
|
|
6
6
|
* bodies (events, child component inits, inner loops, nested conditionals,
|
|
7
7
|
* branch-scoped texts) flow through the per-arm stringifiers in
|
|
8
8
|
* `loop-child-arm.ts` — no legacy passthrough remains.
|
|
9
|
+
*
|
|
10
|
+
* Row-granularity effects (perf, spec/slot-unification.md §3(c)/§8, the
|
|
11
|
+
* deferred A3b): for the "plain loop row, top-level or branch-scoped" shape
|
|
12
|
+
* — the only shape where `stringifyReactiveEffects` is called alongside
|
|
13
|
+
* `PreambleRegionPlan`s from the SAME renderItem scope (`stringify/loop.ts`'s
|
|
14
|
+
* `stringifyPlainLoop`, `stringify/branch-loop.ts`'s `emitPlain`) — reactive
|
|
15
|
+
* attrs, outer texts, and preamble regions all read the SAME per-item signal
|
|
16
|
+
* dependency, so consolidating them into ONE `createEffect` per row removes
|
|
17
|
+
* N-1 effect objects and subscription-list entries per row without changing
|
|
18
|
+
* the firing profile. Composite loops, component loops, and the anchored
|
|
19
|
+
* (whole-item-conditional) loop shape keep the legacy one-effect-per-slot
|
|
20
|
+
* emission — their reactiveEffects never carry preamble regions, and
|
|
21
|
+
* consolidating them wasn't part of this pass's mechanically-verified scope
|
|
22
|
+
* (see the call sites' docstrings).
|
|
23
|
+
*
|
|
24
|
+
* Profile mode (#1690) carries a `<Component>#binding:<slotId>` id per
|
|
25
|
+
* effect so the profiler attributes a re-run to its source binding — merging
|
|
26
|
+
* bindings into one effect would make every binding on the row share one id.
|
|
27
|
+
* Resolution: profile mode (`plan.profileComponentName` set) keeps the
|
|
28
|
+
* granular per-slot/per-attr effect emission (`emitAttrSlotsGranular` /
|
|
29
|
+
* `emitOuterTexts` below); normal builds get the consolidated row effect.
|
|
30
|
+
* Preamble regions never carried a per-region bfId even before this pass, so
|
|
31
|
+
* they always consolidate into one effect regardless of profile mode.
|
|
9
32
|
*/
|
|
10
33
|
|
|
11
34
|
import { varSlotId, profileBindingId } from '../../utils.ts'
|
|
12
35
|
import { emitAttrUpdate } from '../../emit-reactive.ts'
|
|
13
36
|
import { stringifyLoopChildArm } from './loop-child-arm.ts'
|
|
37
|
+
import { claimPlanLiteral, claimWriterVarName, type ClaimSlotSpec } from './claim-plan.ts'
|
|
14
38
|
import type {
|
|
15
39
|
NestedConditionalPlan,
|
|
40
|
+
ReactiveAttrSlot,
|
|
16
41
|
ReactiveEffectsPlan,
|
|
17
42
|
ReactiveTextEffect,
|
|
18
43
|
} from '../plan/reactive-effects.ts'
|
|
19
44
|
|
|
45
|
+
/** A resolved preamble-patched region (#2389) ready to merge into the row
|
|
46
|
+
* effect — same shape as `plan/loop.ts`'s `PreambleRegionPlan`, restated
|
|
47
|
+
* here so this module doesn't need a cross-import into the loop plan file
|
|
48
|
+
* for one two-field shape. */
|
|
49
|
+
export interface RowPreambleRegion {
|
|
50
|
+
slotId: string
|
|
51
|
+
valueExpr: string
|
|
52
|
+
}
|
|
53
|
+
|
|
20
54
|
export interface StringifyReactiveEffectsOptions {
|
|
21
55
|
/** Indent prefix for every emitted line. */
|
|
22
56
|
indent: string
|
|
23
57
|
/**
|
|
24
58
|
* Element variable to attach effects to (e.g., `__el`, `__existing`,
|
|
25
59
|
* `__csrEl`). The stringifier never inspects it — it is simply substituted
|
|
26
|
-
* into the qsa() /
|
|
60
|
+
* into the qsa() / lazySlots() / insert() call shapes.
|
|
27
61
|
*/
|
|
28
62
|
elVar: string
|
|
29
63
|
/**
|
|
@@ -42,27 +76,88 @@ export interface StringifyReactiveEffectsOptions {
|
|
|
42
76
|
* still resolves through the battle-tested runtime lookup.
|
|
43
77
|
*/
|
|
44
78
|
elementIndexBySlot?: ReadonlyMap<string, number>
|
|
45
|
-
/**
|
|
46
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Rendered path EXPRESSION source (not a plain array — see
|
|
81
|
+
* `ClaimSlotSpec.pathExpr`) for outer text slots, keyed by slotId: perf,
|
|
82
|
+
* #2143's hoisted single-root loop skeleton
|
|
83
|
+
* (`SkeletonSlotPaths.textMarkerPaths`), guarded by `__existing` the same
|
|
84
|
+
* way `__p` is nulled on the hydration branch (the skeleton's simplified
|
|
85
|
+
* markup doesn't describe the real SSR-rendered tree). Only
|
|
86
|
+
* `stringifyPlainLoop`'s hoisted-skeleton path supplies these; every other
|
|
87
|
+
* caller omits it and each text slot's claim-plan entry gets `path: []`
|
|
88
|
+
* — A2's marker-scan fallback resolves it at claim time (sound, just
|
|
89
|
+
* slower), per `spec/slot-unification.md` §5-A3's explicit "cannot be
|
|
90
|
+
* statically pathed" allowance.
|
|
91
|
+
*/
|
|
92
|
+
textClaimPathExprs?: ReadonlyMap<string, string>
|
|
93
|
+
/**
|
|
94
|
+
* Preamble-patched regions (#2389) to merge into the SAME row effect as
|
|
95
|
+
* this scope's reactive attrs/outer texts (row-granularity effects,
|
|
96
|
+
* §3(c)) — only supplied by the plain-loop-row call sites
|
|
97
|
+
* (`stringify/loop.ts`, `stringify/branch-loop.ts`) where
|
|
98
|
+
* `PlainLoopVariant.preambleRegions` exists on the plan. Every other
|
|
99
|
+
* caller omits this and gets the legacy per-slot emission untouched.
|
|
100
|
+
*/
|
|
101
|
+
preambleRegions?: readonly RowPreambleRegion[]
|
|
102
|
+
/**
|
|
103
|
+
* The loop callback's pre-return preamble (already wrapped with the loop
|
|
104
|
+
* param accessor), re-run once per row-effect tick — BEFORE any preamble
|
|
105
|
+
* region read — so `preambleRegions`' `valueExpr`s see freshly recomputed
|
|
106
|
+
* locals. Only meaningful alongside `preambleRegions`.
|
|
107
|
+
*/
|
|
108
|
+
mapPreambleWrapped?: string
|
|
47
109
|
}
|
|
48
110
|
|
|
49
111
|
export function stringifyReactiveEffects(
|
|
50
112
|
lines: string[],
|
|
51
|
-
plan: ReactiveEffectsPlan,
|
|
113
|
+
plan: ReactiveEffectsPlan | null,
|
|
52
114
|
opts: StringifyReactiveEffectsOptions,
|
|
53
115
|
): void {
|
|
54
|
-
const { indent, elVar, bodyIsMultiRoot, elementIndexBySlot,
|
|
116
|
+
const { indent, elVar, bodyIsMultiRoot, elementIndexBySlot, textClaimPathExprs, preambleRegions = [], mapPreambleWrapped } = opts
|
|
55
117
|
const lookup = bodyIsMultiRoot ? 'qsaItem' : 'qsa'
|
|
56
|
-
const pc = plan
|
|
118
|
+
const pc = plan?.profileComponentName
|
|
57
119
|
const bindingBfId = (slotId: string): string => profileBindingId(pc, slotId)
|
|
120
|
+
const attrSlots = plan?.attrSlots ?? []
|
|
121
|
+
const outerTexts = plan?.outerTexts ?? []
|
|
122
|
+
const conditionals = plan?.conditionals ?? []
|
|
123
|
+
|
|
124
|
+
if (pc) {
|
|
125
|
+
// Profile mode: preserve the granular per-slot/per-attr effect shape so
|
|
126
|
+
// every binding keeps its own bfId (see module docstring).
|
|
127
|
+
emitAttrSlotsGranular(lines, indent, elVar, lookup, attrSlots, elementIndexBySlot, bindingBfId)
|
|
128
|
+
emitOuterTexts(lines, indent, elVar, outerTexts, bindingBfId, textClaimPathExprs)
|
|
129
|
+
emitPreambleRegionsEffect(lines, indent, elVar, preambleRegions, mapPreambleWrapped)
|
|
130
|
+
} else {
|
|
131
|
+
emitConsolidatedRowEffect(
|
|
132
|
+
lines, indent, elVar, lookup,
|
|
133
|
+
attrSlots, outerTexts, elementIndexBySlot, textClaimPathExprs,
|
|
134
|
+
preambleRegions, mapPreambleWrapped,
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Reactive conditionals — each emits an insert(...) over `elVar` whose arm
|
|
139
|
+
// bodies dispatch through the per-arm stringifiers. Structurally distinct
|
|
140
|
+
// from the attrs/texts/regions merge above (its own per-branch disposable
|
|
141
|
+
// effects), so it's untouched by row-granularity consolidation.
|
|
142
|
+
for (const cond of conditionals) {
|
|
143
|
+
emitOuterConditional(lines, indent, elVar, cond, pc)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// --- profile-mode / legacy granular emission (one createEffect per slot-attr) ---
|
|
58
148
|
|
|
59
|
-
|
|
60
|
-
|
|
149
|
+
function emitAttrSlotsGranular(
|
|
150
|
+
lines: string[],
|
|
151
|
+
indent: string,
|
|
152
|
+
elVar: string,
|
|
153
|
+
lookup: string,
|
|
154
|
+
attrSlots: readonly ReactiveAttrSlot[],
|
|
155
|
+
elementIndexBySlot: ReadonlyMap<string, number> | undefined,
|
|
156
|
+
bindingBfId: (slotId: string) => string,
|
|
157
|
+
): void {
|
|
158
|
+
for (const slot of attrSlots) {
|
|
61
159
|
const varName = `__ra_${varSlotId(slot.slotId)}`
|
|
62
|
-
const
|
|
63
|
-
const lookupExpr = pIdx !== undefined
|
|
64
|
-
? `__p ? __p[${pIdx}] : ${lookup}(${elVar}, '[bf="${slot.slotId}"]')`
|
|
65
|
-
: `${lookup}(${elVar}, '[bf="${slot.slotId}"]')`
|
|
160
|
+
const lookupExpr = attrLookupExpr(slot.slotId, varName, elVar, lookup, elementIndexBySlot)
|
|
66
161
|
lines.push(`${indent}{ const ${varName} = ${lookupExpr}`)
|
|
67
162
|
lines.push(`${indent}if (${varName}) {`)
|
|
68
163
|
for (const attr of slot.attrs) {
|
|
@@ -74,36 +169,151 @@ export function stringifyReactiveEffects(
|
|
|
74
169
|
}
|
|
75
170
|
lines.push(`${indent}} }`)
|
|
76
171
|
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** ONE createEffect covering every preamble region — never carried a bfId
|
|
175
|
+
* before this pass (regions were never profiled per-binding), so this
|
|
176
|
+
* shape applies in both profile and non-profile mode. */
|
|
177
|
+
function emitPreambleRegionsEffect(
|
|
178
|
+
lines: string[],
|
|
179
|
+
indent: string,
|
|
180
|
+
elVar: string,
|
|
181
|
+
preambleRegions: readonly RowPreambleRegion[],
|
|
182
|
+
mapPreambleWrapped: string | undefined,
|
|
183
|
+
): void {
|
|
184
|
+
if (preambleRegions.length === 0) return
|
|
185
|
+
const slots: ClaimSlotSpec[] = preambleRegions.map(r => ({ id: r.slotId, kind: 'markup', path: [] }))
|
|
186
|
+
const writer = claimWriterVarName(slots, varSlotId)
|
|
187
|
+
lines.push(`${indent}const ${writer} = lazySlots(${elVar}, ${claimPlanLiteral(slots)})`)
|
|
188
|
+
lines.push(`${indent}createEffect(() => {`)
|
|
189
|
+
if (mapPreambleWrapped) lines.push(`${indent} ${mapPreambleWrapped}`)
|
|
190
|
+
for (const region of preambleRegions) {
|
|
191
|
+
lines.push(`${indent} ${writer}('${region.slotId}', ${region.valueExpr})`)
|
|
192
|
+
}
|
|
193
|
+
lines.push(`${indent}})`)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function attrLookupExpr(
|
|
197
|
+
slotId: string,
|
|
198
|
+
varName: string,
|
|
199
|
+
elVar: string,
|
|
200
|
+
lookup: string,
|
|
201
|
+
elementIndexBySlot: ReadonlyMap<string, number> | undefined,
|
|
202
|
+
): string {
|
|
203
|
+
const pIdx = elementIndexBySlot?.get(slotId)
|
|
204
|
+
return pIdx !== undefined
|
|
205
|
+
? `__p ? __p[${pIdx}] : ${lookup}(${elVar}, '[bf="${slotId}"]')`
|
|
206
|
+
: `${lookup}(${elVar}, '[bf="${slotId}"]')`
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// --- row-granularity consolidated emission (ONE createEffect per row) ---
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Emit ONE `createEffect` covering: reactive attr writes (for every slot in
|
|
213
|
+
* `attrSlots`), outer text writes, and preamble-region writes — in that
|
|
214
|
+
* order, mirroring the legacy relative ordering (attrs, then texts, then
|
|
215
|
+
* regions). Attr-slot element lookups are resolved ONCE, outside the
|
|
216
|
+
* effect (identical to the legacy shape — only the WRITE moves inside the
|
|
217
|
+
* shared effect). Outer texts and preamble regions share ONE claimed-slot
|
|
218
|
+
* writer (mixed 'text'/'markup' kinds — `claim-slots.ts`'s `write()` already
|
|
219
|
+
* dispatches per-slot on `kind`), so a row pays for at most one `lazySlots`
|
|
220
|
+
* closure + one claim `Map` instead of one per category.
|
|
221
|
+
*
|
|
222
|
+
* Every attr's write statements are wrapped in their own `{ }` block (not
|
|
223
|
+
* just each slot's) — `emitAttrUpdate`'s 'value'-attr shape emits a bare
|
|
224
|
+
* `const __val = …` with no block of its own, and merging multiple attrs
|
|
225
|
+
* into one function body (previously each had its own arrow-function scope)
|
|
226
|
+
* would let two 'value' attrs in the same row effect collide on `__val`.
|
|
227
|
+
*/
|
|
228
|
+
function emitConsolidatedRowEffect(
|
|
229
|
+
lines: string[],
|
|
230
|
+
indent: string,
|
|
231
|
+
elVar: string,
|
|
232
|
+
lookup: string,
|
|
233
|
+
attrSlots: readonly ReactiveAttrSlot[],
|
|
234
|
+
outerTexts: readonly ReactiveTextEffect[],
|
|
235
|
+
elementIndexBySlot: ReadonlyMap<string, number> | undefined,
|
|
236
|
+
textClaimPathExprs: ReadonlyMap<string, string> | undefined,
|
|
237
|
+
preambleRegions: readonly RowPreambleRegion[],
|
|
238
|
+
mapPreambleWrapped: string | undefined,
|
|
239
|
+
): void {
|
|
240
|
+
if (attrSlots.length === 0 && outerTexts.length === 0 && preambleRegions.length === 0) return
|
|
241
|
+
|
|
242
|
+
// 1. Resolve every attr slot's element reference once, outside the effect.
|
|
243
|
+
for (const slot of attrSlots) {
|
|
244
|
+
const varName = `__ra_${varSlotId(slot.slotId)}`
|
|
245
|
+
lines.push(`${indent}const ${varName} = ${attrLookupExpr(slot.slotId, varName, elVar, lookup, elementIndexBySlot)}`)
|
|
246
|
+
}
|
|
77
247
|
|
|
78
|
-
// 2.
|
|
79
|
-
|
|
80
|
-
|
|
248
|
+
// 2. ONE claimed-slot writer covering outer texts (kind 'text') and
|
|
249
|
+
// preamble regions (kind 'markup') — mixed-kind plans are supported by
|
|
250
|
+
// claim-slots.ts's per-slot `kind` dispatch.
|
|
251
|
+
const claimSlots: ClaimSlotSpec[] = [
|
|
252
|
+
...outerTexts.map((t): ClaimSlotSpec => ({ id: t.slotId, kind: 'text', path: [], pathExpr: textClaimPathExprs?.get(t.slotId) })),
|
|
253
|
+
...preambleRegions.map((r): ClaimSlotSpec => ({ id: r.slotId, kind: 'markup', path: [] })),
|
|
254
|
+
]
|
|
255
|
+
const writer = claimSlots.length > 0 ? claimWriterVarName(claimSlots, varSlotId) : null
|
|
256
|
+
if (writer) {
|
|
257
|
+
lines.push(`${indent}const ${writer} = lazySlots(${elVar}, ${claimPlanLiteral(claimSlots)})`)
|
|
81
258
|
}
|
|
82
259
|
|
|
83
|
-
// 3.
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
|
|
260
|
+
// 3. The single row effect: attr writes, then (if any region needs it) the
|
|
261
|
+
// preamble re-run, then text writes, then region writes. The by-far
|
|
262
|
+
// most common shape — no attrs, no regions, exactly one outer text —
|
|
263
|
+
// collapses to the SAME single-line `createEffect(() => { ... })` the
|
|
264
|
+
// legacy per-text emitter used (doc-examples snapshots pin this byte
|
|
265
|
+
// shape); anything busier (an attr present, multiple slots, or a
|
|
266
|
+
// preamble region) uses the multi-line block.
|
|
267
|
+
if (attrSlots.length === 0 && preambleRegions.length === 0 && outerTexts.length === 1) {
|
|
268
|
+
const text = outerTexts[0]
|
|
269
|
+
lines.push(`${indent}createEffect(() => { ${writer}('${text.slotId}', String(${text.wrappedExpression})) })`)
|
|
270
|
+
return
|
|
271
|
+
}
|
|
272
|
+
lines.push(`${indent}createEffect(() => {`)
|
|
273
|
+
for (const slot of attrSlots) {
|
|
274
|
+
const varName = `__ra_${varSlotId(slot.slotId)}`
|
|
275
|
+
lines.push(`${indent} if (${varName}) {`)
|
|
276
|
+
for (const attr of slot.attrs) {
|
|
277
|
+
lines.push(`${indent} {`)
|
|
278
|
+
for (const stmt of emitAttrUpdate(varName, attr.attrName, attr.wrappedExpression, attr.meta)) {
|
|
279
|
+
lines.push(`${indent} ${stmt}`)
|
|
280
|
+
}
|
|
281
|
+
lines.push(`${indent} }`)
|
|
282
|
+
}
|
|
283
|
+
lines.push(`${indent} }`)
|
|
284
|
+
}
|
|
285
|
+
if (preambleRegions.length > 0 && mapPreambleWrapped) {
|
|
286
|
+
lines.push(`${indent} ${mapPreambleWrapped}`)
|
|
87
287
|
}
|
|
288
|
+
for (const text of outerTexts) {
|
|
289
|
+
lines.push(`${indent} ${writer}('${text.slotId}', String(${text.wrappedExpression}))`)
|
|
290
|
+
}
|
|
291
|
+
for (const region of preambleRegions) {
|
|
292
|
+
lines.push(`${indent} ${writer}('${region.slotId}', ${region.valueExpr})`)
|
|
293
|
+
}
|
|
294
|
+
lines.push(`${indent}})`)
|
|
88
295
|
}
|
|
89
296
|
|
|
90
|
-
function
|
|
297
|
+
function emitOuterTexts(
|
|
91
298
|
lines: string[],
|
|
92
299
|
indent: string,
|
|
93
300
|
elVar: string,
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
301
|
+
texts: readonly ReactiveTextEffect[],
|
|
302
|
+
bindingBfId: (slotId: string) => string,
|
|
303
|
+
textClaimPathExprs?: ReadonlyMap<string, string>,
|
|
97
304
|
): void {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
305
|
+
if (texts.length === 0) return
|
|
306
|
+
const slots: ClaimSlotSpec[] = texts.map(t => ({
|
|
307
|
+
id: t.slotId,
|
|
308
|
+
kind: 'text',
|
|
309
|
+
path: [],
|
|
310
|
+
pathExpr: textClaimPathExprs?.get(t.slotId),
|
|
311
|
+
}))
|
|
312
|
+
const writer = claimWriterVarName(slots, varSlotId)
|
|
313
|
+
lines.push(`${indent}const ${writer} = lazySlots(${elVar}, ${claimPlanLiteral(slots)})`)
|
|
314
|
+
for (const text of texts) {
|
|
315
|
+
lines.push(`${indent}createEffect(() => { ${writer}('${text.slotId}', String(${text.wrappedExpression})) }${bindingBfId(text.slotId)})`)
|
|
105
316
|
}
|
|
106
|
-
lines.push(`${indent}if (${varName}) createEffect(() => { ${varName}.textContent = String(${text.wrappedExpression}) }${bfId}) }`)
|
|
107
317
|
}
|
|
108
318
|
|
|
109
319
|
function emitOuterConditional(
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* These three functions are called from `generate-init.ts` to emit the
|
|
5
5
|
* client-side runtime calls that drive reactive conditionals
|
|
6
6
|
* (`insert(...)`) and reactive loops (`mapArray(...)` /
|
|
7
|
-
* `
|
|
7
|
+
* `mapArrayAnchored(...)`) for a single component.
|
|
8
8
|
*
|
|
9
9
|
* Each entry point thinly wraps the Plan layer:
|
|
10
10
|
*
|
|
@@ -25,6 +25,7 @@ import { internalInvariant } from '../errors.ts'
|
|
|
25
25
|
import { buildInsertPlan } from './control-flow/plan/build-insert.ts'
|
|
26
26
|
import { stringifyInsert } from './control-flow/stringify/insert.ts'
|
|
27
27
|
import { buildLoopPlan } from './control-flow/plan/build-loop.ts'
|
|
28
|
+
import { buildLazyRowScopeInfo } from './control-flow/plan/build-lazy-row.ts'
|
|
28
29
|
import { stringifyLoop } from './control-flow/stringify/loop.ts'
|
|
29
30
|
import {
|
|
30
31
|
buildDynamicLoopDelegationPlan,
|
|
@@ -36,7 +37,7 @@ import { stringifyEventDelegation } from './control-flow/stringify/event-delegat
|
|
|
36
37
|
export function emitConditionalUpdates(lines: string[], ctx: ClientJsContext): void {
|
|
37
38
|
const profileComponentName = ctx.profile ? ctx.componentName : undefined
|
|
38
39
|
for (const elem of ctx.conditionalElements) {
|
|
39
|
-
const plan = buildInsertPlan(elem, { scope: { kind: 'top' }, eventNameMode: 'dom', profileComponentName })
|
|
40
|
+
const plan = buildInsertPlan(elem, { scope: { kind: 'top' }, eventNameMode: 'dom', profileComponentName, lazyScope: buildLazyRowScopeInfo(ctx) })
|
|
40
41
|
stringifyInsert(lines, plan, { leadingIndent: ' ', bodyIndent: ' ' })
|
|
41
42
|
lines.push('')
|
|
42
43
|
}
|
|
@@ -46,7 +47,7 @@ export function emitConditionalUpdates(lines: string[], ctx: ClientJsContext): v
|
|
|
46
47
|
export function emitClientOnlyConditionals(lines: string[], ctx: ClientJsContext): void {
|
|
47
48
|
const profileComponentName = ctx.profile ? ctx.componentName : undefined
|
|
48
49
|
for (const elem of ctx.clientOnlyConditionals) {
|
|
49
|
-
const plan = buildInsertPlan(elem, { scope: { kind: 'top' }, eventNameMode: 'raw', profileComponentName })
|
|
50
|
+
const plan = buildInsertPlan(elem, { scope: { kind: 'top' }, eventNameMode: 'raw', profileComponentName, lazyScope: buildLazyRowScopeInfo(ctx) })
|
|
50
51
|
lines.push(` // @client conditional: ${elem.slotId}`)
|
|
51
52
|
stringifyInsert(lines, plan, { leadingIndent: ' ', bodyIndent: ' ' })
|
|
52
53
|
lines.push('')
|
|
@@ -66,10 +67,14 @@ export function emitClientOnlyConditionals(lines: string[], ctx: ClientJsContext
|
|
|
66
67
|
* event surface, no delegation pass needed
|
|
67
68
|
*/
|
|
68
69
|
export function emitLoopUpdates(lines: string[], ctx: ClientJsContext, unsafeLocalNames: Set<string>): void {
|
|
70
|
+
// Lazy row graph (§9.4) name facts — built once per component, consulted
|
|
71
|
+
// by every plain loop's eligibility gate.
|
|
72
|
+
const lazyScope = buildLazyRowScopeInfo(ctx)
|
|
69
73
|
for (const elem of ctx.loopElements) {
|
|
70
74
|
const plan = buildLoopPlan(elem, {
|
|
71
75
|
unsafeLocalNames,
|
|
72
76
|
profileComponentName: ctx.profile ? ctx.componentName : undefined,
|
|
77
|
+
lazyScope,
|
|
73
78
|
})
|
|
74
79
|
// Stage 3 root cure — a JSX-bearing preamble can only be spliced into a
|
|
75
80
|
// string-templated row (renderPreamble). Every shape that reaches a
|
|
@@ -2,13 +2,18 @@
|
|
|
2
2
|
* Element ref declarations — `const [_s0, _s1] = $(…, 's0', 's1')`.
|
|
3
3
|
*
|
|
4
4
|
* Every element the init body needs to find at hydration time (events,
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* loops, refs, reactive attrs, reactive props, rest-attr elements, child
|
|
6
|
+
* component slots) is emitted via one of two finders:
|
|
7
7
|
*
|
|
8
8
|
* `$(…)` — regular DOM elements (bf-*)
|
|
9
|
-
* `$t(…)` — text comment markers for dynamic text (`<!--bf:sN-->`)
|
|
10
9
|
* `$c(…)` — component slots (bf-s, component-rooted)
|
|
11
10
|
*
|
|
11
|
+
* Dynamic text/JSX content slots (`<!--bf:sN-->` markers) don't need a ref
|
|
12
|
+
* here at all — they resolve through a claimed-slot writer built where
|
|
13
|
+
* they're used (`emitDynamicTextUpdates`/`emitClientOnlyExpressions` in
|
|
14
|
+
* `ir-to-client-js/emit-reactive.ts`, slot unification A3), not a
|
|
15
|
+
* pre-resolved `_sN` var.
|
|
16
|
+
*
|
|
12
17
|
* Slots inside conditional branches are excluded here because
|
|
13
18
|
* `insert()` bindEvents re-queries them when the branch activates.
|
|
14
19
|
* Component slots take precedence over regular slots (#360): when a
|
|
@@ -26,7 +31,6 @@ import { varSlotId } from './utils.ts'
|
|
|
26
31
|
*/
|
|
27
32
|
export function generateElementRefs(ctx: ClientJsContext): string {
|
|
28
33
|
const regularSlots = new Set<string>()
|
|
29
|
-
const textSlots = new Set<string>()
|
|
30
34
|
const componentSlots = new Set<string>()
|
|
31
35
|
const conditionalSlotIds = collectConditionalSlotIds(ctx)
|
|
32
36
|
|
|
@@ -35,11 +39,6 @@ export function generateElementRefs(ctx: ClientJsContext): string {
|
|
|
35
39
|
regularSlots.add(elem.slotId)
|
|
36
40
|
}
|
|
37
41
|
}
|
|
38
|
-
for (const elem of ctx.dynamicElements) {
|
|
39
|
-
if (!elem.insideConditional) {
|
|
40
|
-
textSlots.add(elem.slotId)
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
42
|
for (const elem of ctx.conditionalElements) {
|
|
44
43
|
regularSlots.add(elem.slotId)
|
|
45
44
|
}
|
|
@@ -72,11 +71,10 @@ export function generateElementRefs(ctx: ClientJsContext): string {
|
|
|
72
71
|
regularSlots.delete(slotId)
|
|
73
72
|
}
|
|
74
73
|
|
|
75
|
-
if (regularSlots.size === 0 &&
|
|
74
|
+
if (regularSlots.size === 0 && componentSlots.size === 0) return ''
|
|
76
75
|
|
|
77
76
|
const refLines: string[] = []
|
|
78
77
|
emitSlotRefs(refLines, [...regularSlots], '$')
|
|
79
|
-
emitSlotRefs(refLines, [...textSlots], '$t')
|
|
80
78
|
emitSlotRefs(refLines, [...componentSlots], '$c')
|
|
81
79
|
return refLines.join('\n')
|
|
82
80
|
}
|