第 28 期 - Vue3 编译器 compile 基础内容解析
摘要
本文介绍了 Vue3 编译器 compile 相关内容,包括编译目的、步骤如 AST 生成、转换、函数生成,还涉及相关函数构建转换、有限状态机概念、框架搭建等方面的知识。
一、编译目的与基础示例
在 Vue3 中,编译的目的是把模板代码转换成能被 render 函数渲染的代码。例如有一个模板const template =
<div>hello world</div>,通过
const renderFn = compile(template)就能得到可用于渲染的
renderFn`。
二、编译的四个步骤
(一)错误分析与前置知识
- 抽象语法树 AST
- 它是源代码语法结构的抽象表示,类似之前的 VNode 或 h 函数,但包含更多信息,如 Vue 指令等。一个简单的 AST 示例如下:
{ "type": 0, "children": [ { "type": 1, "tag": "div", "tagType": 0, "props": [], "children": [ { "type": 2, "content": "hello world" } ] } ], "loc": {} }
- JavaScript AST
- 这是在 Vue 编译过程中生成的,相比 AST 增加了和 render 函数生成相关的属性,如
codegenNode
。
{ "type": 0, "children": [ { ...... "codegenNode": { "type": 13, "tag": ""div"", "props": [], "children": [ { "type": 2, "content": "hello world" } ] } } ], "loc": {}, "codegenNode": { "type": 13, "tag": ""div"", "props": [], "children": [ { "type": 2, "content": "hello world" } ] }, "helpers": [null], "components": [], "directives": [], "imports": [], "hoists": [], "temps": [], "cached": [] }
- 这是在 Vue 编译过程中生成的,相比 AST 增加了和 render 函数生成相关的属性,如
- 有限状态机
- 有限状态机有三个要点:状态有限、某一时刻只处于一种状态、满足条件可从一种状态切换到另一种状态。以
<div>hello world</div>
为例,其有限状态机的状态转换分起始标签解析、文本解析、终止标签解析三步,可获取三个tokens
:开始标签<div>
、文本节点hello world
、结束标签</div>
,这个获取tokens
的过程叫模板的标记化。
- 有限状态机有三个要点:状态有限、某一时刻只处于一种状态、满足条件可从一种状态切换到另一种状态。以
(二)用 tokens 构建 AST 的方法
- 框架搭建
- 整个编译器
compile
本质上调用baseCompile
方法,baseCompile
又分成三个大步骤:AST 对象生成(baseParse
方法)、AST 转换成 JavaScript AST(transform
方法)、根据 JavaScript AST 生成render
函数(generate
方法)。
- 整个编译器
- AST 对象生成
- context 实例构建:
context
实例的构建是把模板包裹进对象,方便后续识别token
。例如function createParserContext(content: string): ParserContext
函数就是创建ParserContext
对象的。 - 扫描 token 生成 AST 结构:核心是
parseChildren
方法,它从左到右逐个识别source
,结合有限状态机判断节点类型。其中包含判断方法(如isEnd
、startsWithEndTagOpen
、startsWith
)和处理方法(如advanceBy
)。 - 节点内容的提取处理
- 节点内容提取(
parseElement
方法):用于解析开始/结束标签,并对节点内内容用parseChildren
递归处理。 - 文本内容提取(
parseText
方法):把文本节点完整取出,之后光标右移做下一个token
识别。
- 节点内容提取(
- AST 结构挂载到 Root 节点:在
baseParse
方法中,将生成的 AST 结构挂载到新创建的Root
节点。
- context 实例构建:
(三)AST 转换为 JavaScript AST
- 转换规则与框架搭建
- 转换时要遵守深度优先和父节点
nodeType
受子节点影响的规则。baseCompile
方法先添加好transform
占位,transform
方法框架包括创建上下文对象context
、深度优先转换node
节点、处理根节点信息转化。
- 转换时要遵守深度优先和父节点
- 上下文对象创建:
context
对象包含转换节点的方法列表和其他记录信息。 - 深度优先处理节点
- 核心方法是
traverseNode
和traverseChildren
,traverseNode
的switch
语句体现深度优先。
- 核心方法是
- 转化方法的实现
transformElement
实现:只对ELEMENT
节点处理,给节点新增codegenNode
属性。transformText
实现:核心是把相邻文本内容拼接成表达式。- 根节点转化:把根节点的
codegenNode
属性和其child
的codegenNode
保持一致,还需往根节点挂载helpers
的方法名称。
(四)render 函数生成
- 概念明确:函数本身是一段字符串,
render
函数生成大方向是拼接生成render
函数字符串和把字符串转换成真正的函数。 - 拼接 render 函数字符串
- codegenContext 上下文生成:用来存储已拼接的
render
函数字符串、Vue 运行时全局变量名等信息。 - 前置代码生成:例如
const _Vue = Vue
这部分,由genFunctionPreamble
方法实现。 - render 方法名和参数拼接:方法名和参数用变量形式处理。
- render 中使用的方法解构&重命名:从
helper
中取出方法并统一重命名。 - render 中的方法的调用&参数配置:最复杂的部分,核心在于
genNode
方法,根据不同类型(如文本类型、节点类型)做不同处理。
- codegenContext 上下文生成:用来存储已拼接的
- 转换 render 函数字符串为真实函数:用
Function
构造函数可将拼接好的render
函数字符串转换成真实函数,但 Vue3 源码内部已在vue - compact
模块中封装好了这个转换过程。
扩展阅读
Made by 捣鼓键盘的小麦 / © 2025 Front Talk 版权所有