vue3-compile编译原理(一)-- 脱胎换骨

3,519 阅读3分钟

背景引入

   vue3的学习已经有一段时间了,本着奔浪不奔就可能变成后浪的态度,学习梳理了下vue3的编译,基于vue3的整个编译源码的学习,准备把编译源码按照编译过程分三部分记录学习下来,同各位奔浪一起奔跑.....

一、编译入口及流程

1、目录结构

上面是整个代码的目录结构以及相关文件的主要作用,与模板编译相关的核心代码是compiler-core 中的整个目录,vue3源码连接奉上:github.com/vuejs/vue-n…

2、编译入口
/vue/src/index.ts

在完整版的 index.js 中,调用了 registerRuntimeCompilercompile 注入编译方法,即为模板编译的入口,当然其中还有一些其他的判断,有兴趣的可以进入继续查看源码

二、模板编译流程

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() //解析属性