Generates Go html/template files (.tmpl) and type definitions (_types.go) from the compiler's IR. The output is plain html/template, so any Go server can render it.
npm install @barefootjs/go-template// vite.config.ts
import { defineConfig } from 'vite'
import { barefoot } from '@barefootjs/go-template/vite'
export default defineConfig({
plugins: barefoot({ components: ['components'], templates: 'dist/templates' }),
})#Server integration
The scaffolder ships runnable starters for four Go servers, all built on this adapter:
npm create barefootjs@latest -- --adapter <name>--adapter |
Framework | Router |
|---|---|---|
echo |
Echo | echo.Renderer |
gin |
Gin | gin.Engine |
chi |
Chi | chi.Router (net/http) |
nethttp |
Go standard library | http.ServeMux |
Each scaffold parses the generated .tmpl files into a template.Template with bf.FuncMap() and bf.TemplateFuncMap(root), then renders through bf.NewRenderer(templates, layout). The layout is a bf.LayoutFunc that receives the rendered component, the collected scripts, and portals:
root := template.New("").Funcs(bf.FuncMap())
root.Funcs(bf.TemplateFuncMap(root)) // bf_tmpl: companion defines (JSX children, loop-row portal elements)
template.Must(root.New("Tag").Parse("")) // stub referenced by the generated Slot template
template.Must(root.ParseGlob("dist/templates/*.tmpl"))
renderer := bf.NewRenderer(root, func(ctx *bf.RenderContext) string {
return "<!doctype html><body>" + string(ctx.ComponentHTML) + string(ctx.Scripts) + "</body>"
})
html := renderer.Render(bf.RenderOptions{
ComponentName: "Counter",
Props: &components.CounterProps{Initial: 0},
})Render collects every client script the page needs and passes the <script> tags to the layout as ctx.Scripts, each at most once. Runnable examples for all four servers live under integrations/.
#Options
const adapter = new GoTemplateAdapter({ packageName: 'views' })| Option | Type | Default | Description |
|---|---|---|---|
packageName |
string |
'components' |
Go package name for generated type files |
#Compiled output
"use client"
import { createSignal } from '@barefootjs/client'
export function Counter(props: { initial?: number }) {
const [count, setCount] = createSignal(props.initial ?? 0)
return (
<div>
<span>Count: {count()}</span>
<button onClick={() => setCount(n => n + 1)}>+1</button>
</div>
)
}{{define "Counter"}}
{{if .Scripts}}{{.Scripts.Register "/static/client/barefoot.js"}}{{.Scripts.Register "/static/client/Counter.client.js"}}{{end}}
<div bf-s="{{bfScopeAttr .}}" {{bfPropsAttr .}}>
<span bf="s1">Count: {{bfTextStart "s0"}}{{.Count}}{{bfTextEnd}}</span>
<button bf="s2">+1</button>
</div>
{{end}}Props become capitalized struct fields (props.user.email → .User.Email); operators, .filter()/.sort()/.map() chains and the other array methods translate to bf_* template functions from bf.FuncMap(). Vendor code-splitting is stock Vite manualChunks — see Vite Plugin.
#Type generation
For each component the adapter generates an input struct (the external API), a props struct (adds the hydration fields), and a New{Component}Props() constructor that applies defaults:
// The input type you construct: hydration fields are optional.
type CounterInput struct {
ScopeID string // Optional: if empty, a random ID is generated
BfParent string // Optional: parent scope id
BfMount string // Optional: slot id in parent
Initial int
}
// The props type the template receives; hydration fields are `json:"-"`.
type CounterProps struct {
ScopeID string `json:"-"`
Scripts *bf.ScriptCollector `json:"-"`
Initial int `json:"initial"`
// ... BfIsRoot, BfIsChild, BfParent, BfMount, BfDataKey, Portals, BfCallerProps (all `json:"-"`)
}
func NewCounterProps(input CounterInput) CounterProps| TypeScript | Go |
|---|---|
string |
string |
number |
int (or float64 for decimals) |
boolean |
bool |
T[] |
[]T |
T | undefined |
Pointer type *T or zero value |
| Object type | Named struct |
Child component props carry a ScopeID used for hydration. You do not mint these by hand: Renderer.Render backfills a unique <Component>_<random> id for any child (in a slice or a single field) whose ScopeID is empty, so build child props with just their data (TodoItemProps{Todo: t}). An explicit ScopeID is preserved when you need a stable id.