vuejs/core
Architecture
Vue 3 is a layered framework: a reactivity engine at the bottom, a platform-agnostic runtime built on top, platform-specific runtimes (DOM, test, custom) plugged in via a renderer-options interface, and a compiler that translates .vue templates into JavaScript render functions consumed by that runtime. This page sketches how the layers fit together, then walks through the two main control flows: how a template becomes a render function, and how reactive state changes flow into DOM updates.
Package graph
graph TD
subgraph "Compiler"
sfc["@vue/compiler-sfc"]
cdom["@vue/compiler-dom"]
cssr["@vue/compiler-ssr"]
ccore["@vue/compiler-core"]
sfc --> cdom
sfc --> ccore
cdom --> ccore
cssr --> cdom
end
subgraph "Runtime"
vue["vue"]
rdom["@vue/runtime-dom"]
rcore["@vue/runtime-core"]
reactivity["@vue/reactivity"]
ssr["@vue/server-renderer"]
rtest["@vue/runtime-test"]
vue --> cdom
vue --> rdom
rdom --> rcore
rcore --> reactivity
ssr --> rcore
rtest --> rcore
end
shared["@vue/shared"]
ccore -.shared utils.-> shared
rcore -.shared utils.-> shared
rdom -.shared utils.-> shared
sfc -.shared utils.-> shared
reactivity -.shared utils.-> shared
compat["vue-compat"]
compat --> rdomTwo rules govern the graph and are enforced by code review (see packages/compiler-sfc/src/... and packages/runtime-core/src/... for examples):
- Compiler packages must not import runtime, and vice versa. If a value is needed on both sides, it goes into
@vue/shared. This is the reason files likepackages/shared/src/patchFlags.ts,packages/shared/src/shapeFlags.ts, andpackages/shared/src/normalizeProp.tsexist as a separate package. - No relative cross-package imports. Every cross-package import goes through the package name (
@vue/runtime-core,@vue/reactivity). Path aliasing makes this work in dev (scripts/aliases.js,tsconfig.json#paths); pnpm workspaces and externalization make it work in published builds.
Three bundle dimensions
Each runtime/compiler combination ships in multiple build formats so it can target browsers, bundlers, Node SSR, and Vue 2 compat. The dimensions are controlled by the build flags listed in packages/vue/README.md and produced by rollup.config.js:
- Format:
global(IIFE for<script>),esm-browser(native ESM in browsers),esm-bundler(input to Webpack/Rollup/Vite),cjs(Node SSR). - Compiler inclusion: full builds bundle
compiler-domfor runtime template compilation; runtime-only builds drop it because templates were pre-compiled by the SFC pipeline. - Compat mode:
vue-compatreuses runtime-dom but injects the__COMPAT__flag and thecompat/modules underpackages/runtime-core/src/compat.
Feature flags (__DEV__, __BROWSER__, __SSR__, __VUE_OPTIONS_API__, __VUE_PROD_DEVTOOLS__, __COMPAT__, …) are replaced at build time by @rollup/plugin-replace and let unused branches tree-shake away. See reference/configuration for the full flag list.
From template to render function
A .vue SFC enters the compiler pipeline at packages/compiler-sfc/src/parse.ts and is split into descriptor blocks (<template>, <script>, <script setup>, <style>). The template block is then compiled by @vue/compiler-dom (or @vue/compiler-ssr when ssr: true):
graph LR
src["template source"]
tok["tokenizer<br/>(packages/compiler-core/src/tokenizer.ts)"]
parse["baseParse<br/>(packages/compiler-core/src/parser.ts)"]
ast["AST"]
xform["transform pipeline<br/>(packages/compiler-core/src/transform.ts<br/>+ transforms/*)"]
cgast["codegen AST"]
gen["generate<br/>(packages/compiler-core/src/codegen.ts)"]
code["render(_ctx, _cache) {…}"]
src --> tok --> parse --> ast --> xform --> cgast --> gen --> codeKey passes inside the transform stage:
- Static analysis & cacheStatic (
packages/compiler-core/src/transforms/cacheStatic.ts) marks subtrees that never change and lifts them into module-scope_hoisted_*constants. v-if/v-for/v-slotare structural directive transforms that rewrite the AST into block branches.- transformElement picks
createElementVNodevscreateBlockand computes patch flags (which props are dynamic, whether children need keyed reconciliation). - Codegen emits an ES module that imports runtime helpers (
createElementBlock,openBlock,toDisplayString, …) re-exported by@vue/runtime-core.
For SSR the same parser feeds compiler-ssr's ssrCodegenTransform which produces string-concatenation code instead of vnode calls.
From state change to DOM patch
At runtime the reactivity layer and the renderer cooperate through the scheduler:
graph TD
subgraph "Reactivity (packages/reactivity)"
proxy["Proxy traps<br/>baseHandlers / collectionHandlers"]
track["track()<br/>dep.ts"]
trigger["trigger()<br/>dep.ts"]
effect["ReactiveEffect<br/>effect.ts"]
end
subgraph "Runtime core (packages/runtime-core)"
sched["scheduler.ts<br/>queueJob / flushJobs"]
renderer["renderer.ts<br/>patch / processComponent"]
vnode["vnode.ts"]
end
subgraph "Platform"
nodeOps["nodeOps.ts<br/>(runtime-dom)"]
patchProp["patchProp.ts<br/>(runtime-dom)"]
end
user["user code: state.x = 1"]
user --> proxy --> trigger --> effect
effect -- componentUpdateFn --> sched
sched -- microtask flush --> renderer
renderer --> vnode
renderer --> nodeOps
renderer --> patchPropThe cycle in detail:
- A component's
setup()runs. Returned reactive state is tracked when the render function reads it. Vue wraps the render function in aReactiveEffectwhose scheduler isqueueJob. - When state mutates,
trigger()walks theDepfor that key and re-queues the effect via the scheduler instead of running it synchronously. The scheduler binary-searches into a queue ordered by componentuidso parents update before children. - On the next microtask the scheduler flushes pending jobs (
flushJobsinpackages/runtime-core/src/scheduler.ts). Each job re-runs the component's render function, producing a new vnode tree. - The renderer's
patch()(packages/runtime-core/src/renderer.ts, ~74KB — the largest source file in the runtime) diffs old vs new vnodes. Patch flags emitted by the compiler skip work for parts known to be static. - DOM mutations happen through the
RendererOptionsinjected at app creation:nodeOpsfor create/insert/remove,patchPropfor class/style/event/attribute updates.runtime-domprovides the browser implementations;runtime-testprovides JS-object versions for tests.
Post-flush callbacks (watchPostEffect, mounted hooks, transition leave/enter completion) drain after every job batch via flushPostFlushCbs.
Hydration and SSR
For SSR, @vue/server-renderer produces a string by walking the same component tree but calling SSR-specific render functions emitted by compiler-ssr. On the client, createSSRApp swaps in createHydrationRenderer (packages/runtime-core/src/hydration.ts, ~30KB) which reuses existing DOM nodes instead of creating them, attaches event listeners, and warns on mismatches. Lazy hydration strategies (hydrateOnIdle, hydrateOnVisible, …) live in packages/runtime-core/src/hydrationStrategies.ts.
See features/server-side-rendering and features/hydration for the details.
Built-in components
A handful of components are part of runtime-core but are first-class citizens of the architecture because they require renderer cooperation:
Teleport(packages/runtime-core/src/components/Teleport.ts) renders children into a different DOM target.Suspense(packages/runtime-core/src/components/Suspense.ts) coordinates async children and<script setup>top-level await.KeepAlive(packages/runtime-core/src/components/KeepAlive.ts) caches deactivated subtrees.BaseTransition(packages/runtime-core/src/components/BaseTransition.ts) is the platform-agnostic transition primitive;TransitionandTransitionGroupinruntime-domadd CSS-class plumbing.
Their render hooks are special-cased in renderer.ts via ShapeFlags (see packages/shared/src/shapeFlags.ts). For details, see features/built-in-components.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.