Open-Source Wikis

/

Vue.js

/

vuejs/core overview

/

Architecture

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 --> rdom

Two rules govern the graph and are enforced by code review (see packages/compiler-sfc/src/... and packages/runtime-core/src/... for examples):

  1. 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 like packages/shared/src/patchFlags.ts, packages/shared/src/shapeFlags.ts, and packages/shared/src/normalizeProp.ts exist as a separate package.
  2. 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-dom for runtime template compilation; runtime-only builds drop it because templates were pre-compiled by the SFC pipeline.
  • Compat mode: vue-compat reuses runtime-dom but injects the __COMPAT__ flag and the compat/ modules under packages/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 --> code

Key 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-slot are structural directive transforms that rewrite the AST into block branches.
  • transformElement picks createElementVNode vs createBlock and 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 --> patchProp

The cycle in detail:

  1. A component's setup() runs. Returned reactive state is tracked when the render function reads it. Vue wraps the render function in a ReactiveEffect whose scheduler is queueJob.
  2. When state mutates, trigger() walks the Dep for that key and re-queues the effect via the scheduler instead of running it synchronously. The scheduler binary-searches into a queue ordered by component uid so parents update before children.
  3. On the next microtask the scheduler flushes pending jobs (flushJobs in packages/runtime-core/src/scheduler.ts). Each job re-runs the component's render function, producing a new vnode tree.
  4. 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.
  5. DOM mutations happen through the RendererOptions injected at app creation: nodeOps for create/insert/remove, patchProp for class/style/event/attribute updates. runtime-dom provides the browser implementations; runtime-test provides 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; Transition and TransitionGroup in runtime-dom add 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.

Architecture – Vue.js wiki | Factory