Architecture
Explains how the architecture of Takumi is designed at a high level.
Data Structure
Format
Takumi uses JSON format as it's the most universal format that can be easily serialized and deserialized.
Node
HTML is complex, it defines a lot of elements that are used to build the web. But since we are building static images, only three types of nodes are required: an image, a text, or a container.
The details are described in the Reference section.
fromJsx helper
To bridge the gap between JSX and nodes, we provide a fromJsx helper function that walks a React element tree and emits Takumi nodes. The full rule set is in ## fromJsx rules below.
Styling
Takumi uses Taffy to layout the positions, and Parley to layout the text, so it mostly depends on the features that these two libraries support.
CSS
The syntax parser is inspired by lightningcss, check Reference to get the full list of supported properties.
Tailwind
Originally twrnc was used, but it was removed in favor of our own implementation.
Arbitrary values would just be passed to the CSS parser mentioned above.
Distribution
As Rust Crate
If you want to build a port of Takumi to your own language or just embed in Rust application, install takumi as a dependency.
[dependencies]
takumi = "*" # replace with the latest versionThe documentation is available on docs.rs.
For Node.js Runtime
In order to utilize the power of multithreading and non-blocking, instead of building for wasm, we target napi-rs to build the native binary for Node.js.
The package is published as @takumi-rs/core on npm.
For Edge Runtime / Browser
This is relatively rare, but if you are using a Edge runtime or browser, you can use the @takumi-rs/wasm entrypoint.
And to keep the wasm file small, woff font loading is not supported and there's no bundled fonts (so fonts need to be loaded to render text).
Why no headless browser
Most image-generation solutions are either a headless Chromium eating 300 MB of RAM, or a minimal SVG-to-PNG renderer that falls apart the moment you need real CSS. Takumi is neither.
It's a purpose-built Rust pipeline: CSS parsing, layout, compositing, image output. No browser, no V8, no puppeteer. The same binary runs in a serverless edge function and a long-running Node server, and the same takumi crate embeds directly in any Rust application.
The cost of that choice is a CSS subset, not the whole platform. The benefit is megabytes of memory instead of hundreds, deterministic output across runtimes, and a renderer that fits in a Cloudflare Worker.
fromJsx rules
fromJsx walks a React element tree and emits a Takumi node tree. The mapping is intentionally narrow — there is no DOM at render time, so anything that depends on one is dropped.
Supported:
- Function components are invoked with their props and their return value is walked.
- Server Components that resolve to a
Promiseare awaited before walking. - Fragments (
<>...</>) are flattened into the parent. memocomponents are unwrapped to their inner component.forwardRefcomponents are invoked withref = null.<img>becomes an Image node (src,width,height, style passthrough).<svg>is serialized to a string and rendered throughresvgas an Image node.<br>becomes a Text node with"\n".<style>is collected as a stylesheet (children must be plain text).- Other HTML elements become Container nodes with style and style presets applied.
- Strings and numbers become Text nodes.
- Iterables (arrays, generators) are walked in order; up to 8 children resolve concurrently.
Fallback:
- React Context providers and hooks that need the React dispatcher trigger a one-shot fallback through
react-dom/server'srenderToStaticMarkup. The resulting HTML is parsed back into nodes — context and stateless hooks work, side effects don't.
Stripped or ignored:
- Event handlers (
onClick,onChange, ...) — no event loop at render time. ref— no DOM to bind to.- HTML void elements with no semantics here (
<input>,<meta>, ...) emit nothing. - Anything outside the
styleprop,twprop, or extracted HTML attributes is ignored.
The full implementation lives in takumi-helpers/src/jsx/index.ts.
Last updated on