The Hono adapter generates Hono JSX (.hono.tsx) files from the compiler's IR. It is designed for Hono-based servers and any JSX-compatible TypeScript backend.

npm install @barefootjs/hono

#Basic Usage

import { compile } from '@barefootjs/jsx'
import { HonoAdapter } from '@barefootjs/hono'

const adapter = new HonoAdapter()
const result = compile(source, { adapter })

// result.template  → .hono.tsx file content
// result.clientJs  → .client.js file content

#Options

const adapter = new HonoAdapter({
  injectScriptCollection: true,
  clientJsBasePath: '/static/components/',
  barefootJsPath: '/static/components/barefoot.js',
  clientJsFilename: 'my-component.client.js',
})
Option Type Default Description
injectScriptCollection boolean false Enable script collection via Hono's useRequestContext()
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

#Output Format

#Server Component

A server-only component (no "use client") produces a plain Hono JSX function:

Source:

export function Greeting({ name }: { name: string }) {
  return <h1>Hello, {name}</h1>
}

Output (.hono.tsx):

export function Greeting({ name }: { name: string }) {
  return <h1>Hello, {name}</h1>
}

No hydration markers, no client JS.

#Client Component

A client component produces a Hono JSX function with hydration markers and props serialization:

Source:

"use client"
import { createSignal } from '@barefootjs/dom'

export function Counter({ initial = 0 }: { initial?: number }) {
  const [count, setCount] = createSignal(initial)

  return (
    <div>
      <p>{count()}</p>
      <button onClick={() => setCount(n => n + 1)}>+1</button>
    </div>
  )
}

Output (.hono.tsx):

export function Counter({ initial = 0, __instanceId, __bfScope }: CounterPropsWithHydration) {
  const __scopeId = __instanceId || `Counter_${Math.random().toString(36).slice(2, 8)}`
  const count = () => initial ?? 0
  const setCount = () => {}

  return (
    <div bf-s={__scopeId} {...(__bfPropsJson ? { "bf-p": __bfPropsJson } : {})}>
      <p bf="slot_0">{count()}</p>
      <button bf="slot_1">+1</button>
    </div>
  )
}

Key aspects of the output:

  • bf-s on the root element identifies the component boundary
  • bf="slot_N" marks elements that the client JS will target
  • Signal stubs (count = () => initial ?? 0) allow the template to render the initial value server-side
  • bf-p attribute serializes props as JSON for client-side hydration
  • Event handlers are removed — they exist only in the client JS

#Script Collection

When injectScriptCollection is enabled, the adapter uses Hono's useRequestContext() to register client scripts during SSR:

const adapter = new HonoAdapter({
  injectScriptCollection: true,
  clientJsBasePath: '/static/components/',
  barefootJsPath: '/static/components/barefoot.js',
})

Each component registers its script path during rendering. At the end of the page, the BfScripts component renders the collected <script> tags:

import { BfScripts } from '@barefootjs/hono'

export function Layout({ children }) {
  return (
    <html>
      <body>
        {children}
        <BfScripts />
      </body>
    </html>
  )
}

This ensures each component's client JS is loaded exactly once, even if the component appears multiple times on the page.

#Hydration Props

The adapter extends every client component's props with hydration-related fields:

Prop Purpose
__instanceId Unique instance identifier passed from the parent
__bfScope Parent's scope ID (for nested component communication)
__bfChild Marks this component as a child instance (adds ~ prefix to bf-s value)
data-key Stable key for list-rendered instances

These props are used internally by the hydration system and do not need to be passed manually when using components.

#Conditional Rendering

Ternary expressions in JSX compile to conditional output with hydration markers:

Source:

{isActive() ? <span>Active</span> : <span>Inactive</span>}

Output:

{isActive() ? <span bf-c="slot_2">Active</span> : <span bf-c="slot_2">Inactive</span>}

The client JS uses the bf-c marker to swap DOM nodes when the condition changes.

#Loop Rendering

Array .map() calls compile to JSX map expressions:

Source:

{items().map(item => <li>{item.name}</li>)}

Output:

{items().map(item => <li>{item.name}</li>)}

For loops with child components, the adapter generates unique instance IDs per iteration using the loop index or a key prop.