本系列为慕课网《Vue.js 源码全方位深刻解析》课程的学习笔记css
Flow 是 facebook 出品的 JavaScript 静态类型检查工具。Vue.js 的源码利用了 Flow 作了静态类型检查。vue
JavaScript 是动态类型语言,但因为是弱类型,每每在编译阶段很难发现这些隐患,在运行的时候出现各类各样的BUG。
类型检查是当前动态类型语言的发展趋势,能够在编译期尽早发现(由类型错误引发的)bug,越早在上游发现对项目的成本控制越有益。node
Vue.js 在作 2.0 重构的时候,在 ES2015 的基础上,除了 ESLint 保证代码风格以外,也引入了 Flow 作静态类型检查。之因此选择 Flow,主要是由于 Babel 和 ESLint 都有对应的 Flow 插件以支持语法,能够彻底沿用现有的构建配置,很是小成本的改动就能够拥有静态类型检查的能力。webpack
npm install -g flow-bin
一般类型检查分红 2 种方式:web
先上代码vue-cli
/*@flow*/`` function split(str) { return str.split(' ') } split(11)
Flow 检查上述代码后会报错,由于函数 split 期待的参数是字符串,而咱们输入了数字。npm
Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ index.js:4:14 Cannot call str.split because property split is missing in Number [1]. 1│ /*@flow*/`` 2│ 3│ function split(str) { 4│ return str.split(' ') 5│ } 6│ [1] 7│ split(11) Found 1 error
类型推断的状况下如下代码并不会报错json
/*@flow*/ function add(x, y){ return x + y } add('Hello', 11)
这每每就是隐患所在,flow的正确写法应该是segmentfault
/*@flow*/ function add(x: number, y: number): number { return x + y } add('Hello', 11)
关于flow的具体用法参见官网 flowg官网连接api
Flow 提出了一个 libdef 的概念,能够用来识别这些第三方库或者是自定义类型,而 Vue.js 也利用了这一特性。
在 Vue.js 的主目录下有 .flowconfig 文件, 它是 Flow 的配置文件,这其中的 [libs] 部分用来描述包含指定库定义的目录,默认是名为 flow-typed 的目录。这里 [libs] 配置的是 flow,表示指定的库定义都在 flow 文件夹内。咱们打开这个目录,会发现文件以下:
flow ├── compiler.js # 编译相关 ├── component.js # 组件数据结构 ├── global-api.js # Global API 结构 ├── modules.js # 第三方库定义 ├── options.js # 选项相关 ├── ssr.js # 服务端渲染相关 ├── vnode.js # 虚拟 node 相关
这里的库定义对咱们在后面的源码阅读时会有必定的帮助。
Vue.js 的源码都在 src 目录下,其目录结构以下
src ├── compiler # 编译相关 ├── core # 核心代码 ├── platforms # 不一样平台的支持 ├── server # 服务端渲染 ├── sfc # .vue 文件解析 ├── shared # 共享代码
compiler 目录包含 Vue.js 全部编译相关的代码。它包括把模板解析成 ast 语法树,ast 语法树优化,代码生成等功能。
编译的工做能够在构建时作(借助 webpack、vue-loader 等辅助插件);也能够在运行时作,使用包含构建功能的 Vue.js。咱们每每在开发的时候使用运行时编译,离线每每是用于发布。
关于ast语法树的介绍可见 一看就懂的JS抽象语法树
core 目录包含了 Vue.js 的核心代码,包括内置组件、全局 API 封装,Vue 实例化、观察者、虚拟 DOM、工具函数等等。
Vue.js 是一个跨平台的 MVVM 框架,它能够跑在 web 上,也能够配合 weex 跑在 natvie 客户端上。platform 是 Vue.js 的入口,2 个目录表明 2 个主要入口,分别打包成运行在 web 上和 weex 上的 Vue.js。
server
Vue.js 2.0 支持了服务端渲染,全部服务端渲染相关的逻辑都在这个目录下。
服务端渲染主要的工做是把组件渲染为服务器端的 HTML 字符串,将它们直接发送到浏览器,最后将静态标记"混合"为客户端上彻底交互的应用程序。
一般咱们开发 Vue.js 都会借助 webpack 构建, 而后经过 .vue 单文件的编写组件。
这个目录下的代码逻辑会把 .vue 文件内容解析成一个 JavaScript 的对象。
Vue.js 会定义一些工具方法,这里定义的工具方法都是会被浏览器端的 Vue.js 和服务端的 Vue.js 所共享的。
Vue.js 源码是基于 Rollup 构建的,它的构建相关配置都在 scripts 目录下。
ps:roolup 每每用于纯js的库的构建,webpack功能更为强大一些,能够把css,图片这些也一块儿打到包里。这里Roolup显然更适合一点。
一般一个基于 NPM 托管的项目都会有一个 package.json 文件,它是对项目的描述文件,它的内容其实是一个标准的 JSON 对象。
咱们一般会配置 script 字段做为 NPM 的执行脚本,Vue.js 源码构建的脚本以下:
{ "script": { "build": "node scripts/build.js", "build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer", "build:weex": "npm run build --weex" } }
当在命令行运行 npm run build 的时候,实际上就会执行 node scripts/build.js
咱们对于构建过程分析是基于源码的,先打开构建的入口 JS 文件,在 scripts/build.js 中:
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)
这段代码逻辑很是简单,先从配置文件读取配置,再经过命令行参数对构建配置作过滤,这样就能够构建出不一样用途的 Vue.js 了。接下来咱们看一下配置文件,在 scripts/config.js 中
const builds = { // Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify 'web-runtime-cjs': { entry: resolve('web/entry-runtime.js'), dest: resolve('dist/vue.runtime.common.js'), format: 'cjs', banner }, //... // Runtime+compiler development build (Browser) '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 }, // Runtime+compiler production build (Browser) 'web-full-prod': { entry: resolve('web/entry-runtime-with-compiler.js'), dest: resolve('dist/vue.min.js'), format: 'umd', env: 'production', alias: { he: './entity-decoder' }, banner }, // ... }
对于单个配置,它是遵循 Rollup 的构建规则的。其中 entry 属性表示构建的入口 JS 文件地址,dest 属性表示构建后的 JS 文件地址。format 属性表示构建的格式,cjs 表示构建出来的文件遵循 CommonJS 规范,es 表示构建出来的文件遵循 ES Module 规范。 umd 表示构建出来的文件遵循 UMD 规范。
ps: cjs,es,umd是什么
以上面的web-runtime-cjs 配置为例,它的 entry 是
resolve('web/entry-runtime.js'),先来看一下 resolve 函数的定义。
源码目录:scripts/config.js
const aliases = require('./alias') const resolve = p => { const base = p.split('/')[0] if (aliases[base]) { return path.resolve(aliases[base], p.slice(base.length + 1)) } else { return path.resolve(__dirname, '../', p) } }
11
这里的 resolve 函数实现很是简单,它先把 resolve 函数传入的参数 p 经过 / 作了分割成数组,而后取数组第一个元素设置为 base。在咱们这个例子中,参数 p 是 web/entry-runtime.js,那么 base 则为 web。base 并非实际的路径,它的真实路径借助了别名的配置,咱们来看一下别名配置的代码,
在 scripts/alias 中:
const path = require('path')
module.exports = { vue: path.resolve(__dirname, '../src/platforms/web/entry-runtime-with-compiler'), compiler: path.resolve(__dirname, '../src/compiler'), core: path.resolve(__dirname, '../src/core'), shared: path.resolve(__dirname, '../src/shared'), web: path.resolve(__dirname, '../src/platforms/web'), weex: path.resolve(__dirname, '../src/platforms/weex'), server: path.resolve(__dirname, '../src/server'), entries: path.resolve(__dirname, '../src/entries'), sfc: path.resolve(__dirname, '../src/sfc') }
这里 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。
一般咱们利用 vue-cli 去初始化咱们的 Vue.js 项目的时候会询问咱们用 Runtime Only 版本的仍是 Runtime+Compiler 版本。下面咱们来对比这两个版本。
• Runtime Only
咱们在使用 Runtime Only 版本的 Vue.js 的时候,一般须要借助如 webpack 的 vue-loader 工具把 .vue 文件编译成 JavaScript,由于是在编译阶段作的,因此它只包含运行时的 Vue.js 代码,所以代码体积也会更轻量。
• Runtime+Compiler
咱们若是没有对代码作预编译,但又使用了 Vue 的 template 属性并传入一个字符串,则须要在客户端编译模板,以下所示:
// 须要编译器的版本
new Vue({ template: '<div>{{ hi }}</div>' }) // 这种状况不须要 new Vue({ render (h) { return h('div', this.hi) } })
由于在 Vue.js 2.0 中,最终渲染都是经过 render 函数,若是写 template 属性,则须要编译成 render 函数,那么这个编译过程会发生运行时,因此须要带有编译器的版本。很显然,这个编译过程对性能会有必定损耗,因此一般咱们更推荐使用 Runtime-Only 的 Vue.js。