第 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 版权所有
