@barefootjs/jsx 0.34.0 → 0.35.1
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/analyzer-context.d.ts.map +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1072 -903
- package/dist/ir-to-client-js/collect-elements.d.ts +22 -2
- package/dist/ir-to-client-js/collect-elements.d.ts.map +1 -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-component-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-composite-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-inner-loop.d.ts +14 -2
- package/dist/ir-to-client-js/control-flow/plan/build-inner-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-lazy-row.d.ts +11 -0
- package/dist/ir-to-client-js/control-flow/plan/build-lazy-row.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-loop-child-arm.d.ts +6 -1
- package/dist/ir-to-client-js/control-flow/plan/build-loop-child-arm.d.ts.map +1 -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/build-plain-row.d.ts +52 -0
- package/dist/ir-to-client-js/control-flow/plan/build-plain-row.d.ts.map +1 -0
- package/dist/ir-to-client-js/control-flow/plan/build-reactive-effects.d.ts +5 -2
- package/dist/ir-to-client-js/control-flow/plan/build-reactive-effects.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/inner-loop.d.ts +24 -44
- package/dist/ir-to-client-js/control-flow/plan/inner-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/lazy-conditional.d.ts +6 -7
- package/dist/ir-to-client-js/control-flow/plan/lazy-conditional.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/lazy-preamble.d.ts +5 -7
- package/dist/ir-to-client-js/control-flow/plan/lazy-preamble.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/lazy-row-eligibility.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/loop-child-arm.d.ts +14 -0
- package/dist/ir-to-client-js/control-flow/plan/loop-child-arm.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/loop.d.ts +10 -6
- 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 +11 -9
- package/dist/ir-to-client-js/control-flow/shared.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/composite-loop.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/inner-loop.d.ts +6 -14
- 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/lazy-row.d.ts +1 -3
- package/dist/ir-to-client-js/control-flow/stringify/lazy-row.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/loop-child-arm.d.ts +19 -3
- 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.map +1 -1
- package/dist/ir-to-client-js/control-flow/stringify/reactive-effects.d.ts +5 -1
- package/dist/ir-to-client-js/control-flow/stringify/reactive-effects.d.ts.map +1 -1
- package/dist/ir-to-client-js/csr-substitute.d.ts +16 -0
- package/dist/ir-to-client-js/csr-substitute.d.ts.map +1 -1
- package/dist/ir-to-client-js/emit-reactive.d.ts +54 -0
- package/dist/ir-to-client-js/emit-reactive.d.ts.map +1 -1
- package/dist/ir-to-client-js/emit-registration.d.ts.map +1 -1
- package/dist/ir-to-client-js/html-template.d.ts +2 -2
- 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/index.d.ts.map +1 -1
- package/dist/ir-to-client-js/markup-slots.d.ts +21 -0
- package/dist/ir-to-client-js/markup-slots.d.ts.map +1 -0
- package/dist/ir-to-client-js/reactivity.d.ts +62 -18
- package/dist/ir-to-client-js/reactivity.d.ts.map +1 -1
- package/dist/ir-to-client-js/safe-html.d.ts +158 -0
- package/dist/ir-to-client-js/safe-html.d.ts.map +1 -0
- package/dist/ir-to-client-js/types.d.ts +18 -2
- package/dist/ir-to-client-js/types.d.ts.map +1 -1
- package/dist/ir-to-client-js/utils.d.ts +34 -1
- package/dist/ir-to-client-js/utils.d.ts.map +1 -1
- package/dist/jsx-to-ir.d.ts.map +1 -1
- package/dist/prop-rewrite.d.ts +8 -3
- package/dist/prop-rewrite.d.ts.map +1 -1
- package/dist/props-binding.d.ts +22 -1
- package/dist/props-binding.d.ts.map +1 -1
- package/dist/reactivity-checker.d.ts +18 -0
- package/dist/reactivity-checker.d.ts.map +1 -1
- package/dist/ssr-defaults.d.ts.map +1 -1
- package/dist/types.d.ts +5 -10
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +138 -22
- package/src/__tests__/binding-scope-ratchet.test.ts +25 -2
- package/src/__tests__/build-plain-row-consolidation.test.ts +120 -0
- package/src/__tests__/child-prop-fallback-wrap.test.ts +4 -2
- package/src/__tests__/client-template-escape-soundness.test.ts +538 -0
- package/src/__tests__/compiler-stress-1244.test.ts +8 -8
- package/src/__tests__/csr-template-loop-shadowing.test.ts +4 -2
- package/src/__tests__/flatmap-segments.test.ts +1 -1
- package/src/__tests__/issue-2868-cond-arm-tag-collision.test.ts +185 -0
- package/src/__tests__/issue-2869-attr-dedup-guard.test.ts +141 -0
- package/src/__tests__/lazy-conditional.test.ts +52 -12
- package/src/__tests__/lazy-preamble.test.ts +11 -11
- package/src/__tests__/lazy-row-eligibility.test.ts +8 -3
- package/src/__tests__/loop-child-reactive-attr-const-shadow.test.ts +25 -3
- package/src/__tests__/loop-index-reactivity.test.ts +208 -0
- package/src/__tests__/loop-item-conditional-codegen.test.ts +4 -2
- package/src/__tests__/map-arbitrary-body.test.ts +1 -1
- package/src/__tests__/map-body-brand-checker.test.ts +176 -0
- package/src/__tests__/nested-loop-index-param.test.ts +41 -8
- package/src/__tests__/nested-loop-non-outer-reactivity.test.ts +177 -0
- package/src/__tests__/nested-loop-plain.test.ts +39 -25
- package/src/__tests__/nested-loop-reactive-attrs.test.ts +16 -6
- package/src/__tests__/preamble-branch-arm-rerun.test.ts +135 -0
- package/src/__tests__/reactive-attrs-in-map.test.ts +13 -10
- package/src/__tests__/rewrite-destructured-props.test.ts +37 -0
- package/src/__tests__/safe-html-splice-door.test.ts +61 -0
- package/src/__tests__/ssr-defaults.test.ts +59 -0
- package/src/__tests__/style-css-var-reactive.test.ts +4 -1
- package/src/analyzer-context.ts +12 -10
- package/src/analyzer.ts +4 -0
- package/src/index.ts +8 -2
- package/src/ir-to-client-js/collect-elements.ts +126 -32
- package/src/ir-to-client-js/control-flow/plan/build-branch-loop.ts +20 -28
- package/src/ir-to-client-js/control-flow/plan/build-component-loop.ts +9 -7
- package/src/ir-to-client-js/control-flow/plan/build-composite-loop.ts +12 -6
- package/src/ir-to-client-js/control-flow/plan/build-inner-loop.ts +36 -59
- package/src/ir-to-client-js/control-flow/plan/build-lazy-row.ts +25 -4
- package/src/ir-to-client-js/control-flow/plan/build-loop-child-arm.ts +36 -9
- package/src/ir-to-client-js/control-flow/plan/build-loop.ts +35 -35
- package/src/ir-to-client-js/control-flow/plan/build-plain-row.ts +116 -0
- package/src/ir-to-client-js/control-flow/plan/build-reactive-effects.ts +23 -8
- package/src/ir-to-client-js/control-flow/plan/inner-loop.ts +24 -45
- package/src/ir-to-client-js/control-flow/plan/lazy-conditional.ts +11 -10
- package/src/ir-to-client-js/control-flow/plan/lazy-preamble.ts +11 -13
- package/src/ir-to-client-js/control-flow/plan/lazy-row-eligibility.ts +13 -3
- package/src/ir-to-client-js/control-flow/plan/loop-child-arm.ts +14 -0
- package/src/ir-to-client-js/control-flow/plan/loop.ts +10 -6
- package/src/ir-to-client-js/control-flow/shared.ts +40 -17
- package/src/ir-to-client-js/control-flow/stringify/composite-loop.ts +2 -1
- package/src/ir-to-client-js/control-flow/stringify/inner-loop.ts +20 -66
- package/src/ir-to-client-js/control-flow/stringify/insert.ts +4 -2
- package/src/ir-to-client-js/control-flow/stringify/lazy-row.ts +22 -15
- package/src/ir-to-client-js/control-flow/stringify/loop-child-arm.ts +53 -11
- package/src/ir-to-client-js/control-flow/stringify/loop.ts +4 -2
- package/src/ir-to-client-js/control-flow/stringify/reactive-effects.ts +29 -18
- package/src/ir-to-client-js/csr-substitute.ts +29 -0
- package/src/ir-to-client-js/emit-reactive.ts +92 -6
- package/src/ir-to-client-js/emit-registration.ts +2 -4
- package/src/ir-to-client-js/html-template.ts +67 -245
- package/src/ir-to-client-js/imports.ts +3 -0
- package/src/ir-to-client-js/index.ts +4 -4
- package/src/ir-to-client-js/markup-slots.ts +25 -0
- package/src/ir-to-client-js/reactivity.ts +73 -27
- package/src/ir-to-client-js/safe-html.ts +243 -0
- package/src/ir-to-client-js/types.ts +19 -3
- package/src/ir-to-client-js/utils.ts +58 -10
- package/src/jsx-to-ir.ts +136 -24
- package/src/prop-rewrite.ts +8 -4
- package/src/props-binding.ts +37 -1
- package/src/reactivity-checker.ts +60 -0
- package/src/ssr-defaults.ts +45 -0
- package/src/types.ts +5 -10
- package/src/__tests__/text-slot-escaping.test.ts +0 -65
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Stringify `InnerLoopsPlan` into source lines.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* One emission shape (#2865 — the former second "static forEach,
|
|
5
|
+
* setup-only" shape for a nested loop whose array didn't reference the
|
|
6
|
+
* outer item was removed: it answered "does the array mention the outer
|
|
7
|
+
* item" instead of "is this reactive at all", so it silently froze any
|
|
8
|
+
* other reactivity — a signal, a memo, a prop, a preamble local — the
|
|
9
|
+
* inner array or its rows depended on):
|
|
6
10
|
*
|
|
7
11
|
* reactive — full mapArray:
|
|
8
12
|
* <indent>// Reactive inner loop: <arraySrc>
|
|
@@ -17,23 +21,11 @@
|
|
|
17
21
|
* <indent> per-item conditionals: insert() over __innerEl<uid> (#2706)
|
|
18
22
|
* <indent> return __innerEl<uid>
|
|
19
23
|
* <indent>}) }
|
|
20
|
-
*
|
|
21
|
-
* static — forEach setup-only:
|
|
22
|
-
* <indent>// Initialize <arraySrc> loop components and events
|
|
23
|
-
* <indent>{ const __ic<uid> = <containerExpr>
|
|
24
|
-
* <indent>if (__ic<uid> && <arrayExpr>) <arrayExpr>.forEach((<param>, __innerIdx<uid>) => {
|
|
25
|
-
* <indent> const __innerEl<uid> = __ic<uid>.children[__innerIdx<uid>]
|
|
26
|
-
* <indent> if (!__innerEl<uid>) return
|
|
27
|
-
* <indent> <preludeStatements*> // raw preamble (#1064)
|
|
28
|
-
* <indent> __innerEl<uid>.setAttribute('<keyAttr>', String(<rawKey>))?
|
|
29
|
-
* <indent> emitComponentAndEventSetup(...)
|
|
30
|
-
* <indent> recurse on childLevels
|
|
31
|
-
* <indent>}) }
|
|
32
24
|
*/
|
|
33
25
|
|
|
34
26
|
import { keyAttrName, mapArrayKeyArgs, profileBindingId, varSlotId } from '../../utils.ts'
|
|
35
27
|
import { emitComponentAndEventSetup } from '../shared.ts'
|
|
36
|
-
import {
|
|
28
|
+
import { emitDedupedAttrUpdate, DEDUP_STORE_DECL } from '../../emit-reactive.ts'
|
|
37
29
|
import { emitMultiRootTemplateCloneLines, namespaceWrapForTemplate } from './template-parse.ts'
|
|
38
30
|
import { emitLoopChildRefs } from './loop.ts'
|
|
39
31
|
import { claimPlanLiteral, claimWriterVarName, type ClaimSlotSpec } from './claim-plan.ts'
|
|
@@ -50,18 +42,13 @@ export function stringifyInnerLoops(
|
|
|
50
42
|
pc?: string,
|
|
51
43
|
): void {
|
|
52
44
|
for (const inner of plan) {
|
|
53
|
-
|
|
54
|
-
emitReactive(lines, inner, indent, pc)
|
|
55
|
-
} else {
|
|
56
|
-
emitStatic(lines, inner, indent, pc)
|
|
57
|
-
}
|
|
45
|
+
emitReactive(lines, inner, indent, pc)
|
|
58
46
|
}
|
|
59
47
|
}
|
|
60
48
|
|
|
61
49
|
function emitReactive(lines: string[], inner: InnerLoopPlan, indent: string, pc: string | undefined): void {
|
|
62
50
|
const uid = inner.uidSuffix
|
|
63
51
|
const emit = inner.emit
|
|
64
|
-
if (emit.mode !== 'reactive') return // narrow
|
|
65
52
|
|
|
66
53
|
lines.push(`${indent}// Reactive inner loop: ${inner.arraySrc}`)
|
|
67
54
|
lines.push(`${indent}{ const __ic${uid} = ${inner.containerExpr}`)
|
|
@@ -108,6 +95,8 @@ function emitReactive(lines: string[], inner: InnerLoopPlan, indent: string, pc:
|
|
|
108
95
|
[...emit.events],
|
|
109
96
|
inner.outerLoopParam,
|
|
110
97
|
inner.outerLoopParamBindings,
|
|
98
|
+
false,
|
|
99
|
+
inner.outerLoopIndex,
|
|
111
100
|
)
|
|
112
101
|
}
|
|
113
102
|
if (inner.childLevels.length > 0) {
|
|
@@ -132,11 +121,13 @@ function emitReactive(lines: string[], inner: InnerLoopPlan, indent: string, pc:
|
|
|
132
121
|
lines.push(`${indent} createEffect(() => { ${writer}('${text.slotId}', String(${text.wrappedExpression})) }${profileBindingId(pc, text.slotId)})`)
|
|
133
122
|
}
|
|
134
123
|
}
|
|
124
|
+
if (emit.reactiveAttrs.length > 0) lines.push(`${indent} ${DEDUP_STORE_DECL}`)
|
|
125
|
+
let attrOrdinal = 0
|
|
135
126
|
for (const attr of emit.reactiveAttrs) {
|
|
136
127
|
const targetVar = `__ta_${attr.slotId.replace(/[^a-zA-Z0-9]/g, '_')}`
|
|
137
128
|
lines.push(`${indent} { const ${targetVar} = qsa(__innerEl${uid}, '[bf="${attr.slotId}"]')`)
|
|
138
129
|
lines.push(`${indent} if (${targetVar}) createEffect(() => {`)
|
|
139
|
-
for (const stmt of
|
|
130
|
+
for (const stmt of emitDedupedAttrUpdate(targetVar, attr.attrName, attr.wrappedExpression, attr.meta, attrOrdinal++)) {
|
|
140
131
|
lines.push(`${indent} ${stmt}`)
|
|
141
132
|
}
|
|
142
133
|
lines.push(`${indent} }${profileBindingId(pc, attr.slotId)}) }`)
|
|
@@ -146,7 +137,13 @@ function emitReactive(lines: string[], inner: InnerLoopPlan, indent: string, pc:
|
|
|
146
137
|
// ternary. Mirrors `stringifyBranchInnerLoops`'s identical call for a
|
|
147
138
|
// branch-scoped inner loop's own conditionals.
|
|
148
139
|
if (emit.conditionals.length > 0) {
|
|
149
|
-
|
|
140
|
+
// This inner loop's own preamble (`preludeStatements` above) is not a
|
|
141
|
+
// single re-runnable string like the outer row's `mapPreambleWrapped`,
|
|
142
|
+
// so there is nothing correct to thread here — `readsPreamble &&
|
|
143
|
+
// mapPreambleWrapped`'s guard downstream makes this `undefined` a
|
|
144
|
+
// no-op, exactly as before this fix (inner-loop preambles are out of
|
|
145
|
+
// scope — see PR notes on #2596 follow-up).
|
|
146
|
+
stringifyLoopChildConditionals(lines, emit.conditionals, `${indent} `, pc, undefined)
|
|
150
147
|
}
|
|
151
148
|
// Imperative ref callbacks fire on every renderItem invocation, which
|
|
152
149
|
// means every mount: SSR hydration, initial CSR creation, and same-key
|
|
@@ -163,46 +160,3 @@ function emitReactive(lines: string[], inner: InnerLoopPlan, indent: string, pc:
|
|
|
163
160
|
// instead of the default `data-key`.
|
|
164
161
|
lines.push(`${indent}}, '${inner.markerId}'${mapArrayKeyArgs(profileBindingId(pc, inner.slotId), !!emit.wrappedKey, inner.keyDepth)}) }`)
|
|
165
162
|
}
|
|
166
|
-
|
|
167
|
-
function emitStatic(lines: string[], inner: InnerLoopPlan, indent: string, pc: string | undefined): void {
|
|
168
|
-
const uid = inner.uidSuffix
|
|
169
|
-
const emit = inner.emit
|
|
170
|
-
if (emit.mode !== 'static') return
|
|
171
|
-
|
|
172
|
-
lines.push(`${indent}// Initialize ${inner.arraySrc} loop components and events`)
|
|
173
|
-
lines.push(`${indent}{ const __ic${uid} = ${inner.containerExpr}`)
|
|
174
|
-
// Guard: inner array may be undefined when inside a conditional branch.
|
|
175
|
-
lines.push(`${indent}if (__ic${uid} && ${inner.arrayExpr}) ${inner.arrayExpr}.forEach((${inner.param}, __innerIdx${uid}) => {`)
|
|
176
|
-
lines.push(`${indent} const __innerEl${uid} = __ic${uid}.children[__innerIdx${uid}]`)
|
|
177
|
-
lines.push(`${indent} if (!__innerEl${uid}) return`)
|
|
178
|
-
// Body-entry statements: inner-`.map()` preamble locals (raw, since the
|
|
179
|
-
// forEach param is the literal item — #1064). Emitted before component
|
|
180
|
-
// and event setup so their prop getters / handlers resolve the locals.
|
|
181
|
-
for (const stmt of emit.preludeStatements) {
|
|
182
|
-
lines.push(`${indent} ${stmt}`)
|
|
183
|
-
}
|
|
184
|
-
if (emit.rawKey) {
|
|
185
|
-
lines.push(`${indent} __innerEl${uid}.setAttribute('${keyAttrName(inner.keyDepth)}', String(${emit.rawKey}))`)
|
|
186
|
-
}
|
|
187
|
-
emitComponentAndEventSetup(
|
|
188
|
-
lines,
|
|
189
|
-
`${indent} `,
|
|
190
|
-
`__innerEl${uid}`,
|
|
191
|
-
[...emit.components],
|
|
192
|
-
[...emit.events],
|
|
193
|
-
inner.outerLoopParam,
|
|
194
|
-
inner.outerLoopParamBindings,
|
|
195
|
-
)
|
|
196
|
-
if (inner.childLevels.length > 0) {
|
|
197
|
-
stringifyInnerLoops(lines, inner.childLevels, `${indent} `, pc)
|
|
198
|
-
}
|
|
199
|
-
// Imperative ref callbacks for static inner loops — fire once per
|
|
200
|
-
// forEach iteration (#1244). Static arrays don't reactively re-iterate,
|
|
201
|
-
// so this is effectively a one-shot per item.
|
|
202
|
-
emitLoopChildRefs(lines, emit.childRefs, {
|
|
203
|
-
indent: `${indent} `,
|
|
204
|
-
elVar: `__innerEl${uid}`,
|
|
205
|
-
bodyIsMultiRoot: false,
|
|
206
|
-
})
|
|
207
|
-
lines.push(`${indent}}) }`)
|
|
208
|
-
}
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
31
|
import { emitRefCall, varSlotId } from '../../utils.ts'
|
|
32
|
-
import {
|
|
32
|
+
import { emitDedupedAttrUpdate, DEDUP_STORE_DECL } from '../../emit-reactive.ts'
|
|
33
33
|
import type { InsertPlan, InsertArm, ArmBody, ScopeRef } from '../plan/types.ts'
|
|
34
34
|
import { stringifyBranchLoops } from './branch-loop.ts'
|
|
35
35
|
import { emitListenerLine } from './event-listener.ts'
|
|
@@ -163,10 +163,12 @@ function emitArmBody(
|
|
|
163
163
|
const v = varSlotId(slotId)
|
|
164
164
|
const elVar = `__ra_${v}`
|
|
165
165
|
lines.push(`${indent}{ const ${elVar} = qsa(__branchScope, '[bf="${slotId}"]')`)
|
|
166
|
+
lines.push(`${indent}${DEDUP_STORE_DECL}`)
|
|
166
167
|
lines.push(`${indent}if (${elVar}) {`)
|
|
168
|
+
let ordinal = 0
|
|
167
169
|
for (const attr of attrs) {
|
|
168
170
|
lines.push(`${indent} __disposers.push(createDisposableEffect(() => {`)
|
|
169
|
-
for (const stmt of
|
|
171
|
+
for (const stmt of emitDedupedAttrUpdate(elVar, attr.attrName, attr.expression, attr, ordinal++)) {
|
|
170
172
|
lines.push(`${indent} ${stmt}`)
|
|
171
173
|
}
|
|
172
174
|
lines.push(`${indent} }${bindingBfId(slotId)}))`)
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
*/
|
|
61
61
|
|
|
62
62
|
import { isBooleanAttr } from '../../../html-constants.ts'
|
|
63
|
-
import {
|
|
63
|
+
import { emitDedupedAttrUpdate, dedupGuard } from '../../emit-reactive.ts'
|
|
64
64
|
import { toHtmlAttrName } from '../../utils.ts'
|
|
65
65
|
import type { SkeletonSlotPaths } from '../../html-template.ts'
|
|
66
66
|
import { claimPlanLiteral, type ClaimSlotSpec } from './claim-plan.ts'
|
|
@@ -191,6 +191,11 @@ export function stringifyLazyRowLoop(lines: string[], o: StringifyLazyRowOptions
|
|
|
191
191
|
// --- createRow ---------------------------------------------------------
|
|
192
192
|
const b1 = `${indent} `
|
|
193
193
|
const b2 = `${indent} `
|
|
194
|
+
// Tells the runtime to also call `applyItem` on a pure reorder (#2859
|
|
195
|
+
// follow-up) — see `LazyRowPlan.indexDriven`'s docstring. Omitted (not just
|
|
196
|
+
// `false`) for every loop that never reads the index, byte-stable with
|
|
197
|
+
// this widening's predecessor.
|
|
198
|
+
if (lazyRow.readsIndex) lines.push(`${b1}indexDriven: true,`)
|
|
194
199
|
lines.push(`${b1}createRow: (__e, ${o.indexParam}) => {`)
|
|
195
200
|
// Always bound, even with no reactive bindings: the non-hoisted clone
|
|
196
201
|
// interpolates the per-row template, which reads the param for at least
|
|
@@ -225,6 +230,9 @@ export function stringifyLazyRowLoop(lines: string[], o: StringifyLazyRowOptions
|
|
|
225
230
|
} else {
|
|
226
231
|
lines.push(`${b1}applyItem: (__e) => {`)
|
|
227
232
|
lines.push(`${b2}const ${paramHead} = () => __e.item`)
|
|
233
|
+
// The row's current position (#2859 follow-up) — only when some binding
|
|
234
|
+
// here actually reads it, so every other loop's applyItem is unchanged.
|
|
235
|
+
if (lazyRow.readsIndex) lines.push(`${b2}const ${o.indexParam} = __e.index`)
|
|
228
236
|
lines.push(`${b2}const __r = __e.refs ?? (__e.refs = [])`)
|
|
229
237
|
lines.push(`${b2}const __l = __e.last ?? (__e.last = [])`)
|
|
230
238
|
// Re-run the preamble only when a binding in THIS body reads a local it
|
|
@@ -255,6 +263,8 @@ export function stringifyLazyRowLoop(lines: string[], o: StringifyLazyRowOptions
|
|
|
255
263
|
for (const g of lazyRow.outerPrimeGetters) lines.push(`${b2}${g}()`)
|
|
256
264
|
lines.push(`${b2}for (const __e of __es) {`)
|
|
257
265
|
lines.push(`${b3}const ${paramHead} = () => __e.item`)
|
|
266
|
+
// Same as `applyItem` — the row's current position (#2859 follow-up).
|
|
267
|
+
if (lazyRow.readsIndex) lines.push(`${b3}const ${o.indexParam} = __e.index`)
|
|
258
268
|
lines.push(`${b3}const __r = __e.refs ?? (__e.refs = [])`)
|
|
259
269
|
lines.push(`${b3}const __l = __e.last ?? (__e.last = [])`)
|
|
260
270
|
// Per ENTRY, not once for the batch: the preamble reads the item, so its
|
|
@@ -448,11 +458,13 @@ function emitConditional(
|
|
|
448
458
|
lines.push(`${ind}} }`)
|
|
449
459
|
}
|
|
450
460
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
461
|
+
// `dedupGuard` (the `entry.last`-backed dedup test) and the actual guarded
|
|
462
|
+
// write now live in `emit-reactive.ts` as `emitDedupedAttrUpdate` (#2869) —
|
|
463
|
+
// shared with every eager reactive-attr emission site. This module's only
|
|
464
|
+
// remaining substrate-specific piece is WHERE `__l` comes from: the
|
|
465
|
+
// reconciler's `entry.last` (see the per-mode `applyItem`/`applyOuter`
|
|
466
|
+
// callers below), not a closure-local `const __l = []`, because those are
|
|
467
|
+
// separate invocations with no shared closure of their own to own it.
|
|
456
468
|
|
|
457
469
|
function emitAttrBinding(
|
|
458
470
|
lines: string[],
|
|
@@ -464,21 +476,16 @@ function emitAttrBinding(
|
|
|
464
476
|
// plain read. An ADOPTED row starts with an EMPTY `refs` array and each
|
|
465
477
|
// binding claims its OWN slot on first use — see `elementAccess`.
|
|
466
478
|
const target = mode === 'create' ? `__r[${a.refIndex}]` : elementAccess(a)
|
|
467
|
-
lines.push(`${ind}{ const __t = ${target}`)
|
|
468
|
-
lines.push(`${ind}if (__t) {`)
|
|
469
|
-
lines.push(`${ind} const __x = ${a.wrappedExpression}`)
|
|
470
479
|
const guard = mode === 'create'
|
|
471
480
|
? null
|
|
472
481
|
: mode === 'item'
|
|
473
482
|
? dedupGuard(a.ordinal)
|
|
474
483
|
: `__seed ? (${seedDiffersExpr('__t', a)}) : (${dedupGuard(a.ordinal)})`
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
for (const stmt of
|
|
478
|
-
lines.push(`${
|
|
484
|
+
lines.push(`${ind}{ const __t = ${target}`)
|
|
485
|
+
lines.push(`${ind}if (__t) {`)
|
|
486
|
+
for (const stmt of emitDedupedAttrUpdate('__t', a.attrName, a.wrappedExpression, a.meta, a.ordinal, guard)) {
|
|
487
|
+
lines.push(`${ind} ${stmt}`)
|
|
479
488
|
}
|
|
480
|
-
if (guard) lines.push(`${ind} }`)
|
|
481
|
-
lines.push(`${ind} __l[${a.ordinal}] = __x`)
|
|
482
489
|
lines.push(`${ind}} }`)
|
|
483
490
|
}
|
|
484
491
|
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import { varSlotId, DATA_BF_PH, keyAttrName, mapArrayKeyArgs, profileBindingId } from '../../utils.ts'
|
|
15
15
|
import { emitComponentAndEventSetup } from '../shared.ts'
|
|
16
|
-
import {
|
|
16
|
+
import { emitDedupedAttrUpdate, DEDUP_STORE_DECL } from '../../emit-reactive.ts'
|
|
17
17
|
import { namespaceWrapForTemplate } from './template-parse.ts'
|
|
18
18
|
import { emitListenerLine } from './event-listener.ts'
|
|
19
19
|
import { nameForRegistryRef } from '../../component-scope.ts'
|
|
@@ -46,15 +46,25 @@ export function stringifyBranchReactiveAttrs(
|
|
|
46
46
|
lines: string[],
|
|
47
47
|
plan: readonly ReactiveAttrSlot[],
|
|
48
48
|
indent: string,
|
|
49
|
-
pc
|
|
49
|
+
pc: string | undefined,
|
|
50
|
+
/** The row's preamble re-run statement, when this scope has one (#2596/#2447) — see `conditionGetterExpr`'s docstring for the shared contract. */
|
|
51
|
+
mapPreambleWrapped: string | undefined,
|
|
50
52
|
): void {
|
|
51
53
|
for (const slot of plan) {
|
|
52
54
|
const varName = `__ra_${varSlotId(slot.slotId)}`
|
|
53
55
|
lines.push(`${indent}{ const ${varName} = qsa(__branchScope, '[bf="${slot.slotId}"]')`)
|
|
56
|
+
lines.push(`${indent}${DEDUP_STORE_DECL}`)
|
|
54
57
|
lines.push(`${indent}if (${varName}) {`)
|
|
58
|
+
let ordinal = 0
|
|
55
59
|
for (const attr of slot.attrs) {
|
|
56
60
|
lines.push(`${indent} __disposers.push(createDisposableEffect(() => {`)
|
|
57
|
-
|
|
61
|
+
// One effect per attr, so an attr reading a preamble local needs the
|
|
62
|
+
// declarations inside ITS OWN effect — same rule, same line, as
|
|
63
|
+
// `emitAttrSlotsGranular` (reactive-effects.ts, #2447 follow-up).
|
|
64
|
+
if (attr.readsPreamble && mapPreambleWrapped) {
|
|
65
|
+
lines.push(`${indent} ${mapPreambleWrapped}`)
|
|
66
|
+
}
|
|
67
|
+
for (const stmt of emitDedupedAttrUpdate(varName, attr.attrName, attr.wrappedExpression, attr.meta, ordinal++)) {
|
|
58
68
|
lines.push(`${indent} ${stmt}`)
|
|
59
69
|
}
|
|
60
70
|
lines.push(`${indent} }${profileBindingId(pc, slot.slotId)}))`)
|
|
@@ -156,6 +166,8 @@ export function stringifyBranchInnerLoops(
|
|
|
156
166
|
[...inner.legacyEvents],
|
|
157
167
|
inner.outerLoopParam,
|
|
158
168
|
inner.outerLoopParamBindings,
|
|
169
|
+
false,
|
|
170
|
+
inner.outerLoopIndex,
|
|
159
171
|
)
|
|
160
172
|
}
|
|
161
173
|
const conditionalTexts = inner.reactiveTexts.filter(t => t.insideConditional)
|
|
@@ -177,7 +189,13 @@ export function stringifyBranchInnerLoops(
|
|
|
177
189
|
}
|
|
178
190
|
}
|
|
179
191
|
if (inner.nestedConditionals.length > 0) {
|
|
180
|
-
|
|
192
|
+
// An inner loop's own conditionals are classified against the INNER
|
|
193
|
+
// loop's preamble names, not the outer row's — `BranchInnerLoop`
|
|
194
|
+
// carries no re-runnable preamble string for that scope, so there is
|
|
195
|
+
// nothing correct to inject here. The `readsPreamble && mapPreambleWrapped`
|
|
196
|
+
// guard downstream makes this `undefined` a no-op, exactly as before
|
|
197
|
+
// this fix (inner-loop preambles are out of scope — see PR notes).
|
|
198
|
+
stringifyLoopChildConditionals(lines, inner.nestedConditionals, `${indent} `, pc, undefined)
|
|
181
199
|
}
|
|
182
200
|
lines.push(`${indent} return __bel${uid}`)
|
|
183
201
|
// #2753 Shape B: see the identical comment in `inner-loop.ts` —
|
|
@@ -198,33 +216,55 @@ export function stringifyLoopChildConditionals(
|
|
|
198
216
|
lines: string[],
|
|
199
217
|
conditionals: readonly LoopChildConditionalPlan[],
|
|
200
218
|
indent: string,
|
|
201
|
-
pc
|
|
219
|
+
pc: string | undefined,
|
|
220
|
+
/** The row's preamble re-run statement, when this scope has one (#2596/#2447). */
|
|
221
|
+
mapPreambleWrapped: string | undefined,
|
|
202
222
|
): void {
|
|
203
223
|
for (const cond of conditionals) {
|
|
204
|
-
stringifyLoopChildConditional(lines, cond, indent, pc)
|
|
224
|
+
stringifyLoopChildConditional(lines, cond, indent, pc, mapPreambleWrapped)
|
|
205
225
|
}
|
|
206
226
|
}
|
|
207
227
|
|
|
228
|
+
/**
|
|
229
|
+
* The `() => cond` getter handed to `insert()`. A condition flagged
|
|
230
|
+
* `readsPreamble` (#2596) re-runs the row preamble INSIDE the getter —
|
|
231
|
+
* `insert()` re-invokes this closure on every dependency change to decide
|
|
232
|
+
* the branch, and a preamble local is a plain per-row `const`, not a signal
|
|
233
|
+
* `insert()` can see through on its own. ONE implementation for both the
|
|
234
|
+
* outer (`emitOuterConditional`, reactive-effects.ts) and nested
|
|
235
|
+
* (`stringifyLoopChildConditional`, here) levels so the two can't drift.
|
|
236
|
+
*/
|
|
237
|
+
export function conditionGetterExpr(
|
|
238
|
+
wrappedCondition: string,
|
|
239
|
+
readsPreamble: boolean | undefined,
|
|
240
|
+
mapPreambleWrapped: string | undefined,
|
|
241
|
+
): string {
|
|
242
|
+
return readsPreamble && mapPreambleWrapped
|
|
243
|
+
? `() => { ${mapPreambleWrapped}; return (${wrappedCondition}) }`
|
|
244
|
+
: `() => ${wrappedCondition}`
|
|
245
|
+
}
|
|
246
|
+
|
|
208
247
|
function stringifyLoopChildConditional(
|
|
209
248
|
lines: string[],
|
|
210
249
|
cond: LoopChildConditionalPlan,
|
|
211
250
|
indent: string,
|
|
212
251
|
pc: string | undefined,
|
|
252
|
+
mapPreambleWrapped: string | undefined,
|
|
213
253
|
): void {
|
|
214
254
|
const armIndent = `${indent} `
|
|
215
255
|
// Body-form arrows wire `__bfSlot` captures into the runtime so live
|
|
216
256
|
// `Node` returns from Child-position interpolations are spliced into
|
|
217
257
|
// the parsed fragment instead of being stringified by the surrounding
|
|
218
258
|
// template literal (#1213).
|
|
219
|
-
lines.push(`${indent}insert(${cond.scopeVar}, '${cond.slotId}',
|
|
259
|
+
lines.push(`${indent}insert(${cond.scopeVar}, '${cond.slotId}', ${conditionGetterExpr(cond.wrappedCondition, cond.readsPreamble, mapPreambleWrapped)}, {`)
|
|
220
260
|
lines.push(`${indent} template: () => { const __slots = []; return { html: \`${cond.whenTrueTemplateHtml}\`, slots: __slots } },`)
|
|
221
261
|
lines.push(`${indent} bindEvents: (__branchScope, { isFirstRun: __bfFirstRun = false } = {}) => {`)
|
|
222
|
-
stringifyLoopChildArm(lines, cond.whenTrueArm, armIndent, pc)
|
|
262
|
+
stringifyLoopChildArm(lines, cond.whenTrueArm, armIndent, pc, mapPreambleWrapped)
|
|
223
263
|
lines.push(`${indent} }`)
|
|
224
264
|
lines.push(`${indent}}, {`)
|
|
225
265
|
lines.push(`${indent} template: () => { const __slots = []; return { html: \`${cond.whenFalseTemplateHtml}\`, slots: __slots } },`)
|
|
226
266
|
lines.push(`${indent} bindEvents: (__branchScope, { isFirstRun: __bfFirstRun = false } = {}) => {`)
|
|
227
|
-
stringifyLoopChildArm(lines, cond.whenFalseArm, armIndent, pc)
|
|
267
|
+
stringifyLoopChildArm(lines, cond.whenFalseArm, armIndent, pc, mapPreambleWrapped)
|
|
228
268
|
lines.push(`${indent} }`)
|
|
229
269
|
lines.push(`${indent}}${profileBindingId(pc, cond.slotId)})`)
|
|
230
270
|
}
|
|
@@ -242,6 +282,8 @@ export function stringifyLoopChildArm(
|
|
|
242
282
|
arm: LoopChildArmPlan,
|
|
243
283
|
armIndent: string,
|
|
244
284
|
pc: string | undefined,
|
|
285
|
+
/** The row's preamble re-run statement, when this scope has one (#2596/#2447) — forwarded to this arm's own reactive attrs and nested conditionals. */
|
|
286
|
+
mapPreambleWrapped: string | undefined,
|
|
245
287
|
): void {
|
|
246
288
|
stringifyBranchEventBindings(lines, arm.events, armIndent)
|
|
247
289
|
stringifyBranchChildComponentInits(lines, arm.childComponents, armIndent)
|
|
@@ -257,7 +299,7 @@ export function stringifyLoopChildArm(
|
|
|
257
299
|
if (!hasDisposables) return
|
|
258
300
|
|
|
259
301
|
lines.push(`${armIndent}const __disposers = []`)
|
|
260
|
-
stringifyBranchReactiveAttrs(lines, arm.attrs, armIndent, pc)
|
|
302
|
+
stringifyBranchReactiveAttrs(lines, arm.attrs, armIndent, pc, mapPreambleWrapped)
|
|
261
303
|
|
|
262
304
|
// Nested conditionals: each inner `insert()` is wrapped in its own
|
|
263
305
|
// disposable effect so disposing THIS entry cascades to whatever
|
|
@@ -265,7 +307,7 @@ export function stringifyLoopChildArm(
|
|
|
265
307
|
// conditional's nested-conditional handling in stringify/insert.ts).
|
|
266
308
|
for (const cond of arm.nestedConditionals) {
|
|
267
309
|
lines.push(`${armIndent}__disposers.push(createDisposableEffect(() => {`)
|
|
268
|
-
stringifyLoopChildConditional(lines, cond, `${armIndent} `, pc)
|
|
310
|
+
stringifyLoopChildConditional(lines, cond, `${armIndent} `, pc, mapPreambleWrapped)
|
|
269
311
|
lines.push(`${armIndent}}))`)
|
|
270
312
|
}
|
|
271
313
|
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
32
|
import { emitRefCall, varSlotId, profileBindingId } from '../../utils.ts'
|
|
33
|
-
import {
|
|
33
|
+
import { emitDedupedAttrUpdate, DEDUP_STORE_DECL } from '../../emit-reactive.ts'
|
|
34
34
|
import { stringifyReactiveEffects } from './reactive-effects.ts'
|
|
35
35
|
import { emitTemplateCloneInline, emitLoopItemElementSetup, emitHoistedTemplateDecl, hoistedCloneExpr, namespaceWrapForTemplate, multiRootNamespaceWrapForTemplate, wrapHtmlForNamespace } from './template-parse.ts'
|
|
36
36
|
import { buildSkeletonPathPlan, type SkeletonPathPlan } from './skeleton-paths.ts'
|
|
@@ -452,13 +452,15 @@ export function stringifyStaticLoop(lines: string[], plan: StaticLoopPlan): void
|
|
|
452
452
|
lines.push(` }`)
|
|
453
453
|
}
|
|
454
454
|
lines.push(` if (__iterEl) {`)
|
|
455
|
+
if (attrsBySlot.length > 0) lines.push(` ${DEDUP_STORE_DECL}`)
|
|
456
|
+
let ordinal = 0
|
|
455
457
|
for (const [slotId, attrs] of attrsBySlot) {
|
|
456
458
|
const varName = `__t_${varSlotId(slotId)}`
|
|
457
459
|
lines.push(` const ${varName} = qsa(__iterEl, '[bf="${slotId}"]')`)
|
|
458
460
|
lines.push(` if (${varName}) {`)
|
|
459
461
|
for (const attr of attrs) {
|
|
460
462
|
lines.push(` createEffect(() => {`)
|
|
461
|
-
for (const stmt of
|
|
463
|
+
for (const stmt of emitDedupedAttrUpdate(varName, attr.attrName, attr.expression, attr, ordinal++)) {
|
|
462
464
|
lines.push(` ${stmt}`)
|
|
463
465
|
}
|
|
464
466
|
lines.push(` }${profileBindingId(pc, slotId)})`)
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
*/
|
|
33
33
|
|
|
34
34
|
import { varSlotId, profileBindingId } from '../../utils.ts'
|
|
35
|
-
import {
|
|
36
|
-
import { stringifyLoopChildArm } from './loop-child-arm.ts'
|
|
35
|
+
import { emitDedupedAttrUpdate, DEDUP_STORE_DECL } from '../../emit-reactive.ts'
|
|
36
|
+
import { stringifyLoopChildArm, conditionGetterExpr } from './loop-child-arm.ts'
|
|
37
37
|
import { claimPlanLiteral, claimWriterVarName, type ClaimSlotSpec } from './claim-plan.ts'
|
|
38
38
|
import type {
|
|
39
39
|
NestedConditionalPlan,
|
|
@@ -103,7 +103,11 @@ export interface StringifyReactiveEffectsOptions {
|
|
|
103
103
|
* The loop callback's pre-return preamble (already wrapped with the loop
|
|
104
104
|
* param accessor), re-run once per row-effect tick — BEFORE any preamble
|
|
105
105
|
* region read — so `preambleRegions`' `valueExpr`s see freshly recomputed
|
|
106
|
-
* locals.
|
|
106
|
+
* locals. Also consumed by every OTHER `readsPreamble` site in this row's
|
|
107
|
+
* scope: row-level attrs (`emitAttrSlotsGranular`/`emitConsolidatedRowEffect`),
|
|
108
|
+
* outer condition getters (`emitOuterConditional`), and — forwarded
|
|
109
|
+
* through `stringifyLoopChildArm` (#2596 follow-up) — that row's branch
|
|
110
|
+
* arms' own reactive attrs and nested condition getters.
|
|
107
111
|
*/
|
|
108
112
|
mapPreambleWrapped?: string
|
|
109
113
|
}
|
|
@@ -160,7 +164,9 @@ function emitAttrSlotsGranular(
|
|
|
160
164
|
const varName = `__ra_${varSlotId(slot.slotId)}`
|
|
161
165
|
const lookupExpr = attrLookupExpr(slot.slotId, varName, elVar, lookup, elementIndexBySlot)
|
|
162
166
|
lines.push(`${indent}{ const ${varName} = ${lookupExpr}`)
|
|
167
|
+
lines.push(`${indent}${DEDUP_STORE_DECL}`)
|
|
163
168
|
lines.push(`${indent}if (${varName}) {`)
|
|
169
|
+
let ordinal = 0
|
|
164
170
|
for (const attr of slot.attrs) {
|
|
165
171
|
lines.push(`${indent} createEffect(() => {`)
|
|
166
172
|
// Profile mode keeps one effect per attr, so an attr reading a preamble
|
|
@@ -171,7 +177,7 @@ function emitAttrSlotsGranular(
|
|
|
171
177
|
if (attr.readsPreamble && mapPreambleWrapped) {
|
|
172
178
|
lines.push(`${indent} ${mapPreambleWrapped}`)
|
|
173
179
|
}
|
|
174
|
-
for (const stmt of
|
|
180
|
+
for (const stmt of emitDedupedAttrUpdate(varName, attr.attrName, attr.wrappedExpression, attr.meta, ordinal++)) {
|
|
175
181
|
lines.push(`${indent} ${stmt}`)
|
|
176
182
|
}
|
|
177
183
|
lines.push(`${indent} }${bindingBfId(slot.slotId)})`)
|
|
@@ -233,11 +239,14 @@ function attrLookupExpr(
|
|
|
233
239
|
* dispatches per-slot on `kind`), so a row pays for at most one `lazySlots`
|
|
234
240
|
* closure + one claim `Map` instead of one per category.
|
|
235
241
|
*
|
|
236
|
-
* Every attr
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
242
|
+
* Every attr write goes through `emitDedupedAttrUpdate` (#2869): a
|
|
243
|
+
* self-contained `{ }` block (so two 'value' attrs in the same row effect
|
|
244
|
+
* can't collide on `emitAttrUpdate`'s bare `const __val`) behind a
|
|
245
|
+
* previous-value guard on the row's ONE `__l` store, ordinals sequential
|
|
246
|
+
* across every slot in the row. Without the guard, fusing bindings into one
|
|
247
|
+
* effect meant a change to ANY binding rewrote ALL of them on every rerun —
|
|
248
|
+
* `untrack()` on one binding's read couldn't stop a sibling binding's
|
|
249
|
+
* legitimate change from re-triggering this effect and re-writing it anyway.
|
|
241
250
|
*/
|
|
242
251
|
function emitConsolidatedRowEffect(
|
|
243
252
|
lines: string[],
|
|
@@ -258,6 +267,11 @@ function emitConsolidatedRowEffect(
|
|
|
258
267
|
const varName = `__ra_${varSlotId(slot.slotId)}`
|
|
259
268
|
lines.push(`${indent}const ${varName} = ${attrLookupExpr(slot.slotId, varName, elVar, lookup, elementIndexBySlot)}`)
|
|
260
269
|
}
|
|
270
|
+
// Row-wide dedup store (#2869) — one `__l`, shared by every attr write
|
|
271
|
+
// below via a sequential ordinal, so a sibling binding's legitimate
|
|
272
|
+
// rerun of this shared effect can't force an unchanged binding to
|
|
273
|
+
// rewrite too.
|
|
274
|
+
if (attrSlots.length > 0) lines.push(`${indent}${DEDUP_STORE_DECL}`)
|
|
261
275
|
|
|
262
276
|
// 2. ONE claimed-slot writer covering outer texts (kind 'text') and
|
|
263
277
|
// preamble regions (kind 'markup') — mixed-kind plans are supported by
|
|
@@ -293,15 +307,14 @@ function emitConsolidatedRowEffect(
|
|
|
293
307
|
if (mapPreambleWrapped && (preambleRegions.length > 0 || attrsReadPreamble(attrSlots))) {
|
|
294
308
|
lines.push(`${indent} ${mapPreambleWrapped}`)
|
|
295
309
|
}
|
|
310
|
+
let ordinal = 0
|
|
296
311
|
for (const slot of attrSlots) {
|
|
297
312
|
const varName = `__ra_${varSlotId(slot.slotId)}`
|
|
298
313
|
lines.push(`${indent} if (${varName}) {`)
|
|
299
314
|
for (const attr of slot.attrs) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
lines.push(`${indent} ${stmt}`)
|
|
315
|
+
for (const stmt of emitDedupedAttrUpdate(varName, attr.attrName, attr.wrappedExpression, attr.meta, ordinal++)) {
|
|
316
|
+
lines.push(`${indent} ${stmt}`)
|
|
303
317
|
}
|
|
304
|
-
lines.push(`${indent} }`)
|
|
305
318
|
}
|
|
306
319
|
lines.push(`${indent} }`)
|
|
307
320
|
}
|
|
@@ -354,18 +367,16 @@ function emitOuterConditional(
|
|
|
354
367
|
// scope here (it's a plain per-row `const`, not a signal `insert()` can see
|
|
355
368
|
// through on its own). Same treatment as `readsPreamble` attrs
|
|
356
369
|
// (`emitAttrUpdate`'s callers) get ahead of their own write.
|
|
357
|
-
const conditionGetter = cond.readsPreamble
|
|
358
|
-
? `() => { ${mapPreambleWrapped}; return (${cond.wrappedCondition}) }`
|
|
359
|
-
: `() => ${cond.wrappedCondition}`
|
|
370
|
+
const conditionGetter = conditionGetterExpr(cond.wrappedCondition, cond.readsPreamble, mapPreambleWrapped)
|
|
360
371
|
lines.push(`${indent}insert(${elVar}, '${cond.slotId}', ${conditionGetter}, {`)
|
|
361
372
|
lines.push(`${indent} template: () => { const __slots = []; return { html: \`${cond.whenTrueTemplateHtml}\`, slots: __slots } },`)
|
|
362
373
|
lines.push(`${indent} bindEvents: (__branchScope, { isFirstRun: __bfFirstRun = false } = {}) => {`)
|
|
363
|
-
stringifyLoopChildArm(lines, cond.whenTrueArm, armIndent, pc)
|
|
374
|
+
stringifyLoopChildArm(lines, cond.whenTrueArm, armIndent, pc, mapPreambleWrapped)
|
|
364
375
|
lines.push(`${indent} }`)
|
|
365
376
|
lines.push(`${indent}}, {`)
|
|
366
377
|
lines.push(`${indent} template: () => { const __slots = []; return { html: \`${cond.whenFalseTemplateHtml}\`, slots: __slots } },`)
|
|
367
378
|
lines.push(`${indent} bindEvents: (__branchScope, { isFirstRun: __bfFirstRun = false } = {}) => {`)
|
|
368
|
-
stringifyLoopChildArm(lines, cond.whenFalseArm, armIndent, pc)
|
|
379
|
+
stringifyLoopChildArm(lines, cond.whenFalseArm, armIndent, pc, mapPreambleWrapped)
|
|
369
380
|
lines.push(`${indent} }`)
|
|
370
381
|
lines.push(`${indent}}${profileBindingId(pc, cond.slotId)})`)
|
|
371
382
|
}
|
|
@@ -486,6 +486,35 @@ export function resolveGetterAliases(
|
|
|
486
486
|
return aliases
|
|
487
487
|
}
|
|
488
488
|
|
|
489
|
+
/**
|
|
490
|
+
* Which names in `signals`/`memos` are eligible ALIAS-HOP TARGETS for
|
|
491
|
+
* `resolveGetterAliases`'s `isGetter` predicate — a non-module,
|
|
492
|
+
* non-env-reader signal getter, or a non-module memo name. Env-signal
|
|
493
|
+
* getters are excluded because they resolve through their own
|
|
494
|
+
* request-scoped binding (an adapter's `searchParamsFieldRef`-style
|
|
495
|
+
* lookup or equivalent), never a seeded/`out`-keyed value an alias could
|
|
496
|
+
* usefully point at.
|
|
497
|
+
*
|
|
498
|
+
* Shared by every `resolveGetterAliases` call site (#2813's `ssr-defaults.ts`
|
|
499
|
+
* seeding and the Go adapter's `rootFieldRef` field-routing) so the
|
|
500
|
+
* eligibility rule can't silently drift between them — the "one decision,
|
|
501
|
+
* two implementations" defect class CLAUDE.md calls out (pullfrog review
|
|
502
|
+
* on #2854).
|
|
503
|
+
*/
|
|
504
|
+
export function collectAliasableGetterNames(
|
|
505
|
+
signals: readonly SignalInfo[],
|
|
506
|
+
memos: readonly MemoInfo[],
|
|
507
|
+
): Set<string> {
|
|
508
|
+
const getterNames = new Set<string>()
|
|
509
|
+
for (const sig of signals) {
|
|
510
|
+
if (sig.getter && !sig.isModule && !sig.envReader) getterNames.add(sig.getter)
|
|
511
|
+
}
|
|
512
|
+
for (const memo of memos) {
|
|
513
|
+
if (!memo.isModule) getterNames.add(memo.name)
|
|
514
|
+
}
|
|
515
|
+
return getterNames
|
|
516
|
+
}
|
|
517
|
+
|
|
489
518
|
/**
|
|
490
519
|
* Build the CSR substitution env from the live `ClientJsContext`.
|
|
491
520
|
*
|