@barefootjs/jsx 0.35.1 → 0.35.3
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/index.js +2863 -2796
- package/dist/ir-to-client-js/plan/build-static-array-child-init.d.ts.map +1 -1
- package/dist/ir-to-client-js/plan/static-array-child-init.d.ts +19 -1
- package/dist/ir-to-client-js/plan/static-array-child-init.d.ts.map +1 -1
- package/dist/ir-to-client-js/stringify/static-array-child-init.d.ts.map +1 -1
- package/dist/jsx-to-ir.d.ts.map +1 -1
- package/dist/prop-rewrite.d.ts +26 -0
- package/dist/prop-rewrite.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/binding-scope-ratchet.test.ts +1 -1
- package/src/__tests__/branch-local-in-attr-position.test.ts +24 -12
- package/src/__tests__/client-only-date-lowering.test.ts +29 -1
- package/src/__tests__/issue-2798-static-nested-loop-bindings.test.ts +127 -0
- package/src/__tests__/issue-2856-branch-local-shorthand.test.ts +86 -0
- package/src/__tests__/rewrite-destructured-props.test.ts +10 -5
- package/src/ir-to-client-js/plan/build-static-array-child-init.ts +59 -19
- package/src/ir-to-client-js/plan/static-array-child-init.ts +19 -1
- package/src/ir-to-client-js/stringify/static-array-child-init.ts +39 -0
- package/src/jsx-to-ir.ts +16 -4
- package/src/prop-rewrite.ts +74 -33
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-static-array-child-init.d.ts","sourceRoot":"","sources":["../../../src/ir-to-client-js/plan/build-static-array-child-init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAE,eAAe,
|
|
1
|
+
{"version":3,"file":"build-static-array-child-init.d.ts","sourceRoot":"","sources":["../../../src/ir-to-client-js/plan/build-static-array-child-init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAmD,MAAM,aAAa,CAAA;AAoBnG,OAAO,KAAK,EAQV,yBAAyB,EAC1B,MAAM,8BAA8B,CAAA;AAErC,wBAAgB,8BAA8B,CAC5C,GAAG,EAAE,eAAe,GACnB,yBAAyB,CA4D3B"}
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
* - `outer-nested` — depth 0 の `nestedComponents`。outer forEach で
|
|
8
8
|
* `__iterEl.querySelector(...)` 経由で initChild。
|
|
9
9
|
* - `inner-loop-nested` — depth > 0 の `nestedComponents`。outer + inner
|
|
10
|
-
* forEach の二重ループで initChild
|
|
10
|
+
* forEach の二重ループで initChild。同じ二重ループで
|
|
11
|
+
* プレーン要素の ref / reactive text / reactive attr
|
|
12
|
+
* も配線する (#2798、depth 1 のみ)。
|
|
11
13
|
* - `component-rooted-inner-loop`
|
|
12
14
|
* — outer の loop item root が **child component**
|
|
13
15
|
* (`loop.childComponent`) で、その JSX children に
|
|
@@ -23,6 +25,8 @@
|
|
|
23
25
|
* shapes were determined by inline branching on the IR.
|
|
24
26
|
*/
|
|
25
27
|
import type { PreludeStatements } from '../control-flow/plan/inner-loop.ts';
|
|
28
|
+
import type { LoopChildRefBinding } from '../control-flow/plan/loop.ts';
|
|
29
|
+
import type { LoopChildReactiveAttr, LoopChildReactiveText } from '../types.ts';
|
|
26
30
|
/** Pre-built `{ name: value, ... }` props object expression. */
|
|
27
31
|
export type PropsExpr = string;
|
|
28
32
|
/** A single child-component initialiser inside an inner-loop body. */
|
|
@@ -137,6 +141,20 @@ export interface InnerLoopNestedInitPlan {
|
|
|
137
141
|
depth: number;
|
|
138
142
|
/** Per-component initialisers emitted inside the inner forEach body. */
|
|
139
143
|
comps: readonly InnerLoopComp[];
|
|
144
|
+
/**
|
|
145
|
+
* Reactive attrs on plain elements inside this inner loop's body,
|
|
146
|
+
* grouped by child slot id (#2798 — a static outer array's nested
|
|
147
|
+
* `.map()` over plain elements used to wire up NOTHING: no refs, no
|
|
148
|
+
* reactive text/attr effects, since the whole `elem.innerLoops` pass
|
|
149
|
+
* only ever ran for depth-N CHILD COMPONENTS). Only populated for a
|
|
150
|
+
* depth-1 inner loop under a plain-element-rooted outer item — see
|
|
151
|
+
* `buildStaticArrayChildInitsPlan`'s docstring.
|
|
152
|
+
*/
|
|
153
|
+
attrsBySlot: ReadonlyArray<readonly [string, readonly LoopChildReactiveAttr[]]>;
|
|
154
|
+
/** Reactive text interpolations inside this inner loop's body (#2798). */
|
|
155
|
+
texts: readonly LoopChildReactiveText[];
|
|
156
|
+
/** Imperative ref callbacks on elements inside this inner loop's body (#2798). */
|
|
157
|
+
refs: readonly LoopChildRefBinding[];
|
|
140
158
|
}
|
|
141
159
|
/**
|
|
142
160
|
* Plan for an inner `.map()` of components living inside a **component-rooted**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"static-array-child-init.d.ts","sourceRoot":"","sources":["../../../src/ir-to-client-js/plan/static-array-child-init.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"static-array-child-init.d.ts","sourceRoot":"","sources":["../../../src/ir-to-client-js/plan/static-array-child-init.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"static-array-child-init.d.ts","sourceRoot":"","sources":["../../../src/ir-to-client-js/stringify/static-array-child-init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAEH,OAAO,KAAK,EAMV,yBAAyB,EAC1B,MAAM,oCAAoC,CAAA;
|
|
1
|
+
{"version":3,"file":"static-array-child-init.d.ts","sourceRoot":"","sources":["../../../src/ir-to-client-js/stringify/static-array-child-init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAEH,OAAO,KAAK,EAMV,yBAAyB,EAC1B,MAAM,oCAAoC,CAAA;AAO3C,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,MAAM,EAAE,EACf,KAAK,EAAE,yBAAyB,GAC/B,IAAI,CAIN"}
|
package/dist/jsx-to-ir.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jsx-to-ir.d.ts","sourceRoot":"","sources":["../src/jsx-to-ir.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EACL,KAAK,MAAM,EAgCZ,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,KAAK,eAAe,EAA0E,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"jsx-to-ir.d.ts","sourceRoot":"","sources":["../src/jsx-to-ir.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EACL,KAAK,MAAM,EAgCZ,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,KAAK,eAAe,EAA0E,MAAM,uBAAuB,CAAA;AAmhCpI,wBAAgB,OAAO,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAOhE"}
|
package/dist/prop-rewrite.d.ts
CHANGED
|
@@ -33,6 +33,32 @@ import ts from 'typescript';
|
|
|
33
33
|
* #1425).
|
|
34
34
|
*/
|
|
35
35
|
export declare function collectAstPropRefs(node: ts.Node, propNames: Set<string>, out: Set<string>): void;
|
|
36
|
+
/**
|
|
37
|
+
* Scope-aware rewrite: parse `text` as an expression, walk it with the
|
|
38
|
+
* binding stack, and splice `replacementFor(name)` onto exactly the
|
|
39
|
+
* identifier references that are (a) in `names` and (b) not shadowed by
|
|
40
|
+
* a binding inside the text. Shorthand properties expand
|
|
41
|
+
* (`{ org }` → `{ org: <replacement> }`) so the result stays
|
|
42
|
+
* syntactically valid — this is the one place in the compiler that
|
|
43
|
+
* correctly distinguishes a value reference from an object-literal key
|
|
44
|
+
* / property-access name / shorthand property / binding name, so every
|
|
45
|
+
* caller doing this class of substitution (prop refs, #1425 branch-local
|
|
46
|
+
* refs, …) should route through this rather than growing its own
|
|
47
|
+
* regex-based text splice (#2856 — a second, regex-based substitution
|
|
48
|
+
* mechanism doesn't know shorthand is simultaneously a key and a value,
|
|
49
|
+
* and drops the key).
|
|
50
|
+
*
|
|
51
|
+
* With `allowStatements`, a `text` that fails to parse as a bare
|
|
52
|
+
* expression is retried as a standalone statement list (no wrapping
|
|
53
|
+
* parens) — for callers whose raw-captured text isn't guaranteed to be
|
|
54
|
+
* expression-shaped (e.g. a `.map()`-callback preamble statement).
|
|
55
|
+
*
|
|
56
|
+
* Returns null when `text` doesn't parse cleanly under either attempt —
|
|
57
|
+
* the caller falls back to its own legacy mechanism.
|
|
58
|
+
*/
|
|
59
|
+
export declare function rewriteScopedValueRefs(text: string, names: ReadonlySet<string>, replacementFor: (name: string) => string, opts?: {
|
|
60
|
+
allowStatements?: boolean;
|
|
61
|
+
}): string | null;
|
|
36
62
|
/**
|
|
37
63
|
* Apply the targeted regex rewrite for one or more prop names on a
|
|
38
64
|
* type-stripped expression text. Idempotent under `_p.X` (negative
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prop-rewrite.d.ts","sourceRoot":"","sources":["../src/prop-rewrite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"prop-rewrite.d.ts","sourceRoot":"","sources":["../src/prop-rewrite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,MAAM,YAAY,CAAA;AA6F3B;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,EACtB,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,GACf,IAAI,CAMN;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,EAC1B,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,EACxC,IAAI,CAAC,EAAE;IAAE,eAAe,CAAC,EAAE,OAAO,CAAA;CAAE,GACnC,MAAM,GAAG,IAAI,CAmCf;AA6BD;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,EAC1B,WAAW,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EACzC,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,MAAM,GAChE,MAAM,CAqBR;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,EAAE,CAAC,IAAI,EACb,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,EACtB,aAAa,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,EACnC,WAAW,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EACzC,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,MAAM,GAChE,MAAM,GAAG,SAAS,CAcpB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barefootjs/jsx",
|
|
3
|
-
"version": "0.35.
|
|
3
|
+
"version": "0.35.3",
|
|
4
4
|
"description": "JSX compiler for BarefootJS - transforms JSX to server HTML + client JS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"directory": "packages/jsx"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@barefootjs/shared": "0.35.
|
|
52
|
+
"@barefootjs/shared": "0.35.3"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@barefootjs/client": ">=0.2.0",
|
|
@@ -124,7 +124,7 @@ const ALLOWLIST: Record<string, Partial<Record<Pattern, number>>> = {
|
|
|
124
124
|
// passthrough, called only from `generateNewPropsFunction`'s
|
|
125
125
|
// `emitStaticChildInstances`), `computeDerivedConstFields` and
|
|
126
126
|
// `isStringExpr` (same `generateNewPropsFunction` constructor-context
|
|
127
|
-
// bucket), and `
|
|
127
|
+
// bucket), and `resolveModuleConstAsGo` (guarded in place by
|
|
128
128
|
// `isCurrentLoopItem`/`isOuterLoopParam`, both already `this.scope.lookup`-
|
|
129
129
|
// backed) — 3 are shape-1 (no live scope in the ctor-generation prepass)
|
|
130
130
|
// and 1 is already scope-guarded. The 5th, `renderLoop`'s loop-array
|
|
@@ -305,19 +305,28 @@ describe('branch-local at element-attribute position (#1414)', () => {
|
|
|
305
305
|
expect(hydrate).toContain('foo bar')
|
|
306
306
|
})
|
|
307
307
|
|
|
308
|
-
test('branch-local substitution skips string
|
|
308
|
+
test('branch-local substitution skips string-literal text and correctly resolves a `$`-prefixed identifier', () => {
|
|
309
309
|
// The substitution machinery used to scan with `text.replace(/\b
|
|
310
310
|
// (name1|name2)\b/g, ...)`, which would (a) rewrite occurrences
|
|
311
311
|
// of the name inside string literals into invalid JS and
|
|
312
312
|
// (b) mis-match `\b` between `$` and a word char, splitting an
|
|
313
313
|
// identifier like `$kind` and rewriting just its `kind` tail.
|
|
314
|
-
// The
|
|
315
|
-
//
|
|
316
|
-
//
|
|
317
|
-
//
|
|
318
|
-
//
|
|
319
|
-
//
|
|
320
|
-
//
|
|
314
|
+
// The regex-scanner fallback (`replaceInExprContexts`, still used
|
|
315
|
+
// when text doesn't parse as an expression/statement list) fixes
|
|
316
|
+
// both by skipping opaque tokens — strings, regex, template
|
|
317
|
+
// bodies, comments — and replacing `\b` with `[\w$]` lookarounds.
|
|
318
|
+
// But it treats an entire template literal as one opaque token,
|
|
319
|
+
// so it can't reach a branch-local reference INSIDE a `${...}`
|
|
320
|
+
// hole either — `$kind` used to survive as a literal, undeclared
|
|
321
|
+
// identifier in the emitted output (a latent ReferenceError, the
|
|
322
|
+
// same failure class as #2856). The AST-based
|
|
323
|
+
// `rewriteScopedValueRefs` (#2856) parses the template literal
|
|
324
|
+
// properly and resolves the hole like any other expression
|
|
325
|
+
// position, while still leaving the cooked text (`label:kind=`)
|
|
326
|
+
// alone since it's not an identifier. This test pins a branch-local
|
|
327
|
+
// whose name collides with a token inside a string literal *and*
|
|
328
|
+
// with the tail of a `$`-prefixed identifier used inside a
|
|
329
|
+
// template-literal hole.
|
|
321
330
|
const source = `
|
|
322
331
|
'use client'
|
|
323
332
|
import { createSignal } from '@barefootjs/client'
|
|
@@ -345,9 +354,12 @@ describe('branch-local at element-attribute position (#1414)', () => {
|
|
|
345
354
|
// verbatim — pre-fix, the `kind` inside the template-literal head
|
|
346
355
|
// would have been rewritten into `('plain')`, mangling the string.
|
|
347
356
|
expect(clientJs).toContain('label:kind=')
|
|
348
|
-
// The `$kind` identifier
|
|
349
|
-
//
|
|
350
|
-
//
|
|
351
|
-
|
|
357
|
+
// The `$kind` identifier (distinct from the branch-local `kind`)
|
|
358
|
+
// must resolve to its own branch-local value, not leak through
|
|
359
|
+
// unresolved (which would be a ReferenceError at hydrate — no
|
|
360
|
+
// `$kind` binding exists in the emitted module scope) and not have
|
|
361
|
+
// its `kind` tail mistakenly matched by the `kind` substitution.
|
|
362
|
+
expect(clientJs).not.toMatch(/[^(]\$kind\b/)
|
|
363
|
+
expect(clientJs).toContain("label:kind=${('dollar')}")
|
|
352
364
|
})
|
|
353
365
|
})
|
|
@@ -22,10 +22,38 @@
|
|
|
22
22
|
* about would fire BF021 and never reach the reactive-emission sites this
|
|
23
23
|
* suite is about.
|
|
24
24
|
*/
|
|
25
|
-
import { describe, test, expect } from 'bun:test'
|
|
25
|
+
import { describe, test, expect, beforeAll } from 'bun:test'
|
|
26
26
|
import { compileJSX } from '../index'
|
|
27
27
|
import { TestAdapter } from '../adapters/test-adapter'
|
|
28
28
|
import { ErrorCodes } from '../errors'
|
|
29
|
+
import { getLoweringPlugins } from '../lowering-registry'
|
|
30
|
+
import { BUILTIN_LOWERING_PLUGINS } from '../builtin-lowering-plugins'
|
|
31
|
+
|
|
32
|
+
// #2831 — the persistent, CI-only "test (jsx-2)" flake on this suite's own
|
|
33
|
+
// assertions traced to `date-lowering.test.ts`'s per-test `afterEach`
|
|
34
|
+
// filtering the built-in `'date'` plugin out of the SHARED, module-level
|
|
35
|
+
// `lowering-registry.ts` registry (fixed: dbaba32, "date-lowering.test.ts
|
|
36
|
+
// leaked the built-in 'date' plugin out of the shared registry"). `bun
|
|
37
|
+
// test` runs every file in one process with no guaranteed file order, so a
|
|
38
|
+
// leak in one file's registry manipulation silently starves every
|
|
39
|
+
// LATER-running file that depends on a built-in plugin — this suite's own
|
|
40
|
+
// `datePlugin`/`toLocaleDatePlugin` dependency was the victim. A future
|
|
41
|
+
// leak of the same shape would again fail here as a confusing "lowering
|
|
42
|
+
// wasn't applied" assertion with no clue why. This guard converts that
|
|
43
|
+
// into a named, up-front failure instead.
|
|
44
|
+
beforeAll(() => {
|
|
45
|
+
const registered = new Set(getLoweringPlugins().map((p) => p.name))
|
|
46
|
+
const missing = BUILTIN_LOWERING_PLUGINS.map((p) => p.name).filter((name) => !registered.has(name))
|
|
47
|
+
if (missing.length > 0) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Built-in lowering plugin(s) missing from the shared registry before this suite ran: ${missing.join(', ')}. ` +
|
|
50
|
+
`This suite's assertions depend on them being registered — a SIBLING test file most likely reset or ` +
|
|
51
|
+
`filtered the shared \`lowering-registry.ts\` registry without restoring it (the #2831 bug class: use ` +
|
|
52
|
+
`\`beforeAll\`/\`afterAll\` snapshot-and-restore around a describe block, never a per-test \`afterEach\` ` +
|
|
53
|
+
`filter, when touching this shared, module-level registry).`,
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
})
|
|
29
57
|
|
|
30
58
|
function compile(src: string) {
|
|
31
59
|
const result = compileJSX(src.trimStart(), 'T.tsx', { adapter: new TestAdapter() })
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #2798: a static (non-signal) array's `.map()` faithfully invokes a
|
|
3
|
+
* `ref` callback, and reactively updates a signal-derived text, on the
|
|
4
|
+
* TOP-LEVEL row (`buildStaticLoopPlan`'s `childRefs`/`texts`). But a
|
|
5
|
+
* nested `.map()` INSIDE a static outer row's item — a plain element,
|
|
6
|
+
* not a child component — reached `elem.innerLoops` processing only
|
|
7
|
+
* when it had matching depth-N CHILD COMPONENTS
|
|
8
|
+
* (`buildStaticArrayChildInitsPlan`'s `innerComps.length === 0`
|
|
9
|
+
* skip). A plain-element inner loop under a static outer array had
|
|
10
|
+
* NOTHING wired: no ref invocation, no reactive-text/attr effect — the
|
|
11
|
+
* whole row was silently frozen at its SSR-baked value.
|
|
12
|
+
*
|
|
13
|
+
* (The issue's own repro — a TOP-LEVEL static loop with a `ref` — was
|
|
14
|
+
* already fixed by the time this was investigated; that shape is
|
|
15
|
+
* pinned here too as regression armor against the issue's own
|
|
16
|
+
* misdiagnosis being reintroduced.)
|
|
17
|
+
*/
|
|
18
|
+
import { describe, test, expect } from 'bun:test'
|
|
19
|
+
import { compileJSX } from '../index'
|
|
20
|
+
import { HonoAdapter } from '../../../adapter-hono/src/adapter/index'
|
|
21
|
+
|
|
22
|
+
function compileClientJs(source: string): string {
|
|
23
|
+
const result = compileJSX(source, 'test.tsx', { adapter: new HonoAdapter() })
|
|
24
|
+
expect(result.errors.filter(e => e.severity === 'error')).toHaveLength(0)
|
|
25
|
+
const clientJs = result.files.find(f => f.type === 'clientJs')
|
|
26
|
+
if (!clientJs) throw new Error('No client JS emitted')
|
|
27
|
+
return clientJs.content
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe('static array ref/reactive bindings (#2798)', () => {
|
|
31
|
+
test('top-level static loop still invokes a ref on each row (regression armor)', () => {
|
|
32
|
+
const source = `
|
|
33
|
+
'use client'
|
|
34
|
+
export function StaticRefCase() {
|
|
35
|
+
const items = [{ id: 1 }, { id: 2 }]
|
|
36
|
+
const trackMount = (el: Element) => { el.setAttribute('data-tracked', '1') }
|
|
37
|
+
return (
|
|
38
|
+
<ul>
|
|
39
|
+
{items.map(item => (
|
|
40
|
+
<li key={item.id} ref={trackMount}>{item.id}</li>
|
|
41
|
+
))}
|
|
42
|
+
</ul>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
`
|
|
46
|
+
const js = compileClientJs(source)
|
|
47
|
+
expect(js).toContain('(trackMount)(')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('a static outer loop with a nested .map() invokes the inner ref on each row', () => {
|
|
51
|
+
const source = `
|
|
52
|
+
'use client'
|
|
53
|
+
export function StaticNestedRefCase() {
|
|
54
|
+
const items = [{ id: 1, children: [{ id: 11 }, { id: 12 }] }]
|
|
55
|
+
const trackMount = (el: Element) => { el.setAttribute('data-tracked', '1') }
|
|
56
|
+
return (
|
|
57
|
+
<ul>
|
|
58
|
+
{items.map(item => (
|
|
59
|
+
<li key={item.id}>
|
|
60
|
+
{item.children.map(child => (
|
|
61
|
+
<span key={child.id} ref={trackMount}>{child.id}</span>
|
|
62
|
+
))}
|
|
63
|
+
</li>
|
|
64
|
+
))}
|
|
65
|
+
</ul>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
`
|
|
69
|
+
const js = compileClientJs(source)
|
|
70
|
+
// Double forEach (outer item, inner child), with the ref invoked
|
|
71
|
+
// inside the inner one.
|
|
72
|
+
expect(js).toContain('items.forEach((item, __idx) => {')
|
|
73
|
+
expect(js).toContain('item.children.forEach((child, __innerIdx) => {')
|
|
74
|
+
expect(js).toContain('(trackMount)(')
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('a static outer loop with a nested .map() reactively updates a signal-derived inner text', () => {
|
|
78
|
+
const source = `
|
|
79
|
+
'use client'
|
|
80
|
+
import { createSignal } from '@barefootjs/client'
|
|
81
|
+
export function StaticNestedSignalCase() {
|
|
82
|
+
const items = [{ id: 1, children: [{ id: 11 }, { id: 12 }] }]
|
|
83
|
+
const [count] = createSignal(0)
|
|
84
|
+
return (
|
|
85
|
+
<ul>
|
|
86
|
+
{items.map(item => (
|
|
87
|
+
<li key={item.id}>
|
|
88
|
+
{item.children.map(child => (
|
|
89
|
+
<span key={child.id}>{child.id}:{count()}</span>
|
|
90
|
+
))}
|
|
91
|
+
</li>
|
|
92
|
+
))}
|
|
93
|
+
</ul>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
`
|
|
97
|
+
const js = compileClientJs(source)
|
|
98
|
+
// Pre-fix, this text was frozen at its SSR-baked value — no
|
|
99
|
+
// `createEffect` at all inside the inner forEach body.
|
|
100
|
+
expect(js).toMatch(/createEffect\(\(\) => \{ __bfw_\w+\('s\d+', String\(count\(\)\)\) \}\)/)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test('a static outer loop with a nested .map() wires a reactive attr on the inner element', () => {
|
|
104
|
+
const source = `
|
|
105
|
+
'use client'
|
|
106
|
+
import { createSignal } from '@barefootjs/client'
|
|
107
|
+
export function StaticNestedAttrCase() {
|
|
108
|
+
const items = [{ id: 1, children: [{ id: 11 }, { id: 12 }] }]
|
|
109
|
+
const [sel] = createSignal(11)
|
|
110
|
+
return (
|
|
111
|
+
<ul>
|
|
112
|
+
{items.map(item => (
|
|
113
|
+
<li key={item.id}>
|
|
114
|
+
{item.children.map(child => (
|
|
115
|
+
<span key={child.id} data-active={child.id === sel()}>{child.id}</span>
|
|
116
|
+
))}
|
|
117
|
+
</li>
|
|
118
|
+
))}
|
|
119
|
+
</ul>
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
`
|
|
123
|
+
const js = compileClientJs(source)
|
|
124
|
+
expect(js).toContain('data-active')
|
|
125
|
+
expect(js).toMatch(/createEffect\(\(\) => \{/)
|
|
126
|
+
})
|
|
127
|
+
})
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #2856: `replaceBranchLocalRefs` (jsx-to-ir.ts) used to substitute a
|
|
3
|
+
* branch-local's occurrences via a plain regex token scanner
|
|
4
|
+
* (`replaceInExprContexts`), which has no notion of AST position. An
|
|
5
|
+
* object-literal shorthand property (`{ local }`, simultaneously the
|
|
6
|
+
* key and the value) got its substitution wrapped in parens with the
|
|
7
|
+
* key silently dropped — `{ (_p.tag) }` — invalid JS that broke the
|
|
8
|
+
* whole client bundle's parse at runtime with no compile-time
|
|
9
|
+
* diagnostic.
|
|
10
|
+
*
|
|
11
|
+
* The fix routes both call sites through `rewriteScopedValueRefs`
|
|
12
|
+
* (`prop-rewrite.ts`), the same AST-based, scope-aware walk that
|
|
13
|
+
* already handled this exact key/value duality for destructured props
|
|
14
|
+
* (#2828/#2853). These tests pin the general mechanism directly
|
|
15
|
+
* (`rewriteScopedValueRefs`) — shorthand expansion, and the three
|
|
16
|
+
* sibling positions a naive scanner conflates with a value reference:
|
|
17
|
+
* a member-access name, an explicit object-literal key, and a name
|
|
18
|
+
* shadowed by an inner binding. An end-to-end compiler regression for
|
|
19
|
+
* the issue's own repro lives in
|
|
20
|
+
* `rewrite-destructured-props.test.ts` ("branch-local transitive
|
|
21
|
+
* prop-dep via shorthand reference").
|
|
22
|
+
*/
|
|
23
|
+
import { describe, test, expect } from 'bun:test'
|
|
24
|
+
import { rewriteScopedValueRefs } from '../prop-rewrite.ts'
|
|
25
|
+
|
|
26
|
+
describe('rewriteScopedValueRefs (#2856)', () => {
|
|
27
|
+
test('expands an object-literal shorthand property to `name: value`', () => {
|
|
28
|
+
const out = rewriteScopedValueRefs('{ local }', new Set(['local']), () => '(_p.tag)')
|
|
29
|
+
expect(out).toBe('{ local: (_p.tag) }')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('does not touch a member-access name that merely shares the branch-local name', () => {
|
|
33
|
+
const out = rewriteScopedValueRefs('obj.local', new Set(['local']), () => '(_p.tag)')
|
|
34
|
+
expect(out).toBe('obj.local')
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('does not touch an explicit object-literal key', () => {
|
|
38
|
+
const out = rewriteScopedValueRefs('{ local: 1 }', new Set(['local']), () => '(_p.tag)')
|
|
39
|
+
expect(out).toBe('{ local: 1 }')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('does not substitute a name shadowed by an inner arrow parameter', () => {
|
|
43
|
+
const out = rewriteScopedValueRefs('[1, 2, 3].map((local) => local)', new Set(['local']), () => '(_p.tag)')
|
|
44
|
+
expect(out).toBe('[1, 2, 3].map((local) => local)')
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('substitutes a real value reference alongside an unrelated shadowed one', () => {
|
|
48
|
+
const out = rewriteScopedValueRefs(
|
|
49
|
+
'local + [1, 2, 3].map((local) => local).join(",")',
|
|
50
|
+
new Set(['local']),
|
|
51
|
+
() => '(_p.tag)',
|
|
52
|
+
)
|
|
53
|
+
expect(out).toBe('(_p.tag) + [1, 2, 3].map((local) => local).join(",")')
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('does not touch a destructuring declaration\'s source key (pullfrog review, #2889)', () => {
|
|
57
|
+
// `{ local: renamed } = obj` -- `local` here is the SOURCE key of a
|
|
58
|
+
// renamed destructuring binding, not a value reference. Distinct from
|
|
59
|
+
// the shorthand case above: a renamed destructuring pattern has BOTH
|
|
60
|
+
// `propertyName` (the source key) and `name` (the local binding), and
|
|
61
|
+
// only the latter was excluded pre-fix.
|
|
62
|
+
const out = rewriteScopedValueRefs(
|
|
63
|
+
'(el) => { const { local: renamed } = obj; use(renamed) }',
|
|
64
|
+
new Set(['local']),
|
|
65
|
+
() => '(_p.tag)',
|
|
66
|
+
)
|
|
67
|
+
expect(out).toBe('(el) => { const { local: renamed } = obj; use(renamed) }')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('resolves inside a template-literal interpolation hole, leaving the cooked text alone', () => {
|
|
71
|
+
const out = rewriteScopedValueRefs('`label:local=${local}`', new Set(['local']), () => '(_p.tag)')
|
|
72
|
+
expect(out).toBe('`label:local=${(_p.tag)}`')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('returns null for text that does not parse as an expression when allowStatements is not set', () => {
|
|
76
|
+
const out = rewriteScopedValueRefs('const x = local', new Set(['local']), () => '(_p.tag)')
|
|
77
|
+
expect(out).toBeNull()
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('with allowStatements, resolves a value reference in statement-shaped text', () => {
|
|
81
|
+
const out = rewriteScopedValueRefs('const x = local', new Set(['local']), () => '(_p.tag)', {
|
|
82
|
+
allowStatements: true,
|
|
83
|
+
})
|
|
84
|
+
expect(out).toBe('const x = (_p.tag)')
|
|
85
|
+
})
|
|
86
|
+
})
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, test, expect } from 'bun:test'
|
|
2
|
+
import ts from 'typescript'
|
|
2
3
|
import { compileJSX } from '../index'
|
|
3
4
|
import { HonoAdapter } from '../../../adapter-hono/src/adapter/index'
|
|
4
5
|
|
|
@@ -153,11 +154,15 @@ export function Comp({ tag }: { tag: string }) {
|
|
|
153
154
|
// Pre-fix: the hydrate template's module-scope lambda kept a bare
|
|
154
155
|
// `tag` reference (no binding at that scope -- ReferenceError at pure
|
|
155
156
|
// CSR mount). Post-fix: it resolves through `_p.tag` like every other
|
|
156
|
-
// prop reference
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
// only the prop-dependency question #2828's follow-up fixed.)
|
|
157
|
+
// prop reference, and the shorthand key survives the substitution
|
|
158
|
+
// (#2856 -- the branch-local substitution used to drop it, emitting
|
|
159
|
+
// the invalid `{ (_p.tag) }`).
|
|
160
160
|
expect(js).not.toContain('{ (tag) }')
|
|
161
|
-
expect(js).toContain('{ (_p.tag) }')
|
|
161
|
+
expect(js).not.toContain('{ (_p.tag) }')
|
|
162
|
+
expect(js).toContain('{ local: (_p.tag) }')
|
|
163
|
+
// The emitted client JS must actually parse.
|
|
164
|
+
const sf = ts.createSourceFile('__test.ts', js, ts.ScriptTarget.Latest, true)
|
|
165
|
+
const diagnostics = (sf as unknown as { parseDiagnostics?: unknown[] }).parseDiagnostics
|
|
166
|
+
expect(diagnostics ?? []).toHaveLength(0)
|
|
162
167
|
})
|
|
163
168
|
})
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import type { IRLoopChildComponent, MapCallbackPreamble } from '../../types.ts'
|
|
21
|
-
import type { ClientJsContext, NestedLoop, TopLevelLoop } from '../types.ts'
|
|
21
|
+
import type { ClientJsContext, LoopChildReactiveAttr, NestedLoop, TopLevelLoop } from '../types.ts'
|
|
22
22
|
import { quotePropName, varSlotId, attrValueToString, buildLoopChildIndexExpr } from '../utils.ts'
|
|
23
23
|
import { jsxChildrenPropGetterExpr, renderPreamble, irToHtmlTemplate } from '../html-template.ts'
|
|
24
24
|
|
|
@@ -34,7 +34,7 @@ function staticPreludeStatements(preamble: MapCallbackPreamble | undefined): str
|
|
|
34
34
|
})]
|
|
35
35
|
: []
|
|
36
36
|
}
|
|
37
|
-
import { buildCompSelector } from '../control-flow/shared.ts'
|
|
37
|
+
import { buildCompSelector, buildStaticChildRefBindings } from '../control-flow/shared.ts'
|
|
38
38
|
|
|
39
39
|
/** The inline prop shape carried on `IRLoopChildComponent.props`. */
|
|
40
40
|
type LoopChildCompProp = IRLoopChildComponent['props'][number]
|
|
@@ -66,25 +66,47 @@ export function buildStaticArrayChildInitsPlan(
|
|
|
66
66
|
if (comp.loopDepth) continue // handled in inner-loop pass
|
|
67
67
|
plans.push(buildOuterNestedPlan(elem, comp))
|
|
68
68
|
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!elem.innerLoops) continue
|
|
69
72
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
73
|
+
for (const innerLoop of elem.innerLoops) {
|
|
74
|
+
const innerComps = (elem.nestedComponents ?? []).filter(c =>
|
|
75
|
+
(c.loopDepth ?? 0) === innerLoop.depth && c.innerLoopArray === innerLoop.array,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if (elem.childComponent) {
|
|
79
|
+
// Component-rooted outer item (#1725): the inner `.map()` lives
|
|
80
|
+
// inside the child component's JSX children. The element-offset
|
|
81
|
+
// addressing of `inner-loop-nested` can't reach a fragment-rooted
|
|
82
|
+
// passthrough's flattened items, so use the document-order zip
|
|
83
|
+
// shape instead. (Plain-element bindings on a component-rooted
|
|
84
|
+
// inner loop aren't addressed by this shape either — out of scope
|
|
85
|
+
// here, same as this shape's existing filter/sort limitation.)
|
|
86
|
+
if (innerComps.length === 0) continue
|
|
87
|
+
plans.push(buildComponentRootedInnerLoopPlan(elem, innerLoop, innerComps))
|
|
88
|
+
continue
|
|
87
89
|
}
|
|
90
|
+
|
|
91
|
+
// Plain-element-rooted outer item: `inner-loop-nested`'s element-offset
|
|
92
|
+
// addressing also wires up a plain element's own reactive attrs/texts/
|
|
93
|
+
// refs inside the inner loop (#2798) — previously this whole pass only
|
|
94
|
+
// ever ran for depth-N CHILD COMPONENTS (gated behind
|
|
95
|
+
// `elem.nestedComponents.length > 0` with an `innerComps.length === 0`
|
|
96
|
+
// skip), so a static outer array's nested `.map()` over plain elements
|
|
97
|
+
// wired up NOTHING: no ref invocation, no reactive text/attr effect,
|
|
98
|
+
// even though the outer row's OWN bindings at this same depth-0 level
|
|
99
|
+
// are faithfully wired (`buildStaticLoopPlan`'s `childRefs`/`texts`/
|
|
100
|
+
// `attrsBySlot`). Scoped to depth 1 — `NestedLoop` has no parent link
|
|
101
|
+
// to thread a second offset through for depth ≥ 2, and the inner
|
|
102
|
+
// array expression there reads the depth-1 param, so a flat emission
|
|
103
|
+
// would `ReferenceError`; deeper nesting stays a known limitation.
|
|
104
|
+
if (innerLoop.depth !== 1) continue
|
|
105
|
+
const hasPlainBindings = innerLoop.bindings.refs.length > 0
|
|
106
|
+
|| innerLoop.bindings.reactiveTexts.length > 0
|
|
107
|
+
|| innerLoop.bindings.reactiveAttrs.length > 0
|
|
108
|
+
if (innerComps.length === 0 && !hasPlainBindings) continue
|
|
109
|
+
plans.push(buildInnerLoopNestedPlan(elem, innerLoop, innerComps))
|
|
88
110
|
}
|
|
89
111
|
}
|
|
90
112
|
|
|
@@ -147,6 +169,21 @@ function buildInnerLoopNestedPlan(
|
|
|
147
169
|
propsExpr: buildStaticPropsExpr(comp.props),
|
|
148
170
|
}))
|
|
149
171
|
|
|
172
|
+
// Plain-element bindings (#2798) — grouped/passed the same way
|
|
173
|
+
// `buildStaticLoopPlan` handles the OUTER row's own bindings: `forEach`
|
|
174
|
+
// binds the param as the raw item value, so texts/attrs stay unwrapped
|
|
175
|
+
// (no signal-accessor rewrite) and refs go through
|
|
176
|
+
// `buildStaticChildRefBindings`, not `buildChildRefBindings`.
|
|
177
|
+
const attrsBySlotMap = new Map<string, LoopChildReactiveAttr[]>()
|
|
178
|
+
for (const attr of innerLoop.bindings.reactiveAttrs) {
|
|
179
|
+
let bucket = attrsBySlotMap.get(attr.childSlotId)
|
|
180
|
+
if (!bucket) {
|
|
181
|
+
bucket = []
|
|
182
|
+
attrsBySlotMap.set(attr.childSlotId, bucket)
|
|
183
|
+
}
|
|
184
|
+
bucket.push(attr)
|
|
185
|
+
}
|
|
186
|
+
|
|
150
187
|
return {
|
|
151
188
|
kind: 'inner-loop-nested',
|
|
152
189
|
containerVar: `_${varSlotId(elem.slotId)}`,
|
|
@@ -163,6 +200,9 @@ function buildInnerLoopNestedPlan(
|
|
|
163
200
|
innerPreludeStatements: staticPreludeStatements(innerLoop.preamble),
|
|
164
201
|
depth: innerLoop.depth,
|
|
165
202
|
comps,
|
|
203
|
+
attrsBySlot: [...attrsBySlotMap].map(([slotId, attrs]) => [slotId, attrs] as const),
|
|
204
|
+
texts: innerLoop.bindings.reactiveTexts,
|
|
205
|
+
refs: buildStaticChildRefBindings(innerLoop.bindings.refs),
|
|
166
206
|
}
|
|
167
207
|
}
|
|
168
208
|
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
* - `outer-nested` — depth 0 の `nestedComponents`。outer forEach で
|
|
8
8
|
* `__iterEl.querySelector(...)` 経由で initChild。
|
|
9
9
|
* - `inner-loop-nested` — depth > 0 の `nestedComponents`。outer + inner
|
|
10
|
-
* forEach の二重ループで initChild
|
|
10
|
+
* forEach の二重ループで initChild。同じ二重ループで
|
|
11
|
+
* プレーン要素の ref / reactive text / reactive attr
|
|
12
|
+
* も配線する (#2798、depth 1 のみ)。
|
|
11
13
|
* - `component-rooted-inner-loop`
|
|
12
14
|
* — outer の loop item root が **child component**
|
|
13
15
|
* (`loop.childComponent`) で、その JSX children に
|
|
@@ -24,6 +26,8 @@
|
|
|
24
26
|
*/
|
|
25
27
|
|
|
26
28
|
import type { PreludeStatements } from '../control-flow/plan/inner-loop.ts'
|
|
29
|
+
import type { LoopChildRefBinding } from '../control-flow/plan/loop.ts'
|
|
30
|
+
import type { LoopChildReactiveAttr, LoopChildReactiveText } from '../types.ts'
|
|
27
31
|
|
|
28
32
|
/** Pre-built `{ name: value, ... }` props object expression. */
|
|
29
33
|
export type PropsExpr = string
|
|
@@ -143,6 +147,20 @@ export interface InnerLoopNestedInitPlan {
|
|
|
143
147
|
depth: number
|
|
144
148
|
/** Per-component initialisers emitted inside the inner forEach body. */
|
|
145
149
|
comps: readonly InnerLoopComp[]
|
|
150
|
+
/**
|
|
151
|
+
* Reactive attrs on plain elements inside this inner loop's body,
|
|
152
|
+
* grouped by child slot id (#2798 — a static outer array's nested
|
|
153
|
+
* `.map()` over plain elements used to wire up NOTHING: no refs, no
|
|
154
|
+
* reactive text/attr effects, since the whole `elem.innerLoops` pass
|
|
155
|
+
* only ever ran for depth-N CHILD COMPONENTS). Only populated for a
|
|
156
|
+
* depth-1 inner loop under a plain-element-rooted outer item — see
|
|
157
|
+
* `buildStaticArrayChildInitsPlan`'s docstring.
|
|
158
|
+
*/
|
|
159
|
+
attrsBySlot: ReadonlyArray<readonly [string, readonly LoopChildReactiveAttr[]]>
|
|
160
|
+
/** Reactive text interpolations inside this inner loop's body (#2798). */
|
|
161
|
+
texts: readonly LoopChildReactiveText[]
|
|
162
|
+
/** Imperative ref callbacks on elements inside this inner loop's body (#2798). */
|
|
163
|
+
refs: readonly LoopChildRefBinding[]
|
|
146
164
|
}
|
|
147
165
|
|
|
148
166
|
/**
|