@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,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Performance Optimization
|
|
3
|
+
description: Strategies to minimize bundle size, reduce hydration cost, and optimize runtime reactivity
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Performance Optimization
|
|
7
|
+
|
|
8
|
+
## Zero-JS by Default
|
|
9
|
+
|
|
10
|
+
Components without `"use client"` generate **no client JavaScript**:
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
// Server-only — 0 bytes of client JS
|
|
14
|
+
export function Header() {
|
|
15
|
+
return (
|
|
16
|
+
<header>
|
|
17
|
+
<h1>My App</h1>
|
|
18
|
+
<nav>...</nav>
|
|
19
|
+
</header>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Minimal Hydration
|
|
25
|
+
|
|
26
|
+
Only signals, event handlers, and effects are sent to the client. The HTML structure is never re-created — the server already rendered it.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Reducing Client JS Size
|
|
31
|
+
|
|
32
|
+
### Use Memos for Derived Values
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
// ❌ Redundant signal
|
|
36
|
+
const [count, setCount] = createSignal(0)
|
|
37
|
+
const [doubled, setDoubled] = createSignal(0)
|
|
38
|
+
createEffect(() => setDoubled(count() * 2)) // Extra signal + effect
|
|
39
|
+
|
|
40
|
+
// ✅ Use memo instead
|
|
41
|
+
const [count, setCount] = createSignal(0)
|
|
42
|
+
const doubled = createMemo(() => count() * 2) // Computed, no extra signal
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Prefer Static Arrays
|
|
46
|
+
|
|
47
|
+
The compiler detects static arrays and skips reconciliation:
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
// Static — no reconciliation generated
|
|
51
|
+
const tabs = ['Home', 'About', 'Contact']
|
|
52
|
+
{tabs.map(tab => <Tab label={tab} />)}
|
|
53
|
+
|
|
54
|
+
// Dynamic — reconcileElements needed
|
|
55
|
+
const [items, setItems] = createSignal([...])
|
|
56
|
+
{items().map(item => <Item key={item.id} data={item} />)}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Optimizing Hydration
|
|
62
|
+
|
|
63
|
+
### Stable Keys for Lists
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
// ✅ Stable key — DOM nodes reused when list changes
|
|
67
|
+
{items().map(item => <li key={item.id}>{item.name}</li>)}
|
|
68
|
+
|
|
69
|
+
// ❌ Index key — DOM nodes recreated on reorder
|
|
70
|
+
{items().map((item, i) => <li key={i}>{item.name}</li>)}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Focus Preservation
|
|
74
|
+
|
|
75
|
+
The reconciler preserves focused elements during list updates automatically.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Optimizing Reactivity
|
|
80
|
+
|
|
81
|
+
### `untrack` for One-Time Reads
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
createEffect(() => {
|
|
85
|
+
// Only re-runs when items() changes, not when count() changes
|
|
86
|
+
const currentCount = untrack(() => count())
|
|
87
|
+
console.log(`${items().length} items, count was ${currentCount}`)
|
|
88
|
+
})
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Memo Over Effect + Signal
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
// ❌ Effect → Signal chain (two subscriptions)
|
|
95
|
+
const [total, setTotal] = createSignal(0)
|
|
96
|
+
createEffect(() => setTotal(price() * quantity()))
|
|
97
|
+
|
|
98
|
+
// ✅ Single memo (one subscription)
|
|
99
|
+
const total = createMemo(() => price() * quantity())
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Guard DOM Updates
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
createEffect(() => {
|
|
106
|
+
const cls = isActive() ? 'active' : 'inactive'
|
|
107
|
+
if (element.className !== cls) {
|
|
108
|
+
element.className = cls // Only touches DOM when value changes
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The compiler already generates guarded updates for text content and common attributes. This applies to custom effects only.
|
|
114
|
+
|
|
115
|
+
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: xyflow browser bundle
|
|
3
|
+
description: How to serve @barefootjs/xyflow as a pre-built browser chunk via an importmap
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# xyflow browser bundle
|
|
7
|
+
|
|
8
|
+
`@barefootjs/xyflow` ships a pre-minified ESM variant, `dist/xyflow.browser.min.js`, with `@barefootjs/client*` left as externals. Apps that want to serve xyflow as an independently-cached static asset can copy this file instead of re-bundling.
|
|
9
|
+
|
|
10
|
+
## Why a pre-built variant
|
|
11
|
+
|
|
12
|
+
Bundling xyflow into a large client entry adds ~270 KB of d3 + wrapper code to every cold-visit payload. Re-bundling it manually as a separate chunk requires remembering three externals:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
--external '@barefootjs/client'
|
|
16
|
+
--external '@barefootjs/client/runtime'
|
|
17
|
+
--external '@barefootjs/client/reactive'
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Missing any one of them silently inlines a second copy of the reactive primitives, breaking signal propagation across the boundary (fitView becomes a no-op, `FlowContext` reads from the wrong owner, etc.). The pre-built variant has all three applied already.
|
|
21
|
+
|
|
22
|
+
## Setup
|
|
23
|
+
|
|
24
|
+
**1. Copy the file into your static output:**
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
cp node_modules/@barefootjs/xyflow/dist/xyflow.browser.min.js \
|
|
28
|
+
public/static/components/xyflow.js
|
|
29
|
+
|
|
30
|
+
# Optional: copy the sourcemap for readable DevTools stacks
|
|
31
|
+
cp node_modules/@barefootjs/xyflow/dist/xyflow.browser.min.js.map \
|
|
32
|
+
public/static/components/xyflow.js.map
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**2. Add an importmap to your HTML:**
|
|
36
|
+
|
|
37
|
+
```html
|
|
38
|
+
<script type="importmap">
|
|
39
|
+
{
|
|
40
|
+
"imports": {
|
|
41
|
+
"@barefootjs/client": "/static/components/barefoot.js",
|
|
42
|
+
"@barefootjs/client/runtime": "/static/components/barefoot.js",
|
|
43
|
+
"@barefootjs/client/reactive": "/static/components/barefoot.js",
|
|
44
|
+
"@barefootjs/xyflow": "/static/components/xyflow.js"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
</script>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The three `@barefootjs/client*` entries all pointing at the same file is what makes the browser deduplicate them into a single module instance, so reactive primitives share one `Listener`/`Owner` global.
|
|
51
|
+
|
|
52
|
+
## package.json fields
|
|
53
|
+
|
|
54
|
+
The file is also exposed via the `umd` export condition and the `unpkg`/`jsdelivr` top-level fields:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"exports": {
|
|
59
|
+
".": {
|
|
60
|
+
"umd": "./dist/xyflow.browser.min.js",
|
|
61
|
+
"import": "./dist/index.js"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"unpkg": "./dist/xyflow.browser.min.js",
|
|
65
|
+
"jsdelivr": "./dist/xyflow.browser.min.js"
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
CDNs like unpkg and jsDelivr resolve the top-level fields automatically, so `https://unpkg.com/@barefootjs/xyflow` serves the pre-built variant.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Advanced
|
|
3
|
+
description: Compiler internals, IR schema, error codes, and performance optimization for contributors and adapter authors.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Advanced
|
|
7
|
+
|
|
8
|
+
- [IR Schema Reference](./advanced/ir-schema.md) — The intermediate representation node types and metadata
|
|
9
|
+
- [Compiler Internals](./advanced/compiler-internals.md) — Pipeline phases, reactivity analysis, and code generation
|
|
10
|
+
- [Error Codes Reference](./advanced/error-codes.md) — All compiler errors and warnings with solutions
|
|
11
|
+
- [Performance Optimization](./advanced/performance.md) — Best practices for minimal client JS and fast hydration
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Children & Slots
|
|
3
|
+
description: Accept nested JSX content via the children prop and enable polymorphic rendering with the Slot component.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Children & Slots
|
|
7
|
+
|
|
8
|
+
Nested JSX content is passed via the `children` prop. The `Slot` component enables polymorphic rendering with `asChild`.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## Children
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
<Card>
|
|
15
|
+
<h2>Title</h2>
|
|
16
|
+
<p>Body text</p>
|
|
17
|
+
</Card>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
function Card(props: { children?: Child }) {
|
|
22
|
+
return <div className="card">{props.children}</div>
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`children` is typed as `Child`, which covers JSX elements, strings, numbers, and arrays.
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## Passing Children Through
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
function Panel(props: { title: string; children?: Child }) {
|
|
33
|
+
return (
|
|
34
|
+
<section>
|
|
35
|
+
<h2>{props.title}</h2>
|
|
36
|
+
<div className="panel-body">{props.children}</div>
|
|
37
|
+
</section>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Wrapping `children` in a fragment (`<>{props.children}</>`) is **transparent** — the compiler skips the fragment without extra hydration markers. See [Fragment](../rendering/fragment.md).
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
## The `Slot` Component
|
|
46
|
+
|
|
47
|
+
`Slot` merges props and classes onto its child element, enabling the **`asChild` pattern**:
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import { Slot } from './slot'
|
|
51
|
+
|
|
52
|
+
function Button({ className, asChild, children, ...props }: ButtonProps) {
|
|
53
|
+
const classes = `btn btn-primary ${className}`
|
|
54
|
+
|
|
55
|
+
if (asChild) {
|
|
56
|
+
return <Slot className={classes} {...props}>{children}</Slot>
|
|
57
|
+
}
|
|
58
|
+
return <button className={classes} {...props}>{children}</button>
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### How `Slot` Works
|
|
63
|
+
|
|
64
|
+
`Slot` extracts the child's tag, merges `className` (space-separated), spreads remaining props, and renders the child's tag with the merged result.
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
// Input
|
|
68
|
+
<Slot className="btn" onClick={handleClick}>
|
|
69
|
+
<a href="/home">Home</a>
|
|
70
|
+
</Slot>
|
|
71
|
+
|
|
72
|
+
// Output
|
|
73
|
+
<a href="/home" className="btn" onClick={handleClick}>Home</a>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
If `children` is not a valid element (e.g., a string), `Slot` falls back to rendering it inside a fragment.
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
## The `asChild` Pattern
|
|
80
|
+
|
|
81
|
+
`asChild` delegates rendering to the child element — the component's styling without its default HTML tag.
|
|
82
|
+
|
|
83
|
+
### Default rendering (no `asChild`)
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
<Button variant="primary">Click me</Button>
|
|
87
|
+
// Renders: <button className="btn btn-primary">Click me</button>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### With `asChild`
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
<Button variant="primary" asChild>
|
|
94
|
+
<a href="/dashboard">Go to Dashboard</a>
|
|
95
|
+
</Button>
|
|
96
|
+
// Renders: <a href="/dashboard" className="btn btn-primary">Go to Dashboard</a>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The `<a>` tag receives Button's classes and props. The component controls styling; the caller controls the element.
|
|
100
|
+
|
|
101
|
+
### When to Use `asChild`
|
|
102
|
+
|
|
103
|
+
- Navigation buttons (render `<a>` with button styling)
|
|
104
|
+
- Custom triggers (dialog or dropdown)
|
|
105
|
+
- Semantic elements with reused component styles
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
// Dialog trigger as a custom element
|
|
109
|
+
<DialogTrigger asChild>
|
|
110
|
+
<span role="button" tabIndex={0}>Open</span>
|
|
111
|
+
</DialogTrigger>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
## Compound Components
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
<Dialog open={open()} onOpenChange={setOpen}>
|
|
119
|
+
<DialogTrigger>Open</DialogTrigger>
|
|
120
|
+
<DialogOverlay />
|
|
121
|
+
<DialogContent>
|
|
122
|
+
<DialogHeader>
|
|
123
|
+
<DialogTitle>Confirm</DialogTitle>
|
|
124
|
+
<DialogDescription>Are you sure?</DialogDescription>
|
|
125
|
+
</DialogHeader>
|
|
126
|
+
<DialogFooter>
|
|
127
|
+
<DialogClose>Cancel</DialogClose>
|
|
128
|
+
<Button onClick={handleConfirm}>Yes</Button>
|
|
129
|
+
</DialogFooter>
|
|
130
|
+
</DialogContent>
|
|
131
|
+
</Dialog>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Sub-components read shared state from a context provider. See [Context API](./context-api.md).
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
## List Rendering
|
|
138
|
+
|
|
139
|
+
```tsx
|
|
140
|
+
{todos().map(todo => (
|
|
141
|
+
<TodoItem
|
|
142
|
+
key={todo.id}
|
|
143
|
+
todo={todo}
|
|
144
|
+
onToggle={() => handleToggle(todo.id)}
|
|
145
|
+
onDelete={() => handleDelete(todo.id)}
|
|
146
|
+
/>
|
|
147
|
+
))}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`key` is required for efficient list updates (warning `BF023` if missing).
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Component Authoring
|
|
3
|
+
description: Learn how to write server and client components in BarefootJS using JSX functions.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Component Authoring
|
|
7
|
+
|
|
8
|
+
Components are functions that return JSX, in two kinds: **server components** and **client components**.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## Server Components
|
|
12
|
+
|
|
13
|
+
Server components render HTML on the server with no client-side JavaScript.
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
export function Greeting({ name }: { name: string }) {
|
|
17
|
+
return <h1>Hello, {name}</h1>
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Server components can access databases, read files, and use secrets. They produce a template rendered once per request.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
## Client Components
|
|
25
|
+
|
|
26
|
+
Client components use reactive primitives and ship JavaScript to the browser. They require the `"use client"` directive:
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
"use client"
|
|
30
|
+
import { createSignal } from '@barefootjs/client'
|
|
31
|
+
|
|
32
|
+
export function Counter() {
|
|
33
|
+
const [count, setCount] = createSignal(0)
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<button onClick={() => setCount(n => n + 1)}>
|
|
37
|
+
Count: {count()}
|
|
38
|
+
</button>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The compiler produces a **marked template** (server HTML with `bf-*` attributes) and **client JS** (signals, effects, event handlers). See [How It Works](../core-concepts/how-it-works.md#two-phase-compilation) for details.
|
|
44
|
+
|
|
45
|
+
### When `"use client"` Is Required
|
|
46
|
+
|
|
47
|
+
Add `"use client"` when a component uses:
|
|
48
|
+
|
|
49
|
+
- `createSignal`, `createEffect`, `createMemo`
|
|
50
|
+
- `onMount`, `onCleanup`, `untrack`
|
|
51
|
+
- `createContext`, `useContext`
|
|
52
|
+
- Event handlers (`onClick`, `onChange`, etc.)
|
|
53
|
+
|
|
54
|
+
Without the directive:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
error[BF001]: 'use client' directive required for components with createSignal
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
## Component Naming
|
|
62
|
+
|
|
63
|
+
Component names must start with an uppercase letter:
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
// ✅ Component
|
|
67
|
+
function TodoItem() { ... }
|
|
68
|
+
|
|
69
|
+
// ❌ Error BF042
|
|
70
|
+
function todoItem() { ... }
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
## Compilation Output
|
|
75
|
+
|
|
76
|
+
**Source:**
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
"use client"
|
|
80
|
+
import { createSignal } from '@barefootjs/client'
|
|
81
|
+
|
|
82
|
+
export function Toggle() {
|
|
83
|
+
const [on, setOn] = createSignal(false)
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<button onClick={() => setOn(prev => !prev)}>
|
|
87
|
+
{on() ? 'ON' : 'OFF'}
|
|
88
|
+
</button>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Marked template:**
|
|
94
|
+
|
|
95
|
+
<!-- tabs:adapter -->
|
|
96
|
+
<!-- tab:Hono -->
|
|
97
|
+
```tsx
|
|
98
|
+
export function Toggle({ __instanceId, ... }) {
|
|
99
|
+
const __scopeId = __instanceId || `Toggle_${...}`
|
|
100
|
+
const on = () => false
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<button bf-s={__scopeId} bf="s1">
|
|
104
|
+
{on() ? <>{bfComment("cond-start:s0")}{'ON'}{bfComment("cond-end:s0")}</>
|
|
105
|
+
: <>{bfComment("cond-start:s0")}{'OFF'}{bfComment("cond-end:s0")}</>}
|
|
106
|
+
</button>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
<!-- tab:Go Template -->
|
|
111
|
+
```go-template
|
|
112
|
+
{{define "Toggle"}}
|
|
113
|
+
<button bf-s="{{bfScopeAttr .}}" bf="s1">
|
|
114
|
+
{{if .On}}{{bfComment "cond-start:s0"}}{{"ON"}}{{bfComment "cond-end:s0"}}
|
|
115
|
+
{{else}}{{bfComment "cond-start:s0"}}{{"OFF"}}{{bfComment "cond-end:s0"}}{{end}}
|
|
116
|
+
</button>
|
|
117
|
+
{{end}}
|
|
118
|
+
```
|
|
119
|
+
<!-- /tabs -->
|
|
120
|
+
|
|
121
|
+
**Client JS:**
|
|
122
|
+
|
|
123
|
+
```js
|
|
124
|
+
import { $, createSignal, hydrate, insert } from '@barefootjs/client'
|
|
125
|
+
|
|
126
|
+
export function initToggle(__scope, _p = {}) {
|
|
127
|
+
if (!__scope) return
|
|
128
|
+
|
|
129
|
+
const [on, setOn] = createSignal(false)
|
|
130
|
+
|
|
131
|
+
const [_s1, _s0] = $(__scope, 's1', 's0')
|
|
132
|
+
|
|
133
|
+
insert(__scope, 's0', () => on(), {
|
|
134
|
+
template: () => `<!--bf-cond-start:s0-->ON<!--bf-cond-end:s0-->`,
|
|
135
|
+
bindEvents: (__branchScope) => {}
|
|
136
|
+
}, {
|
|
137
|
+
template: () => `<!--bf-cond-start:s0-->OFF<!--bf-cond-end:s0-->`,
|
|
138
|
+
bindEvents: (__branchScope) => {}
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
if (_s1) _s1.addEventListener('click', () => { setOn(prev => !prev) })
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
hydrate('Toggle', { init: initToggle, template: ... })
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Only the conditional branch bound to `on()` updates when the signal changes. The `insert()` function handles DOM swapping using comment markers as boundaries.
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
## Composition Rules
|
|
151
|
+
|
|
152
|
+
| From | To | Allowed |
|
|
153
|
+
|------|----|---------|
|
|
154
|
+
| Server component | Server component | ✅ |
|
|
155
|
+
| Server component | Client component | ✅ |
|
|
156
|
+
| Client component | Client component | ✅ |
|
|
157
|
+
| Client component | Server component | ❌ |
|
|
158
|
+
|
|
159
|
+
Server-only code does not exist in the browser. The compiler emits `BF003` if a client component imports a server component.
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
// Page.tsx — server component
|
|
163
|
+
import { Counter } from './Counter' // "use client" ✅
|
|
164
|
+
import { UserList } from './UserList' // server-only ✅
|
|
165
|
+
|
|
166
|
+
export function Page() {
|
|
167
|
+
return (
|
|
168
|
+
<div>
|
|
169
|
+
<UserList /> {/* Server → Server */}
|
|
170
|
+
<Counter /> {/* Server → Client */}
|
|
171
|
+
</div>
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
// Dashboard.tsx — "use client"
|
|
178
|
+
import { Counter } from './Counter' // ✅ Client → Client
|
|
179
|
+
import { UserList } from './UserList' // ❌ BF003: Client → Server
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Ref Callbacks
|
|
183
|
+
|
|
184
|
+
`ref` callbacks provide imperative DOM access. The callback receives the element after mount:
|
|
185
|
+
|
|
186
|
+
```tsx
|
|
187
|
+
"use client"
|
|
188
|
+
import { createEffect } from '@barefootjs/client'
|
|
189
|
+
|
|
190
|
+
export function AutoFocus() {
|
|
191
|
+
const handleMount = (el: HTMLInputElement) => {
|
|
192
|
+
el.focus()
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return <input ref={handleMount} placeholder="Focused on mount" />
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Combine with `createEffect` for reactive DOM updates:
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
202
|
+
const handleMount = (el: HTMLElement) => {
|
|
203
|
+
createEffect(() => {
|
|
204
|
+
el.className = isActive() ? 'active' : 'inactive'
|
|
205
|
+
})
|
|
206
|
+
}
|
|
207
|
+
```
|