Vue 3.x 单文件组件(SFC)模板编译过程解析

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-ifv-forv-modelv-on 等指令,并将其转换为对应的代码生成节点。

对于插值表达式 {{ msg }} 或指令中的动态表达式,编译器会分析其内容,并在生成代码时添加上下文前缀。

静态提升是 Vue 3 最重要的优化之一。编译器会识别出不依赖任何响应式数据的纯静态节点 (如没有绑定、没有插值、没有指令的 <div>),并将其提升到渲染函数之外。

转换器会为每个动态节点 标记一个 PatchFlag(补丁标志),指示该节点在更新时哪些部分可能发生变化。这是一个位掩码,运行时可以快速判断需要比较的内容。

生成(Generate):从 AST 到 render 函数

生成阶段是编译的最后一步,它的任务是将转换后的 AST 转换成 JavaScript 代码字符串(即 render 函数)。

generate 函数会递归遍历 AST,调用不同的代码生成函数(genNodegenElementgenExpression 等)拼接字符串。

枚举 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);
}

最后

  1. 在线编译工具
相关推荐
kyriewen2 小时前
React性能优化:从“卡成狗”到“丝般顺滑”的5个秘诀
前端·react.js·性能优化
helloweilei2 小时前
Web Streams 简介
前端·javascript
悟空瞎说2 小时前
Flutter热更新 Shorebird CodePush 原理、实现细节及费用说明
前端·flutter
didadida2622 小时前
从“不存在”的重复请求,聊到 Web 存储的深坑
前端
xiaominlaopodaren2 小时前
Three.js 渲染原理-透明渲染为什么这么难
前端
米丘2 小时前
vue3.x 内置指令有哪些?
前端·vue.js
米丘2 小时前
Vue 3.x 模板编译优化:静态提升、预字符串化与 Block Tree
前端·vue.js·编译原理
We་ct2 小时前
HTML5 原生拖拽 API 基础原理与核心机制
前端·javascript·html·api·html5·浏览器·拖拽
是上好佳佳佳呀2 小时前
【前端(八)】CSS3 属性值笔记:渐变、自定义字体与字体图标
前端·笔记·css3