事前准备
首先例行编译vue的sourcemap:
- 克隆或下载 vue官方源码
npm i -g rollup
- 修改
package.json
里的dev
,添加 sourcemap
我们准备如下内容,方便我们调试rollup -w -c scripts/config.js
--sourcemap
--environment TARGET:web-full-dev
<!DOCTYPE html>
<html>
<head>
<title>Vue源码剖析</title>
<script src="/dist/vue.js"></script>
</head>
<body>
<div id="demo">
<!-- 静态节点,不需要比对 -->
<h1>Vue模板<span>编译</span></h1>
<p v-if="foo">{{foo}}</p>
<comp></comp>
</div>
<script>
Vue.component("comp", {
template: "<div>I am comp</div>",
});
// 创建实例
const app = new Vue({
el: "#demo",
data: { foo: "foo" },
});
// 输出render函数
console.log(app.$options.render);
</script>
</body>
</html>
思路大纲
- 模板从哪里来? render > template > el
- 准备 src/compiler/index.js#compilerToFunction 用来把模板变成render函数
const render= compileToFunctions(template);
- 这里要用到ast,就是用js来描述html语法,相比后面的vdom,前者更为底层
- 解析为ast的过程是一堆正则,头疼,最终得到ast
- 处理静态节点标记
- 准备生成代码generate,最终目的拿到 h 渲染函数,具体细节是字符串拼装
字符串转函数,同时拥有指定的作用域 用到了with 和 new Function
export function compileToFunctions(template) {
parseHTML(template);
let code = generate(root);
let render = `with(this){return ${code}}`;
let renderFn = new Function(render);
return renderFn
}
以上是模板编译,接下来考虑挂载技术实现
准备
src/lifecycle.js
中的mountComponent
,利用_render
得到虚拟DOM,这里就到renderMixin
方法给Vue拓展_render
方法了- 这里就是对
_v
创建文本,_c
创建元素 _s _render返回vnode - vnode用来描述虚拟DOM,和 sanbbdom一样的概念
_update
把虚拟DOM变为真实DOM,替换操作 用到了patch方法- 第一次patch,oldVNode为空节点,是直接挂载
以上就是vue初渲染流程。
源码分析
entry-runtime-with-compiler.js
因为我们使用的 vue 打包编译器+运行时。通过之前的介绍,可以进入
src/platforms/web/entry-runtime-with-compiler.js
因为我们是走的 template 模板,会看到 类似 if(template)
的判断代码。
如图所示已经拿到了模板内容,接下来就是如何解析了。
我们在 compileToFunctions
打断点,进入。
to-function.js
我们进入 compileToFunctions
内部,发现返回了一个function,这里对 options
做了修改,不重要。
最终看到,把 template传给 compile
函数。
进入这个函数。
create-compiler.js
返现还是包装了一层 function,跳过,定位到这一行:
const compiled = baseCompile(template.trim(),finalOptions)
发现,之前的操作其实是对options做修正,终于要进入编译了。我们进入这个函数
compiler/index.js
终于对 template这个字符串进行操作了。整个编译分三步: 解析 + 优化 + 生成。
解析
于是就有了这行代码:
const ast = parse(template.rom().options)
这里忽略 parse的具体实现,最终我们拿到了 ast,如上图所示:这是一个对象,通过空间换时间,我们对常用的属性做了解析。
优化
我们的template里有一些静态的内容,比如静态节点,后续vue不应当关心这些内容,因为不会发生变化。
我们会在截图的 Line17 做了 optimize 做了优化。
优化的结果是,多了 static 标记。后续看到 static 如果是 true,干脆就不关注了。
之前看vue作者再b站开直播,讲到vue3会对这一块做更细致的操作。
生成
通过 generate
操作,我们就拿到了 render函数。
这样 渲染函数拿到了,就可以执行后续的操作了。注意这里是 字符串。
to-function
拿到 字符串渲染函数了,后续如何render呢,我们退回 to-funciton
这个文件,会看到
res.render = createFunction(compiled.render,funGenErrors)
刚才的截图已知, compiled.render
是一个字符串。我们关注 createFunction
是如何操作的。
定位到 function createFunction
我们发现核心代码:
return new Function(code)
直接把 字符串变为Function。
这样整个渲染函数的流程就走通了。
整体流程如下: