@barefootjs/jsx 0.31.4 → 0.31.5
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/jsx-adapter.d.ts.map +1 -1
- package/dist/adapters/loop-bound-names.d.ts +18 -0
- package/dist/adapters/loop-bound-names.d.ts.map +1 -1
- package/dist/adapters/test-adapter.d.ts.map +1 -1
- package/dist/augment-inherited-props.d.ts +12 -2
- package/dist/augment-inherited-props.d.ts.map +1 -1
- package/dist/compiler.d.ts.map +1 -1
- package/dist/debug.d.ts.map +1 -1
- package/dist/free-refs.d.ts +11 -2
- package/dist/free-refs.d.ts.map +1 -1
- package/dist/index.js +765 -649
- package/dist/ir-to-client-js/collect-elements.d.ts.map +1 -1
- 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/reactive-effects.d.ts +7 -0
- package/dist/ir-to-client-js/control-flow/plan/reactive-effects.d.ts.map +1 -1
- package/dist/ir-to-client-js/html-template.d.ts.map +1 -1
- package/dist/ir-to-client-js/reactivity.d.ts +16 -0
- package/dist/ir-to-client-js/reactivity.d.ts.map +1 -1
- package/dist/ir-to-client-js/types.d.ts +16 -0
- package/dist/ir-to-client-js/types.d.ts.map +1 -1
- package/dist/ir-to-client-js/utils.d.ts +15 -0
- package/dist/ir-to-client-js/utils.d.ts.map +1 -1
- package/dist/module-exports.d.ts +64 -0
- package/dist/module-exports.d.ts.map +1 -1
- package/dist/scope/binding-scope.d.ts +1 -1
- package/dist/types.d.ts +41 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/binding-scope-ratchet.test.ts +146 -21
- package/src/__tests__/component-type-parameters.test.ts +70 -0
- package/src/__tests__/csr-materialize-loop-preamble-shadow.test.ts +10 -1
- package/src/__tests__/free-refs.test.ts +1 -1
- package/src/__tests__/mutable-binding-writers.test.ts +133 -0
- package/src/__tests__/preamble-conditional-reactivity.test.ts +191 -0
- package/src/__tests__/signal-setter-updater-type.test.ts +81 -0
- package/src/adapters/jsx-adapter.ts +29 -3
- package/src/adapters/loop-bound-names.ts +18 -0
- package/src/adapters/test-adapter.ts +4 -1
- package/src/augment-inherited-props.ts +13 -1
- package/src/compiler.ts +18 -0
- package/src/debug.ts +34 -21
- package/src/free-refs.ts +14 -5
- package/src/ir-to-client-js/collect-elements.ts +17 -2
- package/src/ir-to-client-js/control-flow/plan/build-reactive-effects.ts +1 -0
- package/src/ir-to-client-js/control-flow/plan/reactive-effects.ts +7 -0
- package/src/ir-to-client-js/control-flow/stringify/reactive-effects.ts +12 -2
- package/src/ir-to-client-js/html-template.ts +5 -0
- package/src/ir-to-client-js/reactivity.ts +4 -2
- package/src/ir-to-client-js/types.ts +16 -0
- package/src/ir-to-client-js/utils.ts +15 -0
- package/src/jsx-to-ir.ts +117 -1
- package/src/module-exports.ts +137 -0
- package/src/scope/binding-scope.ts +1 -1
- package/src/types.ts +41 -0
package/src/jsx-to-ir.ts
CHANGED
|
@@ -845,7 +845,7 @@ function makeBindingEnv(ctx: TransformContext): BindingEnvironment {
|
|
|
845
845
|
// mutated (cached on the immutable `BindingScope`) — a stable
|
|
846
846
|
// snapshot even if `ctx.scope` is later reassigned by an enclosing
|
|
847
847
|
// visitor frame, which swaps the instance rather than mutating it.
|
|
848
|
-
|
|
848
|
+
loopValueBoundNames: boundNames,
|
|
849
849
|
checker: a.checker,
|
|
850
850
|
}
|
|
851
851
|
ctx._bindingEnv = env
|
|
@@ -5021,6 +5021,13 @@ function transformMapCall(
|
|
|
5021
5021
|
// the row effect re-runs the preamble ahead of the write.
|
|
5022
5022
|
if (preamble && !isStaticArray) {
|
|
5023
5023
|
markPreambleAttrSlots(children, new Set(preamble.declaredNames), ctx)
|
|
5024
|
+
// #2596 follow-up to the two passes above — the CONDITION-position twin.
|
|
5025
|
+
// See `markPreambleConditionalReactivity`'s docstring for why this one is
|
|
5026
|
+
// gated on `reactiveNames` (genuinely signal-derived) rather than the
|
|
5027
|
+
// blanket `declaredNames` the region/attr passes use.
|
|
5028
|
+
if (preamble.reactiveNames && preamble.reactiveNames.length > 0) {
|
|
5029
|
+
markPreambleConditionalReactivity(children, new Set(preamble.reactiveNames), ctx)
|
|
5030
|
+
}
|
|
5024
5031
|
}
|
|
5025
5032
|
|
|
5026
5033
|
// Collect nested components for both static and dynamic arrays.
|
|
@@ -5457,6 +5464,67 @@ function markPreambleAttrSlots(
|
|
|
5457
5464
|
visit(nodes)
|
|
5458
5465
|
}
|
|
5459
5466
|
|
|
5467
|
+
/**
|
|
5468
|
+
* Grant the IR `reactive` flag (and a slot id) to a loop-body CONDITIONAL
|
|
5469
|
+
* whose condition bare-references a preamble local that is itself reactive
|
|
5470
|
+
* (#2596) — `reactiveNames`, computed by `computePreambleReactiveNames`.
|
|
5471
|
+
*
|
|
5472
|
+
* Root cause this closes: a plain (non-preamble) condition's `reactive` flag
|
|
5473
|
+
* comes from `isReactiveExpression`/`isReactiveOrigin` run against the
|
|
5474
|
+
* condition's OWN text at `transformConditional`/`transformLogicalAnd`/the
|
|
5475
|
+
* if-else-chain builder — none of which know about an enclosing loop's
|
|
5476
|
+
* `.map()` callback preamble (that's assembled and only becomes available
|
|
5477
|
+
* on `IRLoop` after `children` is already built, same ordering constraint
|
|
5478
|
+
* `collectPreambleRegions`/`markPreambleAttrSlots` work around). A condition
|
|
5479
|
+
* that's just a bare reference to a preamble local (`label ? <A/> : <B/>`)
|
|
5480
|
+
* therefore falls through every one of those classifiers with `reactive:
|
|
5481
|
+
* false` even when `label`'s initializer reads a signal — the classifiers
|
|
5482
|
+
* only ever see the token `label`, never its declaration.
|
|
5483
|
+
*
|
|
5484
|
+
* Deliberately narrower than `collectPreambleRegions`/`markPreambleAttrSlots`:
|
|
5485
|
+
* those two mark ANY reference to `declaredNames` because their region-patch
|
|
5486
|
+
* effect re-runs the WHOLE preamble unconditionally on row update, so a
|
|
5487
|
+
* merely item-derived local (`const label = item.title`) still ends up
|
|
5488
|
+
* correctly tracked by ordinary signal auto-tracking inside that effect —
|
|
5489
|
+
* marking it reactive there costs nothing extra. A conditional is different:
|
|
5490
|
+
* `reactive: true` here means Phase 2 wraps it in an `insert()` that swaps
|
|
5491
|
+
* whole DOM subtrees, which is real cost and, for an item-only local, pure
|
|
5492
|
+
* waste — the row already renders fresh whenever it's (re)constructed. So
|
|
5493
|
+
* this only fires for `reactiveNames`, the subset PROVEN to depend on an
|
|
5494
|
+
* actual external signal/memo/prop.
|
|
5495
|
+
*
|
|
5496
|
+
* Mirrors the sibling passes' traversal shape (conditional arms included).
|
|
5497
|
+
*/
|
|
5498
|
+
function markPreambleConditionalReactivity(
|
|
5499
|
+
nodes: IRNode[],
|
|
5500
|
+
reactiveNames: ReadonlySet<string>,
|
|
5501
|
+
ctx: TransformContext,
|
|
5502
|
+
): void {
|
|
5503
|
+
if (reactiveNames.size === 0) return
|
|
5504
|
+
const visit = (list: IRNode[]): void => {
|
|
5505
|
+
for (const node of list) {
|
|
5506
|
+
switch (node.type) {
|
|
5507
|
+
case 'element':
|
|
5508
|
+
case 'fragment':
|
|
5509
|
+
visit(node.children)
|
|
5510
|
+
break
|
|
5511
|
+
case 'conditional': {
|
|
5512
|
+
if (!node.reactive) {
|
|
5513
|
+
const refs = extractFreeIdentifiersFromText(node.condition)
|
|
5514
|
+
if ([...refs].some((r) => reactiveNames.has(r))) {
|
|
5515
|
+
node.reactive = true
|
|
5516
|
+
if (!node.slotId) node.slotId = generateSlotId(ctx)
|
|
5517
|
+
}
|
|
5518
|
+
}
|
|
5519
|
+
visit([node.whenTrue, ...(node.whenFalse ? [node.whenFalse] : [])])
|
|
5520
|
+
break
|
|
5521
|
+
}
|
|
5522
|
+
}
|
|
5523
|
+
}
|
|
5524
|
+
}
|
|
5525
|
+
visit(nodes)
|
|
5526
|
+
}
|
|
5527
|
+
|
|
5460
5528
|
/**
|
|
5461
5529
|
* Expression-bearing source text of an attribute value, for the
|
|
5462
5530
|
* free-identifier scan. Only used as a FALLBACK — an attribute normally
|
|
@@ -5503,6 +5571,52 @@ function collectPreambleDeclaredNames(stmt: ts.Statement, out: Set<string>): voi
|
|
|
5503
5571
|
}
|
|
5504
5572
|
}
|
|
5505
5573
|
|
|
5574
|
+
/**
|
|
5575
|
+
* Which {@link MapCallbackPreamble.declaredNames} are themselves reactive —
|
|
5576
|
+
* their OWN initializer reads a signal / memo / reactive prop, directly or
|
|
5577
|
+
* transitively through an earlier declaration in the same preamble (#2596).
|
|
5578
|
+
*
|
|
5579
|
+
* Reuses the same Phase-1 classifier (`isReactiveExpression`) already applied
|
|
5580
|
+
* to every non-preamble condition/attribute — this doesn't invent a second
|
|
5581
|
+
* reactivity heuristic, it runs the existing one over one more declaration
|
|
5582
|
+
* list. `ctx.patterns.constants` (the component-level constant chain
|
|
5583
|
+
* `isSignalOrMemoReference`/`isPropsReference` already walk) doesn't reach
|
|
5584
|
+
* these names — they're scoped to the `.map()` callback body, never added to
|
|
5585
|
+
* `ctx.patterns` — so without this pass a preamble local reading a signal is
|
|
5586
|
+
* invisible to the classifier.
|
|
5587
|
+
*
|
|
5588
|
+
* Value-declaration statements only (`const`/`let x = expr`, including
|
|
5589
|
+
* destructuring targets, which mark every bound name reactive together since
|
|
5590
|
+
* they share one initializer). A non-declaration preamble statement
|
|
5591
|
+
* (assignment, loop, side-effecting call) contributes no reactive names —
|
|
5592
|
+
* consistent with `neutralPreambleDeclarations`'s all-or-nothing DSL gate
|
|
5593
|
+
* treating anything past that as un-lowerable, though this walk (client/JS-
|
|
5594
|
+
* runtime SSR only) doesn't require the same all-or-nothing: it simply skips
|
|
5595
|
+
* what it can't classify.
|
|
5596
|
+
*/
|
|
5597
|
+
function computePreambleReactiveNames(
|
|
5598
|
+
statements: readonly ts.Statement[],
|
|
5599
|
+
ctx: TransformContext,
|
|
5600
|
+
): Set<string> {
|
|
5601
|
+
const reactiveNames = new Set<string>()
|
|
5602
|
+
for (const stmt of statements) {
|
|
5603
|
+
if (!ts.isVariableStatement(stmt)) continue
|
|
5604
|
+
for (const decl of stmt.declarationList.declarations) {
|
|
5605
|
+
if (!decl.initializer) continue
|
|
5606
|
+
const boundNames = new Set<string>()
|
|
5607
|
+
collectBindingNames(decl.name, boundNames)
|
|
5608
|
+
const initText = ctx.getJS(decl.initializer)
|
|
5609
|
+
const initFreeRefs = extractFreeIdentifiersFromNode(decl.initializer)
|
|
5610
|
+
const readsEarlierReactive = [...initFreeRefs].some((r) => reactiveNames.has(r))
|
|
5611
|
+
const isReactive = readsEarlierReactive || isReactiveExpression(initText, ctx, decl.initializer)
|
|
5612
|
+
if (isReactive) {
|
|
5613
|
+
for (const n of boundNames) reactiveNames.add(n)
|
|
5614
|
+
}
|
|
5615
|
+
}
|
|
5616
|
+
}
|
|
5617
|
+
return reactiveNames
|
|
5618
|
+
}
|
|
5619
|
+
|
|
5506
5620
|
/**
|
|
5507
5621
|
* Build a {@link MapCallbackPreamble} from value-only (JSX-free) pre-return
|
|
5508
5622
|
* statements — the Stage-2 fold preamble and the plain block-body preamble.
|
|
@@ -5526,6 +5640,7 @@ function preambleFromValueStatements(
|
|
|
5526
5640
|
typedParts.push(raw0.endsWith(';') ? raw0 : raw0 + ';')
|
|
5527
5641
|
segments.push(tjs !== js ? { kind: 'js', text: js, templateText: tjs } : { kind: 'js', text: js })
|
|
5528
5642
|
}
|
|
5643
|
+
const reactiveNames = computePreambleReactiveNames(statements, ctx)
|
|
5529
5644
|
return {
|
|
5530
5645
|
segments: trimPreambleSegments(segments),
|
|
5531
5646
|
ssrText: tsxSourceText(typedParts.join(' ')),
|
|
@@ -5533,6 +5648,7 @@ function preambleFromValueStatements(
|
|
|
5533
5648
|
// Value-only preambles accumulate no JSX, so no child needs the array join.
|
|
5534
5649
|
builderNames: [],
|
|
5535
5650
|
declarations: neutralPreambleDeclarations(statements, ctx) ?? undefined,
|
|
5651
|
+
reactiveNames: reactiveNames.size > 0 ? [...reactiveNames] : undefined,
|
|
5536
5652
|
}
|
|
5537
5653
|
}
|
|
5538
5654
|
|
package/src/module-exports.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* This is a compiler-layer concern, not adapter-specific.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import ts from 'typescript'
|
|
8
9
|
import type { ComponentIR, ParamInfo } from './types.ts'
|
|
9
10
|
import { identifierPattern } from './identifier-pattern.ts'
|
|
10
11
|
|
|
@@ -161,6 +162,142 @@ export function findReachableNames(
|
|
|
161
162
|
return reachable
|
|
162
163
|
}
|
|
163
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Which of `candidates` does `bodyText` ASSIGN to?
|
|
167
|
+
*
|
|
168
|
+
* Reachability above answers "is this declaration referenced?", which is
|
|
169
|
+
* the right question for pruning SSR-irrelevant code. It is the wrong
|
|
170
|
+
* question for a MUTABLE binding: a surviving `let` whose only writer got
|
|
171
|
+
* pruned is left declared-and-read but never assigned, and TypeScript's
|
|
172
|
+
* control-flow analysis then narrows it to `never` at every guarded use
|
|
173
|
+
* (#2598). `closeOverWritersOfMutableBindings` uses this to restore the
|
|
174
|
+
* missing half of that pair.
|
|
175
|
+
*
|
|
176
|
+
* Recognizes the forms that actually write a local binding:
|
|
177
|
+
* `x = …`, `x += …` (and every other compound operator), `x++`, `--x`
|
|
178
|
+
* Destructuring assignment (`[x] = …`, `({ x } = …)`) is deliberately NOT
|
|
179
|
+
* recognized: it never appears in the ref/handler shapes this exists for,
|
|
180
|
+
* and a wrong guess here over-retains rather than fails loudly, so leaving
|
|
181
|
+
* it out keeps the retained set honest. If one shows up, it will present
|
|
182
|
+
* as this same `never` narrowing and can be added with a fixture.
|
|
183
|
+
*
|
|
184
|
+
* Parsed with the TS AST, not matched as text: `identifierPattern` (used
|
|
185
|
+
* for reference detection above) cannot tell a write from a read, and a
|
|
186
|
+
* regex for `name\s*=` would match `name == x`, a `name=` inside a string
|
|
187
|
+
* or JSX attribute, and a property write `obj.name = x` that assigns
|
|
188
|
+
* nothing of the sort.
|
|
189
|
+
*/
|
|
190
|
+
export function findAssignedNames(
|
|
191
|
+
bodyText: string,
|
|
192
|
+
candidates: ReadonlySet<string>,
|
|
193
|
+
): Set<string> {
|
|
194
|
+
const assigned = new Set<string>()
|
|
195
|
+
if (candidates.size === 0) return assigned
|
|
196
|
+
|
|
197
|
+
const sf = ts.createSourceFile(
|
|
198
|
+
'bf-assignment-scan.tsx',
|
|
199
|
+
bodyText,
|
|
200
|
+
ts.ScriptTarget.Latest,
|
|
201
|
+
/* setParentNodes */ false,
|
|
202
|
+
ts.ScriptKind.TSX,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
// A bare Identifier on the left of an assignment — `obj.x = …` is a
|
|
206
|
+
// PropertyAccessExpression and writes through the binding rather than to
|
|
207
|
+
// it, so it does not count.
|
|
208
|
+
const record = (target: ts.Node): void => {
|
|
209
|
+
if (ts.isIdentifier(target) && candidates.has(target.text)) {
|
|
210
|
+
assigned.add(target.text)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const visit = (node: ts.Node): void => {
|
|
215
|
+
if (ts.isBinaryExpression(node) && isAssignmentOperator(node.operatorToken.kind)) {
|
|
216
|
+
record(node.left)
|
|
217
|
+
} else if (
|
|
218
|
+
(ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) &&
|
|
219
|
+
(node.operator === ts.SyntaxKind.PlusPlusToken || node.operator === ts.SyntaxKind.MinusMinusToken)
|
|
220
|
+
) {
|
|
221
|
+
record(node.operand)
|
|
222
|
+
}
|
|
223
|
+
ts.forEachChild(node, visit)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
ts.forEachChild(sf, visit)
|
|
227
|
+
return assigned
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* `findReachableNames`, plus the invariant it cannot express on its own:
|
|
232
|
+
* **a mutable binding that survives keeps the declarations that write it.**
|
|
233
|
+
*
|
|
234
|
+
* Reachability is seeded from the RENDERED JSX, which has already had the
|
|
235
|
+
* client-only attributes stripped — `ref={setRef}` leaves no `setRef`
|
|
236
|
+
* behind, and `onClick={handleClick}` is rendered as `onClick={() => {}}`.
|
|
237
|
+
* That is deliberate: code reachable only from a handler is client-only
|
|
238
|
+
* and should not be emitted into an SSR template.
|
|
239
|
+
*
|
|
240
|
+
* It goes wrong when a `let` outlives its writer. The binding survives
|
|
241
|
+
* because some OTHER surviving declaration reads it, while its only
|
|
242
|
+
* assignment lived in a pruned handler — so the emitted template declares
|
|
243
|
+
* it, reads it, and never assigns it. TypeScript's control-flow analysis
|
|
244
|
+
* concludes it is permanently `null`, narrows every guarded use to `never`,
|
|
245
|
+
* and each member access on it fails:
|
|
246
|
+
*
|
|
247
|
+
* let highlightEl: HTMLElement | null = null // writer was pruned
|
|
248
|
+
* const syncScroll = () => {
|
|
249
|
+
* if (highlightEl && textareaEl) {
|
|
250
|
+
* highlightEl.scrollTop = textareaEl.scrollTop // TS2339 on `never`
|
|
251
|
+
* }
|
|
252
|
+
* }
|
|
253
|
+
*
|
|
254
|
+
* Pulling the writers back in restores the source's shape for exactly the
|
|
255
|
+
* bindings that survived — nothing else. The retained writer is dead code
|
|
256
|
+
* at SSR (it only ever runs from a hydrated event), which is the same
|
|
257
|
+
* harmless-unused-declaration trade `generateModuleScopeDeclarations`
|
|
258
|
+
* already makes deliberately.
|
|
259
|
+
*
|
|
260
|
+
* Iterates to a fixpoint because a newly retained writer can read further
|
|
261
|
+
* declarations, and can itself write another mutable binding. Bounded by
|
|
262
|
+
* the declaration count: each round either adds a name or stops.
|
|
263
|
+
*/
|
|
264
|
+
export function closeOverWritersOfMutableBindings(
|
|
265
|
+
primaryRefs: string,
|
|
266
|
+
declarations: { name: string; body: string }[],
|
|
267
|
+
mutableNames: ReadonlySet<string>,
|
|
268
|
+
): Set<string> {
|
|
269
|
+
let reachable = findReachableNames(primaryRefs, declarations)
|
|
270
|
+
if (mutableNames.size === 0) return reachable
|
|
271
|
+
|
|
272
|
+
let seedText = primaryRefs
|
|
273
|
+
for (let round = 0; round <= declarations.length; round++) {
|
|
274
|
+
const survivingMutables = new Set(
|
|
275
|
+
[...reachable].filter(name => mutableNames.has(name)),
|
|
276
|
+
)
|
|
277
|
+
if (survivingMutables.size === 0) return reachable
|
|
278
|
+
|
|
279
|
+
const added = declarations
|
|
280
|
+
.filter(d => !reachable.has(d.name))
|
|
281
|
+
.filter(d => findAssignedNames(d.body, survivingMutables).size > 0)
|
|
282
|
+
.map(d => d.name)
|
|
283
|
+
if (added.length === 0) return reachable
|
|
284
|
+
|
|
285
|
+
// Re-seed by NAME rather than merging sets directly, so each retained
|
|
286
|
+
// writer's own transitive dependencies come along through the same
|
|
287
|
+
// traversal instead of a second, divergent one.
|
|
288
|
+
seedText += '\n' + added.join('\n')
|
|
289
|
+
reachable = findReachableNames(seedText, declarations)
|
|
290
|
+
}
|
|
291
|
+
return reachable
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function isAssignmentOperator(kind: ts.SyntaxKind): boolean {
|
|
295
|
+
return (
|
|
296
|
+
kind >= ts.SyntaxKind.FirstAssignment &&
|
|
297
|
+
kind <= ts.SyntaxKind.LastAssignment
|
|
298
|
+
)
|
|
299
|
+
}
|
|
300
|
+
|
|
164
301
|
/**
|
|
165
302
|
* Extract parameter names from a function expression string.
|
|
166
303
|
* Handles: arrow functions, single-param arrows, function expressions.
|
|
@@ -201,7 +201,7 @@ export class BindingScope {
|
|
|
201
201
|
* qualifies, including a preamble local shadowing a module const.
|
|
202
202
|
* These call `isBound` / `boundNames()`.
|
|
203
203
|
* - REACTIVITY / SLOT-ID CLASSIFIERS (`referencesLoopParam`,
|
|
204
|
-
* `hasReactiveAttributes`, and the `BindingEnvironment.
|
|
204
|
+
* `hasReactiveAttributes`, and the `BindingEnvironment.loopValueBoundNames`
|
|
205
205
|
* feed built from `makeBindingEnv`, all in `jsx-to-ir.ts`) ask
|
|
206
206
|
* "does this expression read a value that changes per row and so
|
|
207
207
|
* needs its own patchable slot" — a preamble local already gets
|
package/src/types.ts
CHANGED
|
@@ -901,6 +901,28 @@ export interface MapCallbackPreamble {
|
|
|
901
901
|
* keeps the plain interpolation it always had.
|
|
902
902
|
*/
|
|
903
903
|
builderNames: string[]
|
|
904
|
+
/**
|
|
905
|
+
* The subset of {@link declaredNames} whose OWN initializer is itself
|
|
906
|
+
* reactive — reads a signal / memo / reactive prop, directly or
|
|
907
|
+
* transitively through an earlier preamble declaration (#2596). `undefined`
|
|
908
|
+
* (never an empty array) when the preamble contributes no such name, or
|
|
909
|
+
* when it isn't a value-only declaration sequence this analysis covers
|
|
910
|
+
* (see `preambleFromValueStatements`'s caller — a JSX-building preamble
|
|
911
|
+
* doesn't compute this and leaves it unset).
|
|
912
|
+
*
|
|
913
|
+
* Used to decide whether a loop-body CONDITIONAL whose condition
|
|
914
|
+
* bare-references a preamble local should carry the IR `reactive` flag
|
|
915
|
+
* (`markPreambleConditionalReactivity`, jsx-to-ir.ts). Deliberately NOT the
|
|
916
|
+
* same test `collectPreambleRegions`/`markPreambleAttrSlots` use for
|
|
917
|
+
* text/attr positions — those mark ANY reference to `declaredNames`
|
|
918
|
+
* reactive because their region-patch effect re-runs the whole preamble
|
|
919
|
+
* unconditionally on row update, and ordinary signal auto-tracking inside
|
|
920
|
+
* that effect picks up genuine dependencies regardless of the IR flag. A
|
|
921
|
+
* conditional instead swaps whole DOM subtrees via `insert()`, so it stays
|
|
922
|
+
* unwrapped unless the local it reads is proven to depend on an actual
|
|
923
|
+
* external signal — a bare `item.title`-derived local needs no such wrap.
|
|
924
|
+
*/
|
|
925
|
+
reactiveNames?: string[]
|
|
904
926
|
}
|
|
905
927
|
|
|
906
928
|
/**
|
|
@@ -1940,6 +1962,25 @@ export interface IRMetadata {
|
|
|
1940
1962
|
isClientComponent: boolean
|
|
1941
1963
|
typeDefinitions: TypeDefinition[]
|
|
1942
1964
|
propsType: TypeInfo | null
|
|
1965
|
+
/**
|
|
1966
|
+
* The component function's own generic type parameter list, verbatim
|
|
1967
|
+
* from source (each `node.getText()`, joined and wrapped in `<...>`),
|
|
1968
|
+
* e.g. `<NodeType extends NodeBase = NodeBase, EdgeType extends
|
|
1969
|
+
* EdgeBase = EdgeBase>`. `null` when the component isn't generic.
|
|
1970
|
+
*
|
|
1971
|
+
* A generic function component's props type (and often its body) keeps
|
|
1972
|
+
* referencing these names verbatim in emitted output (e.g. `props:
|
|
1973
|
+
* FlowComponentProps<NodeType, EdgeType>`, `createFlowStore<NodeType,
|
|
1974
|
+
* EdgeType>(props)`) — without the function's own declaration also
|
|
1975
|
+
* carrying the type parameters, those references are unresolved names
|
|
1976
|
+
* in the emitted `.tsx` (TS2304). Emitters that print a `function
|
|
1977
|
+
* <name>(...)` signature for the component must splice this verbatim
|
|
1978
|
+
* between the name and the parameter list. Optional (rather than
|
|
1979
|
+
* required) so the many hand-built `IRMetadata` test fixtures across
|
|
1980
|
+
* the suite don't need updating for a field that is `null` for the
|
|
1981
|
+
* overwhelming majority of (non-generic) components.
|
|
1982
|
+
*/
|
|
1983
|
+
typeParameters?: string | null
|
|
1943
1984
|
propsParams: ParamInfo[]
|
|
1944
1985
|
/** Name of the props object parameter (e.g., 'props' in `function Component(props: Props)`) */
|
|
1945
1986
|
propsObjectName: string | null
|