@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.
Files changed (45) hide show
  1. package/dist/docs/core/README.md +84 -0
  2. package/dist/docs/core/adapters/adapter-architecture.md +262 -0
  3. package/dist/docs/core/adapters/csr.md +78 -0
  4. package/dist/docs/core/adapters/custom-adapter.md +357 -0
  5. package/dist/docs/core/adapters/go-template-adapter.md +269 -0
  6. package/dist/docs/core/adapters/hono-adapter.md +199 -0
  7. package/dist/docs/core/adapters.md +37 -0
  8. package/dist/docs/core/advanced/code-splitting.md +142 -0
  9. package/dist/docs/core/advanced/compiler-internals.md +331 -0
  10. package/dist/docs/core/advanced/error-codes.md +261 -0
  11. package/dist/docs/core/advanced/ir-schema.md +65 -0
  12. package/dist/docs/core/advanced/performance.md +115 -0
  13. package/dist/docs/core/advanced/xyflow-browser-bundle.md +69 -0
  14. package/dist/docs/core/advanced.md +11 -0
  15. package/dist/docs/core/components/children-slots.md +150 -0
  16. package/dist/docs/core/components/component-authoring.md +207 -0
  17. package/dist/docs/core/components/context-api.md +236 -0
  18. package/dist/docs/core/components/portals.md +192 -0
  19. package/dist/docs/core/components/props-type-safety.md +97 -0
  20. package/dist/docs/core/components/styling.md +37 -0
  21. package/dist/docs/core/components.md +19 -0
  22. package/dist/docs/core/core-concepts/ai-native.md +83 -0
  23. package/dist/docs/core/core-concepts/backend-freedom.md +31 -0
  24. package/dist/docs/core/core-concepts/how-it-works.md +147 -0
  25. package/dist/docs/core/core-concepts/mpa-style.md +36 -0
  26. package/dist/docs/core/core-concepts/reactivity.md +35 -0
  27. package/dist/docs/core/core-concepts.md +28 -0
  28. package/dist/docs/core/introduction.md +87 -0
  29. package/dist/docs/core/llms.txt +53 -0
  30. package/dist/docs/core/quick-start.mdx +160 -0
  31. package/dist/docs/core/reactivity/create-effect.md +125 -0
  32. package/dist/docs/core/reactivity/create-memo.md +91 -0
  33. package/dist/docs/core/reactivity/create-signal.md +128 -0
  34. package/dist/docs/core/reactivity/on-cleanup.md +67 -0
  35. package/dist/docs/core/reactivity/on-mount.md +70 -0
  36. package/dist/docs/core/reactivity/props-reactivity.md +91 -0
  37. package/dist/docs/core/reactivity/untrack.md +69 -0
  38. package/dist/docs/core/reactivity.md +28 -0
  39. package/dist/docs/core/rendering/client-directive.md +82 -0
  40. package/dist/docs/core/rendering/fragment.md +43 -0
  41. package/dist/docs/core/rendering/jsx-compatibility.md +163 -0
  42. package/dist/docs/core/rendering.md +14 -0
  43. package/dist/index.js +25490 -0
  44. package/dist/tokens.json +74 -0
  45. package/package.json +37 -0
