随着vue3.x的正式版推出,再加上长时间的使用vue,本着对本质的追求,学习总结一下源码的学习。考虑到3.x的相关环境还不是很稳定,未来一段时间,大多数时候用的应该还是2.x,先从2.x版本源码学习,相对网上参考资料也多。总结结合知识汇总下。
Flow
Flow 是 facebook 出品的 JavaScript 静态类型检查工具。Vue.js 的源码利用了 Flow 做了静态类型检查,所以了解 Flow 有助于我们阅读源码。
类似的还有TypeScript,这里推荐下https://ts.xcatliu.com/,相对官网文档,更容易学习TS
类型检测
类型检测的优点是有目共睹的,对于js其灵活性既是优点也有缺陷,过于灵活的副作用是很容易就写出非常隐蔽的隐患代码,在编译期甚至看上去都不会报错,但在运行阶段就可能出现各种奇怪的 bug。
类型检查,就是在编译期尽早发现(由类型错误引起的)bug,又不影响代码运行(不需要运行时动态检查类型),使编写 JavaScript 具有和编写 Java 等强类型语言相近的体验。
为什么用flow
Vue.js 在做 2.0 重构的时候,在 ES2015 的基础上,除了 ESLint 保证代码风格之外,也引入了 Flow 做静态类型检查。之所以选择 Flow,主要是因为 Babel 和 ESLint 都有对应的 Flow 插件以支持语法,可以完全沿用现有的构建配置,非常小成本的改动就可以拥有静态类型检查的能力。
备注:这里个人推荐多人协作使用ts,个人或者需要类型检测可灵活配置时使用flow。
语法
这个简单写一下相关语法,熟悉ts或者后台语言,很容易理解这方面的知识。具体没提到的推荐在官网查看。
工作方式
- 类型推断:通过变量的使用上下文来推断出变量类型,然后根据这些推断来检查类型。
- 类型注释:事先注释好我们期待的类型,Flow 会基于这些注释来判断。
类型推断
不需要任何代码修改即可进行类型检查,最小化开发者的工作量。它不会强制你改变开发习惯,因为它会自动推断出变量的类型。这就是所谓的类型推断,Flow 最重要的特性之一。
1 | /*@flow*/ //有这个注释定义时,flow才会去识别 |
类型注释
1 | /*@flow*/ |
vue源码中的flow
想引用第三方库,或者自定义一些类型,但 Flow 并不认识,因此检查的时候会报错。为了解决这类问题,Flow 提出了一个 libdef 的概念,可以用来识别这些第三方库或者是自定义类型,而 Vue.js 也利用了这一特性。
在 Vue.js 的主目录下有 .flowconfig 文件, 它是 Flow 的配置文件,感兴趣的同学可以看官方文档。这其中的 [libs] 部分用来描述包含指定库定义的目
录,默认是名为 flow-typed 的目录。
1 | flow |
关于weex,推荐官网https://weex.apache.org/zh/guide/introduction.html,简单来讲就是一套代码多个平台。
vue源码目录
主代码在src目录下,简单结构如下。
1 | src |
compiler
compiler 目录包含 Vue.js 所有编译相关的代码。它包括把模板解析成 ast 语法树,ast 语法树优化,代码生成等功能。
编译的工作可以在构建时做(借助 webpack、vue-loader 等辅助插件);也可以在运行时做,使用包含构建功能的 Vue.js。显然,编译是一项耗性能的工作,所以更推荐前者——离线编译。
core
core 目录包含了 Vue.js 的核心代码,包括内置组件、全局 API 封装,Vue 实例化、观察者、虚拟 DOM、工具函数等等。
platforms
Vue.js 是一个跨平台的 MVVM 框架,它可以跑在 web 上,也可以配合 weex 跑在 native 客户端上。platform 是 Vue.js 的入口,2 个目录代表 2 个主要入口,分别打包成运行在 web 上和 weex 上的 Vue.js。
server
Vue.js 2.0 支持了服务端渲染,所有服务端渲染相关的逻辑都在这个目录下。注意:这部分代码是跑在服务端的 Node.js
服务端渲染主要的工作是把组件渲染为服务器端的 HTML 字符串,将它们直接发送到浏览器,最后将静态标记”混合”为客户端上完全交互的应用程序。
sfc
通常我们开发 Vue.js 都会借助 webpack 构建, 然后通过 .vue 单文件来编写组件。这个目录下的代码逻辑会把 .vue 文件内容解析成一个 JavaScript 的对象。
shared
Vue.js 会定义一些工具方法,这里定义的工具方法都是会被浏览器端的 Vue.js 和服务端的 Vue.js 所共享的。
源码构建
Vue.js 源码是基于 Rollup 构建的,它的构建相关配置都在 scripts 目录下。
构建脚本
基于 NPM 托管的项目都会有一个 package.json 文件。总共有 3 条命令,作用都是构建 Vue.js,后面 2 条是在第一条命令的基础上,添加一些环境参数。
1 | { |
构建过程
构建的入口 JS 文件,在 scripts/build.js 中
1 | let builds = require('./config').getAllBuilds() |
配置文件,在 scripts/config.js 中:
1 | const builds = { |
其中 entry
属性表示构建的入口 JS 文件地址,dest
属性表示构建后的 JS 文件地址。format
属性表示构建的格式,cjs
表示构建出来的文件遵循 CommonJS 规范,es
表示构建出来的文件遵循 ES Module 规范。 umd
表示构建出来的文件遵循 UMD 规范。
主要使用web端,以 web-runtime-cjs
配置为例,它的 entry
是 resolve('web/entry-runtime.js')
下面是 resolve
函数的定义。
1 | // 源码目录:scripts/config.js |
首先把 resolve
函数传入的参数 p
通过 / 做了分割成数组,然后取数组第一个元素设置为 base
。在我们这个例子中,参数 p
是 web/entry-runtime.js
,那么 base
则为 web
。base
并不是实际的路径,它的真实路径借助了别名的配置,我们来看一下别名配置的代码,在 scripts/alias
中:
1 | const path = require('path') |
这里 web
对应的真实的路径是 path.resolve(__dirname, '../src/platforms/web')
,这个路径就找到了 Vue.js
源码的 web
目录。然后 resolve
函数通过 path.resolve(aliases[base]
, p.slice(base.length + 1))
找到了最终路径,它就是 Vue.js
源码 web
目录下的 entry-runtime.js
。因此,web-runtime-cjs
配置对应的入口文件就找到了。
它经过 Rollup
的构建打包后,最终会在 dist
目录下生成 vue.runtime.common.js
关于Runtime Only OR Runtime + Compiler
- Runtime Only
通常需要借助如 webpack 的 vue-loader 工具把 .vue 文件编译成 JavaScript,因为是在编译阶段做的,所以它只包含运行时的 Vue.js 代码,因此代码体积也会更轻量。
- Runtime + Compiler
没有对代码做预编译,但又使用了 Vue 的 template 属性并传入一个字符串,则需要在客户端编译模板,如下所示:
1 | // 需要编译器的版本 |
在 Vue.js 2.0 中,最终渲染都是通过 render 函数。
入口
在 web 应用下,Runtime + Compiler
构建出来的 Vue.js
,它的入口是 src/platforms/web/entry-runtime-with-compiler.js
1 | /* @flow */ |
通过import Vue from './runtime/index'
,它定义在 src/platforms/web/runtime/index.js
中:
1 | import Vue from 'core/index' |
通过import Vue from 'core/index'
,在 src/core/index.js 中
:
1 | import Vue from './instance/index' |
vue定义
通过import Vue from './instance/index'
,在 src/core/instance/index.js
中
1 | import { initMixin } from './init' |
可以看到vue是一个用 Function 实现的类,我们只能通过 new Vue 去实例化它。
initGlobalAPI(Vue)
在整个初始化过程中,除了给它的原型 prototype 上扩展方法,还会给 Vue 这个对象本身扩展全局的静态方法,它的定义在 src/core/global-api/index.js
中
1 | export function initGlobalAPI (Vue: GlobalAPI) { |
Vue 官网中关于全局 API 都可以在这里找到。有一点要注意的是,Vue.util 暴露的方法最好不要依赖,因为它可能经常会发生变化,是不稳定的。