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 traceKey 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 viaflushPostFlushCbs. - Recursion guard
RECURSION_LIMIT = 100warns when an effect mutates its own dependency, helping diagnose accidental infinite loops. SchedulerJobFlagsis 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:
- Initializes props (
packages/runtime-core/src/componentProps.ts) — normalize options, validate, apply defaults, freeze in dev. - Initializes slots (
packages/runtime-core/src/componentSlots.ts). - 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 viaproxyRefs. - Falls back to Options API: data, methods, computed, watch, lifecycle hooks, mixins (
packages/runtime-core/src/componentOptions.ts). - Either attaches a user-provided
renderfunction or invokes the runtime template compiler (set viaregisterRuntimeCompiler) on thetemplateoption.
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 atargetselector/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 aSuspenseBoundarywith a list of pending dependencies; once all resolve, the renderer swaps to default slot content.<script setup>top-level await is wrapped viawithAsyncContext.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/onDeactivatedhooks fire here.BaseTransitionis the platform-agnostic skeleton;TransitionandTransitionGroupinruntime-domadd 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-domcallscreateRendererwith browser-specificnodeOpsandpatchProp, then re-exports everything.server-rendererimportsssrUtils(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 viaregisterRuntimeCompiler.- The compiler emits code that imports symbols from
vue(createElementBlock,openBlock,withDirectives,withCtx,Fragment,Text,Comment,Static, …). Those names are listed inpackages/compiler-core/src/runtimeHelpers.tsand re-exported from this package.
Entry points for modification
- New built-in component? Add a file under
components/, branch inrenderer.ts'spatch(), add aShapeFlagin@vue/shared. - New scheduler ordering? Edit
scheduler.ts. Run the benchmarks first. - New lifecycle hook? Add a
LifecycleHooksenum entry inenums.ts, expose a function inapiLifecycle.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 fromruntime-domso thevuebuild 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.