@@ -0,0 +1,357 @@
1
+ ---
2
+ title: Writing a Custom Adapter
3
+ description: Step-by-step guide to building a custom adapter using the TestAdapter as a reference.
4
+ ---
5
+
6
+ # Writing a Custom Adapter
7
+
8
+ Build a custom adapter using the `TestAdapter` (`packages/jsx/src/adapters/test-adapter.ts`) as reference — a minimal working adapter that generates JSX output.
9
+
10
+
11
+ ## Step 1: Implement `TemplateAdapter`
12
+
13
+ Extend `BaseAdapter` or implement `TemplateAdapter` directly:
14
+
15
+ ```typescript
16
+ import type {
17
+ ComponentIR,
18
+ IRNode,
19
+ IRElement,
20
+ IRText,
21
+ IRExpression,
22
+ IRConditional,
23
+ IRLoop,
24
+ IRComponent,
25
+ IRFragment,
26
+ ParamInfo,
27
+ } from '../types'
28
+ import { type AdapterOutput, BaseAdapter } from './interface'
29
+
30
+ export class TestAdapter extends BaseAdapter {
31
+ name = 'test'
32
+ extension = '.test.tsx'
33
+
34
+ private componentName: string = ''
35
+
36
+ generate(ir: ComponentIR): AdapterOutput {
37
+ this.componentName = ir.metadata.componentName
38
+
39
+ const imports = this.generateImports(ir)
40
+ const types = this.generateTypes(ir)
41
+ const component = this.generateComponent(ir)
42
+
43
+ const template = [imports, types, component].filter(Boolean).join('\n\n')
44
+
45
+ return {
46
+ template,
47
+ types: types || undefined,
48
+ extension: this.extension,
49
+ }
50
+ }
51
+
52
+ // ... node rendering methods (see below)
53
+ }
54
+ ```
55
+
56
+ ## Step 2: Implement `renderNode()`
57
+
58
+ Route each IR node to the correct rendering method:
59
+
60
+ ```typescript
61
+ renderNode(node: IRNode): string {
62
+ switch (node.type) {
63
+ case 'element': return this.renderElement(node)
64
+ case 'text': return (node as IRText).value
65
+ case 'expression': return this.renderExpression(node)
66
+ case 'conditional': return this.renderConditional(node)
67
+ case 'loop': return this.renderLoop(node)
68
+ case 'component': return this.renderComponent(node)
69
+ case 'fragment': return this.renderChildren((node as IRFragment).children)
70
+ case 'slot': return '{children}'
71
+ default: return ''
72
+ }
73
+ }
74
+ ```
75
+
76
+ ## Step 3: Implement Element Rendering
77
+
78
+ Render the tag, attributes, hydration markers, and children:
79
+
80
+ ```typescript
81
+ renderElement(element: IRElement): string {
82
+ const tag = element.tag
83
+ const attrs = this.renderAttributes(element)
84
+ const children = this.renderChildren(element.children)
85
+
86
+ let hydrationAttrs = ''
87
+ if (element.needsScope) {
88
+ hydrationAttrs += ' bf-s={__scopeId}'
89
+ }
90
+ if (element.slotId) {
91
+ hydrationAttrs += ` bf="${element.slotId}"`
92
+ }
93
+
94
+ if (children) {
95
+ return `<${tag}${attrs}${hydrationAttrs}>${children}</${tag}>`
96
+ } else {
97
+ return `<${tag}${attrs}${hydrationAttrs} />`
98
+ }
99
+ }
100
+ ```
101
+
102
+ ### Attributes
103
+
104
+ ```typescript
105
+ private renderAttributes(element: IRElement): string {
106
+ const parts: string[] = []
107
+
108
+ for (const attr of element.attrs) {
109
+ const attrName = attr.name === 'class' ? 'className' : attr.name
110
+
111
+ if (attr.name === '...') {
112
+ parts.push(`{...${attr.value}}`)
113
+ } else if (attr.value === null) {
114
+ parts.push(attrName) // Boolean attribute
115
+ } else if (attr.dynamic) {
116
+ parts.push(`${attrName}={${attr.value}}`)
117
+ } else {
118
+ parts.push(`${attrName}="${attr.value}"`)
119
+ }
120
+ }
121
+
122
+ // Event handlers — render as no-op stubs for SSR
123
+ for (const event of element.events) {
124
+ const handlerName = `on${event.name.charAt(0).toUpperCase()}${event.name.slice(1)}`
125
+ parts.push(`${handlerName}={() => {}}`)
126
+ }
127
+
128
+ return parts.length > 0 ? ' ' + parts.join(' ') : ''
129
+ }
130
+ ```
131
+
132
+ The TestAdapter renders event handlers as no-op stubs for JSX. Non-JSX adapters omit them — handlers exist only in client JS.
133
+
134
+
135
+ ## Step 4: Implement Expression Rendering
136
+
137
+ Reactive expressions with a `slotId` get a hydration marker for client JS updates:
138
+
139
+ ```typescript
140
+ renderExpression(expr: IRExpression): string {
141
+ if (expr.expr === 'null' || expr.expr === 'undefined') {
142
+ return 'null'
143
+ }
144
+ if (expr.reactive && expr.slotId) {
145
+ return `<span bf="${expr.slotId}">{${expr.expr}}</span>`
146
+ }
147
+ return `{${expr.expr}}`
148
+ }
149
+ ```
150
+
151
+ Non-JSX adapters convert expressions to the target language (e.g., `count()` → `{{.Count}}`).
152
+
153
+
154
+ ## Step 5: Implement Conditional Rendering
155
+
156
+ Ternaries pass through in JSX adapters:
157
+
158
+ ```typescript
159
+ renderConditional(cond: IRConditional): string {
160
+ const whenTrue = this.renderNode(cond.whenTrue)
161
+ const whenFalse = this.renderNode(cond.whenFalse)
162
+
163
+ return `{${cond.condition} ? ${whenTrue} : ${whenFalse || 'null'}}`
164
+ }
165
+ ```
166
+
167
+ **Input (JSX):**
168
+ ```tsx
169
+ {isActive ? <span>Active</span> : <span>Inactive</span>}
170
+ ```
171
+
172
+ **Output (TestAdapter):**
173
+ ```tsx
174
+ {isActive ? <span>Active</span> : <span>Inactive</span>}
175
+ ```
176
+
177
+ Non-JSX adapters translate to the target conditional syntax (e.g., `{{if .IsActive}}...{{else}}...{{end}}`).
178
+
179
+
180
+ ## Step 6: Implement Loop Rendering
181
+
182
+ `.map()` calls stay as JSX:
183
+
184
+ ```typescript
185
+ renderLoop(loop: IRLoop): string {
186
+ const indexParam = loop.index ? `, ${loop.index}` : ''
187
+ const children = this.renderChildren(loop.children)
188
+
189
+ return `{${loop.array}.map((${loop.param}${indexParam}) => ${children})}`
190
+ }
191
+ ```
192
+
193
+ **Input (JSX):**
194
+ ```tsx
195
+ {items.map(item => <li key={item.id}>{item.name}</li>)}
196
+ ```
197
+
198
+ **Output (TestAdapter):**
199
+ ```tsx
200
+ {items.map((item) => <li key={item.id}>{item.name}</li>)}
201
+ ```
202
+
203
+ Non-JSX adapters translate to the target iteration syntax (e.g., `{{range .Items}}...{{end}}`).
204
+
205
+
206
+ ## Step 7: Implement Component Rendering
207
+
208
+ Pass the parent's scope ID to nested components:
209
+
210
+ ```typescript
211
+ renderComponent(comp: IRComponent): string {
212
+ const props = this.renderComponentProps(comp)
213
+ const children = this.renderChildren(comp.children)
214
+
215
+ const scopeAttr = ' __bfScope={__scopeId}'
216
+
217
+ if (children) {
218
+ return `<${comp.name}${props}${scopeAttr}>${children}</${comp.name}>`
219
+ } else {
220
+ return `<${comp.name}${props}${scopeAttr} />`
221
+ }
222
+ }
223
+ ```
224
+
225
+ ## Step 8: Implement Hydration Markers
226
+
227
+ ```typescript
228
+ renderScopeMarker(instanceIdExpr: string): string {
229
+ return `bf-s={${instanceIdExpr}}`
230
+ }
231
+
232
+ renderSlotMarker(slotId: string): string {
233
+ return `bf="${slotId}"`
234
+ }
235
+
236
+ renderCondMarker(condId: string): string {
237
+ return `bf-c="${condId}"`
238
+ }
239
+ ```
240
+
241
+ ## Step 9: Generate Signal Initializers
242
+
243
+ Signal getters return initial values during SSR via stub functions:
244
+
245
+ ```typescript
246
+ private generateSignalInitializers(ir: ComponentIR): string {
247
+ const lines: string[] = []
248
+
249
+ for (const signal of ir.metadata.signals) {
250
+ lines.push(` const ${signal.getter} = () => ${signal.initialValue}`)
251
+ lines.push(` const ${signal.setter} = () => {}`)
252
+ }
253
+
254
+ for (const memo of ir.metadata.memos) {
255
+ lines.push(` const ${memo.name} = ${memo.computation}`)
256
+ }
257
+
258
+ return lines.join('\n')
259
+ }
260
+ ```
261
+
262
+ `const [count, setCount] = createSignal(initial)` becomes:
263
+ ```typescript
264
+ const count = () => initial // getter returns initial value
265
+ const setCount = () => {} // setter is a no-op on the server
266
+ ```
267
+
268
+ ## Optional: Type Generation
269
+
270
+ For typed backends, implement `generateTypes()`:
271
+
272
+ ```typescript
273
+ generateTypes(ir: ComponentIR): string | null {
274
+ const lines: string[] = []
275
+
276
+ const propsTypeName = ir.metadata.propsType?.raw
277
+ if (propsTypeName) {
278
+ lines.push(`type ${this.componentName}PropsWithHydration = ${propsTypeName} & {`)
279
+ lines.push(' __instanceId?: string')
280
+ lines.push(' __bfScope?: string')
281
+ lines.push('}')
282
+ }
283
+
284
+ return lines.length > 0 ? lines.join('\n') : null
285
+ }
286
+ ```
287
+
288
+ For dynamically-typed backends, return `null`.
289
+
290
+
291
+ ## Testing
292
+
293
+ ```typescript
294
+ import { compileJsxToIR } from '@barefootjs/jsx'
295
+ import { TestAdapter } from './test-adapter'
296
+
297
+ const source = `
298
+ "use client"
299
+ import { createSignal } from '@barefootjs/client'
300
+
301
+ export function Counter({ initial = 0 }: { initial?: number }) {
302
+ const [count, setCount] = createSignal(initial)
303
+ return (
304
+ <div>
305
+ <p>{count()}</p>
306
+ <button onClick={() => setCount(n => n + 1)}>+1</button>
307
+ </div>
308
+ )
309
+ }
310
+ `
311
+
312
+ const ir = compileJsxToIR(source)
313
+ const adapter = new TestAdapter()
314
+ const output = adapter.generate(ir)
315
+
316
+ console.log(output.template)
317
+ // export function Counter({ __instanceId, ... }) {
318
+ // const __scopeId = ...
319
+ // const count = () => 0
320
+ //
321
+ // return (
322
+ // <div bf-s={__scopeId}>
323
+ // <span bf="s1">Count: {bfText("s0")}{count()}{bfTextEnd()}</span>
324
+ // <button bf="s2" onClick={() => {}}>+1</button>
325
+ // </div>
326
+ // )
327
+ // }
328
+ ```
329
+
330
+
331
+ ## Checklist
332
+
333
+ Ensure you handle:
334
+
335
+ - [ ] All IR node types (`element`, `text`, `expression`, `conditional`, `loop`, `component`, `fragment`, `slot`)
336
+ - [ ] Hydration markers (`bf-s`, `bf`, `bf-c`) on interactive elements
337
+ - [ ] Static vs. dynamic attributes
338
+ - [ ] Boolean HTML attributes (`disabled`, `checked`, etc.)
339
+ - [ ] Spread attributes (`{...props}`)
340
+ - [ ] Signal getter stubs for server-side initial values
341
+ - [ ] Nested component scope passing
342
+ - [ ] Props serialization (`bf-p` attribute) for client hydration
343
+ - [ ] Script registration for client JS loading
344
+ - [ ] `/* @client */` directive (skip client-only expressions server-side)
345
+
346
+ Production adapters also handle:
347
+
348
+ - [ ] Void HTML elements (`<input>`, `<br>`, etc.) — no closing tag
349
+ - [ ] Expression translation to the target template language
350
+ - [ ] Type generation for typed backend languages
351
+ - [ ] `if-statement` and `provider` IR node types
352
+
353
+ ### Reference Implementations
354
+
355
+ - **TestAdapter** (`packages/jsx/src/adapters/test-adapter.ts`) — Minimal working adapter used throughout this guide
356
+ - **HonoAdapter** (`packages/adapter-hono/src/adapter/hono-adapter.ts`) — Production JSX-to-JSX adapter with script collection via Hono's request context
357
+ - **GoTemplateAdapter** (`packages/adapter-go-template/src/adapter/go-template-adapter.ts`) — Production adapter with expression translation, type generation, and array method mapping
@@ -0,0 +1,269 @@
1
+ ---
2
+ title: Go Template Adapter
3
+ description: Generate Go html/template files and type definitions from the compiler's IR.
4
+ ---
5
+
6
+ # Go Template Adapter
7
+
8
+ Generates Go `html/template` files (`.tmpl`) and type definitions (`_types.go`) from the compiler's IR.
9
+
10
+ ```
11
+ npm install @barefootjs/go-template
12
+ ```
13
+
14
+
15
+ ## Basic Usage
16
+
17
+ ```typescript
18
+ import { compile } from '@barefootjs/jsx'
19
+ import { GoTemplateAdapter } from '@barefootjs/go-template'
20
+
21
+ const adapter = new GoTemplateAdapter()
22
+ const result = compile(source, { adapter })
23
+
24
+ // result.template → .tmpl file content
25
+ // result.types → _types.go file content
26
+ // result.clientJs → .client.js file content
27
+ ```
28
+
29
+
30
+ ## Options
31
+
32
+ ```typescript
33
+ const adapter = new GoTemplateAdapter({
34
+ packageName: 'views',
35
+ })
36
+ ```
37
+
38
+ | Option | Type | Default | Description |
39
+ |--------|------|---------|-------------|
40
+ | `packageName` | `string` | `'components'` | Go package name for generated type files |
41
+
42
+
43
+ ## Output Format
44
+
45
+ ### Server Component
46
+
47
+ **Source:**
48
+
49
+ ```tsx
50
+ export function Greeting(props: { name: string }) {
51
+ return <p>Hello, {props.name}!</p>
52
+ }
53
+ ```
54
+
55
+ **Output (.tmpl):**
56
+
57
+ ```go-template
58
+ {{define "Greeting"}}
59
+ <p bf-s="{{bfScopeAttr .}}" {{bfPropsAttr .}} bf="s1">
60
+ Hello, {{bfTextStart "s0"}}{{.Name}}{{bfTextEnd}}!
61
+ </p>
62
+ {{end}}
63
+ ```
64
+
65
+ - `bfScopeAttr` — generates the `bf-s` scope ID
66
+ - `bfPropsAttr` — serializes props for client hydration
67
+ - `bfTextStart` / `bfTextEnd` — text node markers (rendered as `<!--bf:s0-->...<!--/-->`)
68
+
69
+ ### Client Component
70
+
71
+ **Source:**
72
+
73
+ ```tsx
74
+ "use client"
75
+ import { createSignal } from '@barefootjs/client'
76
+
77
+ export function Counter(props: { initial?: number }) {
78
+ const [count, setCount] = createSignal(props.initial ?? 0)
79
+
80
+ return (
81
+ <div>
82
+ <span>Count: {count()}</span>
83
+ <button onClick={() => setCount(n => n + 1)}>+1</button>
84
+ </div>
85
+ )
86
+ }
87
+ ```
88
+
89
+ **Output (.tmpl):**
90
+
91
+ ```go-template
92
+ {{define "Counter"}}
93
+ {{if .Scripts}}{{.Scripts.Register "/static/client/barefoot.js"}}{{.Scripts.Register "/static/client/Counter.client.js"}}{{end}}
94
+ <div bf-s="{{bfScopeAttr .}}" {{bfPropsAttr .}}>
95
+ <span bf="s1">Count: {{bfTextStart "s0"}}{{.Count}}{{bfTextEnd}}</span>
96
+ <button bf="s2">+1</button>
97
+ </div>
98
+ {{end}}
99
+ ```
100
+
101
+
102
+ ## Expression Translation
103
+
104
+ ### Property Access
105
+
106
+ ```
107
+ props.name → .Name
108
+ props.user.email → .User.Email
109
+ ```
110
+
111
+ Field names are automatically capitalized to follow Go conventions.
112
+
113
+ ### Comparisons
114
+
115
+ | JavaScript | Go Template |
116
+ |-----------|-------------|
117
+ | `a === b` | `eq .A .B` |
118
+ | `a !== b` | `ne .A .B` |
119
+ | `a > b` | `gt .A .B` |
120
+ | `a < b` | `lt .A .B` |
121
+ | `a >= b` | `ge .A .B` |
122
+ | `a <= b` | `le .A .B` |
123
+
124
+ ### Arithmetic
125
+
126
+ | JavaScript | Go Template |
127
+ |-----------|-------------|
128
+ | `a + b` | `bf_add .A .B` |
129
+ | `a - b` | `bf_sub .A .B` |
130
+ | `a * b` | `bf_mul .A .B` |
131
+ | `a / b` | `bf_div .A .B` |
132
+
133
+ ### Logical Operators
134
+
135
+ | JavaScript | Go Template |
136
+ |-----------|-------------|
137
+ | `a && b` | `and .A .B` |
138
+ | `a \|\| b` | `or .A .B` |
139
+ | `!a` | `not .A` |
140
+
141
+
142
+ ## Array Methods
143
+
144
+ ### `.map()`
145
+
146
+ ```tsx
147
+ {items().map(item => <li key={item}>{item}</li>)}
148
+ ```
149
+
150
+ ```go-template
151
+ {{range $_, $item := .Items}}
152
+ <li>{{bfTextStart "s0"}}{{.Item}}{{bfTextEnd}}</li>
153
+ {{end}}
154
+ ```
155
+
156
+ ### `.filter().map()`
157
+
158
+ ```tsx
159
+ {items().filter(item => item.active).map(item => <li key={item.id}>{item.name}</li>)}
160
+ ```
161
+
162
+ ```go-template
163
+ {{range $_, $item := .Items}}{{if .Active}}
164
+ <li>{{bfTextStart "s0"}}{{.Item.Name}}{{bfTextEnd}}</li>
165
+ {{end}}{{end}}
166
+ ```
167
+
168
+ For complex filter predicates, the adapter generates template block functions.
169
+
170
+ ### `.sort().map()` / `.toSorted().map()`
171
+
172
+ ```tsx
173
+ {items.toSorted((a, b) => a.priority - b.priority).map(t => <li key={t.id}>{t.name}</li>)}
174
+ ```
175
+
176
+ ```go-template
177
+ {{range bf_sort .Items "Priority" "asc"}}
178
+ <li>{{bfTextStart "s0"}}{{.Name}}{{bfTextEnd}}</li>
179
+ {{end}}
180
+ ```
181
+
182
+ ### Other Array Methods
183
+
184
+ | JavaScript | Go Template |
185
+ |-----------|-------------|
186
+ | `arr.find(fn)` | `bf_find` |
187
+ | `arr.findIndex(fn)` | `bf_find_index` |
188
+ | `arr.every(fn)` | `bf_every` |
189
+ | `arr.some(fn)` | `bf_some` |
190
+ | `arr.length` | `len .Arr` |
191
+
192
+
193
+ ## Type Generation
194
+
195
+ For each component, the adapter generates:
196
+
197
+ 1. **Input struct** — external API
198
+ 2. **Props struct** — internal representation (includes hydration fields)
199
+ 3. **Constructor** — `New{Component}Props()` with defaults
200
+
201
+ ### Type Mapping
202
+
203
+ | TypeScript | Go |
204
+ |-----------|-----|
205
+ | `string` | `string` |
206
+ | `number` | `int` (or `float64` for decimals) |
207
+ | `boolean` | `bool` |
208
+ | `T[]` | `[]T` |
209
+ | `T \| undefined` | Pointer type `*T` or zero value |
210
+ | Object type | Named struct |
211
+
212
+ ### Nested Components
213
+
214
+ ```tsx
215
+ export function TodoList({ items }: { items: TodoItem[] }) {
216
+ return (
217
+ <ul>
218
+ {items.map(item => <TodoItem key={item.id} {...item} />)}
219
+ </ul>
220
+ )
221
+ }
222
+ ```
223
+
224
+ ## Conditional Rendering
225
+
226
+ Ternaries become `{{if}}...{{else}}...{{end}}`:
227
+
228
+ **Source:**
229
+
230
+ ```tsx
231
+ {loggedIn() ? <span>Welcome back!</span> : <span>Please log in</span>}
232
+ ```
233
+
234
+ **Output:**
235
+
236
+ ```go-template
237
+ {{if .LoggedIn}}<span bf-c="s0">Welcome back!</span>{{else}}<span bf-c="s0">Please log in</span>{{end}}
238
+ ```
239
+
240
+ Element branches use `bf-c` for conditional markers. Text-only ternaries use `bfComment` markers instead.
241
+
242
+
243
+ ## Script Registration
244
+
245
+ Client components register their scripts via the `.Scripts` interface:
246
+
247
+ ```go-template
248
+ {{if .Scripts}}{{.Scripts.Register "/static/client/barefoot.js"}}{{.Scripts.Register "/static/client/Counter.client.js"}}{{end}}
249
+ ```
250
+
251
+ The `ScriptCollector` tracks needed scripts and renders `<script>` tags at page end. Each script loads at most once.
252
+
253
+
254
+ ## Go Helper Functions
255
+
256
+ These helper functions must be in the Go template `FuncMap`:
257
+
258
+ | Function | Purpose |
259
+ |----------|---------|
260
+ | `bf_add`, `bf_sub`, `bf_mul`, `bf_div` | Arithmetic operations |
261
+ | `bf_neg` | Unary negation |
262
+ | `bf_filter` | Filter a slice by field/value |
263
+ | `bf_sort` | Sort a slice by field/direction |
264
+ | `bf_find`, `bf_find_index` | Find element/index in a slice |
265
+ | `bf_every`, `bf_some` | Test if all/any elements match |
266
+ | `bf_json` | JSON-encode a value for props serialization |
267
+ | `bf_concat` | String concatenation |
268
+
269
+ Provided by the BarefootJS Go runtime package.