Open-Source Wikis

/

Vue.js

/

Packages

/

runtime-core

vuejs/core

runtime-core

Active contributors: Evan You, edison, HcySunYang

Purpose

@vue/runtime-core is the platform-agnostic Vue 3 runtime. It defines components, vnodes, the reconciler, the scheduler, the lifecycle hook system, the Composition API, the Options API, slots, async components, hydration, and the built-in components (Teleport, Suspense, KeepAlive, BaseTransition). Platform-specific runtimes (runtime-dom, runtime-test, custom renderers) consume runtime-core and supply node operations through RendererOptions.

This is the largest package: 65 source files and ~20,700 lines of TypeScript. The renderer alone (renderer.ts) is ~74KB.

Directory layout

packages/runtime-core/src/
├── index.ts                    # public API barrel
├── apiCreateApp.ts             # createApp / app.use / app.mount
├── apiAsyncComponent.ts        # defineAsyncComponent
├── apiComputed.ts              # re-exports computed with watcher integration
├── apiDefineComponent.ts       # defineComponent typings
├── apiInject.ts                # provide / inject / hasInjectionContext
├── apiLifecycle.ts             # onMounted, onUpdated, onErrorCaptured, …
├── apiSetupHelpers.ts          # defineProps/Emits/Slots/Model macros (runtime stubs)
├── apiWatch.ts                 # watch / watchEffect / watchSync / watchPost
├── component.ts                # createComponentInstance, setupComponent (~35KB, the spine)
├── componentEmits.ts           # emit() implementation
├── componentOptions.ts         # Options API: data, methods, computed, watch, mixins
├── componentProps.ts           # prop validation, normalization, default resolution
├── componentPublicInstance.ts  # the `this` proxy used in templates and Options API
├── componentRenderContext.ts   # withCtx for render-function scoping
├── componentRenderUtils.ts     # renderComponentRoot, normalizeVNode
├── componentSlots.ts           # slot resolution, default slot, slot fallback
├── components/
│   ├── BaseTransition.ts       # platform-agnostic transition primitive
│   ├── KeepAlive.ts            # cache deactivated subtrees
│   ├── Suspense.ts             # async boundary + fallback
│   └── Teleport.ts             # render to a different DOM target
├── compat/                     # Vue 2 compatibility layer (gated by __COMPAT__)
├── customFormatter.ts          # Chrome devtools formatter
├── devtools.ts                 # __VUE_DEVTOOLS_GLOBAL_HOOK__ glue
├── directives.ts               # withDirectives + DirectiveHook resolution
├── enums.ts                    # LifecycleHooks enum
├── errorHandling.ts            # callWithErrorHandling, ErrorCodes, ErrorTypeStrings
├── featureFlags.ts             # warns when bundler hasn't replaced feature flags
├── h.ts                        # h() — the hand-written render-function helper
├── helpers/                    # renderList, renderSlot, useId, useModel, useTemplateRef, …
├── hmr.ts                      # hot-reload bookkeeping
├── hydration.ts                # SSR hydration (~30KB)
├── hydrationStrategies.ts      # hydrateOnIdle/Visible/MediaQuery/Interaction
├── internalObject.ts           # internal-instance brand symbol
├── profiling.ts                # performance.mark for app.config.performance
├── renderer.ts                 # patch / mountComponent / processX (~74KB)
├── rendererTemplateRef.ts      # template ref binding
├── scheduler.ts                # queueJob, flushJobs, nextTick
├── vnode.ts                    # createVNode, openBlock, createBlock, normalizeChildren
└── warning.ts                  # warn() with component trace

Key abstractions

