前言
最近在学习Vue2的源码,打算将学到的东西记录下来并分享给大家,与一起学习与进步。
一、准备
- 克隆Vue2源码:
git clone https://github.com/vuejs/vue.git - 安装依赖:
npm i - 安装rollup,因为Vue是用rollup进行打包的。
npm i -g rollup - 修改package.json文件中的dev命令脚本,在其中添加一个
sourcemap即可,这一步是为了在浏览器进行断点调试时,改完后如下:
"dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev"
到这里,基本的准备工作就完成了。
二、目录结构
接下来,再来了解一下目录结构,这样才能知道从何下手以及找到对应的代码文件。
图2-1
图中标注了主要的目录名称,其中最主要的当属src源码这个目录,这个目录里面就是实现Vue的核心代码文件,如下图:
当然,这里没有将每个目录都介绍一遍,只是将主要的先标注下,其他的后续遇到了再说明。
三、找到打包入口
在看别人的源码时,我们一般都会先从打包入口文件开始,再一步一步往下看。读Vue源码亦是如此,
我们可以打开package.json,因为Vue打包是通过dev这个命令去执行的,而要打包时必然要去执行一个配置文件,那么从命令中可以看出,他是去执行的scripts/config.js这个配置文件,如下图:
紧接着,我们可以在上图中看到dev命令中的末尾表明了要执行哪个目标,由于Vue会根据不同的场景打包出不同的版本,所以会设置众多环境变量。而dev要打包的是针对web的开发完整版(包含运行时和编译器),即web-full-dev。
我们可以复制这个文件的路径,在vscode中使用ctrl+p快捷键,进行文件查找,然后打开,搜索web-full-dev,可以看到如下图中的代码:
图中表明了web-full-dev这个版本对应的打包入口文件路径,打包路径,对应的环境变量等信息,而我们要找的文件就是对应的web/entry-runtime-with-compiler.js,而直接这么去找,我们发现是找不到的,这是因为Vue他使用了别名,省掉了路径的前缀,通过图中的resovle方法,我们可以找到如下图对应的代码:
通过这个方法,他就能获取到对应的文件,打开alias这个别名文件,我们就能看到下图中的代码:
由上操作我们可以得到打包入口文件完整的相对路径就是src\platforms\web\entry-runtime-with-compiler.js
四、理解打包入口的作用
打开文件,我们定位到Vue.prototype.$mount方法这里来,我将多余的代码进行了删减,对于比较重要的地方进行了相关注释说明,大家可以对照源码文件的对应代码,这样能够看的更加清晰明了。
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
// $options即new Vue中传入的对象
const options = this.$options
/*
下列代码块的目的是为了处理编译这件事,
我们知道new Vue中传入的对象中可以是render函数,template标签模板,el选项,进行渲染,
如:
const app = new Vue({
el: '#app',
templete: '#app',
templete: '<div>templete 你好</div>',
render() {
return h('div', 'render 你好')
},
data: {
str: '你好'
}
})
根据下列代码的if先后顺序可得,他的处理顺序为:render -> template -> el选项
只要满足了其中一个渲染方式,后面的都不再执行
*/
if (!options.render) {
let template = options.template
// 如果$options中有template
if (template) {
if (typeof template === 'string') {
// 如果template属性值是以#开头的,则表示这是一个id选择器
if (template.charAt(0) === '#') {
// 定位到idToTemplate方法,可知Vue是将其对应的元素的innerHTML进行了渲染
template = idToTemplate(template)
}
} else if (template.nodeType) {
// 如果template的属性值是node元素,则直接渲染其innerHTML
template = template.innerHTML
} else {
return this
}
} else if (el) {
// 如果传入的是el选择,则通过getOuterHTML方法,将内容渲染到对应的元素内
template = getOuterHTML(el)
}
// 如果存在templete,则编译它,获取render函数
if (template) {
// 编译过程:templete -> render
const {
render,
staticRenderFns
} = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
// 将获取到的渲染函数赋值给选项,将来使用
options.render = render
options.staticRenderFns = staticRenderFns
}
}
// 执行默认的挂载操作
return mount.call(this, el, hydrating)
}
看完上述代码与注释说明,大概就清楚了它的作用,接下来,我们看下以下代码示例,如果用户同时设置了el,template,render函数,那么它将会怎么执行,以及它判断的先后顺序
const app = new Vue({
el: '#app',
templete: '#app',
templete: '<div>templete 你好</div>',
render() {
return h('div', 'render 你好')
},
data: {
str: '你好'
}
})
通过刚才对打包入口文件中的理解,我们可以知道,它会先判断$option配置项中有没有render函数,如果没有,就去判断有没有template,如果没有,则去判断有没有el选项。
即:render -> template -> el选项
只要满足上述中的一个条件,就直接进行渲染,后续条件将不再判断与执行。
五、总结
入口文件:
- 路径:
src\platforms\web\entry-runtime-with-compiler.js - 作用:处理render函数 / template / el选项,处理编译这件事
以上就是阅读Vue源码的相关准备,以及我对打包入口文件的学习与理解,如有不对,请多指正,谢谢大家。