@barefootjs/jsx 0.35.0 → 0.35.2
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/index.js +731 -691
- 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-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.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-plain-row.d.ts.map +1 -1
- package/dist/ir-to-client-js/control-flow/plan/build-reactive-effects.d.ts +3 -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/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/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/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/html-template.d.ts.map +1 -1
- 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/types.d.ts +18 -2
- package/dist/ir-to-client-js/types.d.ts.map +1 -1
- package/dist/reactivity-checker.d.ts +18 -0
- package/dist/reactivity-checker.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +128 -10
- package/src/__tests__/binding-scope-ratchet.test.ts +26 -3
- package/src/__tests__/child-prop-fallback-wrap.test.ts +4 -2
- package/src/__tests__/compiler-stress-1244.test.ts +8 -8
- 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__/loop-index-reactivity.test.ts +208 -0
- package/src/__tests__/map-body-brand-checker.test.ts +176 -0
- package/src/__tests__/nested-loop-index-param.test.ts +27 -5
- 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 +12 -4
- package/src/__tests__/preamble-branch-arm-rerun.test.ts +135 -0
- package/src/__tests__/style-css-var-reactive.test.ts +4 -1
- package/src/analyzer-context.ts +12 -10
- package/src/ir-to-client-js/collect-elements.ts +110 -32
- package/src/ir-to-client-js/control-flow/plan/build-composite-loop.ts +2 -0
- package/src/ir-to-client-js/control-flow/plan/build-inner-loop.ts +28 -51
- package/src/ir-to-client-js/control-flow/plan/build-lazy-row.ts +7 -2
- package/src/ir-to-client-js/control-flow/plan/build-loop-child-arm.ts +33 -6
- package/src/ir-to-client-js/control-flow/plan/build-plain-row.ts +7 -1
- package/src/ir-to-client-js/control-flow/plan/build-reactive-effects.ts +18 -6
- package/src/ir-to-client-js/control-flow/plan/inner-loop.ts +24 -45
- package/src/ir-to-client-js/control-flow/plan/loop-child-arm.ts +14 -0
- 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 +12 -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/emit-reactive.ts +88 -5
- package/src/ir-to-client-js/html-template.ts +9 -1
- package/src/ir-to-client-js/reactivity.ts +73 -27
- package/src/ir-to-client-js/types.ts +19 -3
- package/src/jsx-to-ir.ts +21 -4
- package/src/reactivity-checker.ts +60 -0
|
@@ -400,7 +400,19 @@ export interface LoopChildRef {
|
|
|
400
400
|
export interface BranchLoop extends LoopCore {
|
|
401
401
|
kind: 'branch'
|
|
402
402
|
index: string | null // Index parameter (e.g., 'i')
|
|
403
|
-
template: string // HTML template for each item
|
|
403
|
+
template: string // HTML template for each item, item-only wrapped (no index — see `templateIndexed`)
|
|
404
|
+
/**
|
|
405
|
+
* Same render as `template`, but with `index` also wrapped as an accessor
|
|
406
|
+
* (`i()`) — rendered structurally at IR time (`irToHtmlTemplate`'s
|
|
407
|
+
* `loopParams`), never via a post-hoc regex pass over `template` (#2868:
|
|
408
|
+
* a word-boundary regex over assembled HTML can match a bare tag name
|
|
409
|
+
* colliding with the index identifier, e.g. `<i>` -> `<i()>`). Present
|
|
410
|
+
* iff the loop declares an index; `build-plain-row.ts` picks this over
|
|
411
|
+
* `template` once lazy eligibility rules out the lazy row plan (which
|
|
412
|
+
* needs the index UNWRAPPED — `mapArrayLazy`'s `createRow` hands it a
|
|
413
|
+
* plain number, never an accessor).
|
|
414
|
+
*/
|
|
415
|
+
templateIndexed?: string
|
|
404
416
|
containerSlotId: string // bf slot ID of the container element (e.g., 's1' for <ul bf="s1">)
|
|
405
417
|
preamble?: MapCallbackPreamble
|
|
406
418
|
// Composite loop fields (loops whose body contains child components)
|
|
@@ -489,8 +501,6 @@ export interface NestedLoop extends LoopCore {
|
|
|
489
501
|
* (and any subsequent reads) are in scope (#1052).
|
|
490
502
|
*/
|
|
491
503
|
preamble?: MapCallbackPreamble
|
|
492
|
-
/** Whether the inner array references the outer loop param (needs reactive mapArray) */
|
|
493
|
-
refsOuterParam?: boolean
|
|
494
504
|
/** Child components inside inner loop items (for initChild/createComponent) */
|
|
495
505
|
childComponents?: import('../types.ts').IRLoopChildComponent[]
|
|
496
506
|
/** True when this loop is inside a conditional branch (handled by insert() bindEvents instead) */
|
|
@@ -622,6 +632,12 @@ export interface TopLevelLoop extends LoopCore {
|
|
|
622
632
|
slotId: string
|
|
623
633
|
index: string | null
|
|
624
634
|
template: string
|
|
635
|
+
/**
|
|
636
|
+
* Same render as `template`, but with `index` also wrapped as an accessor
|
|
637
|
+
* (`i()`) — see `BranchLoop.templateIndexed` for why this exists as a
|
|
638
|
+
* second structured render rather than a post-hoc regex pass (#2868).
|
|
639
|
+
*/
|
|
640
|
+
templateIndexed?: string
|
|
625
641
|
/**
|
|
626
642
|
* Per-iteration HTML template for static-array loops that need to
|
|
627
643
|
* self-heal on CSR mount (#1247). Unlike `template`, this variant skips
|
package/src/jsx-to-ir.ts
CHANGED
|
@@ -45,7 +45,7 @@ import type { IRLoopSort, FunctionInfo, ConstantInfo } from './types.ts'
|
|
|
45
45
|
import { formatParamWithType } from './module-exports.ts'
|
|
46
46
|
import { createError, ErrorCodes, internalInvariant } from './errors.ts'
|
|
47
47
|
import { CLIENT_BUILTIN_SOURCE, isClientBuiltinName, type ClientBuiltinTag } from './builtins.ts'
|
|
48
|
-
import { containsReactiveExpression } from './reactivity-checker.ts'
|
|
48
|
+
import { containsReactiveExpression, collectReactiveBrandLeaves } from './reactivity-checker.ts'
|
|
49
49
|
import {
|
|
50
50
|
rewriteBarePropRefs as rewriteBarePropRefsCore,
|
|
51
51
|
collectAstPropRefs,
|
|
@@ -2429,7 +2429,13 @@ function transformExpressionInner(
|
|
|
2429
2429
|
// A reactive brand-package condition (e.g. `form.field('x').error() &&
|
|
2430
2430
|
// …`) can't be SSR-evaluated, so defer the whole conditional rather
|
|
2431
2431
|
// than raising BF061 — same routing as a manual `/* @client */` (#1638).
|
|
2432
|
-
|
|
2432
|
+
// Check `ir.type` first: `shouldAutoDeferReactiveBrand` only matters for
|
|
2433
|
+
// IRConditional, but walks the checker over `expr` — for every other
|
|
2434
|
+
// shape (in particular IRLoop, whose `expr` is a `.map()` callback body
|
|
2435
|
+
// full of JSX) that walk is both wasted work and, historically, exactly
|
|
2436
|
+
// what tripped the getJS trust-boundary assertion on a JSX-bearing node
|
|
2437
|
+
// (#2867/#2830).
|
|
2438
|
+
if (ir.type === 'conditional' && (isClientOnly || shouldAutoDeferReactiveBrand(expr, ctx))) {
|
|
2433
2439
|
ir.clientOnly = true
|
|
2434
2440
|
if (!ir.slotId) {
|
|
2435
2441
|
ir.slotId = generateSlotId(ctx)
|
|
@@ -8210,10 +8216,21 @@ function isReactiveExpression(expr: string, ctx: TransformContext, astNode?: ts.
|
|
|
8210
8216
|
function shouldAutoDeferReactiveBrand(expr: ts.Expression, ctx: TransformContext): boolean {
|
|
8211
8217
|
const checker = ctx.analyzer.checker
|
|
8212
8218
|
if (!checker) return false
|
|
8213
|
-
|
|
8219
|
+
// Read only the brand-carrying leaf(s), never `expr` itself: `expr` can be
|
|
8220
|
+
// a `.map()` callback or a ternary/`&&` with JSX arms, and getJS() refuses
|
|
8221
|
+
// to stringify a JSX-bearing node (the write-side trust boundary — JSX
|
|
8222
|
+
// must travel as structured segments, never raw text). Each leaf is an
|
|
8223
|
+
// identifier / property access / call callee, none of which can contain
|
|
8224
|
+
// JSX, so getJS() is safe on them regardless of what the rest of `expr`
|
|
8225
|
+
// contains (#2867/#2830 — this used to call getJS(expr) directly and
|
|
8226
|
+
// throw whenever the checker was active and expr had JSX in it).
|
|
8227
|
+
const leaves = collectReactiveBrandLeaves(expr, checker)
|
|
8228
|
+
if (leaves.length === 0) return false
|
|
8214
8229
|
// Native signals/memos (incl. chained-const aliases) are SSR-derivable —
|
|
8215
8230
|
// leave them to the normal template path so their initial value renders.
|
|
8216
|
-
|
|
8231
|
+
// Preserves the original "any native ref anywhere defers to non-deferred"
|
|
8232
|
+
// semantics by checking every reactive-brand leaf, not just the first.
|
|
8233
|
+
if (leaves.some((leaf) => isSignalOrMemoReference(ctx.getJS(leaf), ctx))) return false
|
|
8217
8234
|
return true
|
|
8218
8235
|
}
|
|
8219
8236
|
|
|
@@ -191,3 +191,63 @@ export function containsReactiveExpression(node: ts.Node, checker: ts.TypeChecke
|
|
|
191
191
|
incrementCounter('reactivityChecks')
|
|
192
192
|
return brandTypeReactivityAnalyzer.analyze(node, checker).isReactive
|
|
193
193
|
}
|
|
194
|
+
|
|
195
|
+
/** Subtree JSX check — also used by analyzer-context.ts's getJS() guard. */
|
|
196
|
+
export function nodeContainsJsx(node: ts.Node): boolean {
|
|
197
|
+
if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node) || ts.isJsxFragment(node)) return true
|
|
198
|
+
return ts.forEachChild(node, nodeContainsJsx) ?? false
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Every node that itself carries the `Reactive<T>` brand within `node`,
|
|
203
|
+
* without stopping at the first match (unlike `analyze`, which only needs
|
|
204
|
+
* one hit to answer "is this reactive"). A caller that needs to read the
|
|
205
|
+
* *text* of the reactive part(s) — e.g. to tell a brand-package wrapper
|
|
206
|
+
* apart from a plain native signal/memo call (#2867) — must not stringify
|
|
207
|
+
* `node` itself: for a `.map()` callback or a ternary with JSX arms, `node`
|
|
208
|
+
* is JSX-bearing, and the write-side trust boundary (`getJS()`,
|
|
209
|
+
* `analyzer-context.ts`) rejects producing text for a JSX-bearing node.
|
|
210
|
+
* Each returned node is JSX-free: property access and identifier leaves
|
|
211
|
+
* cannot contain JSX by construction; a call leaf is the whole call
|
|
212
|
+
* (preserving the `name(...)` shape native-signal/memo regex patterns
|
|
213
|
+
* match against) unless its arguments happen to contain JSX, in which case
|
|
214
|
+
* only the callee is returned instead.
|
|
215
|
+
*/
|
|
216
|
+
export function collectReactiveBrandLeaves(node: ts.Node, checker: ts.TypeChecker): ts.Node[] {
|
|
217
|
+
const leaves: ts.Node[] = []
|
|
218
|
+
const visit = (n: ts.Node): void => {
|
|
219
|
+
if (ts.isPropertyAccessExpression(n)) {
|
|
220
|
+
try {
|
|
221
|
+
if (isReactiveType(queryType(checker, n))) {
|
|
222
|
+
leaves.push(n)
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
} catch {
|
|
226
|
+
// fall through to walking the object part
|
|
227
|
+
}
|
|
228
|
+
visit(n.expression)
|
|
229
|
+
return
|
|
230
|
+
}
|
|
231
|
+
if (ts.isIdentifier(n)) {
|
|
232
|
+
try {
|
|
233
|
+
if (isReactiveType(queryType(checker, n))) leaves.push(n)
|
|
234
|
+
} catch {
|
|
235
|
+
// not reactive
|
|
236
|
+
}
|
|
237
|
+
return
|
|
238
|
+
}
|
|
239
|
+
if (ts.isCallExpression(n)) {
|
|
240
|
+
try {
|
|
241
|
+
if (isReactiveType(queryType(checker, n.expression))) {
|
|
242
|
+
leaves.push(nodeContainsJsx(n) ? n.expression : n)
|
|
243
|
+
return
|
|
244
|
+
}
|
|
245
|
+
} catch {
|
|
246
|
+
// fall through to walking children
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
ts.forEachChild(n, visit)
|
|
250
|
+
}
|
|
251
|
+
visit(node)
|
|
252
|
+
return leaves
|
|
253
|
+
}
|