@barefootjs/cli 0.35.5 → 0.35.6
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/docs/core/README.mdx +1 -1
- package/dist/docs/core/advanced/compiler-internals.md +19 -4
- package/dist/docs/core/advanced/error-codes.md +18 -61
- package/dist/docs/core/components/props-type-safety.md +3 -2
- package/dist/docs/core/quick-start.mdx +1 -1
- package/dist/docs/core/reactivity/props-reactivity.md +69 -31
- package/dist/index.js +6078 -5840
- package/package.json +5 -5
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
|
|
61
61
|
- [IR Schema Reference](./advanced/ir-schema.md) — Node types, metadata, hydration markers
|
|
62
62
|
- [Compiler Internals](./advanced/compiler-internals.md) — Pipeline phases, reactivity analysis, code generation
|
|
63
|
-
- [Error Codes Reference](./advanced/error-codes.md) — All
|
|
63
|
+
- [Error Codes Reference](./advanced/error-codes.md) — All BF-prefixed errors with solutions
|
|
64
64
|
- [Performance Optimization](./advanced/performance.md) — Minimal client JS, fast hydration, efficient reactivity
|
|
65
65
|
|
|
66
66
|
---
|
|
@@ -75,17 +75,32 @@ Files with reactive APIs but no `"use client"` emit **BF001**:
|
|
|
75
75
|
error[BF001]: 'use client' directive required for components with createSignal
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
### Props
|
|
78
|
+
### Destructured Props Stay Live
|
|
79
79
|
|
|
80
80
|
```tsx
|
|
81
|
-
// ⚠️ BF043: Destructuring captures values once — may lose reactivity
|
|
82
81
|
function Child({ count }: Props) { ... }
|
|
83
82
|
|
|
84
|
-
//
|
|
83
|
+
// Compiles identically to direct access:
|
|
85
84
|
function Child(props: Props) { ... }
|
|
86
85
|
```
|
|
87
86
|
|
|
88
|
-
|
|
87
|
+
Both forms record the same `propsParams` metadata in Phase 1. Phase 2 (client JS emission)
|
|
88
|
+
rewrites every value-position read of a name bound by the destructured PARAMETER — inside
|
|
89
|
+
effects, memos, handlers, text, reactive attributes, anywhere in the generated `init*` body —
|
|
90
|
+
to a live `_p.<key>` read, the same read `props.xxx` compiles to. See
|
|
91
|
+
`rewriteDestructuredPropReads` (`ir-to-client-js/rewrite-destructured-props.ts`) and
|
|
92
|
+
`livePropReadExpr` (`props-binding.ts`).
|
|
93
|
+
|
|
94
|
+
Destructuring in the function BODY (`const { count } = props`, or the equivalent `const count
|
|
95
|
+
= props.count`) is a pure single-prop alias — the analyzer's IR can't tell the two shapes
|
|
96
|
+
apart, and treats them identically — and is ALSO rewritten live, via the same
|
|
97
|
+
`rewriteDestructuredPropReads` door's `rewriteBodyAliasReads` branch
|
|
98
|
+
(`resolveBodyPropAliases`, `props-binding.ts`): the local's own extraction is deleted from the
|
|
99
|
+
emitted body (it would otherwise shadow the very references the rewrite is trying to make
|
|
100
|
+
live) and every reference to it becomes `_p.count`, same as the parameter form. This does NOT
|
|
101
|
+
apply once the local does a real computation of its own (`const doubled = count * 2` stays an
|
|
102
|
+
ordinary once-evaluated local) or is reassigned (`let { count } = props` is never treated as
|
|
103
|
+
a live alias).
|
|
89
104
|
|
|
90
105
|
---
|
|
91
106
|
|
|
@@ -317,64 +317,41 @@ See [JSX Compatibility](../rendering/jsx-compatibility.md) for the full worked e
|
|
|
317
317
|
|
|
318
318
|
---
|
|
319
319
|
|
|
320
|
-
## Component Errors (
|
|
320
|
+
## Component Errors (BF044–BF049)
|
|
321
321
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
```tsx
|
|
329
|
-
// ⚠️ BF043
|
|
330
|
-
function Child({ count }: Props) {
|
|
331
|
-
return <span>{count}</span> // count is captured once
|
|
332
|
-
}
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
```
|
|
336
|
-
warning[BF043]: Destructuring props in function parameters captures values once.
|
|
337
|
-
= help: Use `props.count` for reactive access, or suppress with // @bf-ignore props-destructuring
|
|
338
|
-
```
|
|
339
|
-
|
|
340
|
-
**Fix options:**
|
|
341
|
-
|
|
342
|
-
1. Use direct props access:
|
|
343
|
-
|
|
344
|
-
```tsx
|
|
345
|
-
function Child(props: Props) {
|
|
346
|
-
return <span>{props.count}</span> // Reactive
|
|
347
|
-
}
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
2. Suppress if intentional (static initial value):
|
|
351
|
-
|
|
352
|
-
```tsx
|
|
353
|
-
// @bf-ignore props-destructuring
|
|
354
|
-
function Child({ initialCount }: Props) {
|
|
355
|
-
const [count, setCount] = createSignal(initialCount)
|
|
356
|
-
return <span>{count()}</span>
|
|
357
|
-
}
|
|
358
|
-
```
|
|
322
|
+
<!--
|
|
323
|
+
BF043 (Props Destructuring warning) is retired: destructured props read
|
|
324
|
+
live, the same as `props.xxx` access, so there is no reactivity difference
|
|
325
|
+
left to warn about. See [Props Reactivity](../reactivity/props-reactivity.md).
|
|
326
|
+
-->
|
|
359
327
|
|
|
360
328
|
<a id="bf044"></a>
|
|
361
329
|
|
|
362
330
|
### BF044 — Signal/Memo Getter Not Called
|
|
363
331
|
|
|
364
|
-
**Trigger:** Signal/memo getter passed without calling it
|
|
332
|
+
**Trigger:** Signal/memo getter passed without calling it in a RENDERED
|
|
333
|
+
position — a DOM element attribute or a JSX text child, where the value
|
|
334
|
+
becomes literal output.
|
|
365
335
|
|
|
366
336
|
```tsx
|
|
367
337
|
// ❌ BF044
|
|
368
|
-
<
|
|
338
|
+
<div count={count} /> // Passing getter function, not the value
|
|
369
339
|
```
|
|
370
340
|
|
|
371
341
|
**Fix:**
|
|
372
342
|
|
|
373
343
|
```tsx
|
|
374
344
|
// ✅ Fixed
|
|
375
|
-
<
|
|
345
|
+
<div count={count()} />
|
|
376
346
|
```
|
|
377
347
|
|
|
348
|
+
**Not triggered on a component prop:** `<Child count={count} />` compiles —
|
|
349
|
+
a component prop is an opaque value handed to the child, not rendered
|
|
350
|
+
output, and passing a live getter there is this codebase's deliberate
|
|
351
|
+
Context-Provider idiom (the child calls it at its own read site). See
|
|
352
|
+
[`spec/compiler.md`'s BF044 section](https://github.com/piconic-ai/barefootjs/blob/main/spec/compiler.md#signalmemo-getter-not-called-bf044)
|
|
353
|
+
for the full rule.
|
|
354
|
+
|
|
378
355
|
<a id="bf049"></a>
|
|
379
356
|
|
|
380
357
|
### BF049 — Rich-Typed Prop Not Hydratable
|
|
@@ -457,25 +434,6 @@ export function Page() {
|
|
|
457
434
|
|
|
458
435
|
---
|
|
459
436
|
|
|
460
|
-
## Suppressing Warnings
|
|
461
|
-
|
|
462
|
-
Suppress with `@bf-ignore`:
|
|
463
|
-
|
|
464
|
-
```tsx
|
|
465
|
-
// @bf-ignore props-destructuring
|
|
466
|
-
function Component({ checked }: Props) {
|
|
467
|
-
// Warning suppressed
|
|
468
|
-
}
|
|
469
|
-
```
|
|
470
|
-
|
|
471
|
-
**Available rules:**
|
|
472
|
-
|
|
473
|
-
| Rule ID | Error Code | Description |
|
|
474
|
-
|---------|------------|-------------|
|
|
475
|
-
| `props-destructuring` | BF043 | Props destructuring in function parameters |
|
|
476
|
-
|
|
477
|
-
---
|
|
478
|
-
|
|
479
437
|
## Error Code Quick Reference
|
|
480
438
|
|
|
481
439
|
| Code | Severity | Description |
|
|
@@ -486,7 +444,6 @@ function Component({ checked }: Props) {
|
|
|
486
444
|
| BF013 | Error | Reactive primitive called through an unresolved namespace import |
|
|
487
445
|
| BF021 | Error | Unsupported JSX pattern for SSR |
|
|
488
446
|
| BF023 | Error | Missing key in list |
|
|
489
|
-
| BF043 | Warning | Props destructuring breaks reactivity |
|
|
490
447
|
| BF044 | Error | Signal/memo getter passed without calling it |
|
|
491
448
|
| BF049 | Error | Rich-typed prop read by client code cannot survive hydration |
|
|
492
449
|
| BF054 | Error | Built-in `<Async>` / `<Region>` used without `@barefootjs/client` import |
|
|
@@ -35,10 +35,11 @@ function Button(props: { variant?: 'default' | 'primary'; children?: Child }) {
|
|
|
35
35
|
}
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
For initial-value-only props, default parameter syntax works
|
|
38
|
+
For initial-value-only props, default parameter syntax works — destructuring is fully
|
|
39
|
+
reactive (see [Props Reactivity](../reactivity/props-reactivity.md)), so there is nothing to
|
|
40
|
+
suppress:
|
|
39
41
|
|
|
40
42
|
```tsx
|
|
41
|
-
// @bf-ignore props-destructuring
|
|
42
43
|
function Counter({ initial = 0 }: { initial?: number }) {
|
|
43
44
|
const [count, setCount] = createSignal(initial)
|
|
44
45
|
return <button onClick={() => setCount(n => n + 1)}>{count()}</button>
|
|
@@ -88,7 +88,7 @@ export function Counter(props: CounterProps) {
|
|
|
88
88
|
}
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
See [Client Directive](./rendering/client-directive.md), [`createSignal`](./reactivity/create-signal.md), and [`createMemo`](./reactivity/create-memo.md) for what each piece does. Props are read via `props.initial
|
|
91
|
+
See [Client Directive](./rendering/client-directive.md), [`createSignal`](./reactivity/create-signal.md), and [`createMemo`](./reactivity/create-memo.md) for what each piece does. Props are read via `props.initial` here, but destructuring them in the function parameter (`function Counter({ initial }: CounterProps)`) works identically — both forms compile to the same live read, so pick whichever reads better. [Props Reactivity](./reactivity/props-reactivity.md) covers the full rule.
|
|
92
92
|
|
|
93
93
|
The Counter is mounted in `server.tsx`:
|
|
94
94
|
|
|
@@ -5,10 +5,13 @@ description: How prop access patterns determine whether reactive updates propaga
|
|
|
5
5
|
|
|
6
6
|
# Props Reactivity
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
**`props.xxx` access, destructuring the props parameter, and destructuring the props object
|
|
9
|
+
inside the function body are all equally reactive.** The compiler wraps dynamic prop
|
|
10
|
+
expressions in getters, and every value-position read of a prop — written any of the three
|
|
11
|
+
ways — compiles to a live read of that getter.
|
|
9
12
|
|
|
10
13
|
|
|
11
|
-
## Direct Access
|
|
14
|
+
## Direct Access
|
|
12
15
|
|
|
13
16
|
`props.xxx` maintains reactivity. Each access calls the underlying getter:
|
|
14
17
|
|
|
@@ -22,55 +25,87 @@ function Display(props: { value: number }) {
|
|
|
22
25
|
```
|
|
23
26
|
|
|
24
27
|
|
|
25
|
-
## Destructuring
|
|
28
|
+
## Destructuring In The Parameter
|
|
26
29
|
|
|
27
|
-
Destructuring
|
|
30
|
+
Destructuring the props **parameter** is also fully reactive. Every reference to a
|
|
31
|
+
destructured prop name — in a `createEffect` body, a `createMemo` computation, an event
|
|
32
|
+
handler, a reactive attribute, plain text, anywhere — compiles to the same live read
|
|
33
|
+
`props.xxx` would:
|
|
28
34
|
|
|
29
35
|
```tsx
|
|
30
36
|
function Display({ value }: { value: number }) {
|
|
31
37
|
createEffect(() => {
|
|
32
|
-
console.log(value) //
|
|
38
|
+
console.log(value) // Re-runs when parent updates value
|
|
33
39
|
})
|
|
34
40
|
return <span>{value}</span>
|
|
35
41
|
}
|
|
36
42
|
```
|
|
37
43
|
|
|
38
|
-
|
|
44
|
+
There is no local variable holding a stale, captured-at-mount copy — `value` above compiles
|
|
45
|
+
to a live read of the parent's getter at every reference site, the same as `props.value`
|
|
46
|
+
would. A destructure default (`{ value = 0 }`) is evaluated live too: it re-applies on every
|
|
47
|
+
read, not just once at mount.
|
|
39
48
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
|
|
50
|
+
## Destructuring In The Body
|
|
51
|
+
|
|
52
|
+
Destructuring inside the function body is also fully reactive, as long as the destructured
|
|
53
|
+
name is a **pure alias** of a single prop — nothing computed from it:
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
function Display(props: { value: number }) {
|
|
57
|
+
const { value } = props
|
|
58
|
+
createEffect(() => {
|
|
59
|
+
console.log(value) // Re-runs when parent updates value
|
|
60
|
+
})
|
|
61
|
+
return <span>{value}</span>
|
|
62
|
+
}
|
|
49
63
|
```
|
|
50
64
|
|
|
51
|
-
|
|
65
|
+
The compiler recognizes `const { value } = props` (and the equivalent `const value =
|
|
66
|
+
props.value`) as a pure passthrough, drops the local extraction entirely, and rewrites every
|
|
67
|
+
reference to `value` to a live `props.value` read instead — the same rewrite the parameter
|
|
68
|
+
form gets. A destructure default (`const { value = 0 } = props`) and a renamed binding
|
|
69
|
+
(`const { value: v } = props`) are both covered the same way.
|
|
70
|
+
|
|
71
|
+
This does NOT apply once the local does its own computation, or is reassigned:
|
|
52
72
|
|
|
53
73
|
```tsx
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
74
|
+
function Display(props: { value: number }) {
|
|
75
|
+
const { value } = props
|
|
76
|
+
const doubled = value * 2 // `doubled` is an ordinary once-evaluated local
|
|
77
|
+
let { count } = props
|
|
78
|
+
count += 1 // `count` is reassigned, so it can't be a live alias either
|
|
79
|
+
return <span>{doubled}</span>
|
|
58
80
|
}
|
|
59
81
|
```
|
|
60
82
|
|
|
83
|
+
`doubled` is a real computation, not a passthrough — it is an ordinary local, evaluated once
|
|
84
|
+
at its declaration (same as it would be with any other access pattern). A `let` binding is
|
|
85
|
+
never treated as a live alias, since rewriting a later assignment to it would mean silently
|
|
86
|
+
writing through to the caller's prop.
|
|
61
87
|
|
|
62
|
-
## When Destructuring Is Safe
|
|
63
88
|
|
|
64
|
-
|
|
89
|
+
## When To Prefer Which
|
|
90
|
+
|
|
91
|
+
The three reactive forms — `props.xxx`, parameter destructuring, and body destructuring —
|
|
92
|
+
behave identically at runtime, so the choice between them is style, not correctness:
|
|
93
|
+
|
|
94
|
+
- Destructuring (parameter or body) reads naturally and is usually the better default for
|
|
95
|
+
components with a handful of named props.
|
|
96
|
+
- `props.xxx` avoids repeating a long prop list at the call site, and is the natural fit for
|
|
97
|
+
a component that mostly forwards its props (`...rest`) rather than naming each one.
|
|
65
98
|
|
|
66
99
|
|
|
67
100
|
## Summary
|
|
68
101
|
|
|
69
|
-
| Pattern | Reactive? |
|
|
70
|
-
|
|
71
|
-
| `props.value` | Yes |
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
102
|
+
| Pattern | Reactive? |
|
|
103
|
+
|---------|-----------|
|
|
104
|
+
| `props.value` | Yes |
|
|
105
|
+
| `function C({ value }: Props)` — parameter destructuring | Yes |
|
|
106
|
+
| `const { value } = props` — body destructuring (pure alias) | Yes |
|
|
107
|
+
| `const doubled = value * 2` — a computation, not a pure alias | No, evaluated once at declaration (same as any other once-evaluated local) |
|
|
108
|
+
| `createSignal(props.value)` | `props.value` is reactive, the signal it seeds is independent thereafter |
|
|
74
109
|
|
|
75
110
|
|
|
76
111
|
## How It Works
|
|
@@ -85,7 +120,10 @@ The compiler transforms dynamic prop expressions into getters:
|
|
|
85
120
|
{ get value() { return count() } }
|
|
86
121
|
```
|
|
87
122
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
This is the same model as SolidJS
|
|
123
|
+
`props.value` calls the getter directly. A `value` destructured in the PARAMETER, or aliased
|
|
124
|
+
by a pure body destructure, compiles to the exact same getter call at every reference site —
|
|
125
|
+
the compiler rewrites each one, rather than binding a plain local that would only read the
|
|
126
|
+
getter once. This is the same reactive-getter model as SolidJS; unlike SolidJS, BarefootJS
|
|
127
|
+
performs that rewrite for both the parameter form and the body-alias form, so there is no
|
|
128
|
+
"don't destructure props" caveat left — only the ordinary rule that a real computation
|
|
129
|
+
(`value * 2`) is evaluated once, same as it would be anywhere else in the function.
|