Symbol File Description
App / AppContext packages/runtime-core/src/apiCreateApp.ts The user-facing app handle and the inheritable per-app config (mixins, directives, components, errorHandler).
ComponentInternalInstance packages/runtime-core/src/component.ts Vue's internal handle for a mounted component. Holds ctx, props, slots, emit, update (the render effect), lifecycle queues, parent/root pointers.
ComponentPublicInstance packages/runtime-core/src/componentPublicInstance.ts The this exposed inside templates and Options API methods. A proxy that resolves names across props, data, setup state, ctx, computed, methods.
VNode packages/runtime-core/src/vnode.ts Plain JS object with type, props, children, shapeFlag, patchFlag, el, key, ref, …
createRenderer(options) packages/runtime-core/src/renderer.ts The factory that takes RendererOptions (nodeOps + patchProp) and returns { render, hydrate, createApp }.
patch(n1, n2, container, …) packages/runtime-core/src/renderer.ts The single function the renderer recurses through. Branches on ShapeFlags.
queueJob(job) / flushJobs() packages/runtime-core/src/scheduler.ts Microtask-batched component update scheduler. Sorts by uid for parent-before-child ordering.
setupComponent(instance) packages/runtime-core/src/component.ts Runs setup(), falls back to Options API normalization, attaches the render function.
withDirectives(vnode, dirs) packages/runtime-core/src/directives.ts Attaches a directive binding list that is invoked at the appropriate lifecycle phase.
Suspense, Teleport, KeepAlive, BaseTransition packages/runtime-core/src/components/* Built-in components recognized by the renderer via ShapeFlags.
hydrate(vnode, container) packages/runtime-core/src/hydration.ts Walk SSR-rendered DOM and bind events/refs without re-creating nodes.

How a component renders

sequenceDiagram
    participant User as createApp(...)
    participant App as App.mount
    participant Renderer
    participant Comp as ComponentInternalInstance
    participant Effect as ReactiveEffect
    participant Sched as scheduler
    User->>App: createApp(MyComp).mount('#app')
    App->>Renderer: render(rootVNode, container)
    Renderer->>Comp: createComponentInstance + setupComponent
    Comp->>Comp: run setup() / Options API
    Comp->>Effect: new ReactiveEffect(componentUpdateFn, queueJob)
    Effect->>Renderer: patch(null, subTree, container)  // initial mount
    Note over User,Effect: later state mutates …
    Effect->>Sched: queueJob(componentUpdateFn)
    Sched-->>Effect: flush on next microtask
    Effect->>Renderer: patch(prevSubTree, newSubTree, container)

The renderer arranges its logic as processX / mountX / patchX per vnode kind: processElement, processComponent, processFragment, processText, processCommentNode, processSuspense, processTeleport. Most edits land in one of these branches.

Scheduler

The scheduler in packages/runtime-core/src/scheduler.ts is the single coordination point that turns synchronous trigger() calls into batched, ordered DOM updates:

  • Pending jobs are stored in a queue sorted by id so that a parent's update runs before its children's. The id is the component's uid (parents have smaller ids because they're created first).
  • A single Promise.resolve().then(flushJobs) microtask handles all enqueued jobs at the next opportunity.
  • Pre-flush callbacks (watchers with flush: 'pre') are inserted with the same id as the matching component so they execute right before that component re-renders.
  • Post-flush callbacks (mounted hook, watchPostEffect, transition leave/enter completion) drain after every flush via flushPostFlushCbs.
  • Recursion guard RECURSION_LIMIT = 100 warns when an effect mutates its own dependency, helping diagnose accidental infinite loops.
  • SchedulerJobFlags is a bitset (QUEUED | PRE | ALLOW_RECURSE | DISPOSED) carried on each job for state tracking.

nextTick(fn?) returns a thenable that resolves after the current flush; it is what user code uses to wait for DOM updates after a state mutation.

Components and the Options API

packages/runtime-core/src/component.ts is the spine. createComponentInstance allocates the ComponentInternalInstance, attaches an EffectScope, and returns the handle. setupComponent then:

  1. Initializes props (packages/runtime-core/src/componentProps.ts) — normalize options, validate, apply defaults, freeze in dev.
  2. Initializes slots (packages/runtime-core/src/componentSlots.ts).
  3. Runs setup(props, ctx). If it returns a function, that becomes the render function. If it returns an object, it is merged into the public proxy via proxyRefs.
  4. Falls back to Options API: data, methods, computed, watch, lifecycle hooks, mixins (packages/runtime-core/src/componentOptions.ts).
  5. Either attaches a user-provided render function or invokes the runtime template compiler (set via registerRuntimeCompiler) on the template option.

Lifecycle hooks (packages/runtime-core/src/apiLifecycle.ts) are pushed onto per-instance arrays keyed by LifecycleHooks enum (enums.ts). The renderer pulls them at the right moments. onErrorCaptured is special — it's the only hook that participates in the error-bubble walk performed by errorHandling.ts.

Built-in components

These are real components but the renderer special-cases them in patch():

  • Teleport (packages/runtime-core/src/components/Teleport.ts) holds a target selector/element; the renderer mounts children there instead of the parent's container. Updates patch via the original location so DOM order in the source tree matches the runtime.
  • Suspense (packages/runtime-core/src/components/Suspense.ts) renders fallback content while async children resolve. It owns a SuspenseBoundary with a list of pending dependencies; once all resolve, the renderer swaps to default slot content. <script setup> top-level await is wrapped via withAsyncContext.
  • KeepAlive (packages/runtime-core/src/components/KeepAlive.ts) caches subtree by component name/key in an LRU. Deactivated subtrees stay in memory; reactivation moves them back into place. onActivated/onDeactivated hooks fire here.
  • BaseTransition is the platform-agnostic skeleton; Transition and TransitionGroup in runtime-dom add CSS-class transitions and FLIP animations on top.

See features/built-in-components for a longer treatment.

Custom renderer support

A non-DOM renderer is created by calling:

const { render, createApp } = createRenderer<HostNode, HostElement>({
  createElement,
  createText,
  createComment,
  setText,
  setElementText,
  insert,
  remove,
  parentNode,
  nextSibling,
  patchProp,
});

@vue/runtime-test, Vue Native, Lottie renderers, and the like supply their own implementations. The shape of RendererOptions is the contract; everything else falls out of runtime-core's genericity.

Hydration

packages/runtime-core/src/hydration.ts (~30KB) handles SSR hydration. The createHydrationRenderer factory in renderer.ts returns a renderer whose mount walks existing nodes via nextSibling instead of creating them. Mismatches produce dev warnings (with detailed payloads when __VUE_PROD_HYDRATION_MISMATCH_DETAILS__ is on); the renderer falls back to a full client-side mount on irrecoverable mismatches.

packages/runtime-core/src/hydrationStrategies.ts exposes hydrateOnIdle, hydrateOnVisible, hydrateOnMediaQuery, and hydrateOnInteraction for lazy hydration of async components.

Compat layer

packages/runtime-core/src/compat/ contains the Vue 2 compatibility surface — compatConfig.ts, global.ts, instance.ts, etc. The whole directory is gated by __COMPAT__, so prod runtime builds drop it entirely. The vue-compat package re-exports these.

Integration points

  • runtime-dom calls createRenderer with browser-specific nodeOps and patchProp, then re-exports everything.
  • server-renderer imports ssrUtils (a curated bag of internal exports gated by __SSR__) to walk the component tree without a DOM.
  • vue (the public package) imports the runtime compiler and registers it via registerRuntimeCompiler.
  • The compiler emits code that imports symbols from vue (createElementBlock, openBlock, withDirectives, withCtx, Fragment, Text, Comment, Static, …). Those names are listed in packages/compiler-core/src/runtimeHelpers.ts and re-exported from this package.

Entry points for modification

  • New built-in component? Add a file under components/, branch in renderer.ts's patch(), add a ShapeFlag in @vue/shared.
  • New scheduler ordering? Edit scheduler.ts. Run the benchmarks first.
  • New lifecycle hook? Add a LifecycleHooks enum entry in enums.ts, expose a function in apiLifecycle.ts, call it from the renderer at the right point.
  • New runtime helper that the compiler emits? Add it here, list it in packages/compiler-core/src/runtimeHelpers.ts, and ensure it's re-exported from runtime-dom so the vue build picks it up.

For how the compiler-emitted helpers connect to the runtime, see packages/compiler-core. For reactivity wiring, see packages/reactivity.

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

runtime-core – Vue.js wiki | Factory