API for creating custom renderers that target platforms other than the DOM. This allows Vue to render to canvas, native mobile, terminals, or any custom platform.
createRenderer()
Creates a custom renderer by providing platform-specific node operations. This is the core API for building renderers that target non-DOM environments.
Type Signature:
function createRenderer<
HostNode = RendererNode,
HostElement = RendererElement
>(
options: RendererOptions<HostNode, HostElement>
): Renderer<HostElement>
An object containing platform-specific implementations of node operations. See RendererOptions for details.
Generic Parameters
The type representing a node in your target platform (e.g., Node for DOM, CanvasNode for canvas).
The type representing an element in your target platform (e.g., Element for DOM).
Returns
A Renderer object with render() and createApp() methods.
Renderer Interface:
interface Renderer<HostElement = RendererElement> {
render: RootRenderFunction<HostElement>
createApp: CreateAppFunction<HostElement>
}
Basic Example
import { createRenderer } from '@vue/runtime-core'
const { render, createApp } = createRenderer({
createElement(tag) {
return { tag, children: [] }
},
insert(child, parent, anchor) {
if (anchor) {
const index = parent.children.indexOf(anchor)
parent.children.splice(index, 0, child)
} else {
parent.children.push(child)
}
},
remove(child) {
const parent = child.parentNode
if (parent) {
const index = parent.children.indexOf(child)
parent.children.splice(index, 1)
}
},
createText(text) {
return { text }
},
createComment(text) {
return { comment: text }
},
setText(node, text) {
node.text = text
},
setElementText(el, text) {
el.children = [{ text }]
},
parentNode(node) {
return node.parentNode
},
nextSibling(node) {
return node.nextSibling
},
patchProp(el, key, prevValue, nextValue) {
el[key] = nextValue
}
})
Canvas Renderer Example
import { createRenderer } from '@vue/runtime-core'
class CanvasElement {
constructor(type) {
this.type = type
this.props = {}
this.children = []
}
draw(ctx, x, y) {
// Implement drawing logic
}
}
const { createApp } = createRenderer({
createElement(type) {
return new CanvasElement(type)
},
insert(el, parent) {
parent.children.push(el)
},
remove(el) {
const parent = el.parent
if (parent) {
const i = parent.children.indexOf(el)
parent.children.splice(i, 1)
}
},
patchProp(el, key, prevValue, nextValue) {
el.props[key] = nextValue
},
createText(text) {
return { text }
},
createComment(text) {
return { comment: text }
},
setText(node, text) {
node.text = text
},
setElementText(el, text) {
el.textContent = text
},
parentNode(node) {
return node.parent
},
nextSibling(node) {
return null
}
})
// Use the custom renderer
const app = createApp({
render() {
return h('rect', { x: 0, y: 0, width: 100, height: 100 })
}
})
app.mount(canvasElement)
RendererOptions
The configuration object for creating a custom renderer. All platform-specific operations must be implemented.
Type Definition:
interface RendererOptions<
HostNode = RendererNode,
HostElement = RendererElement
> {
patchProp(
el: HostElement,
key: string,
prevValue: any,
nextValue: any,
namespace?: ElementNamespace,
parentComponent?: ComponentInternalInstance | null
): void
insert(
el: HostNode,
parent: HostElement,
anchor?: HostNode | null
): void
remove(el: HostNode): void
createElement(
type: string,
namespace?: ElementNamespace,
isCustomizedBuiltIn?: string,
vnodeProps?: VNodeProps | null
): HostElement
createText(text: string): HostNode
createComment(text: string): HostNode
setText(node: HostNode, text: string): void
setElementText(node: HostElement, text: string): void
parentNode(node: HostNode): HostElement | null
nextSibling(node: HostNode): HostNode | null
querySelector?(selector: string): HostElement | null
setScopeId?(el: HostElement, id: string): void
cloneNode?(node: HostNode): HostNode
insertStaticContent?(
content: string,
parent: HostElement,
anchor: HostNode | null,
namespace: ElementNamespace,
start?: HostNode | null,
end?: HostNode | null
): [HostNode, HostNode]
}
Required Methods
Patches a property/attribute on an element.patchProp(el, key, prevValue, nextValue, namespace?, parentComponent?)
- el: The element to patch
- key: Property/attribute name
- prevValue: Previous value
- nextValue: New value
- namespace: Optional namespace (svg, mathml)
- parentComponent: Optional parent component instance
Inserts a node into a parent, optionally before an anchor.insert(el, parent, anchor?)
- el: The node to insert
- parent: The parent element
- anchor: Optional sibling to insert before
Removes a node from its parent.
Creates a new element node.createElement(type, namespace?, isCustomizedBuiltIn?, vnodeProps?)
- type: Tag name or element type
- namespace: Optional namespace
- isCustomizedBuiltIn: For customized built-in elements
- vnodeProps: VNode props for element creation hints
Sets the text content of a text node.
- node: The text node
- text: New text content
Sets the text content of an element node.setElementText(node, text)
- node: The element
- text: New text content
Returns the parent of a node.parentNode(node) => HostElement | null
Returns the next sibling of a node.nextSibling(node) => HostNode | null
Optional Methods
Query for an element (used for mounting with CSS selector).querySelector(selector) => HostElement | null
Sets a scope ID attribute for scoped CSS.
Clones a node (used for optimization).cloneNode(node) => HostNode
Inserts static content (used for hoisted static nodes).insertStaticContent(content, parent, anchor, namespace, start?, end?) => [HostNode, HostNode]
createHydrationRenderer()
Creates a custom renderer with hydration support. Used for server-side rendering scenarios.
Type Signature:
function createHydrationRenderer(
options: RendererOptions<Node, Element>
): HydrationRenderer
interface HydrationRenderer extends Renderer<Element | ShadowRoot> {
hydrate: RootHydrateFunction
}
options
RendererOptions<Node, Element>
required
Renderer options with DOM types (Node and Element).
Returns
A HydrationRenderer with render(), createApp(), and hydrate() methods.
Example
import { createHydrationRenderer } from '@vue/runtime-core'
import { nodeOps } from './node-ops'
import { patchProp } from './patch-prop'
const { createApp, hydrate } = createHydrationRenderer({
...nodeOps,
patchProp
})
export { createApp, hydrate }
Real-World Examples
Vue Native Renderer
import { createRenderer } from '@vue/runtime-core'
import { NativeElement } from './native-element'
const { createApp } = createRenderer({
createElement(type) {
return new NativeElement(type)
},
insert(el, parent, anchor) {
parent.insertBefore(el, anchor)
},
remove(el) {
el.parent?.removeChild(el)
},
patchProp(el, key, prevValue, nextValue) {
el.setAttribute(key, nextValue)
},
createText(text) {
return new NativeTextNode(text)
},
createComment() {
return null // Native platforms might not support comments
},
setText(node, text) {
node.text = text
},
setElementText(el, text) {
el.textContent = text
},
parentNode(node) {
return node.parent
},
nextSibling(node) {
return node.nextSibling
}
})
Terminal Renderer
import { createRenderer } from '@vue/runtime-core'
import blessed from 'blessed'
const { createApp } = createRenderer({
createElement(type) {
switch (type) {
case 'box':
return blessed.box()
case 'text':
return blessed.text()
case 'list':
return blessed.list()
default:
return blessed.box()
}
},
insert(el, parent) {
parent.append(el)
},
remove(el) {
el.detach()
},
patchProp(el, key, prevValue, nextValue) {
el[key] = nextValue
},
createText(text) {
return blessed.text({ content: text })
},
createComment() {
return null
},
setText(node, text) {
node.setContent(text)
},
setElementText(el, text) {
el.setContent(text)
},
parentNode(node) {
return node.parent
},
nextSibling(node) {
const parent = node.parent
const index = parent?.children.indexOf(node)
return parent?.children[index + 1] || null
}
})
// Create terminal UI
const screen = blessed.screen()
const app = createApp({
render() {
return h('box', { border: { type: 'line' } }, [
h('text', 'Hello Terminal!')
])
}
})
app.mount(screen)
screen.key(['escape', 'q', 'C-c'], () => process.exit(0))
screen.render()
Best Practices
- Implement
cloneNode() for better static node handling
- Implement
insertStaticContent() for hoisted content optimization
- Minimize work in frequently called methods like
patchProp()
Type Safety
import { createRenderer } from '@vue/runtime-core'
interface MyNode {
type: string
parent: MyElement | null
}
interface MyElement extends MyNode {
props: Record<string, any>
children: MyNode[]
}
const { createApp } = createRenderer<MyNode, MyElement>({
// Fully typed renderer options
})
Error Handling
const { createApp } = createRenderer({
createElement(type) {
try {
return new CustomElement(type)
} catch (err) {
console.error(`Failed to create element: ${type}`, err)
throw err
}
},
// ... other methods
})
Custom renderers allow Vue to target any platform. Vue’s DOM renderer and server renderer are both built using this API.