开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情
前言
vue3都出来了,我甚至都没有看过vue2完整的源码(只是了解过简化版的vue的部分源码),感觉很是羞愧。借此机会,鞭策自己,每天抽点时间打卡vue源码。 预计会十篇文章来记录,鞭策自己!
let's do it !!!
准备工作:下载源码
首先打开github上vue的源码,现在使用2.6.x中最新的版本是:2.6.14(2.7.x版本是兼容vue3的特性,这次学习不升级考虑)
三部曲
点击
code,点击Download ZIP,点击 解压缩,通过webstorm打开项目
目录结构
利用tree操作生成一个树结构(本人记录的一个操作小记录),然后挑挑拣拣,如下:
D:.
├─benchmarks 性能基准测试
├─dist 打包输出目录
├─examples 测试案例
├─flow flow语法的类型声明
├─packages 额外的包
├─scripts 配置文件目录
├─src 源码目录(重点)
│ ├─compiler 编译器
│ │ ├─codegen 吧AST转换成render
│ │ ├─directives 生成render函数钱处理的
│ │ └─parser 解析编译
│ ├─core 运行时的核心包
│ │ ├─components 内部封装的全局组件
│ │ ├─global-api 内部全局的API
│ │ ├─instance Vue实例相关的
│ │ ├─observer 响应式原理
│ │ ├─util 工具方法
│ │ └─vdom 虚拟dom相关,著名的patch
│ ├─platforms 平台相关的编译器代码
│ ├─server 服务器渲染相关
│ ├─sfc 转换成单文件处理
│ └─shared 全局共享的工具常量
├─test 测试目录
└─types TS类型声明
启动
打开package.json,可以看到scripts中dev,现在改动一下:[加了一个--sourcemap]
sourcemap: 本质是一个信息文件,存储代码转换前后的对应位置关系,是源代码和生产代码的映射,在开发环境下可以使开发者更方便的调试。但是生产环境不需要配置,以免代码暴露泄露
"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev --sourcemap",
npm run dev //启动vue源码项目
// 接下来可以在exanples新建一个最简单的测试文件:test/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vue-core-test1</title>
<script src="../../dist/vue.js"></script>
</head>
<body>
<div id="app">
{{ message }}
</div>
<script>
debugger
new Vue({
el: '#app',
data() {
return {
message: 'hello world'
}
}
})
</script>
</body>
</html>
寻找入口文件
"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev --sourcemap",
从上面这句话可以分解出几个内容,现在分别解释一下:
- rollup -w -c : rollup是一个javascript模块打包器,-w:rollup的配置,启动监听模式,文件更新会自动打包;-c:指定rollup打包的配置文件
- scripts/config.js : 打包的入口文件
- --environment : 运行的、打包的环境,通过node中的process.env获取
- TARGET:web-full-dev: 作为一个配置,在打包文件中用于参数查找
scripts/config.js
//第三步
const builds = { // 定义一个对象
...
'web-full-dev': {
entry: resolve('web/entry-runtime-with-compiler.js'),
dest: resolve('dist/vue.js'),
format: 'umd',
env: 'development',
alias: { he: './entity-decoder' },
banner
},
...
}
// 第二步
function genConfig (name) {
const opts = builds[name]
const config = {
...
}
....
return config
}
// 第一步
if (process.env.TARGET) {
module.exports = genConfig(process.env.TARGET)
} else {
exports.getBuild = genConfig
exports.getAllBuilds = () => Object.keys(builds).map(genConfig)
}
启动 npm run dev,rollup开始打包,进入
scripts/config.js,进入第一步,判断TARGET存在,进入第二步,genConfig('web-full-dev'),找到第三步中的‘web-full-dev’,entry为入口文件
src/platforms/web/entry-runtime-with-compiler.js
可以看到该文件主要是对$mount的一个重新定义,添加了对模板的一些处理办法
- 没有template,根据el中找
- 有template,直接用
- ...
对应的就是我们vue2项目的main.js中:
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
入口文件解析:entry-runtime-with-compiler.js
<script>
const vm = new Vue({
el: '#app',
data() {
return {
message: 'hello world'
}
}
})
vm.$mount('#app')
console.log(vm.$options.render)
</script>
// 作用:将VUe的html模板编译成render函数
const mount = Vue.prototype.$mount // $mount 在Vue实例上
Vue.prototype.$mount = function (
el?: string | Element, // 接收el 容器的id,一般就是‘#app’
hydrating?: boolean
): Component {
el = el && query(el)
...
// options: 实例提供的实参比如:{router,store,...}
const options = this.$options
if (!options.render) {
let template = options.template
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
template = idToTemplate(template)
}
} else if (template.nodeType) {
template = template.innerHTML
} else {
return this
}
} else if (el) {
template = getOuterHTML(el) // 根据id找到outerHtml,也就是html模板
}
if (template) {
// 进入到此方法,编译html模板生成render
const { render, staticRenderFns } = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render // 这样就可以通过vm.$options.render得到上图打印的结果
options.staticRenderFns = staticRenderFns
}
}
return mount.call(this, el, hydrating)
}
实例化Vue的时候提供了render,template,el,Vue的优先级是render > template> el
在此过程中compileToFunctions这个函数是最重要的
- 将html模板解析成AST
- 对AST优化
- 根据AST生成render函数
后记
本文仅作为自己一个阅读记录,具体还是要看大佬们的文章