Open-Source Wikis

/

Vue.js

/

Packages

/

compiler-core

vuejs/core

compiler-core

Active contributors: Evan You, edison, daiwei

Purpose

@vue/compiler-core is the platform-agnostic template compiler. It tokenizes, parses, transforms, and code-generates a Vue template into a JavaScript render function that calls the runtime helpers exported by vue/@vue/runtime-core. It is the largest compiler package by source size (~11,000 lines, 30 files) and the foundation that compiler-dom, compiler-ssr, and compiler-sfc extend.

Directory layout

packages/compiler-core/src/
├── index.ts              # public API barrel
├── compile.ts            # baseCompile + getBaseTransformPreset
├── options.ts            # ParserOptions, TransformOptions, CodegenOptions
├── parser.ts             # baseParse → AST (~30KB)
├── tokenizer.ts          # streaming tokenizer state machine (~36KB)
├── ast.ts                # AST node types and constructors
├── transform.ts          # transform driver (TransformContext, traverseNode)
├── codegen.ts            # generate() → JS source
├── runtimeHelpers.ts     # symbolic ids for runtime imports
├── errors.ts             # ErrorCodes + messages
├── utils.ts              # AST helpers
├── babelUtils.ts         # walk identifiers, scope analysis
├── validateExpression.ts # disallow certain JS constructs in templates
├── transforms/
│   ├── transformElement.ts        # the core element-vnode codegen
│   ├── transformExpression.ts     # rewrites identifiers (foo → _ctx.foo)
│   ├── transformText.ts           # merges adjacent text nodes
│   ├── transformSlotOutlet.ts     # <slot/> handling
│   ├── transformVBindShorthand.ts # :foo (shorthand) special-cases
│   ├── cacheStatic.ts             # static hoisting / cache
│   ├── vIf.ts                     # v-if / v-else / v-else-if
│   ├── vFor.ts                    # v-for
│   ├── vSlot.ts                   # named/scoped slots
│   ├── vModel.ts                  # base v-model (compiler-dom overrides)
│   ├── vOn.ts                     # base v-on (compiler-dom overrides)
│   ├── vBind.ts                   # :prop / .modifier
│   ├── vMemo.ts                   # v-memo
│   ├── vOnce.ts                   # v-once
│   └── noopDirectiveTransform.ts  # for v-cloak and similar
└── compat/
    └── compatConfig.ts            # Vue 2 compat warnings (gated by __COMPAT__)

The pipeline

graph LR
    src["template source"]
    tok["tokenizer.ts<br/>state machine"]
    parse["baseParse<br/>parser.ts"]
    ast["AST"]
    xform["transform<br/>transform.ts"]
    cgast["AST + codegenNode"]
    gen["generate<br/>codegen.ts"]
    code["render(_ctx, _cache) {…}"]

    src --> tok --> parse --> ast --> xform --> cgast --> gen --> code

Three substeps inside transform:

  1. Walk the tree depth-first, applying every nodeTransform from options.nodeTransforms and every directiveTransform matching directives on each element.
  2. cacheStatic runs before codegen and lifts unchanging subtrees to module-scope _hoisted_* constants. This is the optimization that makes static parts of templates allocation-free.
  3. createRootCodegen wraps the whole thing in a Block so the runtime can flat-walk dynamic descendants.

Key abstractions

Symbol File Description
baseParse(source, options) packages/compiler-core/src/parser.ts Tokenizer-driven recursive parser. Builds the RootNode AST.
Tokenizer packages/compiler-core/src/tokenizer.ts A 36KB state machine; one of the most-tuned files in the codebase. Emits start tags, end tags, attributes, interpolation, comments, CDATA.
RootNode, ElementNode, TextNode, InterpolationNode, DirectiveNode packages/compiler-core/src/ast.ts Discriminated AST union. Each node has a loc (source location) and an optional codegenNode.
transform(root, options) packages/compiler-core/src/transform.ts Drives the visit. Maintains a TransformContext with currentNode, parent, helpers (which runtime helpers to import), hoists, cached.
NodeTransform, DirectiveTransform, StructuralDirectiveTransform packages/compiler-core/src/transform.ts Plug-in shapes. StructuralDirectiveTransform is what v-if, v-for, v-slot use to short-circuit normal traversal.
transformElement packages/compiler-core/src/transforms/transformElement.ts The biggest transform (~28KB). Builds createElementVNode/createBlock calls, computes PatchFlags, packs props arguments.
transformExpression packages/compiler-core/src/transforms/transformExpression.ts Rewrites template identifiers using babelUtils.walkIdentifiers. With prefixIdentifiers: true (the default for ESM-bundler builds and SSR), it qualifies free identifiers as _ctx.foo so render functions are pure.
cacheStatic packages/compiler-core/src/transforms/cacheStatic.ts Static-hoisting pass. Determines getConstantType(node) for every element and lifts those that are fully constant.
generate(ast, options) packages/compiler-core/src/codegen.ts Walks codegenNode and writes function render(_ctx, _cache) { return (_openBlock(), _createElementBlock("div", null, [...])) }.
RuntimeHelpers packages/compiler-core/src/runtimeHelpers.ts Symbol → name table (OPEN_BLOCK, CREATE_BLOCK, CREATE_ELEMENT_BLOCK, CREATE_VNODE, CREATE_ELEMENT_VNODE, CREATE_COMMENT, CREATE_TEXT, CREATE_STATIC, RESOLVE_COMPONENT, RESOLVE_DYNAMIC_COMPONENT, RESOLVE_DIRECTIVE, WITH_DIRECTIVES, RENDER_LIST, RENDER_SLOT, WITH_CTX, KEEP_ALIVE, TELEPORT, SUSPENSE, WITH_MEMO, …). The runtime imports are guaranteed to be re-exported by vue.

