写作前面
自抛弃jq框架起,一直用vue,写个大大小小的项目,当然也写过不少bug,对源码偶尔也会深入,但是一直没有系统的归纳和整理。所以鄙人打算在掘金做个源码小记录,也当做个笔记吧,对了本人一直项目都是vue2+,所以还是从最熟悉的2.+源码开始讲,磨刀不误砍柴工,我们就从flow开始吧,当然也会有小伙伴说都3.0了,还有必要看2.+的源码吗?额,这个见仁见智吧
flow
Flow 是 facebook 出品的 JavaScript 静态类型检查工具。Vue.js2.0 的源码利用了 Flow 做了静态类型检查,所以了解 Flow 有助于我们阅读源码。当然2021年的我们肯定会问,ts不香吗为啥要用flow, 以下是尤大大的回答:
总结就是生态、成本,总而言之,存在即合理,划重点哦,
Flow 的工作方式
通常类型检查分成 2 种方式:
1.类型推断:通过变量的使用上下文来推断出变量类型,然后根据这些推断来检查类型。
2.类型注释:事先注释好我们期待的类型,Flow 会基于这些注释来判断。
类型判断
它不需要任何代码修改即可进行类型检查,最小化开发者的工作量。它不会强制你改变开发习惯,因为它会自动推断出变量的类型。这就是所谓的类型推断,Flow 最重要的特性之一。
通过一个简单例子说明一下:
/*@flow*/
function split(str) {
return str.split(' ')
}
split(11)
复制代码
Flow 检查上述代码后会报错,因为函数 split 期待的参数是字符串,而我们输入了数字。
类型注释
如上所述,类型推断是 Flow 最有用的特性之一,不需要编写类型注释就能获取有用的反馈。但在某些特定的场景下,添加类型注释可以提供更好更明确的检查依据。
考虑如下代码:
/*@flow*/
function add(x, y){
return x + y
}
add('Hello', 11)
复制代码
Flow 检查上述代码时检查不出任何错误,因为从语法层面考虑, + 即可以用在字符串上,也可以用在数字上,我们并没有明确指出 add() 的参数必须为数字。
在这种情况下,我们可以借助类型注释来指明期望的类型。类型注释是以冒号 : 开头,可以在函数参数,返回值,变量声明中使用。
如果我们在上段代码中添加类型注释,就会变成如下:
/*@flow*/
function add(x: number, y: number): number {
return x + y
}
add('Hello', 11)
复制代码
现在 Flow 就能检查出错误,因为函数参数的期待类型为数字,而我们提供了字符串。
数组
/*@flow*/
var arr: Array<number> = [1, 2, 3]
arr.push('Hello')
复制代码
数组类型注释的格式是 Array,T 表示数组中每项的数据类型。在上述代码中,arr 是每项均为数字的数组。如果我们给这个数组添加了一个字符串,Flow 能检查出错误。
更多就参考官方文档吧,机票不谢
Vue整个目录结构介绍
为了直观的查看目录我们可以通过tree命令来查看src目录下的文件夹。先大概对源码的结构有一个大体的认识,
├─compiler # 编译的相关逻辑
│ ├─codegen
│ ├─directives
│ └─parser
├─core # vue核心代码
│ ├─components # vue中的内置组件 keep-alive
│ ├─global-api # vue中的全局api
│ ├─instance # vue中的核心逻辑
│ ├─observer # vue中的响应式原理
│ ├─util
│ └─vdom # vue中的虚拟dom模块
├─platforms # 平台代码
│ ├─web # web逻辑 - vue
│ │ ├─compiler
│ │ ├─runtime
│ │ ├─server
│ │ └─util
│ └─weex # weex逻辑 - app
│ ├─compiler
│ ├─runtime
│ └─util
├─server # 服务端渲染模块
├─sfc # 用于编译.vue文件
└─shared # 共享的方法和常量
复制代码
complier
这里包含了vue所有编译的代码,我们平时写的最多的组件模板tempalte,就是解析成 ast 语法树,当然这里还有ast 语法树优化,代码生成等功能。
编译的工作可以在构建时做(借助 webpack、vue-loader 等辅助插件);也可以在运行时做,使用包含构建功能的 Vue.js。显然,编译是一项耗性能的工作,所以更推荐前者——离线编译。
core
core 目录包含了 Vue.js 的核心代码,包括内置组件、全局 API 封装,Vue 实例化、观察者、虚拟 DOM、工具函数等等。
这里的代码可谓是 Vue.js 的灵魂,也是我们之后需要重点分析的地方。
platforms
Vue.js 是一个跨平台的 MVVM 框架,它可以跑在 web 上,也可以配合 weex 跑在 natvie 客户端上。platform 是 Vue.js 的入口,2 个目录代表 2 个主要入口,分别打包成运行在 web 上和 weex 上的 Vue.js。
server
Vue.js 2.0 支持了服务端渲染,所有服务端渲染相关的逻辑都在这个目录下。注意:这部分代码是跑在服务端的 Node.js,不要和跑在浏览器端的 Vue.js 混为一谈。
服务端渲染主要的工作是把组件渲染为服务器端的 HTML 字符串,将它们直接发送到浏览器,最后将静态标记”混合”为客户端上完全交互的应用程序。
sfc
通常我们开发 Vue.js 都会借助 webpack 构建, 然后通过 .vue 单文件的编写组件。
shared
Vue.js 会定义一些工具方法,这里定义的工具方法都是会被浏览器端的 Vue.js 和服务端的 Vue.js 所共享的。
这个目录下的代码逻辑会把 .vue 文件内容解析成一个 JavaScript 的对象。
打包流程
找到入口文件
首先我们可以在package.json 文件中,找到构建脚本命令,然后从命令中找到对应的入口scripts/build.js
{
"script": {
"build": "node scripts/build.js",
"build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer",
"build:weex": "npm run build --weex"
}
}
复制代码
找到文件
let builds = require('./config').getAllBuilds()
// filter builds via command line arg
if (process.argv[2]) {
const filters = process.argv[2].split(',')
builds = builds.filter(b => {
return filters.some(f => b.output.file.indexOf(f) > -1 || b._name.indexOf(f) > -1)
})
} else {
// filter out weex builds by default
builds = builds.filter(b => {
return b.output.file.indexOf('weex') === -1
})
}
build(builds)
复制代码
大概意思就是从config获取配置文件,然后从命令中获取参数,过滤成我们想要的参数,最后可以构建出不同用途的vue.js
根据配置产出dist
大家可以看看dist目录
config.js文件
我们现在回头看看刚刚读取的配置文件require(‘./config‘).getAllBuilds()
const builds = {
// Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify
'web-runtime-cjs-dev': {
entry: resolve('web/entry-runtime.js'),
dest: resolve('dist/vue.runtime.common.dev.js'),
format: 'cjs',
env: 'development',
banner
},
'web-runtime-cjs-prod': {
entry: resolve('web/entry-runtime.js'),
dest: resolve('dist/vue.runtime.common.prod.js'),
format: 'cjs',
env: 'production',
banner
}
....
复制代码
这里说明一下,其实这里是遵循rollup的规则,。其中 entry 属性表示构建的入口 JS 文件地址,dest 属性表示构建后的 JS 文件地址。format 属性表示构建的格式,cjs 表示构建出来的文件遵循 CommonJS 规范,es 表示构建出来的文件遵循 ES Module 规范。 umd 表示构建出来的文件遵循 UMD 规范。
其实顺藤摸瓜,仔细的你会发现,config文件构建入口就是platforms文件夹下面的web和wexx的js,这里举栗子说明一下
src/platforms/web/entry-runtime.js
src/platforms/web/entry-runtime-with-compiler.js
复制代码
我们可以通过打包的配置找到我们需要的入口,这两个区别在于是否涵盖compiler逻辑,我们在开发时一般使用的是entry-runtime,可以减小vue的体积,但是同样在开发时也不能再使用template,.vue文件中的template是通过vue-loader来进行编译的,和我们所说的compiler无关哈。
new Vue({
template:`<div></div>`
})
复制代码
这样的template必须要使用带compiler的入口才能进行模板的解析
今天先记录到这边吧