Vue 的单文件组件(Single-File Component,简称 SFC)以 .vue 为扩展名,将模板、逻辑和样式整合在一个文件中。但浏览器无法直接理解这种格式,因此需要经过编译工具(如 Vite、Webpack + vue-loader)将其转换为标准的 JavaScript 模块。这个过程由 @vue/compiler-sfc、@vue/compiler-dom 和 @vue/compiler-core 协同完成。
编译流程概览:三部曲
Vue 3 的编译器采用了模块化设计,主要由三个部分组成:
@vue/compiler-sfc:专门处理.vue文件,负责将文件拆分成<template>、<script>和<style>三大块。@vue/compiler-dom:专注于 DOM 平台相关的编译逻辑,是浏览器端的适配层。@vue/compiler-core:平台无关的核心编译器,实现了编译的三大核心阶段:解析 (Parse) 、转换 (Transform) 和生成 (Generate)

模板编译
模板编译的核心流程,即 @vue/compiler-core 中发生的 Parse → Transform → Generate 三个阶段
解析(Parse):从模板字符串到 AST
解析器(Parser)的输入是模板字符串,输出是抽象语法树(AST)。AST 以树形结构描述模板的节点类型、属性、指令等信息。
转换(Transform):为运行时优化做准备
转换阶段遍历 AST ,对节点进行修改、添加元数据,并生成代码生成节点(codegenNode)。
编译器会识别模板中的 v-if、v-for、v-model、v-on 等指令,并将其转换为对应的代码生成节点。
对于插值表达式 {{ msg }} 或指令中的动态表达式,编译器会分析其内容,并在生成代码时添加上下文前缀。
静态提升是 Vue 3 最重要的优化之一。编译器会识别出不依赖任何响应式数据的纯静态节点 (如没有绑定、没有插值、没有指令的 <div>),并将其提升到渲染函数之外。
转换器会为每个动态节点 标记一个 PatchFlag(补丁标志),指示该节点在更新时哪些部分可能发生变化。这是一个位掩码,运行时可以快速判断需要比较的内容。
生成(Generate):从 AST 到 render 函数
生成阶段是编译的最后一步,它的任务是将转换后的 AST 转换成 JavaScript 代码字符串(即 render 函数)。
generate 函数会递归遍历 AST,调用不同的代码生成函数(genNode、genElement、genExpression 等)拼接字符串。
枚举 ConstantTypes
js
enum ConstantTypes {
NOT_CONSTANT = 0, // 非常量表达式,在编译时无法确定其值
CAN_SKIP_PATCH, // 1 可以跳过补丁的常量,通常是静态节点
CAN_CACHE, // 2 可以缓存的常量,其值在编译时已知
CAN_STRINGIFY, // 3 可以字符串化的常量,其值可以在编译时转换为字符串
}
枚举 NodeTypes
js
enum NodeTypes {
ROOT, // 0 根节点,整个模板入口
ELEMENT, // 1 元素节点 ,如 <div>、<span> 等 HTML 元素或 Vue 组件
TEXT, // 2 文本节点,如普通文本内容
COMMENT, // 3 注释节点,如 <!-- comment -->
SIMPLE_EXPRESSION, // 4 简单表达式节点,如 message
INTERPOLATION, // 5 插值节点,如 {{ message }}
ATTRIBUTE, // 6 属性节点,如 v-model
DIRECTIVE, // 7 指令节点,如 v-if
// containers
COMPOUND_EXPRESSION, // 8 复合表达式节点,由多个表达式组成
IF, // 9 条件节点,如 v-if
IF_BRANCH, // 10 条件分支节点,如 v-else、v-else-if
FOR, // 11 循环节点,如 v-for
TEXT_CALL, // 12 文本调用节点,用于处理带表达式的文本
// codegen
VNODE_CALL, // 13 VNode 调用节点,用于创建 VNode 实例
JS_CALL_EXPRESSION, // 14 调用表达式节点,如 function() 或 method()
JS_OBJECT_EXPRESSION, // 15 JS对象表达式
JS_PROPERTY, // 16 JS属性表达式,如 obj.prop
JS_ARRAY_EXPRESSION, // 17 JS数组表达式,如 [1, 2, 3]
JS_FUNCTION_EXPRESSION, // 18 JS函数表达式,如 function() {}
JS_CONDITIONAL_EXPRESSION, // 19 条件表达式,如 a ? b : c
JS_CACHE_EXPRESSION, // 20 缓存表达式,用于缓存计算结果
// ssr codegen
JS_BLOCK_STATEMENT,
JS_TEMPLATE_LITERAL,
JS_IF_STATEMENT,
JS_ASSIGNMENT_EXPRESSION,
JS_SEQUENCE_EXPRESSION,
JS_RETURN_STATEMENT,
}
示例 v-for
js
<ul>
<li v-for="item in tag" :key="item">信息{{ item }} end</li>
</ul>
一、 parse 解析 @vue/compiler-core



