@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,331 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Compiler Internals
|
|
3
|
+
description: How the BarefootJS compiler transforms JSX into marked templates and client JavaScript.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Compiler Internals
|
|
7
|
+
|
|
8
|
+
## Pipeline Overview
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
┌─────────────────────────────────────────────────────┐
|
|
12
|
+
│ JSX Source (.tsx with "use client") │
|
|
13
|
+
└──────────────────────┬──────────────────────────────┘
|
|
14
|
+
↓
|
|
15
|
+
┌──────────────────────┴──────────────────────────────┐
|
|
16
|
+
│ 1. Analyzer (analyzer.ts) │
|
|
17
|
+
│ Single-pass AST visitor │
|
|
18
|
+
│ Extracts: signals, memos, effects, props, types │
|
|
19
|
+
└──────────────────────┬──────────────────────────────┘
|
|
20
|
+
↓
|
|
21
|
+
┌──────────────────────┴──────────────────────────────┐
|
|
22
|
+
│ 2. JSX → IR (jsx-to-ir.ts) │
|
|
23
|
+
│ Transforms JSX AST to IR node tree │
|
|
24
|
+
│ Assigns slotIds, detects reactivity │
|
|
25
|
+
└──────────────────────┬──────────────────────────────┘
|
|
26
|
+
↓
|
|
27
|
+
┌────────────┴────────────┐
|
|
28
|
+
↓ ↓
|
|
29
|
+
┌─────────┴─────────┐ ┌───────────┴──────────┐
|
|
30
|
+
│ 3a. Adapter │ │ 3b. IR → Client JS │
|
|
31
|
+
│ IR → Template │ │ ir-to-client-js/ │
|
|
32
|
+
│ (e.g., Hono JSX) │ │ Hydration code │
|
|
33
|
+
└────────────────────┘ └──────────────────────┘
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Entry Points
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// Async — reads files from disk
|
|
40
|
+
compileJSX(entryPath: string, readFile: ReadFileFn, options: CompileOptions): Promise<CompileResult>
|
|
41
|
+
|
|
42
|
+
// Sync — source string input
|
|
43
|
+
compileJSX(source: string, filePath: string, options: CompileOptions): CompileResult
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Both support multi-component files.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Phase 1: Analysis
|
|
51
|
+
|
|
52
|
+
The analyzer (`analyzer.ts`) performs a **single-pass** AST walk using TypeScript's compiler API.
|
|
53
|
+
|
|
54
|
+
### Extracted Data
|
|
55
|
+
|
|
56
|
+
| Category | Data | Example |
|
|
57
|
+
|----------|------|---------|
|
|
58
|
+
| Signals | getter/setter names, initial value, type | `[count, setCount] = createSignal(0)` |
|
|
59
|
+
| Memos | name, computation expression, type | `doubled = createMemo(() => count() * 2)` |
|
|
60
|
+
| Effects | effect body | `createEffect(() => { ... })` |
|
|
61
|
+
| onMounts | callback body | `onMount(() => { ... })` |
|
|
62
|
+
| Props | parameter style, type info, defaults | `(props: ButtonProps)` or `({ label }: Props)` |
|
|
63
|
+
| Imports | source, specifiers | `import { createSignal } from '@barefootjs/client'` |
|
|
64
|
+
| Constants | name, value, dependencies | `const baseClass = 'btn'` |
|
|
65
|
+
| Functions | name, body, parameters | `function handleClick() { ... }` |
|
|
66
|
+
| Types | interfaces, type aliases | `interface ButtonProps { ... }` |
|
|
67
|
+
| JSX Return | the return statement's JSX | `return <button>...</button>` |
|
|
68
|
+
| Conditional Returns | early returns inside `if` blocks | `if (loading) return <Spinner />` |
|
|
69
|
+
|
|
70
|
+
### `"use client"` Validation
|
|
71
|
+
|
|
72
|
+
Files with reactive APIs but no `"use client"` emit **BF001**:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
error[BF001]: 'use client' directive required for components with createSignal
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Props Destructuring Detection
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
// ⚠️ BF043: Destructuring captures values once — may lose reactivity
|
|
82
|
+
function Child({ count }: Props) { ... }
|
|
83
|
+
|
|
84
|
+
// ✅ No warning — direct access maintains reactivity
|
|
85
|
+
function Child(props: Props) { ... }
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Suppress with `// @bf-ignore props-destructuring`.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Phase 2: JSX → IR
|
|
93
|
+
|
|
94
|
+
`jsxToIR` (`jsx-to-ir.ts`) transforms the analyzed JSX AST into the IR node tree.
|
|
95
|
+
|
|
96
|
+
### Reactivity Detection
|
|
97
|
+
|
|
98
|
+
Two-tier strategy to determine if an expression is reactive:
|
|
99
|
+
|
|
100
|
+
1. **TypeChecker path** — Walks the AST and checks each node's type for the `Reactive<T>` brand via `checker.getTypeAtLocation()`. This detects all reactive getters: signals, memos, and library-provided reactive accessors (e.g., `FieldReturn.error`, `FormReturn.isSubmitting`).
|
|
101
|
+
|
|
102
|
+
2. **Regex fallback** — Pattern-matches known signal/memo names and props references. Used when the TypeChecker cannot resolve imported types.
|
|
103
|
+
|
|
104
|
+
Reactive if any match:
|
|
105
|
+
- A `Reactive<T>`-branded type (via TypeChecker)
|
|
106
|
+
- A signal getter: `count()` — regex pattern `\bcount\s*\(`
|
|
107
|
+
- A memo: `doubled()` — same pattern
|
|
108
|
+
- A props reference: `props.value` — per-prop name matching (excludes `children`)
|
|
109
|
+
- A local constant derived from any of the above (taint analysis)
|
|
110
|
+
|
|
111
|
+
### Slot ID Assignment
|
|
112
|
+
|
|
113
|
+
Elements receive a `slotId` when they have:
|
|
114
|
+
|
|
115
|
+
1. Event handlers (`onClick`, `onInput`, etc.)
|
|
116
|
+
2. Dynamic children (reactive expressions, loops, conditionals)
|
|
117
|
+
3. Reactive attributes (`class={expr()}`, `value={signal()}`)
|
|
118
|
+
4. Refs (`ref={callback}`)
|
|
119
|
+
5. Component references (always need initialization)
|
|
120
|
+
|
|
121
|
+
### Filter/Sort Chain Parsing
|
|
122
|
+
|
|
123
|
+
`.filter()` and `.sort()` chains before `.map()` are parsed for template-level evaluation:
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
{todos().filter(t => !t.done).sort((a, b) => a.date - b.date).map(t => (
|
|
127
|
+
<li key={t.id}>{t.name}</li>
|
|
128
|
+
))}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Simple patterns compile for template-level evaluation. Complex patterns trigger **BF021**. See [Error Codes](./error-codes.md#bf021--unsupported-jsx-pattern).
|
|
132
|
+
|
|
133
|
+
### Auto Scope Wrapping
|
|
134
|
+
|
|
135
|
+
If the IR root is a Provider with no wrapper element, the compiler wraps it in `<div style="display:contents">` for scope identification during hydration.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Phase 3a: Template Generation (Adapter)
|
|
140
|
+
|
|
141
|
+
See [Adapter Architecture](../adapters/adapter-architecture.md). Each adapter handles:
|
|
142
|
+
- `renderElement()` — HTML elements with hydration markers
|
|
143
|
+
- `renderExpression()` — Dynamic values in the target template language
|
|
144
|
+
- `renderConditional()` — Template-level conditionals
|
|
145
|
+
- `renderLoop()` — Template-level iteration (with filter/sort if supported)
|
|
146
|
+
- `renderComponent()` — Child component includes
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Phase 3b: Client JS Generation
|
|
151
|
+
|
|
152
|
+
### 1. Element Collection
|
|
153
|
+
|
|
154
|
+
| Category | Description | Example |
|
|
155
|
+
|----------|-------------|---------|
|
|
156
|
+
| `interactiveElements` | Elements with event handlers | `<button onClick={...}>` |
|
|
157
|
+
| `dynamicElements` | Elements with reactive text | `<span>{count()}</span>` |
|
|
158
|
+
| `conditionalElements` | Ternary/logical conditionals | `{open() ? <A/> : <B/>}` |
|
|
159
|
+
| `loopElements` | Array `.map()` loops | `{items().map(...)}` |
|
|
160
|
+
| `refElements` | Elements with ref callbacks | `<input ref={inputRef}>` |
|
|
161
|
+
| `reactiveAttrs` | Elements with reactive attributes | `<div class={cls()}>` |
|
|
162
|
+
| `clientOnlyElements` | `/* @client */` expressions | Skipped during SSR |
|
|
163
|
+
|
|
164
|
+
### 2. Dependency Resolution
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
// "Early" constants — no reactive deps, emitted first
|
|
168
|
+
const baseClass = 'btn'
|
|
169
|
+
const THRESHOLD = 10
|
|
170
|
+
|
|
171
|
+
// "Late" constants — reference signals/memos, emitted after signal creation
|
|
172
|
+
const displayValue = `Count: ${count()}`
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### 3. Controlled Signal Detection
|
|
176
|
+
|
|
177
|
+
When a signal name matches a prop name:
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
function Switch(props: Props) {
|
|
181
|
+
const [checked, setChecked] = createSignal(props.checked ?? false)
|
|
182
|
+
// ^^^^^^^ matches props.checked
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
A sync effect is generated:
|
|
187
|
+
|
|
188
|
+
```javascript
|
|
189
|
+
createEffect(() => {
|
|
190
|
+
const __val = _p.checked
|
|
191
|
+
if (__val !== undefined) setChecked(__val)
|
|
192
|
+
})
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### 4. Code Generation Order
|
|
196
|
+
|
|
197
|
+
```javascript
|
|
198
|
+
import { $, $t, createEffect, createMemo, createSignal, hydrate, onMount } from '@barefootjs/client'
|
|
199
|
+
|
|
200
|
+
export function initCounter(__scope, _p = {}) {
|
|
201
|
+
if (!__scope) return
|
|
202
|
+
|
|
203
|
+
// 1. Early constants (no reactive deps)
|
|
204
|
+
const baseClass = 'counter'
|
|
205
|
+
|
|
206
|
+
// 2. Local functions / handlers (before signals so signal initializers
|
|
207
|
+
// can reference them, e.g., createSignal(toArray(_p.x)))
|
|
208
|
+
const handleClick = () => { setCount(n => n + 1) }
|
|
209
|
+
|
|
210
|
+
// 3. Signals, memos, controlled signal sync, and late constants
|
|
211
|
+
const [count, setCount] = createSignal(_p.initial ?? 0)
|
|
212
|
+
createEffect(() => { // controlled signal sync
|
|
213
|
+
const __val = _p.initial
|
|
214
|
+
if (__val !== undefined) setCount(__val)
|
|
215
|
+
})
|
|
216
|
+
const doubled = createMemo(() => count() * 2)
|
|
217
|
+
|
|
218
|
+
// 4. Element references (always destructured, always returns array)
|
|
219
|
+
// $() — regular elements: querySelector('[bf="id"]') within scope
|
|
220
|
+
// $t() — text nodes: find comment marker <!--bf:id-->
|
|
221
|
+
const [_s3] = $(__scope, 's3')
|
|
222
|
+
const [_s0, _s2] = $t(__scope, 's0', 's2')
|
|
223
|
+
|
|
224
|
+
// 5. Dynamic text updates
|
|
225
|
+
createEffect(() => {
|
|
226
|
+
const __val = count()
|
|
227
|
+
if (_s0 && !__val?.__isSlot) _s0.nodeValue = String(__val ?? '')
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
// 6. Reactive attribute updates
|
|
231
|
+
createEffect(() => {
|
|
232
|
+
if (_s3) { _s3.disabled = !!(count() > 10) }
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
// 7. Conditional updates (insert with comment markers)
|
|
236
|
+
// insert(__scope, 's4', () => isOpen(), trueBranch, falseBranch)
|
|
237
|
+
|
|
238
|
+
// 8. Loop updates (mapArray with reconciliation)
|
|
239
|
+
// mapArray(() => items(), _s5, null, (item, idx, existing) => { ... })
|
|
240
|
+
|
|
241
|
+
// 9. Event handlers
|
|
242
|
+
if (_s3) _s3.addEventListener('click', handleClick)
|
|
243
|
+
|
|
244
|
+
// 10. Reactive prop bindings / child component props
|
|
245
|
+
// 11. Ref callbacks
|
|
246
|
+
// 12. User-defined effects and onMounts
|
|
247
|
+
createEffect(() => { console.log('Count changed:', count()) })
|
|
248
|
+
onMount(() => { console.log('Mounted') })
|
|
249
|
+
|
|
250
|
+
// 13. Provider setup and child component initialization
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Registration: hydrate() registers the component and initializes all
|
|
254
|
+
// instances on the page. Template inclusion depends on two factors:
|
|
255
|
+
// 1. Static template (no signal deps) → always included
|
|
256
|
+
// 2. CSR fallback template → only when used as a child component
|
|
257
|
+
// Top-level-only components with signals skip template to save bytes.
|
|
258
|
+
hydrate('Counter', {
|
|
259
|
+
init: initCounter,
|
|
260
|
+
template: (_p) => `<div><p bf="s1">Count: <!--bf:s0-->${(0)}<!--/--></p>...</div>`
|
|
261
|
+
})
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### 5. Import Detection
|
|
265
|
+
|
|
266
|
+
Only used imports are included:
|
|
267
|
+
|
|
268
|
+
```javascript
|
|
269
|
+
import { $, $t, createEffect, createMemo, createSignal, hydrate, onMount } from '@barefootjs/client'
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### 6. Template Registration
|
|
273
|
+
|
|
274
|
+
**Static template** — No signal-dependent expressions:
|
|
275
|
+
|
|
276
|
+
```javascript
|
|
277
|
+
hydrate('Button', {
|
|
278
|
+
init: initButton,
|
|
279
|
+
template: (_p) => `<button ${(_p.className ?? '') != null ? 'class="' + (_p.className ?? '') + '"' : ''} bf="s0">${_p.children}</button>`
|
|
280
|
+
})
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
**CSR fallback template** — Component has signals but is used as a child in the same file:
|
|
284
|
+
|
|
285
|
+
```javascript
|
|
286
|
+
// StatusBadge is used by Dashboard in the same file → gets CSR fallback
|
|
287
|
+
hydrate('StatusBadge', {
|
|
288
|
+
init: initStatusBadge,
|
|
289
|
+
template: (_p) => `<span bf="s0">${_p.active ? 'on' : 'off'}</span>`
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
// Dashboard is top-level only → no template (saves bytes)
|
|
293
|
+
hydrate('Dashboard', { init: initDashboard })
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
**No template** — Top-level-only components with signals. Hydrated from server HTML only.
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Multi-Component Files
|
|
301
|
+
|
|
302
|
+
Two-pass approach for multi-component files:
|
|
303
|
+
|
|
304
|
+
1. **Pass 1** — Detect exports, analyze, and generate IR for each
|
|
305
|
+
2. **Between passes** — Build `usedAsChild` set via `collectComponentNamesFromIR()`
|
|
306
|
+
3. **Pass 2** — Generate templates and client JS; only child components get CSR fallback templates
|
|
307
|
+
4. Merge templates (deduplicate imports/types) and client JS (combine imports)
|
|
308
|
+
|
|
309
|
+
```tsx
|
|
310
|
+
// Both compiled from the same file
|
|
311
|
+
export function Button(props: ButtonProps) { ... }
|
|
312
|
+
export function IconButton(props: IconButtonProps) { ... }
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Debugging Tips
|
|
318
|
+
|
|
319
|
+
### View the IR
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
const result = compileJSX(source, 'file.tsx', { adapter })
|
|
323
|
+
console.log(JSON.stringify(result.ir, null, 2))
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### View generated client JS
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
console.log(result.clientJs)
|
|
330
|
+
```
|
|
331
|
+
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Error Codes Reference
|
|
3
|
+
description: BF-prefixed compiler error codes with explanations and fixes.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Error Codes Reference
|
|
7
|
+
|
|
8
|
+
Errors follow the format `BF` + 3-digit code with source location and fix suggestions.
|
|
9
|
+
|
|
10
|
+
## Format
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
error[BF001]: 'use client' directive required for components with createSignal
|
|
14
|
+
|
|
15
|
+
--> src/components/Counter.tsx:3:1
|
|
16
|
+
|
|
|
17
|
+
3 | import { createSignal } from '@barefootjs/client'
|
|
18
|
+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
19
|
+
|
|
|
20
|
+
= help: Add 'use client' at the top of the file
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Directive Errors (BF001–BF003)
|
|
26
|
+
|
|
27
|
+
### BF001 — Missing `"use client"` Directive
|
|
28
|
+
|
|
29
|
+
**Trigger:** Reactive APIs used without `"use client"`.
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
// ❌ BF001
|
|
33
|
+
import { createSignal } from '@barefootjs/client'
|
|
34
|
+
export function Counter() {
|
|
35
|
+
const [count, setCount] = createSignal(0)
|
|
36
|
+
return <button onClick={() => setCount(n => n + 1)}>{count()}</button>
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Fix:**
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
// ✅ Fixed
|
|
44
|
+
"use client"
|
|
45
|
+
import { createSignal } from '@barefootjs/client'
|
|
46
|
+
export function Counter() { ... }
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### BF003 — Client Component Importing Server Component
|
|
50
|
+
|
|
51
|
+
**Trigger:** Client component imports from a file without `"use client"`.
|
|
52
|
+
|
|
53
|
+
**Fix:** Add `"use client"` to the imported file, or import only types/constants.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Signal Errors (BF011)
|
|
58
|
+
|
|
59
|
+
### BF011 — Module-Level Reactive Declaration
|
|
60
|
+
|
|
61
|
+
**Trigger:** A `createSignal` or `createMemo` call at module scope without a leading `/* @client */` directive.
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
'use client'
|
|
65
|
+
import { createSignal } from '@barefootjs/client'
|
|
66
|
+
// ❌ BF011 — module-level signal without opt-in
|
|
67
|
+
const [count, setCount] = createSignal(0)
|
|
68
|
+
export function Counter() {
|
|
69
|
+
return <button onClick={() => setCount(count() + 1)}>{count()}</button>
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Fix (option A):** Move the declaration inside the component function so each mount gets its own state.
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
'use client'
|
|
77
|
+
import { createSignal } from '@barefootjs/client'
|
|
78
|
+
|
|
79
|
+
export function Counter() {
|
|
80
|
+
const [count, setCount] = createSignal(0)
|
|
81
|
+
return <button onClick={() => setCount(count() + 1)}>{count()}</button>
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Fix (option B):** Prefix the declaration with `/* @client */` to opt into client-only module-scope state. The signal is emitted at module scope in the client bundle and SSR renders a placeholder for any reference. Intended for "global signal" / "store" patterns shared across components.
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
'use client'
|
|
89
|
+
import { createSignal } from '@barefootjs/client'
|
|
90
|
+
|
|
91
|
+
/* @client */
|
|
92
|
+
const [count, setCount] = createSignal(0)
|
|
93
|
+
|
|
94
|
+
export function Counter() {
|
|
95
|
+
return <button onClick={() => setCount(count() + 1)}>{count()}</button>
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## JSX Errors (BF021–BF023)
|
|
102
|
+
|
|
103
|
+
### BF021 — Unsupported JSX Pattern
|
|
104
|
+
|
|
105
|
+
**Trigger:** Array method chain before `.map()` cannot compile to SSR template.
|
|
106
|
+
|
|
107
|
+
#### SSR-Compatible Chains
|
|
108
|
+
|
|
109
|
+
- `.filter().map()`
|
|
110
|
+
- `.sort().map()` / `.toSorted().map()`
|
|
111
|
+
- `.filter().sort().map()`
|
|
112
|
+
- `.sort().filter().map()`
|
|
113
|
+
|
|
114
|
+
Other chains (`.reduce()`, `.slice()`, `.flatMap()`) fall back to client-side evaluation.
|
|
115
|
+
|
|
116
|
+
#### filter: Supported Predicates
|
|
117
|
+
|
|
118
|
+
- Property access: `t.done`, `t.price`
|
|
119
|
+
- Literals: `'active'`, `5`, `true`
|
|
120
|
+
- Comparison: `===`, `!==`, `>`, `<`, `>=`, `<=`
|
|
121
|
+
- Arithmetic: `+`, `-`, `*`, `/`, `%`
|
|
122
|
+
- Logical: `&&`, `||`, `!`
|
|
123
|
+
- Ternary: `cond ? a : b`
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
// ✅ SSR-compilable
|
|
127
|
+
{items().filter(t => !t.done).map(t => <li>{t.name}</li>)}
|
|
128
|
+
{items().filter(t => t.price > 100 && t.active).map(t => <li>{t.name}</li>)}
|
|
129
|
+
|
|
130
|
+
// ❌ BF021 — typeof, function calls, nested higher-order methods are not supported
|
|
131
|
+
{items().filter(t => typeof t === 'string').map(...)}
|
|
132
|
+
{items().filter(t => customFn(t)).map(...)}
|
|
133
|
+
{items().filter(t => t.tags.some(tag => tag.featured)).map(...)}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### sort: Supported Comparators
|
|
137
|
+
|
|
138
|
+
Simple subtraction: `(a, b) => a.field - b.field`:
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
// ✅ SSR-compilable
|
|
142
|
+
{items().sort((a, b) => a.price - b.price).map(...)} // ascending
|
|
143
|
+
{items().toSorted((a, b) => b.date - a.date).map(...)} // descending
|
|
144
|
+
|
|
145
|
+
// ❌ BF021 — block bodies, localeCompare, ternary operators, etc. are not supported
|
|
146
|
+
{items().sort((a, b) => { return a.price - b.price }).map(...)}
|
|
147
|
+
{items().sort((a, b) => a.name.localeCompare(b.name)).map(...)}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
#### Workaround
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
{/* @client */ todos().filter(t => t.items.some(i => i.done)).map(t => (
|
|
154
|
+
<li>{t.name}</li>
|
|
155
|
+
))}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### BF023 — Missing Key in List
|
|
159
|
+
|
|
160
|
+
**Trigger:** `.map()` loop without `key` prop.
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
// ❌ BF023
|
|
164
|
+
{items().map(item => <li>{item.name}</li>)}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Fix:**
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
// ✅ Add key
|
|
171
|
+
{items().map(item => <li key={item.id}>{item.name}</li>)}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Component Errors (BF043–BF044)
|
|
177
|
+
|
|
178
|
+
### BF043 — Props Destructuring (Warning)
|
|
179
|
+
|
|
180
|
+
**Trigger:** Props destructured in function parameter.
|
|
181
|
+
|
|
182
|
+
```tsx
|
|
183
|
+
// ⚠️ BF043
|
|
184
|
+
function Child({ count }: Props) {
|
|
185
|
+
return <span>{count}</span> // count is captured once
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
warning[BF043]: Destructuring props in function parameters captures values once.
|
|
191
|
+
= help: Use `props.count` for reactive access, or suppress with // @bf-ignore props-destructuring
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Fix options:**
|
|
195
|
+
|
|
196
|
+
1. Use direct props access:
|
|
197
|
+
|
|
198
|
+
```tsx
|
|
199
|
+
function Child(props: Props) {
|
|
200
|
+
return <span>{props.count}</span> // Reactive
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
2. Suppress if intentional (static initial value):
|
|
205
|
+
|
|
206
|
+
```tsx
|
|
207
|
+
// @bf-ignore props-destructuring
|
|
208
|
+
function Child({ initialCount }: Props) {
|
|
209
|
+
const [count, setCount] = createSignal(initialCount)
|
|
210
|
+
return <span>{count()}</span>
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### BF044 — Signal/Memo Getter Not Called
|
|
215
|
+
|
|
216
|
+
**Trigger:** Signal/memo getter passed without calling it.
|
|
217
|
+
|
|
218
|
+
```tsx
|
|
219
|
+
// ❌ BF044
|
|
220
|
+
<Child count={count} /> // Passing getter function, not the value
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**Fix:**
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
// ✅ Fixed
|
|
227
|
+
<Child count={count()} />
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Suppressing Warnings
|
|
233
|
+
|
|
234
|
+
Suppress with `@bf-ignore`:
|
|
235
|
+
|
|
236
|
+
```tsx
|
|
237
|
+
// @bf-ignore props-destructuring
|
|
238
|
+
function Component({ checked }: Props) {
|
|
239
|
+
// Warning suppressed
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**Available rules:**
|
|
244
|
+
|
|
245
|
+
| Rule ID | Error Code | Description |
|
|
246
|
+
|---------|------------|-------------|
|
|
247
|
+
| `props-destructuring` | BF043 | Props destructuring in function parameters |
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## Error Code Quick Reference
|
|
252
|
+
|
|
253
|
+
| Code | Severity | Description |
|
|
254
|
+
|------|----------|-------------|
|
|
255
|
+
| BF001 | Error | Missing `"use client"` directive |
|
|
256
|
+
| BF003 | Error | Client component importing server component |
|
|
257
|
+
| BF011 | Error | Module-level reactive declaration without `/* @client */` |
|
|
258
|
+
| BF021 | Error | Unsupported JSX pattern for SSR |
|
|
259
|
+
| BF023 | Error | Missing key in list |
|
|
260
|
+
| BF043 | Warning | Props destructuring breaks reactivity |
|
|
261
|
+
| BF044 | Error | Signal/memo getter passed without calling it |
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: IR Schema Reference
|
|
3
|
+
description: JSON tree structure of the Intermediate Representation consumed by adapters and client-JS generation.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# IR Schema Reference
|
|
7
|
+
|
|
8
|
+
The IR is a JSON tree between JSX parsing and output generation. Adapters consume IR without knowledge of the original JSX syntax.
|
|
9
|
+
|
|
10
|
+
## Pipeline Position
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
JSX Source → [Phase 1: analyzer + jsx-to-ir] → IR → [Phase 2a: adapter] → Template
|
|
14
|
+
→ [Phase 2b: ir-to-client-js] → Client JS
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Node Types
|
|
18
|
+
|
|
19
|
+
Defined in [`packages/jsx/src/types.ts`](../../../packages/jsx/src/types.ts):
|
|
20
|
+
|
|
21
|
+
| Type | Description |
|
|
22
|
+
|------|-------------|
|
|
23
|
+
| `IRElement` | HTML/SVG element |
|
|
24
|
+
| `IRText` | Static text |
|
|
25
|
+
| `IRExpression` | Dynamic expression (`{braces}`) |
|
|
26
|
+
| `IRConditional` | Branching via ternary or logical expressions |
|
|
27
|
+
| `IRLoop` | List rendering via `.map()` (including filter/sort) |
|
|
28
|
+
| `IRComponent` | Child component reference |
|
|
29
|
+
| `IRFragment` | JSX fragment (`<>...</>`) |
|
|
30
|
+
| `IRIfStatement` | Early return within a component body |
|
|
31
|
+
| `IRProvider` | Context Provider |
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Hydration Markers
|
|
36
|
+
|
|
37
|
+
`slotId` and `needsScope` map to HTML attributes:
|
|
38
|
+
|
|
39
|
+
| IR Field | HTML Output | Purpose |
|
|
40
|
+
|----------|------------|---------|
|
|
41
|
+
| `needsScope: true` | `bf-s="ComponentName"` | Component root boundary |
|
|
42
|
+
| `slotId: "0"` | `bf="0"` | Reference for interactive elements |
|
|
43
|
+
| Conditional `slotId` | `bf-c="1"` | Anchor for conditional branches |
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Debugging
|
|
49
|
+
|
|
50
|
+
Pass `outputIR: true` to inspect the IR:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { compileJSX } from '@barefootjs/jsx'
|
|
54
|
+
|
|
55
|
+
const result = compileJSX(source, 'Counter.tsx', {
|
|
56
|
+
adapter: new HonoAdapter(),
|
|
57
|
+
outputIR: true,
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
// result.ir contains the full ComponentIR
|
|
61
|
+
console.log(JSON.stringify(result.ir, null, 2))
|
|
62
|
+
|
|
63
|
+
// result.additionalFiles includes the *.ir.json file
|
|
64
|
+
// e.g., { path: 'Counter.ir.json', content: '...' }
|
|
65
|
+
```
|