Open-Source Wikis

/

Vue.js

/

Packages

/

runtime-dom

vuejs/core

runtime-dom

Active contributors: Evan You, edison, daiwei

Purpose

@vue/runtime-dom adapts @vue/runtime-core to the browser. It supplies the nodeOps and patchProp implementations the renderer needs, ships DOM-only built-in components (Transition, TransitionGroup), DOM-only directives (v-show, v-model for various form controls), and the public createApp/createSSRApp entry points. It is also the home of the Custom Elements integration (defineCustomElement).

The package is small relative to runtime-core (~5,000 lines, 17 files): the heavy lifting is in core; this package mostly bridges to the DOM.

Directory layout

packages/runtime-dom/src/
├── index.ts              # exports + createApp/createSSRApp
├── nodeOps.ts            # createElement/createText/insert/remove/setText/...
├── patchProp.ts          # dispatcher: class | style | event | attr | prop
├── apiCustomElement.ts   # defineCustomElement, VueElement, useShadowRoot, useHost (~22KB)
├── jsx.ts                # JSX type definitions for HTML/SVG/MathML elements (~48KB)
├── modules/
│   ├── attrs.ts          # patchAttr (DOM attributes)
│   ├── class.ts          # patchClass
│   ├── events.ts         # patchEvent (delegated, with timestamp dedup)
│   ├── props.ts          # patchDOMProp (DOM properties like value, checked)
│   └── style.ts          # patchStyle
├── components/
│   ├── Transition.ts     # CSS transitions + JS hooks
│   └── TransitionGroup.ts # FLIP move animations
├── directives/
│   ├── vModel.ts         # vModelText, vModelCheckbox, vModelRadio, vModelSelect, vModelDynamic
│   ├── vOn.ts            # withModifiers, withKeys
│   └── vShow.ts          # display: none toggle
└── helpers/
    ├── useCssModule.ts   # SFC <style module> binding
    └── useCssVars.ts     # SFC v-bind in CSS

Key abstractions

Symbol File Description
createApp / createSSRApp packages/runtime-dom/src/index.ts The two public app constructors. Lazily instantiate a renderer or hydration renderer on first call.
render packages/runtime-dom/src/index.ts Direct render entry, used by app.mount and tests.
nodeOps packages/runtime-dom/src/nodeOps.ts Implements RendererOptions's create/insert/remove/parentNode for DOM nodes.
patchProp packages/runtime-dom/src/patchProp.ts Dispatcher: looks at the prop name and delegates to the right module.
patchEvent packages/runtime-dom/src/modules/events.ts Attaches a delegated invoker on the element with a timestamp-based dedupe to prevent stale handlers running for events fired before the new one was attached.
Transition packages/runtime-dom/src/components/Transition.ts DOM transition wrapper around BaseTransition; resolves CSS class names per phase.
TransitionGroup packages/runtime-dom/src/components/TransitionGroup.ts List transitions with FLIP move animations.
vModelText, vModelCheckbox, vModelRadio, vModelSelect, vModelDynamic packages/runtime-dom/src/directives/vModel.ts Per-input-type two-way binding. The compiler picks one based on the host element.
vShow packages/runtime-dom/src/directives/vShow.ts Toggles style.display while preserving the original value.
withModifiers, withKeys packages/runtime-dom/src/directives/vOn.ts Compose modifier wrappers for v-on (.stop, .prevent, .self, .once, .passive, .capture, key filters).
defineCustomElement, VueElement packages/runtime-dom/src/apiCustomElement.ts Wrap a Vue component as a Web Component. Uses Shadow DOM by default.

How createApp works

// packages/runtime-dom/src/index.ts
let renderer: Renderer<Element | ShadowRoot> | HydrationRenderer

function ensureRenderer() {
  return (
    renderer ||
    (renderer = createRenderer<Node, Element | ShadowRoot>(rendererOptions))
  )
}

export const createApp = ((...args) => {
  const app = ensureRenderer().createApp(...args)
  // override mount() to accept a CSS selector or an element/ShadowRoot
  // and inject in-DOM template support

  return app
}) as CreateAppFunction<Element>