二、转换

json
{
"codegenNode": {
"type": 13,
"tag": "\"li\"",
"props": {
"type": 15,
"properties": [
{
"type": 16,
"key": {
"type": 4,
"content": "key",
"isStatic": true,
"constType": 3
},
"value": {
"type": 4,
"content": "item",
"isStatic": false,
"constType": 0,
"ast": null
}
}
]
},
"children": {
"type": 8,
"children": [
{
"type": 2,
"content": "信息"
},
" + ",
{
"type": 5,
"content": {
"type": 4,
"content": "item",
"isStatic": false,
"constType": 0,
"ast": null
}
},
" + ",
{
"type": 2,
"content": " end"
}
]
},
"patchFlag": 1,
"isBlock": true,
"disableTracking": false,
"isComponent": false
}
}
三、生成代码
js
import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, toDisplayString as _toDisplayString } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("ul", null, [
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.tag, (item) => {
return (_openBlock(), _createElementBlock("li", { key: item }, "信息" + _toDisplayString(item) + " end", 1 /* TEXT */))
}), 128 /* KEYED_FRAGMENT */))
]))
}
ForNode
typescript
interface ForNode extends Node {
type: NodeTypes.FOR
// 存储遍历的数据源表达式
source: ExpressionNode
// 存储遍历项的别名
valueAlias: ExpressionNode | undefined
// 存储键的别名(数组索引或对象键)
// v-for="(value, key) in object":keyAlias 为 { type: NodeTypes.SIMPLE_EXPRESSION, content: 'key' }
keyAlias: ExpressionNode | undefined
// 存储对象索引的别名(仅在遍历对象时使用)
objectIndexAlias: ExpressionNode | undefined
// 存储 v-for 指令的解析结果
parseResult: ForParseResult
// 存储循环的子节点,即 v-for 循环的内容
children: TemplateChildNode[]
// 存储代码生成阶段的中间结果
codegenNode?: ForCodegenNode
}
ForParseResult
typescript
interface ForParseResult {
// 存储 v-for 指令中的数据源表达式
source: ExpressionNode
// 存储 v-for 指令中的值别名
value: ExpressionNode | undefined
// 存储 v-for 指令中的键别名
key: ExpressionNode | undefined
// 存储 v-for 指令中的索引别名(仅在遍历对象时使用)
index: ExpressionNode | undefined
// 标记解析是否已完成
finalized: boolean
}
ForCodegenNode
js
interface ForCodegenNode extends VNodeCall {
// 标记 v-for 生成的节点总是块级节点
isBlock: true
// 指定 v-for 生成的节点标签为 FRAGMENT
tag: typeof FRAGMENT
// 明确 v-for 生成的片段没有属性
props: undefined
// 存储列表渲染的表达式
children: ForRenderListExpression
// 存储补丁标志,用于虚拟 DOM 的更新优化
patchFlag: PatchFlags
// 标记是否禁用块跟踪
disableTracking: boolean
}
ForRenderListExpression
js
interface ForRenderListExpression extends CallExpression {
// 指定调用的函数为 RENDER_LIST 辅助函数
callee: typeof RENDER_LIST
// 第一个元素:ExpressionNode 类型,表示数据源表达式
// 第二个元素:ForIteratorExpression 类型,表示迭代器表达式
arguments: [ExpressionNode, ForIteratorExpression]
}
ForIteratorExpression
js
interface ForIteratorExpression extends FunctionExpression {
// 存储迭代器函数的返回值类型
returns?: BlockCodegenNode
}
示例 v-if
js
<div>
<p v-if="tag === 1">这里是header</p>
<p v-else-if="tag === 2">这里是body</p>
<p v-else>这里是footer</p>
</div>
一、parse 解析
第一个 p 节点

