@barefootjs/cli 0.1.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/docs/core/README.md +84 -0
- package/dist/docs/core/adapters/adapter-architecture.md +262 -0
- package/dist/docs/core/adapters/csr.md +78 -0
- package/dist/docs/core/adapters/custom-adapter.md +357 -0
- package/dist/docs/core/adapters/go-template-adapter.md +269 -0
- package/dist/docs/core/adapters/hono-adapter.md +199 -0
- package/dist/docs/core/adapters.md +37 -0
- package/dist/docs/core/advanced/code-splitting.md +142 -0
- package/dist/docs/core/advanced/compiler-internals.md +331 -0
- package/dist/docs/core/advanced/error-codes.md +261 -0
- package/dist/docs/core/advanced/ir-schema.md +65 -0
- package/dist/docs/core/advanced/performance.md +115 -0
- package/dist/docs/core/advanced/xyflow-browser-bundle.md +69 -0
- package/dist/docs/core/advanced.md +11 -0
- package/dist/docs/core/components/children-slots.md +150 -0
- package/dist/docs/core/components/component-authoring.md +207 -0
- package/dist/docs/core/components/context-api.md +236 -0
- package/dist/docs/core/components/portals.md +192 -0
- package/dist/docs/core/components/props-type-safety.md +97 -0
- package/dist/docs/core/components/styling.md +37 -0
- package/dist/docs/core/components.md +19 -0
- package/dist/docs/core/core-concepts/ai-native.md +83 -0
- package/dist/docs/core/core-concepts/backend-freedom.md +31 -0
- package/dist/docs/core/core-concepts/how-it-works.md +147 -0
- package/dist/docs/core/core-concepts/mpa-style.md +36 -0
- package/dist/docs/core/core-concepts/reactivity.md +35 -0
- package/dist/docs/core/core-concepts.md +28 -0
- package/dist/docs/core/introduction.md +87 -0
- package/dist/docs/core/llms.txt +53 -0
- package/dist/docs/core/quick-start.mdx +160 -0
- package/dist/docs/core/reactivity/create-effect.md +125 -0
- package/dist/docs/core/reactivity/create-memo.md +91 -0
- package/dist/docs/core/reactivity/create-signal.md +128 -0
- package/dist/docs/core/reactivity/on-cleanup.md +67 -0
- package/dist/docs/core/reactivity/on-mount.md +70 -0
- package/dist/docs/core/reactivity/props-reactivity.md +91 -0
- package/dist/docs/core/reactivity/untrack.md +69 -0
- package/dist/docs/core/reactivity.md +28 -0
- package/dist/docs/core/rendering/client-directive.md +82 -0
- package/dist/docs/core/rendering/fragment.md +43 -0
- package/dist/docs/core/rendering/jsx-compatibility.md +163 -0
- package/dist/docs/core/rendering.md +14 -0
- package/dist/index.js +25490 -0
- package/dist/tokens.json +74 -0
- package/package.json +37 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: createMemo
|
|
3
|
+
description: Creates a cached derived value that recomputes only when its dependencies change.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# createMemo
|
|
7
|
+
|
|
8
|
+
Creates a cached derived value. Recomputes only when its dependencies change.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { createMemo } from '@barefootjs/client'
|
|
12
|
+
|
|
13
|
+
const getter = createMemo<T>(fn: () => T): Memo<T>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Returns a read-only getter typed as `Memo<T>` (alias for `Reactive<() => T>`).
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## Basic Usage
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
const [count, setCount] = createSignal(2)
|
|
23
|
+
const doubled = createMemo(() => count() * 2)
|
|
24
|
+
|
|
25
|
+
doubled() // 4
|
|
26
|
+
setCount(5)
|
|
27
|
+
doubled() // 10
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
## When to Use
|
|
32
|
+
|
|
33
|
+
Use `createMemo` when you have a **derived value** that:
|
|
34
|
+
|
|
35
|
+
- Depends on one or more signals
|
|
36
|
+
- Is used in multiple places (avoids recalculating)
|
|
37
|
+
- Involves a non-trivial computation
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
const [todos, setTodos] = createSignal<Todo[]>([])
|
|
41
|
+
const [filter, setFilter] = createSignal<'all' | 'active' | 'done'>('all')
|
|
42
|
+
|
|
43
|
+
const filteredTodos = createMemo(() => {
|
|
44
|
+
const list = todos()
|
|
45
|
+
switch (filter()) {
|
|
46
|
+
case 'active': return list.filter(t => !t.done)
|
|
47
|
+
case 'done': return list.filter(t => t.done)
|
|
48
|
+
default: return list
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
// Used in multiple effects and JSX expressions
|
|
53
|
+
createEffect(() => console.log(filteredTodos().length))
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
For simple expressions used once, a memo is unnecessary:
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
// No memo needed
|
|
60
|
+
<p>{count() * 2}</p>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
## Chaining Memos
|
|
65
|
+
|
|
66
|
+
Memos can depend on other memos:
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
const [count, setCount] = createSignal(1)
|
|
70
|
+
const doubled = createMemo(() => count() * 2)
|
|
71
|
+
const quadrupled = createMemo(() => doubled() * 2)
|
|
72
|
+
|
|
73
|
+
createEffect(() => {
|
|
74
|
+
console.log(quadrupled()) // 4
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
setCount(3) // Logs: 12 (3 → 6 → 12)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Each memo in the chain only recomputes when its direct dependencies change.
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
## Memo vs Effect
|
|
84
|
+
|
|
85
|
+
| | `createMemo` | `createEffect` |
|
|
86
|
+
|---|---|---|
|
|
87
|
+
| Returns a value | Yes (getter function) | No |
|
|
88
|
+
| Triggers other effects | Yes (acts as a signal) | No |
|
|
89
|
+
| Used for | Derived data | Side effects (DOM, fetch, logging) |
|
|
90
|
+
|
|
91
|
+
Internally, `createMemo` is sugar over `createSignal` + `createEffect`. A memo behaves like a read-only signal to the rest of the reactive system.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: createSignal
|
|
3
|
+
description: Creates a reactive getter/setter pair for managing state.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# createSignal
|
|
7
|
+
|
|
8
|
+
Creates a reactive value. Returns a getter/setter pair.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { createSignal } from '@barefootjs/client'
|
|
12
|
+
|
|
13
|
+
const [getter, setter] = createSignal<T>(initialValue: T)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**Type:**
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
type Reactive<T> = T & { readonly __reactive: true }
|
|
20
|
+
|
|
21
|
+
type Signal<T> = [
|
|
22
|
+
Reactive<() => T>, // getter (carries Reactive brand)
|
|
23
|
+
(valueOrFn: T | ((prev: T) => T)) => void // setter
|
|
24
|
+
]
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The getter carries the `Reactive<T>` phantom brand — a compile-time marker for reactive expression detection. No runtime cost.
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
## Basic Usage
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
const [count, setCount] = createSignal(0)
|
|
34
|
+
|
|
35
|
+
// Read — call the getter
|
|
36
|
+
count() // 0
|
|
37
|
+
|
|
38
|
+
// Write — pass a value
|
|
39
|
+
setCount(5)
|
|
40
|
+
count() // 5
|
|
41
|
+
|
|
42
|
+
// Write — pass an updater function
|
|
43
|
+
setCount(n => n + 1)
|
|
44
|
+
count() // 6
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The getter is a **function call** — `count()`, not `count`. This is how the reactivity system tracks dependencies.
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
## Equality Check
|
|
51
|
+
|
|
52
|
+
The setter uses `Object.is` to compare values. Identical values do not trigger effects:
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
const [name, setName] = createSignal('Alice')
|
|
56
|
+
setName('Alice') // No effect runs — value unchanged
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
For objects and arrays, this means you need a new reference to trigger an update:
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
const [todos, setTodos] = createSignal([{ text: 'Buy milk' }])
|
|
63
|
+
|
|
64
|
+
// ❌ Mutating the same array — no update
|
|
65
|
+
const list = todos()
|
|
66
|
+
list.push({ text: 'Walk dog' })
|
|
67
|
+
setTodos(list) // Same reference, Object.is returns true
|
|
68
|
+
|
|
69
|
+
// ✅ New array — triggers update
|
|
70
|
+
setTodos([...todos(), { text: 'Walk dog' }])
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
## With Effects
|
|
75
|
+
|
|
76
|
+
Reading a signal inside an effect subscribes the effect to changes:
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
const [count, setCount] = createSignal(0)
|
|
80
|
+
|
|
81
|
+
createEffect(() => {
|
|
82
|
+
console.log('Count is:', count()) // Runs whenever count changes
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
setCount(1) // Logs: "Count is: 1"
|
|
86
|
+
setCount(2) // Logs: "Count is: 2"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
## With JSX
|
|
91
|
+
|
|
92
|
+
Signal getters in JSX expressions create fine-grained DOM updates:
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
"use client"
|
|
96
|
+
import { createSignal } from '@barefootjs/client'
|
|
97
|
+
|
|
98
|
+
export function Counter() {
|
|
99
|
+
const [count, setCount] = createSignal(0)
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<div>
|
|
103
|
+
<p>{count()}</p>
|
|
104
|
+
<button onClick={() => setCount(n => n + 1)}>+1</button>
|
|
105
|
+
</div>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The compiler generates an effect that updates only the `<p>` text content when `count` changes — the rest of the DOM is untouched.
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
## Type Inference
|
|
114
|
+
|
|
115
|
+
Type is inferred from the initial value:
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
const [count, setCount] = createSignal(0) // Signal<number>
|
|
119
|
+
const [name, setName] = createSignal('Alice') // Signal<string>
|
|
120
|
+
const [visible, setVisible] = createSignal(false) // Signal<boolean>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
For union types or complex types, specify the type parameter:
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
const [user, setUser] = createSignal<User | null>(null)
|
|
127
|
+
const [filter, setFilter] = createSignal<'all' | 'active' | 'done'>('all')
|
|
128
|
+
```
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: onCleanup
|
|
3
|
+
description: Registers a cleanup function that runs when the owning effect re-runs or the component is destroyed.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# onCleanup
|
|
7
|
+
|
|
8
|
+
Registers a cleanup function. Called when the owning effect re-runs or the component is destroyed.
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
import { onCleanup } from '@barefootjs/client'
|
|
12
|
+
|
|
13
|
+
onCleanup(fn: () => void): void
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## Basic Usage
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
createEffect(() => {
|
|
21
|
+
const timer = setInterval(() => console.log('tick'), 1000)
|
|
22
|
+
onCleanup(() => clearInterval(timer))
|
|
23
|
+
})
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
On re-run, the cleanup function runs first, clearing the previous interval before creating a new one.
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## Multiple Cleanups
|
|
30
|
+
|
|
31
|
+
`onCleanup` can be called multiple times. Cleanups execute in reverse order (last registered, first called):
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
createEffect(() => {
|
|
35
|
+
const controller = new AbortController()
|
|
36
|
+
onCleanup(() => controller.abort())
|
|
37
|
+
|
|
38
|
+
const listener = () => setHash(window.location.hash)
|
|
39
|
+
window.addEventListener('hashchange', listener)
|
|
40
|
+
onCleanup(() => window.removeEventListener('hashchange', listener))
|
|
41
|
+
})
|
|
42
|
+
// On cleanup: removeEventListener runs first, then abort
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
## With `onMount`
|
|
47
|
+
|
|
48
|
+
`onCleanup` works inside `onMount` for one-time setup/teardown:
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
onMount(() => {
|
|
52
|
+
const handleResize = () => setWidth(window.innerWidth)
|
|
53
|
+
window.addEventListener('resize', handleResize)
|
|
54
|
+
onCleanup(() => window.removeEventListener('resize', handleResize))
|
|
55
|
+
})
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
## Where It Works
|
|
60
|
+
|
|
61
|
+
`onCleanup` must be called within a reactive context:
|
|
62
|
+
|
|
63
|
+
- Inside `createEffect`
|
|
64
|
+
- Inside `onMount`
|
|
65
|
+
- During component initialization
|
|
66
|
+
|
|
67
|
+
Calling it outside these contexts has no effect.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: onMount
|
|
3
|
+
description: Runs a callback once when the component initializes, without tracking signal dependencies.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# onMount
|
|
7
|
+
|
|
8
|
+
Runs once when the component initializes. Signal accesses inside are **not** tracked.
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
import { onMount } from '@barefootjs/client'
|
|
12
|
+
|
|
13
|
+
onMount(fn: () => void): void
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## Basic Usage
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
onMount(() => {
|
|
21
|
+
console.log('Component initialized')
|
|
22
|
+
})
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Unlike `createEffect`, this function never re-runs. It executes once at initialization time.
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
## Common Patterns
|
|
29
|
+
|
|
30
|
+
### Browser state
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
onMount(() => {
|
|
34
|
+
const hash = window.location.hash
|
|
35
|
+
setFilter(hash === '#/active' ? 'active' : 'all')
|
|
36
|
+
})
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Event listeners
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
onMount(() => {
|
|
43
|
+
const handleHashChange = () => setFilter(getFilterFromHash())
|
|
44
|
+
window.addEventListener('hashchange', handleHashChange)
|
|
45
|
+
onCleanup(() => window.removeEventListener('hashchange', handleHashChange))
|
|
46
|
+
})
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Focus
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
onMount(() => {
|
|
53
|
+
inputElement.focus()
|
|
54
|
+
})
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
## How It Works
|
|
59
|
+
|
|
60
|
+
`onMount` is equivalent to `createEffect(() => untrack(fn))`. The function runs inside an effect context (so `onCleanup` works), but `untrack` prevents dependency tracking.
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
## `onMount` vs `createEffect`
|
|
64
|
+
|
|
65
|
+
| | `onMount` | `createEffect` |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| Runs on init | Yes | Yes |
|
|
68
|
+
| Re-runs on signal change | No | Yes |
|
|
69
|
+
| Tracks dependencies | No | Yes |
|
|
70
|
+
| Supports `onCleanup` | Yes | Yes |
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Props Reactivity
|
|
3
|
+
description: How prop access patterns determine whether reactive updates propagate in BarefootJS components.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Props Reactivity
|
|
7
|
+
|
|
8
|
+
**How you access props determines whether updates propagate.** The compiler wraps dynamic prop expressions in getters.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## Direct Access — Reactive
|
|
12
|
+
|
|
13
|
+
`props.xxx` maintains reactivity. Each access calls the underlying getter:
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
function Display(props: { value: number }) {
|
|
17
|
+
createEffect(() => {
|
|
18
|
+
console.log(props.value) // Re-runs when parent updates value
|
|
19
|
+
})
|
|
20
|
+
return <span>{props.value}</span>
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
## Destructuring — Captures Once
|
|
26
|
+
|
|
27
|
+
Destructuring calls the getter once and stores the result. The value does not update:
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
function Display({ value }: { value: number }) {
|
|
31
|
+
createEffect(() => {
|
|
32
|
+
console.log(value) // Stale — captured at component init
|
|
33
|
+
})
|
|
34
|
+
return <span>{value}</span>
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The compiler emits `BF043` when it detects props destructuring in a client component:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
warning[BF043]: Props destructuring breaks reactivity
|
|
42
|
+
|
|
43
|
+
--> src/components/Display.tsx:1:18
|
|
44
|
+
|
|
|
45
|
+
1 | function Display({ value }: { value: number }) {
|
|
46
|
+
| ^^^^^^^^^
|
|
47
|
+
|
|
|
48
|
+
= help: Access props via `props.value` to maintain reactivity
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Suppress with `@bf-ignore` when capturing intentionally (e.g., initial values):
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
// @bf-ignore props-destructuring
|
|
55
|
+
function Counter({ initial }: { initial: number }) {
|
|
56
|
+
const [count, setCount] = createSignal(initial)
|
|
57
|
+
return <button onClick={() => setCount(n => n + 1)}>{count()}</button>
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
## When Destructuring Is Safe
|
|
63
|
+
|
|
64
|
+
Destructuring is safe for **initial values** of local state and for values that never change (`id`, static labels).
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
## Summary
|
|
68
|
+
|
|
69
|
+
| Pattern | Reactive? | Use when |
|
|
70
|
+
|---------|-----------|----------|
|
|
71
|
+
| `props.value` | Yes | You need live updates from parent |
|
|
72
|
+
| `const { value } = props` | No | Value is used once (e.g., initial state) |
|
|
73
|
+
| `createSignal(props.value)` | `props.value` is reactive, signal is independent | Creating local state from a prop |
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
## How It Works
|
|
77
|
+
|
|
78
|
+
The compiler transforms dynamic prop expressions into getters:
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
// Parent
|
|
82
|
+
<Child value={count()} />
|
|
83
|
+
|
|
84
|
+
// Compiled props object
|
|
85
|
+
{ get value() { return count() } }
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
- `props.value` → calls getter → calls `count()` → dependency tracked
|
|
89
|
+
- `const { value } = props` → calls getter once → stores the number → no further tracking
|
|
90
|
+
|
|
91
|
+
This is the same model as SolidJS. If you are coming from React, this is the key behavioral difference.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: untrack
|
|
3
|
+
description: Executes a function without tracking signal dependencies in the current reactive context.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# untrack
|
|
7
|
+
|
|
8
|
+
Executes a function without tracking signal dependencies.
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
import { untrack } from '@barefootjs/client'
|
|
12
|
+
|
|
13
|
+
untrack<T>(fn: () => T): T
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Returns the value produced by `fn`.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## Basic Usage
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
const [count, setCount] = createSignal(0)
|
|
23
|
+
const [name, setName] = createSignal('Alice')
|
|
24
|
+
|
|
25
|
+
createEffect(() => {
|
|
26
|
+
// count() IS tracked — this effect re-runs when count changes
|
|
27
|
+
console.log('count:', count())
|
|
28
|
+
|
|
29
|
+
// name() is NOT tracked — changing name alone won't trigger this effect
|
|
30
|
+
console.log('name:', untrack(() => name()))
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
setCount(1) // Effect re-runs
|
|
34
|
+
setName('Bob') // Effect does NOT re-run
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
## When to Use
|
|
39
|
+
|
|
40
|
+
### Read without subscribing
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
createEffect(() => {
|
|
44
|
+
// Re-run only when items change, not when sortOrder changes
|
|
45
|
+
const sorted = [...items()].sort(untrack(() => sortOrder()) === 'asc' ? compare : reverseCompare)
|
|
46
|
+
setDisplayList(sorted)
|
|
47
|
+
})
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Log without dependencies
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
createEffect(() => {
|
|
54
|
+
const value = computedResult()
|
|
55
|
+
console.log('Updated at:', untrack(() => new Date().toISOString()))
|
|
56
|
+
})
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Break circular dependencies
|
|
60
|
+
|
|
61
|
+
`untrack` breaks cycles where two signals depend on each other through effects:
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
createEffect(() => {
|
|
65
|
+
const a = signalA()
|
|
66
|
+
const b = untrack(() => signalB()) // Read B without tracking
|
|
67
|
+
setResult(a + b)
|
|
68
|
+
})
|
|
69
|
+
```
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Reactivity
|
|
3
|
+
description: Fine-grained reactive primitives inspired by SolidJS, including signals, effects, memos, and lifecycle hooks.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Reactivity
|
|
7
|
+
|
|
8
|
+
All reactive primitives are imported from `@barefootjs/client`:
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
"use client"
|
|
12
|
+
import { createSignal, createEffect, createMemo, onMount, onCleanup, untrack } from '@barefootjs/client'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## API Reference
|
|
16
|
+
|
|
17
|
+
| API | Description |
|
|
18
|
+
|-----|-------------|
|
|
19
|
+
| [`createSignal`](./reactivity/create-signal.md) | Create a reactive value |
|
|
20
|
+
| [`createEffect`](./reactivity/create-effect.md) | Run side effects when dependencies change |
|
|
21
|
+
| [`createMemo`](./reactivity/create-memo.md) | Create a cached derived value |
|
|
22
|
+
| [`onMount`](./reactivity/on-mount.md) | Run once on component initialization |
|
|
23
|
+
| [`onCleanup`](./reactivity/on-cleanup.md) | Register cleanup for effects and lifecycle |
|
|
24
|
+
| [`untrack`](./reactivity/untrack.md) | Read signals without tracking dependencies |
|
|
25
|
+
|
|
26
|
+
## Guides
|
|
27
|
+
|
|
28
|
+
- [Props Reactivity](./reactivity/props-reactivity.md) — How props stay reactive, and when destructuring breaks it
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: /* @client */ Directive
|
|
3
|
+
description: Mark JSX expressions for client-only evaluation when the compiler cannot translate them to server templates.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /* @client */ Directive
|
|
7
|
+
|
|
8
|
+
Marks a JSX expression for **client-only evaluation**. The server renders a placeholder; the browser evaluates the expression at runtime.
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
{/* @client */ expression}
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## When to Use
|
|
16
|
+
|
|
17
|
+
The compiler emits `BF021` for expressions it cannot translate to a marked template. `/* @client */` resolves the error by opting into client-only evaluation.
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
error[BF021]: Expression cannot be compiled to marked template
|
|
21
|
+
|
|
22
|
+
--> src/components/Dashboard.tsx:15:10
|
|
23
|
+
|
|
|
24
|
+
15 | {items().reduce((sum, x) => sum + x.price, 0)}
|
|
25
|
+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
26
|
+
|
|
|
27
|
+
= help: Add /* @client */ to evaluate this expression on the client only
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
See [JSX Compatibility — Limitations](./jsx-compatibility.md#limitations) for the full list of unsupported patterns.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## How It Works
|
|
34
|
+
|
|
35
|
+
The compiler skips template generation for the expression. The server outputs a comment marker; the client JS evaluates it:
|
|
36
|
+
|
|
37
|
+
**Server output:**
|
|
38
|
+
|
|
39
|
+
```html
|
|
40
|
+
<!--bf-client:s2--><!--/-->
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Client JS:**
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
// @client: s2
|
|
47
|
+
createEffect(() => {
|
|
48
|
+
updateClientMarker(__scope, 's2', todos().filter(t => !t.done).length)
|
|
49
|
+
})
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
## Examples
|
|
54
|
+
|
|
55
|
+
### Unsupported patterns
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
// Nested higher-order methods
|
|
59
|
+
{/* @client */ items().filter(x => x.tags().filter(t => t.active).length > 0)}
|
|
60
|
+
|
|
61
|
+
// Unsupported array methods
|
|
62
|
+
{/* @client */ items().reduce((sum, x) => sum + x.price, 0)}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Explicit client-only evaluation
|
|
66
|
+
|
|
67
|
+
Even for patterns the compiler supports, you can use `/* @client */` to skip server evaluation. The [TodoApp example](https://github.com/piconic-ai/barefootjs/blob/main/integrations/shared/components/TodoApp.tsx) uses this approach:
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
// These expressions CAN compile without @client, but the developer
|
|
71
|
+
// chose client-only evaluation here
|
|
72
|
+
checked={/* @client */ todos().every(t => t.done)}
|
|
73
|
+
|
|
74
|
+
<strong>{/* @client */ todos().filter(t => !t.done).length}</strong>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Compare with the [TodoAppSSR version](https://github.com/piconic-ai/barefootjs/blob/main/integrations/shared/components/TodoAppSSR.tsx), which omits `/* @client */` and lets the compiler generate marked template equivalents for the same expressions.
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
## Trade-off
|
|
81
|
+
|
|
82
|
+
`/* @client */` means **no server-rendered content** for the expression — users see a placeholder until client JS loads. Omit the directive when the compiler can generate a template equivalent to get server-rendered initial values.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Fragment
|
|
3
|
+
description: Using fragments to render children without a wrapper element, including transparent fragment behavior.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Fragment
|
|
7
|
+
|
|
8
|
+
Fragments (`<>...</>`) are supported. They render children without a wrapper element.
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
<>
|
|
12
|
+
<h1>Title</h1>
|
|
13
|
+
<p>Description</p>
|
|
14
|
+
</>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## Transparent Fragments
|
|
19
|
+
|
|
20
|
+
A fragment that passes through `children` is treated as transparent — the compiler skips it and processes the children directly:
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
<>{children}</>
|
|
24
|
+
<>{props.children}</>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
No extra hydration markers are generated for transparent fragments.
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
## Fragments and Hydration
|
|
31
|
+
|
|
32
|
+
Fragments don't produce a DOM node. For conditional fragments, the compiler uses HTML comment markers as boundaries:
|
|
33
|
+
|
|
34
|
+
```html
|
|
35
|
+
<!--bf-cond-start:s0-->
|
|
36
|
+
<h1>Title</h1>
|
|
37
|
+
<p>Description</p>
|
|
38
|
+
<!--bf-cond-end:s0-->
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The client runtime uses these comment markers to locate and swap the fragment content when the condition changes.
|
|
42
|
+
|
|
43
|
+
For component roots that return a fragment, each direct child element is marked with `bf-s` so the runtime can find them during hydration.
|