---
title: How It Works
description: How one JSX file becomes a marked server template plus a client script, and how hydration joins the two.
---

# How It Works

## Two-Phase Compilation

One JSX source file produces two outputs:

```
JSX Source
    ↓
[Phase 1] Analyze + Transform → IR (Intermediate Representation)
    ↓
[Phase 2a] IR → Marked Template  (server)
[Phase 2b] IR → Client JS        (browser)
```

Phase 1 produces a backend-independent IR: structure, reactive expressions, event handlers, types. Phase 2 renders it twice — as a **marked template** in the chosen [adapter](./adapters.md)'s format, and as **client JS** that creates signals and binds handlers to the marked elements.

Source:

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

export function Counter() {
  const [count, setCount] = createSignal(0)

  return (
    <button onClick={() => setCount(n => n + 1)}>
      Count: {count()}
    </button>
  )
}
```

Marked template (Phase 2a) — `bf-s` is the component scope, `bf="s1"` the button, and the `s0` markers bracket the reactive text:

```tsx
export function Counter({ __instanceId, ... }) {
  const __scopeId = __instanceId || `Counter_${Math.random().toString(36).slice(2, 8)}`
  const count = () => 0

  return (
    <button bf-s={__scopeId} bf="s1">
      Count: {bfText("s0")}{count()}{bfTextEnd()}
    </button>
  )
}
```

Client JS (Phase 2b) — the effect writes `count()` into slot `s0`, the handler attaches to `s1`:

```js
import { $, createEffect, createSignal, escapeText, escapeTextOrNode, hydrate, lazySlots } from '@barefootjs/client/runtime'

export function initCounter(__scope, _p = {}) {
  if (!__scope) return

  const [count, setCount] = createSignal(0)

  const [_s1] = $(__scope, 's1')
  const __bfw_s0 = lazySlots(__scope, [{ id: 's0', kind: 'markup', path: [] }])
  createEffect(() => {
    const __val = count()
    __bfw_s0('s0', escapeTextOrNode(__val))
  })

  if (_s1) _s1.addEventListener('click', () => { setCount(n => n + 1) })
}

hydrate('Counter', {
  init: initCounter,
  template: (_p) => `<button bf="s1"> Count: <!--bf:s0-->${escapeText((0))}<!--/--></button>`
})
```

## Hydration

`bf-*` attributes in the marked template tell the client script where to attach:

| Marker | Purpose | Example |
|--------|---------|---------|
| `bf-s` | Component scope boundary (`~` = child) | `<div bf-s="Counter_a1b2">` |
| `bf` | Interactive element (slot) | `<p bf="s0">` |
| `bf-p` | Serialized props JSON | `<div bf-p='{"initial":5}'>` |
| `bf-c` | Conditional block | `<div bf-c="s2">` |
| `bf-po` | Portal owner scope ID | `<div bf-po="Dialog_a1b2">` |
| `bf-pi` | Portal container ID | `<div bf-pi="bf-portal-1">` |
| `bf-pp` | Portal placeholder | `<template bf-pp="bf-portal-1">` |
| `bf-i` | List item marker | `<li bf-i>` |

1. The server renders HTML with markers, props embedded in `bf-p`.
2. The browser loads the client JS.
3. `hydrate()` finds uninitialized `bf-s` elements.
4. The init function runs once per scope — signals, effects, handlers.
5. The runtime tracks initialized scopes; none runs twice.

## Inspecting the output

`compileJSX(source, file, { adapter, outputIR: true })` adds the IR to `result.files` as a `.ir.json` entry (`type: 'ir'`). `bf debug graph <component>` prints a component's signal graph. The full pipeline and IR schema are in [spec/compiler.md](https://github.com/piconic-ai/barefootjs/blob/main/spec/compiler.md).

## What this means for performance

- Components without `"use client"` ship zero JavaScript.
- Hydration touches only marked nodes; the server-rendered HTML is never re-rendered.
- Give list items a stable `key` (`item.id`, not the index) so rows are reused, not recreated.
