WebUI WebAssembly #

WebUI provides browser-ready WebAssembly bindings through wasm-bindgen. The bindings are built as three variants so you can choose only the parser, only the handler, or the combined playground bundle.

Variants #

VariantImport pathExportsUse when
Handlerwasm/handler/webui_wasm_handler.jsProtocolYou already have protocol bytes and only need rendering
Parserwasm/parser/webui_wasm_parser.jsbuild_protocolYou need to compile virtual browser files into protocol bytes
Allwasm/all/webui_wasm_all.jsParser and handler exportsYou need both sides in one module, such as the docs playground

The handler-only bundle excludes webui-parser, and the parser-only bundle excludes webui-handler. The combined bundle keeps the previous playground behavior.

Building the WASM bundles #

cargo xtask build-wasm

The output is generated under docs/.webui-press/public/wasm/ for the documentation playground and release staging. Rebuild it when Rust code in webui-wasm, webui-parser, webui-handler, or webui-protocol changes.

Handler-only API #

Use the handler bundle when the protocol was built elsewhere and loaded as protobuf bytes in the browser.

import init, { Protocol } from './wasm/handler/webui_wasm_handler.js';

await init();

const protocolBytes = new Uint8Array(await (await fetch('/protocol.bin')).arrayBuffer());
const protocol = new Protocol(protocolBytes, 'webui');
const html = protocol.render(
  '{"title": "Hello"}',
  { entry: 'index.html', requestPath: '/' },
);

Keep the Protocol instance alive across renders. It decodes protobuf, builds deterministic indices, and binds the plugin once.

Protocol #

MethodDescription
render(stateJson, options?)Return complete rendered HTML as a string
renderStream(stateJson, onChunk, options?)Invoke callbacks coalesced around a 16 KiB target
streamResponse(options?)Open a progressive StreamingSession returning one Uint8Array per call
renderPartial(stateJson, entry, requestPath, inventoryHex)Return a complete JSON partial response with active-route projected state
renderComponentTemplates(componentTags, inventoryHex)Return requested template payloads and updated inventory
tokens()Return CSS token names in build order

For a complete static/CDN service worker example using this callback to write a ReadableStream and mirror --theme token injection in the browser, see Serverless Architecture.

StreamingSession #

When the entry declares <boundary> directives, streamResponse() opens a session that returns bytes instead of writing them, which maps directly onto a ReadableStream controller in a service worker:

const session = protocol.streamResponse({ entry: 'index.html', requestPath: '/' });
const rows = session.boundary('rows');

const body = new ReadableStream({
  async start(controller) {
    controller.enqueue(session.writeShell(baseState));
    controller.enqueue(session.writeBoundary(rows, await loadRows()));
    controller.enqueue(session.finish({}));
    controller.close();
  },
});
MemberDescription
boundary(name)Resolve an authored boundary name to its integer handle
boundaryCount / finishedDeclared boundary count; whether finish() ran
writeShell(state)Document prefix through the first semantic flush
writeBoundary(id, state, mode?)One boundary's markup and checkpoint ("final" or "updatable")
update(id, state)Projected state patch to an updatable boundary
finish(state)Tail checkpoint, terminal record, and document suffix

The API and its ordering rules are identical on Node, C, and C#, so the same server logic ports between them unchanged.

Parser-only API #

Use the parser bundle when browser code needs to compile an in-memory file map into protocol bytes.

import init, { build_protocol } from './wasm/parser/webui_wasm_parser.js';

await init();

const files = {
  'index.html': '<h1>{{title}}</h1>',
  'my-card.html': '<p><slot></slot></p>',
  'my-card.css': 'p { color: red; }',
};

const protocolBytes = build_protocol(
  files,
  'index.html',
  [projectionManifest],
);

build_protocol(files, entry, projectionManifests?) #

Parse virtual files into a WebUI protocol without rendering.

ParameterTypeDescription
filesRecord<string, string>Map of filenames to content
entrystringEntry HTML filename
projectionManifestsobject[]Optional bundler manifest fragments

Returns protobuf-serialized WebUIProtocol as a Uint8Array. Throws on missing entry files, invalid templates, invalid component authoring, or protocol serialization errors.

Without manifests, initial and scripted navigation state remain full. With manifests, WASM applies the shared schema/build-ID validation, fragment merge, and strict coverage rules. Because virtual WASM builds have no filesystem, they cannot repeat disk stale-file checks and never analyze JavaScript.

Component discovery follows the virtual file map convention: HTML files with a hyphen in the name are registered as components, such as my-card.html for <my-card>. Matching .css files are paired and inlined with CssStrategy::Style.

Combined API #

Use the combined bundle when you want parser and handler exports from one module.

import init, { build_protocol, Protocol } from './wasm/all/webui_wasm_all.js';

await init();

const protocolBytes = build_protocol(files, 'index.html');
let html = '';
const protocol = new Protocol(protocolBytes);
protocol.renderStream(stateJson, (chunk) => {
  html += chunk;
}, { entry: 'index.html', requestPath: '/' });

The documentation playground imports this combined bundle and uses build_protocol followed by new Protocol(...) so it can measure compile and render time separately.

Differences from server-side rendering #

AspectServer (CLI / Rust / Node)WASM (Browser)
Protocol formatProtobuf binaryProtobuf bytes
CSS strategyLink by default, Style or Module when configuredStyle for virtual file builds
File I/OFilesystem and component discovery sourcesVirtual file map
StreamingSupported by native handlersProtocol.renderStream() calls a batched JavaScript callback, and Protocol.streamResponse() returns progressive chunks
Bundle choiceNative crates/addonsHandler-only, parser-only, or combined WASM