Patch flag generation

The compiler's most important optimization is PatchFlags. transformElement decides, per element, which of these to set on the generated createElementVNode call:

  • TEXT — the only dynamic part is text interpolation.
  • CLASS, STYLE, PROPS — only those bind types change. PROPS carries the changed-key list.
  • FULL_PROPS — has v-bind="object" so any key may change.
  • HYDRATE_EVENTS — has events that need attaching during hydration.
  • STABLE_FRAGMENT — children list never reorders. Used for v-for over a constant key.
  • KEYED_FRAGMENT / UNKEYED_FRAGMENT — children may reorder.
  • NEED_PATCH — non-render-output thing (ref, custom directive).
  • DYNAMIC_SLOTS — slot content is dynamic.
  • HOISTED — vnode itself is module-scope and never patches.

The patch flag list lives in packages/shared/src/patchFlags.ts; the compiler is the only producer, the runtime is the only consumer.

v-if, v-for, v-slot

These are structural directives and rewrite the AST rather than just transforming a single node:

  • vIf.ts collects v-if / v-else-if / v-else siblings into an IfNode with multiple branches. Codegen emits a chain of conditional expressions wrapped in a block.
  • vFor.ts produces a ForNode whose codegen calls _renderList(source, (item, key, index) => …). It infers a stable key from :key="item.id" and chooses STABLE_FRAGMENT vs KEYED_FRAGMENT accordingly.
  • vSlot.ts (buildSlots, trackVForSlotScopes, trackSlotScopes) handles <template #name>, scoped slots, and dynamic slot names. It produces a slots object literal whose entries are functions wrapped in _withCtx.

Errors

packages/compiler-core/src/errors.ts defines ErrorCodes and a stable message table. Errors are emitted via createCompilerError(code, loc) from any transform; the parser's own errors carry codes from the same enum. The hosting compiler (compiler-dom, compiler-sfc) usually surfaces these with a code frame produced by @vue/shared's generateCodeFrame.

Integration points

  • Re-exported as the substrate of @vue/compiler-dom, which wraps baseCompile with DOM-flavored options.
  • compiler-ssr reuses transformExpression, transformBind, transformOn, transformStyle, trackSlotScopes, and the parser, but installs its own SSR transforms on top.
  • compiler-sfc calls baseCompile (via compiler-dom) for the template block, then post-processes the result.
  • The runtime helper names listed in runtimeHelpers.ts must match the names actually exported from runtime-core / runtime-dom. Updating one without the other breaks the build.

Entry points for modification

  • New directive? Add a DirectiveTransform (or StructuralDirectiveTransform) in transforms/, register it in getBaseTransformPreset (compile.ts).
  • New patch-flag opportunity? Edit transformElement.ts to detect the case and set the flag; update runtime-core/src/renderer.ts and runtime-core/src/vnode.ts to honor it. Don't forget the patch-flag table in @vue/shared.
  • Parser bug? Most fixes land in tokenizer.ts (the state-machine table) or parser.ts (where the tokens are assembled). The tokenizer is heavily commented because it is not pleasant to read.
  • New cache-static heuristic? cacheStatic.ts and the getConstantType function are the right place; be careful with the bail conditions.

For the SFC pipeline that wraps this compiler, see packages/compiler-sfc. For the SSR pipeline, see packages/compiler-ssr.

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

compiler-core – Vue.js wiki | Factory