第二个 p 节点

第三个 p 节点

二、转换

第一个分支


第二个分支
三、生成代码
js
import { openBlock as _openBlock, createElementBlock as _createElementBlock, createCommentVNode as _createCommentVNode } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", null, [
(_ctx.tag === 1)
? (_openBlock(), _createElementBlock("p", { key: 0 }, "这里是header"))
: (_ctx.tag === 2)
? (_openBlock(), _createElementBlock("p", { key: 1 }, "这里是body"))
: (_openBlock(), _createElementBlock("p", { key: 2 }, "这里是footer"))
]))
}
IfNode
js
interface IfNode extends Node {
type: NodeTypes.IF
// 存储条件分支数组,每个分支对应一个 v-if、v-else-if 或 v-else 指令
branches: IfBranchNode[]
// 存储代码生成阶段的中间结果
codegenNode?: IfConditionalExpression | CacheExpression // <div v-if v-once>
}
IfBranchNode
js
interface IfBranchNode extends Node {
type: NodeTypes.IF_BRANCH
// 存储分支的条件表达式
condition: ExpressionNode | undefined // else
// 存储分支的子节点,即条件为真时要渲染的内容
children: TemplateChildNode[]
// 存储用户为条件分支提供的键,用于优化虚拟 DOM 更新
userKey?: AttributeNode | DirectiveNode
// 标记该分支是否来自 <template> 元素的 v-if 指令
isTemplateIf?: boolean
}
ConditionalExpression
js
interface ConditionalExpression extends Node {
// 节点类型,值为 19,标识这是一个条件表达式节点
type: NodeTypes.JS_CONDITIONAL_EXPRESSION
// 条件测试表达式,对应三元表达式中的 condition 部分
test: JSChildNode
// 条件为真时的表达式,对应三元表达式中的 trueValue 部分
consequent: JSChildNode
// 条件为假时的表达式,对应三元表达式中的 falseValue 部分
alternate: JSChildNode
// 代码生成时是否需要换行,用于格式化输出
newline: boolean
}
示例 v-on
html
<button
@click="handleClick3"
@click.stop="
handleClick();
handleClick2($event);
"
>
增加
</button>
一、parse 解析



二、转换



三、生成代码
js
import { withModifiers as _withModifiers, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("button", {
onClick: [
_ctx.handleClick3,
_withModifiers($event => {
_ctx.handleClick();
_ctx.handleClick2($event);
}, ["stop"])
]
}, " 增加 ", 8 /* PROPS */, ["onClick"]))
}
示例 v-text
js
<div>
<p v-text="count"></p>
</div>
一、parse

二、转换

三、生成代码
js
import { toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", null, [
_createElementVNode("p", {
textContent: _toDisplayString(_ctx.count)
}, null, 8 /* PROPS */, ["textContent"])
]))
}
示例 插值
js
<div>
<p>{{ tag }}</p>
</div>
一、parse 解析 @vue/compiler-core

二、transform 转换

三、generate 生成代码
js
import { toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", null, [
_createElementVNode("p", null, _toDisplayString(_ctx.tag), 1 /* TEXT */)
]))
}
InterpolationNode
js
interface InterpolationNode extends Node {
type: NodeTypes.INTERPOLATION
content: ExpressionNode
}
示例 v-model
js
<div>
<input v-model="message" type="text" placeholder="请输入" />
</div>
一、parse 解析

二、转换
指令属性

普通属性

