notes/architecture/06-RENDERERS.md
Dioxus supports multiple rendering backends through a common trait-based abstraction. Each renderer implements the same interfaces to enable cross-platform component reuse.
The bridge between VirtualDOM and real DOM implementations:
append_children(id, count) - Add N nodes to elementassign_node_id(path, id) - Mark element at template pathcreate_placeholder(id) - Create marker nodecreate_text_node(value, id) - Create text nodeload_template(template, index, id) - Clone from template cachereplace_node_with(id, count) - Replace elementset_attribute(name, ns, value, id) - Update attributecreate_event_listener(name, id) - Register listenerremove_node(id) - Delete elementMaps platform-specific events to standardized Dioxus types:
PlatformEventData (renderer-specific) to typed event dataPlatform Event → Captured by renderer
→ Converted via HtmlEventConverter
→ runtime.handle_event(name, event, element_id)
→ VirtualDOM handlers invoked
→ State changes → Re-render
→ WriteMutations applied
WebsysDom
├── interpreter: Sledgehammer JS interpreter
├── document: web_sys::Document reference
├── root: Root DOM node
├── templates: HashMap<Template, u16>
├── runtime: Rc<Runtime>
└── (hydration): skip_mutations, suspense_hydration_ids
data-dioxus-id attributeWebEventConverter converts web_sys events to Dioxus typesdio_el data attributesskip_mutations = truerehydrate_streaming()launch(root_component, contexts, config)
→ Create VirtualDom
→ Create WebsysDom wrapper
→ If hydrate: Deserialize data, rebuild with skip_mutations
→ Otherwise: vdom.rebuild(&mut websys_dom)
→ Main loop: wait_for_work() → render_immediate() → flush_edits()
Uses Wry webview library with Tao window management.
App
├── unmounted_dom: Cell<Option<VirtualDom>>
├── webviews: HashMap<WindowId, WebviewInstance>
├── shared: Rc<SharedContext>
│ ├── event_handlers: WindowEventHandlers
│ ├── pending_webviews: Vec<PendingWebview>
│ ├── shortcut_manager: ShortcutRegistry
│ └── websocket: EditWebsocket
└── control_flow: ControlFlow
Implements WriteMutations for wry-based rendering:
WryQueue managing mutation batchBrowser event → JavaScript → window.postMessage()
→ Wry intercepts request
→ Extract dioxus-data header (base64 JSON)
→ IpcMessage { method, params }
→ Handle: UserEvent, Query, BrowserOpen, Initialize
dioxus:// custom protocol for asset serving__events path for event processing__file_dialog for file selectiondioxus_asset_resolver for bundled assetsConfig
├── WindowBuilder customization
├── Custom event loop
├── Protocols and async protocols
├── Pre-rendered HTML template
├── Context menu disable flag
├── Background color (RGBA)
└── Devtools support toggle
DioxusNativeWindowRenderer
├── anyrender-vello wrapper
├── WindowRenderer trait implementation
├── GPU features configurable
└── Custom paint support
VirtualDOM components
→ DioxusNativeDOM
→ Blitz DOM tree with CSS
→ Blitz layout engine
→ Vello renderer
→ GPU rendering
Event::NewEvents(StartCause::Init)
→ Create initial window
→ DioxusDocument with VirtualDOM
Resumed → Renderer.resume()
WindowEvent::RedrawRequested
→ VirtualDOM.render_immediate()
→ Mutations applied to Blitz DOM
→ Layout computed
→ Render frame
WindowEvent::Resized → Queue redraw
Client (Browser with WebSocket)
↓ events
Server (VirtualDOM runs here)
↓ mutations (binary protocol)
Client receives mutations → Sledgehammer applies
tokio_util::task::LocalPoolHandleLiveViewPool::launch_virtualdom(ws, || VirtualDom::new())
→ Create MutationState, QueryEngine
→ vdom.rebuild() → Send initial HTML
→ Loop:
tokio::select! {
ws.next() → Handle event/query
vdom.wait_for_work() → Has work
query_rx.recv() → JS query
hot_reload_rx.recv() → Hot reload
}
render_immediate() → Send mutations
MutationState::write_memory_into(&mut bytes)
→ Sledgehammer binary encoding
→ WebSocket transmission
→ Client: window.interpreter.handleEdits(bytes)
LiveviewElement methods:
scroll_offset() → JS: getScrollLeft/Top()
scroll_size() → JS: getScrollWidth/Height()
client_rect() → JS: getBoundingClientRect()
scroll_to(options) → JS: element.scrollTo()
Ultra-compact binary protocol for DOM mutations shared across renderers.
create_text_node(value, id) → Push text node
create_placeholder(id) → Push comment node
append_children(parent_id, count) → Pop and append
replace_with(id, count) → Replace element
set_attribute(id, name, value, ns) → Set DOM attribute
new_event_listener(name, id, bubbles) → Register listener
Every renderer implements the trait differently:
Renderers use Box<dyn Any> for extensible config:
launch(app, configs: Vec<Box<dyn Any>>)
→ For each config: try downcast to expected type
Most renderers delay context setup until first window/request.
All non-web renderers batch mutations for efficiency:
Implement WriteMutations
Implement HtmlEventConverter
Create Launch Function
vdom.render_immediate(&mut mutations)Define Configuration
Box<dyn Any> downcastingOptional Enhancements