@barefootjs/jsx 0.33.6 → 0.35.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/adapters/parsed-expr-emitter.d.ts +17 -0
- package/dist/adapters/parsed-expr-emitter.d.ts.map +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/expression-parser.d.ts +17 -4
- package/dist/expression-parser.d.ts.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +445 -282
- 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-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.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 +2 -0
- 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/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.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/lazy-row.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.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/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/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/lowering-registry.d.ts +12 -0
- package/dist/lowering-registry.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/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 +10 -12
- package/src/__tests__/build-plain-row-consolidation.test.ts +120 -0
- package/src/__tests__/client-template-escape-soundness.test.ts +538 -0
- package/src/__tests__/csr-template-loop-shadowing.test.ts +4 -2
- package/src/__tests__/flatmap-segments.test.ts +1 -1
- 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-item-conditional-codegen.test.ts +4 -2
- package/src/__tests__/lowering-value-position.test.ts +232 -0
- package/src/__tests__/map-arbitrary-body.test.ts +1 -1
- package/src/__tests__/nested-loop-index-param.test.ts +14 -3
- package/src/__tests__/nested-loop-reactive-attrs.test.ts +4 -2
- 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/adapters/parsed-expr-emitter.ts +43 -0
- package/src/analyzer.ts +4 -0
- package/src/expression-parser.ts +63 -29
- package/src/index.ts +10 -3
- package/src/ir-to-client-js/collect-elements.ts +18 -2
- 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 +10 -6
- package/src/ir-to-client-js/control-flow/plan/build-inner-loop.ts +8 -8
- package/src/ir-to-client-js/control-flow/plan/build-lazy-row.ts +18 -2
- package/src/ir-to-client-js/control-flow/plan/build-loop-child-arm.ts +3 -3
- 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 +110 -0
- package/src/ir-to-client-js/control-flow/plan/build-reactive-effects.ts +5 -2
- 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.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/lazy-row.ts +10 -0
- package/src/ir-to-client-js/csr-substitute.ts +29 -0
- package/src/ir-to-client-js/emit-reactive.ts +4 -1
- package/src/ir-to-client-js/emit-registration.ts +2 -4
- package/src/ir-to-client-js/html-template.ts +58 -244
- 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/safe-html.ts +243 -0
- package/src/ir-to-client-js/utils.ts +58 -10
- package/src/jsx-to-ir.ts +115 -20
- package/src/lowering-registry.ts +21 -0
- package/src/prop-rewrite.ts +8 -4
- package/src/props-binding.ts +37 -1
- package/src/ssr-defaults.ts +45 -0
- package/src/types.ts +5 -10
- package/src/__tests__/text-slot-escaping.test.ts +0 -65
|
@@ -23,6 +23,7 @@ import type {
|
|
|
23
23
|
TopLevelLoop,
|
|
24
24
|
} from '../../types.ts'
|
|
25
25
|
import type { IRLoopChildComponent } from '../../../types.ts'
|
|
26
|
+
import type { SafeHtml } from '../../safe-html.ts'
|
|
26
27
|
import type { SkeletonSlotPaths } from '../../html-template.ts'
|
|
27
28
|
import type { ReactiveEffectsPlan } from './reactive-effects.ts'
|
|
28
29
|
import type { InnerLoopsPlan } from './inner-loop.ts'
|
|
@@ -83,15 +84,16 @@ interface DynamicLoopCommon extends LoopPlanCommon {
|
|
|
83
84
|
|
|
84
85
|
/**
|
|
85
86
|
* A resolved preamble-patched region (#2389), ready to emit: `valueExpr` is
|
|
86
|
-
* already wrapped with the loop-param accessor and
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
87
|
+
* already wrapped with the loop-param accessor and already went through the
|
|
88
|
+
* child-splice door (`safe-html.ts`) — the exact value `irToHtmlTemplate`
|
|
89
|
+
* computes for the same node in the row template, so a same-key update's
|
|
90
|
+
* re-patch stays byte-identical to what a fresh mount would render. Typed
|
|
91
|
+
* `SafeHtml` because the region writer is an `innerHTML` write: only a
|
|
92
|
+
* value the door (or a named producer) vouched for may reach it.
|
|
91
93
|
*/
|
|
92
94
|
export interface PreambleRegionPlan {
|
|
93
95
|
slotId: string
|
|
94
|
-
valueExpr:
|
|
96
|
+
valueExpr: SafeHtml
|
|
95
97
|
}
|
|
96
98
|
|
|
97
99
|
/** Per-item ref callback resolved against a child slot for emission (#1244). */
|
|
@@ -237,6 +239,8 @@ interface CompositeLoopVariant extends DynamicLoopCommon {
|
|
|
237
239
|
loopParam: string
|
|
238
240
|
/** Destructured-binding metadata for the loop param. */
|
|
239
241
|
loopParamBindings: TopLevelLoop['paramBindings']
|
|
242
|
+
/** This loop's index param name, when it declares one (#2859). */
|
|
243
|
+
loopIndex: TopLevelLoop['index']
|
|
240
244
|
/** Reactive effects rendered after the SSR/CSR split. */
|
|
241
245
|
reactiveEffects: ReactiveEffectsPlan | null
|
|
242
246
|
/**
|
|
@@ -17,6 +17,7 @@ import type { IRLoopChildComponent, LoopParamBinding } from '../../types.ts'
|
|
|
17
17
|
import { preambleAnalysisText } from '../../types.ts'
|
|
18
18
|
import { quotePropName, wrapLoopParamAsAccessor, irChildrenFreeIds, attrValueToString } from '../utils.ts'
|
|
19
19
|
import { irChildrenToJsExpr, jsxChildrenPropGetterExpr } from '../html-template.ts'
|
|
20
|
+
import { spliceChildValue } from '../safe-html.ts'
|
|
20
21
|
import { emitListenerBlock } from './stringify/event-listener.ts'
|
|
21
22
|
import { nameForRegistryRef } from '../component-scope.ts'
|
|
22
23
|
import { BF_SCOPE, BF_HOST, BF_AT } from '@barefootjs/shared'
|
|
@@ -147,11 +148,12 @@ export function buildChildRefBindings(
|
|
|
147
148
|
refs: readonly LoopChildRef[],
|
|
148
149
|
loopParam: string,
|
|
149
150
|
loopParamBindings: readonly LoopParamBinding[] | undefined,
|
|
151
|
+
loopIndex?: string | null,
|
|
150
152
|
): readonly LoopChildRefBinding[] {
|
|
151
153
|
if (refs.length === 0) return []
|
|
152
154
|
return refs.map(r => ({
|
|
153
155
|
childSlotId: r.childSlotId,
|
|
154
|
-
callback: wrapLoopParamAsAccessor(r.callback, loopParam, loopParamBindings),
|
|
156
|
+
callback: wrapLoopParamAsAccessor(r.callback, loopParam, loopParamBindings, loopIndex),
|
|
155
157
|
}))
|
|
156
158
|
}
|
|
157
159
|
|
|
@@ -241,23 +243,27 @@ export function destructureLoopParam(
|
|
|
241
243
|
/**
|
|
242
244
|
* Resolve `IRLoop.preambleRegions` (#2389) into ready-to-emit
|
|
243
245
|
* `PreambleRegionPlan`s: wrap each source expression with the loop-param
|
|
244
|
-
* accessor and
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
246
|
+
* accessor and hand it to the same door (`spliceChildValue`) the row
|
|
247
|
+
* template's `irToHtmlTemplate` uses for the identical node — so the
|
|
248
|
+
* region-patch effect's re-render (a `'markup'` claim writer, i.e. an
|
|
249
|
+
* `innerHTML` write) matches what a fresh mount would have produced, and
|
|
250
|
+
* the escape decision is made in exactly one place. Shared by the
|
|
251
|
+
* top-level (`build-loop.ts`) and branch (`build-branch-loop.ts`)
|
|
252
|
+
* plain-loop-plan builders.
|
|
249
253
|
*/
|
|
250
254
|
export function buildPreambleRegionPlans(
|
|
251
255
|
regions: readonly PreambleRegionSource[] | undefined,
|
|
252
256
|
loopParam: string,
|
|
253
257
|
loopParamBindings: readonly LoopParamBinding[] | undefined,
|
|
258
|
+
loopIndex?: string | null,
|
|
254
259
|
): readonly PreambleRegionPlan[] {
|
|
255
260
|
if (!regions || regions.length === 0) return []
|
|
256
261
|
return regions.map((r) => {
|
|
257
|
-
const wrapped = wrapLoopParamAsAccessor(r.expr, loopParam, loopParamBindings)
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
262
|
+
const wrapped = wrapLoopParamAsAccessor(r.expr, loopParam, loopParamBindings, loopIndex)
|
|
263
|
+
// The region's row-template twin carries the same `slotId`, so the door
|
|
264
|
+
// takes the slotted arm; no `markupSlotIds` here — a loop-row slot's
|
|
265
|
+
// initial render is plain `escapeText`, matching `irToHtmlTemplate`.
|
|
266
|
+
const valueExpr = spliceChildValue({ expr: r.expr, slotId: r.slotId, joinArrayChild: r.joinArrayChild }, wrapped, {})
|
|
261
267
|
return { slotId: r.slotId, valueExpr }
|
|
262
268
|
})
|
|
263
269
|
}
|
|
@@ -270,8 +276,9 @@ export function buildComponentPropsExpr(
|
|
|
270
276
|
comp: { props: Array<{ name: string; value: import('../../types.ts').AttrValue; isEventHandler: boolean }>, children?: import('../../types.ts').IRNode[] },
|
|
271
277
|
loopParam?: string,
|
|
272
278
|
loopParamBindings?: readonly LoopParamBinding[],
|
|
279
|
+
loopIndex?: string | null,
|
|
273
280
|
): string {
|
|
274
|
-
const wrap = loopParam ? (expr: string) => wrapLoopParamAsAccessor(expr, loopParam, loopParamBindings) : (expr: string) => expr
|
|
281
|
+
const wrap = loopParam ? (expr: string) => wrapLoopParamAsAccessor(expr, loopParam, loopParamBindings, loopIndex) : (expr: string) => expr
|
|
275
282
|
const entries = comp.props.map((p) => {
|
|
276
283
|
if (p.isEventHandler) {
|
|
277
284
|
const handlerExpr = attrValueToString(p.value) ?? 'undefined'
|
|
@@ -347,7 +354,20 @@ export function buildDepthLevels(
|
|
|
347
354
|
}))
|
|
348
355
|
}
|
|
349
356
|
|
|
350
|
-
/**
|
|
357
|
+
/**
|
|
358
|
+
* Emit a single addEventListener call for a child event on a given element.
|
|
359
|
+
*
|
|
360
|
+
* This is a DIRECT, one-time listener attach (composite/inner/conditional-arm
|
|
361
|
+
* loop shapes) — unlike the top-level delegated click dispatcher
|
|
362
|
+
* (`build-event-delegation.ts`), which re-derives the current index at click
|
|
363
|
+
* time via `arr.findIndex(...)` and is unaffected by this. A handler wired
|
|
364
|
+
* here closes over whatever `renderItem` bound its raw index parameter to at
|
|
365
|
+
* ROW CREATION time, so a reference to it needs the same accessor-call
|
|
366
|
+
* rewrite as every other loop-index read in this row (#2859) — otherwise a
|
|
367
|
+
* row that survives a same-key reorder keeps firing the handler with the
|
|
368
|
+
* stale, as-created index forever, the click-handler analogue of the
|
|
369
|
+
* render-output bug #2859 itself fixes.
|
|
370
|
+
*/
|
|
351
371
|
function emitEventSetup(
|
|
352
372
|
ls: string[],
|
|
353
373
|
indent: string,
|
|
@@ -356,8 +376,9 @@ function emitEventSetup(
|
|
|
356
376
|
loopParam?: string,
|
|
357
377
|
loopParamBindings?: readonly LoopParamBinding[],
|
|
358
378
|
bodyIsMultiRoot: boolean = false,
|
|
379
|
+
loopIndex?: string | null,
|
|
359
380
|
): void {
|
|
360
|
-
const handler = loopParam ? wrapLoopParamAsAccessor(ev.handler, loopParam, loopParamBindings) : ev.handler
|
|
381
|
+
const handler = loopParam ? wrapLoopParamAsAccessor(ev.handler, loopParam, loopParamBindings, loopIndex) : ev.handler
|
|
361
382
|
emitListenerBlock(ls, indent, elVar, ev.childSlotId, '__e', ev.eventName, handler, 'dom', bodyIsMultiRoot)
|
|
362
383
|
}
|
|
363
384
|
|
|
@@ -412,14 +433,15 @@ export function emitComponentAndEventSetup(
|
|
|
412
433
|
loopParam?: string,
|
|
413
434
|
loopParamBindings?: readonly LoopParamBinding[],
|
|
414
435
|
bodyIsMultiRoot: boolean = false,
|
|
436
|
+
loopIndex?: string | null,
|
|
415
437
|
): void {
|
|
416
|
-
const wrap = loopParam ? (expr: string) => wrapLoopParamAsAccessor(expr, loopParam, loopParamBindings) : (expr: string) => expr
|
|
438
|
+
const wrap = loopParam ? (expr: string) => wrapLoopParamAsAccessor(expr, loopParam, loopParamBindings, loopIndex) : (expr: string) => expr
|
|
417
439
|
// Multi-root loop bodies (#1212) must search the item's sibling roots
|
|
418
440
|
// and the pre-insertion `__bfExtras` stash; `upsertChildItem` wraps the
|
|
419
441
|
// single-root `upsertChild` semantics in that walk.
|
|
420
442
|
const upsertFn = bodyIsMultiRoot ? 'upsertChildItem' : 'upsertChild'
|
|
421
443
|
for (const comp of comps) {
|
|
422
|
-
const propsExpr = buildComponentPropsExpr(comp, loopParam, loopParamBindings)
|
|
444
|
+
const propsExpr = buildComponentPropsExpr(comp, loopParam, loopParamBindings, loopIndex)
|
|
423
445
|
// Check if children are text-equivalent and reference the loop param — if so,
|
|
424
446
|
// emit a createEffect to reactively update textContent when the signal changes.
|
|
425
447
|
// Text-equivalent: expression, text, or conditional with text-only branches.
|
|
@@ -429,7 +451,8 @@ export function emitComponentAndEventSetup(
|
|
|
429
451
|
const rawChildrenExpr = isTextOnly ? irChildrenToJsExpr(comp.children!) : null
|
|
430
452
|
const childrenFreeIds = isTextOnly && comp.children ? irChildrenFreeIds(comp.children) : undefined
|
|
431
453
|
const childrenRefsLoop = loopParam != null && rawChildrenExpr != null && childrenFreeIds != null
|
|
432
|
-
&& exprRefsLoopBinding(childrenFreeIds, { param: loopParam, paramBindings: loopParamBindings })
|
|
454
|
+
&& (exprRefsLoopBinding(childrenFreeIds, { param: loopParam, paramBindings: loopParamBindings })
|
|
455
|
+
|| (!!loopIndex && childrenFreeIds.has(loopIndex)))
|
|
433
456
|
|
|
434
457
|
const slotIdLit = comp.slotId ? `'${comp.slotId}'` : 'null'
|
|
435
458
|
const keyProp = comp.props.find(p => p.name === 'key')
|
|
@@ -449,7 +472,7 @@ export function emitComponentAndEventSetup(
|
|
|
449
472
|
}
|
|
450
473
|
}
|
|
451
474
|
for (const ev of events) {
|
|
452
|
-
emitEventSetup(ls, indent, elVar, ev, loopParam, loopParamBindings, bodyIsMultiRoot)
|
|
475
|
+
emitEventSetup(ls, indent, elVar, ev, loopParam, loopParamBindings, bodyIsMultiRoot, loopIndex)
|
|
453
476
|
}
|
|
454
477
|
}
|
|
455
478
|
|
|
@@ -53,6 +53,7 @@ export function stringifyCompositeLoop(lines: string[], plan: CompositeLoopPlan)
|
|
|
53
53
|
innerLoops,
|
|
54
54
|
loopParam,
|
|
55
55
|
loopParamBindings,
|
|
56
|
+
loopIndex,
|
|
56
57
|
reactiveEffects,
|
|
57
58
|
childRefs,
|
|
58
59
|
branchClearChildren,
|
|
@@ -124,7 +125,7 @@ export function stringifyCompositeLoop(lines: string[], plan: CompositeLoopPlan)
|
|
|
124
125
|
// variant whose tail needs the row already connected.
|
|
125
126
|
mountRow: true,
|
|
126
127
|
})
|
|
127
|
-
emitComponentAndEventSetup(lines, bodyIndent, '__el', compsArr, eventsArr, loopParam, loopParamBindings, bodyIsMultiRoot)
|
|
128
|
+
emitComponentAndEventSetup(lines, bodyIndent, '__el', compsArr, eventsArr, loopParam, loopParamBindings, bodyIsMultiRoot, loopIndex)
|
|
128
129
|
if (innerLoops.length > 0) {
|
|
129
130
|
stringifyInnerLoops(lines, innerLoops, bodyIndent, pc)
|
|
130
131
|
}
|
|
@@ -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
|
|
@@ -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
|
*
|
|
@@ -11,6 +11,7 @@ import type { ClientJsContext } from './types.ts'
|
|
|
11
11
|
import { toHtmlAttrName, varSlotId, PROPS_PARAM } from './utils.ts'
|
|
12
12
|
import { claimPlanLiteral, claimWriterVarName, type ClaimSlotSpec } from './control-flow/stringify/claim-plan.ts'
|
|
13
13
|
import { createTemplateAwareStringProtector } from './html-template.ts'
|
|
14
|
+
import { DYNAMIC_ELEMENT_WRITER_KIND } from './markup-slots.ts'
|
|
14
15
|
import { datePlugin, DATE_METHODS } from '../date-lowering.ts'
|
|
15
16
|
import { toLocaleDatePlugin, foldedArgToClientJs } from '../to-locale-date-lowering.ts'
|
|
16
17
|
import { tsNodeToParsedExpr } from '../expression-parser.ts'
|
|
@@ -393,9 +394,11 @@ export function emitDynamicTextUpdates(lines: string[], ctx: ClientJsContext): v
|
|
|
393
394
|
// 'text') because the value may be a live Node (e.g. a JSX-returning
|
|
394
395
|
// call like `{themeLogo(id)}`) — the writer splices a Node in by
|
|
395
396
|
// identity, exactly the contract `__bfText` used to provide (#1663).
|
|
397
|
+
// The kind is a named constant so the initial-render side
|
|
398
|
+
// (`markup-slots.ts` → `escapeTextOrMarkup`) reads the same decision.
|
|
396
399
|
let writer = ''
|
|
397
400
|
if (normalElems.length > 0) {
|
|
398
|
-
const slots: ClaimSlotSpec[] = normalElems.map(elem => ({ id: elem.slotId, kind:
|
|
401
|
+
const slots: ClaimSlotSpec[] = normalElems.map(elem => ({ id: elem.slotId, kind: DYNAMIC_ELEMENT_WRITER_KIND, path: [] }))
|
|
399
402
|
writer = claimWriterVarName(slots, varSlotId)
|
|
400
403
|
lines.push(` const ${writer} = lazySlots(__scope, ${claimPlanLiteral(slots)})`)
|
|
401
404
|
}
|
|
@@ -9,6 +9,7 @@ import type { ClientJsContext } from './types.ts'
|
|
|
9
9
|
import { PROPS_PARAM } from './utils.ts'
|
|
10
10
|
import { computeInlinability, toLegacyInlinability } from './compute-inlinability.ts'
|
|
11
11
|
import { canGenerateStaticTemplate, irToComponentTemplate, generateCsrTemplate, createStringProtector } from './html-template.ts'
|
|
12
|
+
import { markupSlotIdsOf } from './markup-slots.ts'
|
|
12
13
|
import { nameForRegistryRef } from './component-scope.ts'
|
|
13
14
|
import { resolveRestSpreadNames } from './prop-handling.ts'
|
|
14
15
|
|
|
@@ -219,10 +220,7 @@ export function emitRegistrationAndHydration(
|
|
|
219
220
|
// Build ComponentDef object for hydrate()
|
|
220
221
|
const defParts: string[] = [`init: init${name}`]
|
|
221
222
|
if (canGenerateStaticTemplate(_ir.root, propNamesForStaticCheck, inlinableConstants, unsafeLocalNames)) {
|
|
222
|
-
|
|
223
|
-
// (#2651) — see `generateCsrTemplate`'s identical derivation below
|
|
224
|
-
// for why this is reused as-is rather than re-derived.
|
|
225
|
-
const markupSlotIds = new Set(ctx.dynamicElements.map(e => e.slotId))
|
|
223
|
+
const markupSlotIds = markupSlotIdsOf(ctx)
|
|
226
224
|
const templateHtml = irToComponentTemplate(_ir.root, inlinableConstants, restSpreadNames, ctx.propsObjectName, markupSlotIds, ctx.restPropsName)
|
|
227
225
|
if (templateHtml) {
|
|
228
226
|
defParts.push(buildTemplateDefPart(ctx, templateHtml))
|