三、生成代码
js
import { vModelText as _vModelText, createElementVNode as _createElementVNode, withDirectives as _withDirectives, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", null, [
_withDirectives(_createElementVNode("input", {
"onUpdate:modelValue": $event => ((_ctx.message) = $event),
type: "text",
placeholder: "请输入"
}, null, 8 /* PROPS */, ["onUpdate:modelValue"]), [
[_vModelText, _ctx.message]
])
]))
}
示例 v-pre
js
<div>
<div v-pre>
这是一个pre标签
<p>这里是header</p>
<button @click="handleClick">这里是body</button>
</div>
</div>
一、解析生成AST


二、转换
三、生成代码

示例 原生元素
js
<template>
<p>这里是 tabTwo 组件</p>
</template>
编译结果
js
import { openBlock as _openBlock, createElementBlock as _createElementBlock } from "/node_modules/.vite/deps/vue.js?v=b3e6ce82";
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return _openBlock(), _createElementBlock("p", null, "这里是 tabTwo 组件");
}
【示例】
js
<template>
<p>这里是 tabTwo 组件</p>
<span>这里是 span 组件</span>
</template>
编译结果
js
import { createElementVNode as _createElementVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "/node_modules/.vite/deps/vue.js?v=b3e6ce82";
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return _openBlock(), _createElementBlock(
_Fragment,
null,
[_cache[0] || (_cache[0] = _createElementVNode(
"p",
null,
"这里是 tabTwo 组件",
-1
/* CACHED */
)), _cache[1] || (_cache[1] = _createElementVNode(
"span",
null,
"这里是 span 组件",
-1
/* CACHED */
))],
64
/* STABLE_FRAGMENT */
);
}
示例 元素img
js
<div>
<img src="@/assets/logo.svg" alt="" />
</div>
一、转换

二、转换


静态资源转换

transformAssetUrl函数

getImportsExpressionExp函数

三、生成代码
js
import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "/node_modules/.vite/deps/vue.js?v=2d0c80e8";
import _imports_0 from "/src/assets/logo.svg?import";
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return _openBlock(), _createElementBlock("div", null, [..._cache[0] || (_cache[0] = [_createElementVNode(
"img",
{
src: _imports_0,
alt: ""
},
null,
-1
/* CACHED */
)])]);
}
示例 组件
js
<template>
<div>
<TabTwo :tabName="tabName" tabId="2" />
</div>
</template>
<script setup lang="ts">
import TabTwo from "@/pages/cloud/components/tabTwo.vue";
import { ref } from "vue";
const tabName = ref("tabTwo");
defineOptions({
name: "CloudIndexView",
});
</script>
一、解析

三、生成代码
js
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return _openBlock(), _createElementBlock("div", null, [_createVNode($setup["TabTwo"], {
tabName: $setup.tabName,
tabId: "2"
}, null, 8, ["tabName"])]);
}
示例 插槽
一、解析
父组件

二、转换
父组件


子组件

遍历节点




三、生成代码
父组件
js
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return _openBlock(), _createElementBlock("div", null, [_createVNode($setup["TabTwo"], {
tabName: $setup.tabName,
tabId: "2"
}, {
default: _withCtx(() => [..._cache[0] || (_cache[0] = [_createElementVNode(
"p",
null,
"插槽内容",
-1
/* CACHED */
)])]),
_: 1
}, 8, ["tabName"])]);
}
子组件
js
import { createElementVNode as _createElementVNode, renderSlot as _renderSlot, openBlock as _openBlock, createElementBlock as _createElementBlock } from "/node_modules/.vite/deps/vue.js?v=2d0c80e8"
function _sfc_render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", null, [
_cache[0] || (_cache[0] = _createElementVNode("p", null, "这里是 tabTwo 组件", -1 /* CACHED */)),
_renderSlot(_ctx.$slots, "default")
]))
}
示例 v-html
一、解析

二、转换



三、生成代码

js
import { openBlock as _openBlock, createElementBlock as _createElementBlock } from "/node_modules/.vite/deps/vue.js?v=2d0c80e8";
const _hoisted_1 = ["innerHTML"];
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return _openBlock(), _createElementBlock("div", { innerHTML: $setup.title }, null, 8, _hoisted_1);
}