Two things to notice:

  • The renderer is built lazily. Importing createApp does not instantiate the renderer; importing only reactivity utilities does not pull in nodeOps or patchProp. This is what enables tree-shaking in builds that don't render anything.
  • app.mount is wrapped to add browser-specific behavior: support for selector strings, copying in-DOM templates as a fallback when no template/render is supplied (with a __UNSAFE__ comment about XSS risk), and stamping data-v-app on the root for SSR detection.

createSSRApp is the same with ensureHydrationRenderer() instead. Once hydration is enabled the cached renderer flips to the hydration variant.

patchProp dispatch

packages/runtime-dom/src/patchProp.ts is a small if/else chain that picks one of the modules:

Prefix / Predicate Module What it does
class modules/class.ts Sets el.className (or setAttribute('class', …) for SVG).
style modules/style.ts Object/string styles; preserves CSSText fallback for non-listed props.
onXxx modules/events.ts Attaches a single delegated invoker keyed by event name; updates the value via the invoker so subsequent updates don't pay the listener add/remove cost.
forceAttr modules/attrs.ts DOM attribute (camelCase → kebab-case for SVG/MathML).
Boolean attr / known DOM prop modules/props.ts Sets the DOM property directly (e.g., el.value, el.checked).

The dispatcher also handles namespaced (xlink:, xmlns:) attributes for SVG and MathML.

Events

patchEvent (modules/events.ts) is the most subtle module. It:

  1. Looks up or creates a single invoker function on the element.
  2. Stores the user handler on the invoker's value property.
  3. The invoker uses e.timeStamp to ignore events that fired before it was patched in — necessary because addEventListener semantics combined with the microtask scheduler can otherwise replay events to the new handler.
  4. Caches getNow() per-element with a switch between Date.now() and performance.now() based on the document's createEvent timeStamp.

This is the kind of code where a perf or correctness PR usually starts with git log -p packages/runtime-dom/src/modules/events.ts to read the bug fixes that produced the current shape.

v-model directives

The compiler (packages/compiler-dom/src/transforms/vModel.ts) inspects the host element/component and rewrites v-model to one of:

  • vModelText<input type="text">, <textarea>. Listens to input (or change if .lazy).
  • vModelCheckbox — checkboxes. Handles array value and single boolean.
  • vModelRadio — radios. Compares value via looseEqual.
  • vModelSelect — selects. Reads <option> children, supports multiple.
  • vModelDynamic — used when the element type isn't known until runtime (<input :type="...">).

Each directive ships SSR initializers via initVModelForSSR to keep server-rendered markup consistent.

Custom Elements

apiCustomElement.ts (~22KB) wraps a Vue component as a customElements.define-compatible class. The VueElement class extends HTMLElement, observes attributes, mounts the component into either Shadow DOM or light DOM (shadowRoot: false), and implements lifecycle (connectedCallback, disconnectedCallback, attributeChangedCallback). useShadowRoot() and useHost() are composables that return the host element/shadow root for the current custom-element instance.

SSR initialization

A subtle bit of plumbing: initDirectivesForSSR (gated by __SSR__) registers the SSR-side renderers for vModel and vShow. The server-renderer package imports this at module load (import 'vue') so SSR output for v-model and v-show matches the client.

Integration points

  • runtime-core is the only direct dependency; everything in this package re-exports runtime-core so import { ref, computed, watch, … } from 'vue' works through runtime-dom.
  • vue-compat reuses runtime-dom and bundles the Vue 2-compat code from runtime-core/src/compat.
  • compiler-dom produces render functions that emit calls to vModelXxx, vShow, withModifiers, withKeys, Transition, and TransitionGroup — all of which are re-exported here.
  • server-renderer imports runtime-dom's SSR-init function so directive output matches client output.

Entry points for modification

  • DOM bug? Most fixes live in modules/ (events.ts is the most-edited file in the package).
  • New transition behavior? components/Transition.ts for the wrapper, BaseTransition in runtime-core for the core hook system.
  • New form-element binding? Add a vModelXxx in directives/vModel.ts and teach compiler-dom's vModel transform to pick it.
  • Custom element regression? apiCustomElement.ts is a single self-contained file.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

runtime-dom – Vue.js wiki | Factory