Rust backend for the Forma Stack — server-side rendering without Node.js. The @getforma/compiler compiles TypeScript components to a binary IR (FMIR). These Rust crates parse that binary and generate HTML, so your server never needs a JavaScript runtime.
Parses FMIR binary files and generates HTML from them. This is the core — everything else builds on top of it.
[dependencies]
forma-ir = "0.1"What it does:
- Parses
.irbinary files into a validatedIrModule(header, opcodes, string table, slot table, island table) - Walks the opcode stream and generates HTML with proper escaping
- Fills dynamic values (text, attributes, conditionals, lists) from typed
SlotData - Emits hydration markers (
<!--f:t0-->,<!--f:s0-->,<!--f:l0-->,<!--f:i0-->) that FormaJS uses for client-side hydration - Compiles to WASM for client-side re-renders in the browser
Use it when:
- You're building your own server (not Axum) and want Forma SSR
- You need the WASM build for client-side island re-rendering
- You're building tools that inspect, transform, or generate IR files
You don't need it directly if: You're using forma-server — it wraps forma-ir for you.
Full SSR middleware for Axum. Renders pages, serves content-hashed assets, generates CSP headers with cryptographic nonces, and handles the Phase 1 (client mount) / Phase 2 (SSR reconcile) pipeline.
[dependencies]
forma-server = "0.1"What it does:
render_page(config)— generates a full HTML page from an IR module + slot dataload_ir_modules(manifest)— loads.irfiles from embedded assets at startupserve_asset(filename)— serves content-hashed assets with Brotli/gzip negotiation andCache-Control: immutablegenerate_nonce()— 256-bit cryptographically random nonce (ring CSPRNG)build_csp_header(nonce)— strict CSP: nounsafe-inline, nounsafe-eval, nonce-based scripts/styles
Two rendering modes:
| Mode | What happens | When to use |
|---|---|---|
| Phase 1: Client Mount | Renders <div id="app"></div> — FormaJS mounts from scratch |
No IR available, or first visit |
| Phase 2: SSR Reconcile | Renders full HTML from IR walker — FormaJS hydrates | IR + slots available, fast first paint |
Use it when: Your backend is Rust + Axum and you want the full Forma SSR pipeline.
use forma_server::{assets, render_page, PageConfig, RenderMode, load_ir_modules};
use rust_embed::Embed;
#[derive(Embed)]
#[folder = "dist/"]
struct Assets;
// At startup: load manifest and IR modules
let manifest = assets::load_manifest::<Assets>();
let (render_modes, ir_modules) = load_ir_modules::<Assets>(&manifest);TypeScript/JSX components
↓
@getforma/compiler → emits .ir binary (FMIR format)
↓
forma-ir → parses binary, walks opcodes, generates HTML
↓
forma-server → Axum middleware: SSR + assets + CSP
↓
HTML response → browser receives server-rendered page
↓
@getforma/core (FormaJS) → hydrates using marker comments, attaches reactivity
- HTML escaping: All dynamic text and attributes are escaped (
&,<,>,") - CSP headers: Strict policy with cryptographic nonces — no
unsafe-inline, nounsafe-eval - Recursion limits: Walker enforces
MAX_RECURSION_DEPTH = 64andMAX_LIST_DEPTH = 4to prevent stack overflow from malicious IR - Comment escaping:
--in HTML comments replaced with--to prevent injection - Script tag escaping:
</script>in props replaced with<\/script> - No unsafe code: Zero
unsafeblocks across both crates - 145 tests including XSS payload verification and malformed input handling
| Package | Description |
|---|---|
| @getforma/core | Reactive DOM library — signals, h(), islands, SSR hydration |
| @getforma/compiler | Vite plugin — h() optimization, server transforms, FMIR emission |
| @getforma/build | Production pipeline — bundling, hashing, compression, manifest |
| Package | Description |
|---|---|
| forma-ir | This repo — FMIR binary format: parser, walker, WASM exports |
| forma-server | This repo — Axum middleware: SSR, asset serving, CSP headers |
| Package | Description |
|---|---|
| @getforma/create-app | npx @getforma/create-app — scaffolds Rust server + TypeScript frontend |
git clone https://github.com/getforma-dev/forma.git
cd forma
cargo test --workspace # 145 tests
cargo clippy --workspace # lint
cargo fmt --all --check # format checkMIT