背景引入
vue3的学习已经有一段时间了,本着奔浪不奔就可能变成后浪的态度,学习梳理了下vue3的编译,基于vue3的整个编译源码的学习,准备把编译源码按照编译过程分三部分记录学习下来,同各位奔浪一起奔跑.....
一、编译入口及流程
1、目录结构
上面是整个代码的目录结构以及相关文件的主要作用,与模板编译相关的核心代码是compiler-core 中的整个目录,vue3源码连接奉上:github.com/vuejs/vue-n…
2、编译入口
/vue/src/index.ts
在完整版的 index.js
中,调用了 registerRuntimeCompiler
将 compile
注入编译方法,即为模板编译的入口,当然其中还有一些其他的判断,有兴趣的可以进入继续查看源码
二、模板编译流程
vue2编译
vue3编译
整个编译流程其实主要分为三部分:整个编译过程跟vue2的编译的过程有
template模板字符串 ----》编译为最基础的AST ----> transform基础AST增加patchflag等熟悉 ----》 generate基于AST生成render函数
2、vue2 vs vue3
<div name="test">
<!-- 这是注释 -->
<p>{{ test }}</p>
一个文本节点
<div>good job</div>
</div>
vue2编译后结果:
function render() {
with(this) {
return _c('div', {
attrs: {
"name": "test"
}
}, [_c('p', [_v(_s(test))]), _v("\n 一个文本节点\n "), _c('div', [_v(
"good job")])])
}
}
vue3编译后结果:
import {
createCommentVNode as _createCommentVNode,
toDisplayString as _toDisplayString,
createVNode as _createVNode,
createTextVNode as _createTextVNode,
openBlock as _openBlock,
createBlock as _createBlock
} from "vue"
const _hoisted_1 = {
name: "test"
}
const _hoisted_2 = /*#__PURE__*/ _createTextVNode(" 一个文本节点 ")
const _hoisted_3 = /*#__PURE__*/ _createVNode("div", null, "good job", -1 /* HOISTED */ )
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", _hoisted_1, [
_createCommentVNode(" 这是注释 "),
_createVNode("p", null, _toDisplayString(_ctx.test), 1 /* TEXT */ ),
_hoisted_2,
_hoisted_3
]))
}
通过对比发现最终都返回render函数,但是render函数的结构和样式上有了很大的差别,同时有了特殊的含义注释,在接下来的过程中我们会一点点接口神秘面纱....
3、源码初现
源码的第一部分 baseParse主要的作用就是将 template模板字符串编译为基础AST树,但是我们可以思考下他说怎么转换生成的呢?
(1)、类比前端模板引擎
(2)、类比babel编译过程
(3)、vue3解析
之所以用前端模板和bable的编译过程来类别vue3的编译,其实他们是有很多共性的,
比如:前端模板编译
const data = {
'title':'模板标题',
'content':'模板内容'
}
const temp = '
<div>{{d.title}}</div>
<div>{{d.content}}</div>
这种 data + temp---》html的 很容易想到基于正则的进行匹配替换。
比如:bable编译
function test(){
const a = b
}
这个函数字符串如何进行词法解析 、预发解析呢?其实也是基于正则不断的对字符串进行选择、截取、 匹配.....直到最有一个字符完成,function等关键字等同于div等标签的关键字,函数的"{"(开始左括号) 和 ”}“(结束右括号) 等同于div标签的"
vue3中将template--->baseParse解析为AST的过程也是一样的:
4、父子关系
单纯的正则匹配和解析只是获取了标签本身的属性、特性等数据,但是template本身的标签是有父子层级关系的,标签与标签直接的关系怎么来维护呢?了解过webkit渲染原理的应该很容易想到,栈:一个先进后出的数据结构
//解析过程中:
<div name="test">
<!-- 这是注释 -->
<p>{{ test }}</p>
一个文本节点
<div>good job</div>
</div>
总结:
vue3的第一部分其实是不断将模板字符串通过正则去解析、截取、匹配特殊数据和属性......过程,同时在解析中用栈的形式维护保存正在解析的标签
- parseChildren() // 主入口
- parseInterpolation() //解析插值表达式
- parseComment() //解析注释
- parseBogusComment() //解析文档声明
- parseTag() //解析标签
- parseText() //解析普通文本
- parseAttribute() //解析属性