js/ui/realm
js/ui/realm.ts
fino:ui/realm — render components in an isolated realm.
A component is a synchronous function, so a tree that depends on data which has not loaded yet cannot wait for it. This module resolves that without making components async: the component runs inside a child realm alongside whatever asynchronous work feeds its signals, republishes its tree on every revision, and the realm's own event loop decides when there is nothing left to render. Draining is completion — the render resolves with the last tree the child published before it exited.
A realm is held open by pending work, not by an open port, so a rendering
child cannot idle indefinitely waiting to be asked for more. Render a whole
batch in one pass instead: renderRealmAll() spawns one isolate, sends the
shared data once, and returns a tree per item, which is what a build
producing many pages from one component wants anyway.
Everything crossing the boundary is portable by construction, which is the
point: props are JSON going in, trees are PortableVNode coming back, and
the component cannot reach a host object on the parent side. A theme, a
plugin, or a page template is therefore untrusted input that renders under
whatever import rules the parent grants it.
import { renderRealm } from 'fino:ui/realm';
import { renderToHtml } from 'fino:ui/html';
const tree = await renderRealm('./page.tsx', { props: { title: 'Home' } });
const html = renderToHtml(tree);Interfaces
interface RealmRenderOptions {
Realm construction options shared by every rendering mode.
Properties
overrides?: ImportMap | ImportRule[]
Import rules granted to the rendering child.
Realm defaults apply when omitted. Pass ImportMap.deny([...]) to render a
component that should reach nothing beyond the modules it is granted.
process?: boolean
Run the child in a separate OS process rather than on the reactor pool.
interface RenderRealmOptions extends RealmRenderOptions {
Options for rendering one component tree.
Properties
props?: PortableValue
Props passed to the component. Must be JSON data.
interface RenderRealmAllOptions extends RealmRenderOptions {
Options for rendering one component across many prop sets.
Properties
items: Array<Record<string, PortableValue>>
Per-render props. One tree is returned per item, in order.
Functions
async function renderRealm(
entry: string,
options: RenderRealmOptions = {},
): Promise<PortableVNode>
Render a component module in a realm and resolve its final tree.
The child republishes its tree on every revision and the last one wins, so a component whose data resolves asynchronously converges without the component itself ever awaiting. The promise resolves once the child's event loop drains and it exits, so work that never settles never completes.
Rejects when the child fails to load or evaluate, when the component throws, and when the child exits without rendering at all.
import { renderRealm } from 'fino:ui/realm';
const tree = await renderRealm('./report.tsx', { props: { period: '2026-Q1' } });function renderRealmAll(
entry: string,
options: RenderRealmAllOptions,
): Promise<PortableVNode[]>
Render one component across many prop sets in a single realm.
Each item is rendered as Component({ ...shared, ...item }) and the trees are
returned in item order. One isolate is created and shared crosses the
boundary once, so a build that renders hundreds of pages from one component
pays the setup and serialization cost a single time.
Completion works exactly as it does for a single render: every revision republishes the whole batch, and the last batch before the child exits is the result.
import { renderRealmAll } from 'fino:ui/realm';
const trees = await renderRealmAll('./theme.tsx', {
shared: { site },
items: pages.map((page) => ({ page })),
});