Generates Hono JSX (.tsx) templates from the compiler's IR. Works with Hono and any JSX-compatible TypeScript backend. It is the default adapter of the scaffold:
npm create barefootjs@latestTo add it by hand:
npm install @barefootjs/hono// vite.config.ts
import { defineConfig } from 'vite'
import { barefoot } from '@barefootjs/hono/vite'
export default defineConfig({
build: { outDir: 'public/components' },
plugins: barefoot({ components: ['components'], templates: 'dist/components' }),
})#Options
barefoot() constructs the HonoAdapter itself; pass adapter options under adapterOptions when you need to change the paths:
barefoot({
components: ['components'],
templates: 'dist/components',
adapterOptions: {
clientJsBasePath: '/static/components/',
barefootJsPath: '/static/components/barefoot.js',
},
})| Option | Type | Default | Description |
|---|---|---|---|
clientJsBasePath |
string |
'/static/components/' |
Base path for client JS files |
barefootJsPath |
string |
'/static/components/barefoot.js' |
Path to the BarefootJS runtime |
clientJsFilename |
string |
'{componentName}.client.js' |
Override the client JS filename |
Under Vite these defaults are superseded by the hashed asset URLs the plugin resolves.
#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>
)
}import { bfText, bfTextEnd } from '@barefootjs/hono/utils'
export function Counter(__allProps: { initial?: number } & { __instanceId?: string; ... }) {
const { __instanceId, ..., ...props } = __allProps
const __scopeId = __instanceId || `Counter_${Math.random().toString(36).slice(2, 8)}`
const count = () => props.initial ?? 0 // signal → server-side stub
return (
<div bf-s={__scopeId} bf-p={__bfPropsJson}>
<span bf="s1">Count: {bfText("s0")}{count()}{bfTextEnd()}</span>
<button onClick={() => {}} bf="s2">+1</button>
</div>
)
}bf-s marks the component scope, bf="sN" the nodes the client JS targets, and bf-p the serialized props. Handlers are no-ops on the server; the client JS attaches the real ones.
#Script collection
Rendering a client component registers its script in Hono's request context. Render <BfScripts /> once, in the layout, and it emits each component's <script> tag once per page regardless of instance count:
import { BfScripts } from '@barefootjs/hono/scripts'
export function Layout({ children }) {
return (
<html>
<body>
{children}
<BfScripts />
</body>
</html>
)
}#Deploying to Cloudflare Workers
barefoot() writes the SSR templates under templates (a server-side source directory your app imports from) and the client build under Vite's build.outDir. Point Workers Assets' assets.directory at build.outDir only; the template directory must not be served. The two trees are separate directories, so no .assetsignore is needed:
// vite.config.ts
export default defineConfig({
build: { outDir: 'dist/static/components' }, // → assets.directory
plugins: barefoot({
components: ['components'],
templates: 'dist/components', // server-only, not deployed as an asset
}),
})#Adding to an Existing Project
For an existing Hono + Cloudflare Workers app that renders with hono/jsx-renderer. barefoot() compiles individual components and never bundles the server app, so a whole-app Vite bundler plugin (e.g. @hono/vite-build) is removed and nothing replaces it. Paths below are the scaffold's.
1. Install.
npm install @barefootjs/client @barefootjs/hono @barefootjs/jsx @barefootjs/shared
npm install -D @barefootjs/vitePin typescript to ^5 (@barefootjs/jsx's peer range): a newer major makes vite build crash at config-load time with TypeError: ts.createPrinter is not a function.
2. Resolve components dist-first. In tsconfig.json, map the alias to the compiled template when it exists and the source otherwise, and exclude the compiled copy from type-checking:
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "@barefootjs/hono/jsx",
"baseUrl": ".",
"paths": { "@/components/*": ["./dist/components/*", "./components/*"] }
},
"exclude": ["node_modules", "dist/components"]
}Vite's dependency pre-scan ignores tsconfig paths, so vite.config.ts also needs resolve.alias pointing at the source tree:
resolve: { alias: { '@/components': resolve(HERE, 'components') } },adapterOptions, assets, and assetsOutputFile are not needed for a retrofit — see Vite Plugin.
3. Wrangler. main in wrangler.jsonc is your uncompiled Hono entry (e.g. src/index.tsx); wrangler's esbuild bundles it and follows the paths mapping into dist/components/. assets.directory covers build.outDir, not templates.
4. Wire the app. Render <BfScripts /> once in the jsxRenderer layout and import components through the alias (import { Counter } from '@/components/Counter'). Existing components can move under components/ one at a time; only files under a configured components directory are compiled.
5. Build and verify.
npx vite build # writes dist/components/ (templates) and public/components/ (hydration JS)
npx wrangler dev # or: npx wrangler deployA converted "use client" component renders with a bf-s attribute and responds to interaction. In development run vite dev alongside wrangler dev so dist/components/ regenerates on change.