Vue源码解析(笔记)

github vue源码分析

认识flow

flow类型检查

安装flow

sudo npm install -g flow-bin

初始化flow

flow init

运行flow命令

命令: flow

在js文件中需要有需要有一下标志

/*@flow*/

例子 void

/*@flow*/

class Bar {
     
    x: string;           // x 是字符串
    y: string | number | void;  // y 可以是字符串或者数字
    z: boolean;

    constructor(x: string, y: string | number | void) {
        this.x = x
        this.y = y
        this.z = false
    }
}

var bar: Bar = new Bar('hello')

var obj: { a: string, b: number, c: Array<string>, d: Bar } = {
    a: 'hello',
    b: 11,
    c: ['hello', 'world'],
    d: new Bar('hello', 3)
}

Vue.js 源码目录设计

src
├── compiler        # 编译相关 
├── core            # 核心代码 
├── platforms       # 不同平台的支持
├── server          # 服务端渲染
├── sfc             # .vue 文件解析
├── shared          # 共享代码

Vue.js 源码构建

Vue.js 源码是基于Rollup 构建的,它的相关配置都在scripts目录下

构建脚本

通常一个基于NPM 托管的项目都会有一个package.json 文件,它的实际内容是JSON对象

我们通常会配置scripts字段作为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"
 }
}

这里共有3条命令,作为都是构建Vue.js,后面2 条是在第一条命令的基础上,添加环境参数。

当在运行 npm run build 的时候 ,实际上就会执行 node scripts/build.js 接下来看看 它实际是怎么构建的

构建过程

我们对于构建过程分析是基于源码的,先打开构建的入口JS文件,在scripts/build.js 中

let builds = require('./config').getAllBuilds()
if(process.argv[2]){
 const filters = process.argv[2].split(',')
 builds = builds.filter(b => {
  return filters.some(f => b.oupput.file.indexOf(f)) > -f || b._name.indexOf(f) > -1
})
} else {
  builds = builds.filter(b => {
   return b.output.file.indexOf('weex') === -1
 })
}

build(builds)

这端代码逻辑非常简单。先从配置文件读取信息,在通过命令行参数对构建配置过滤,这样就可以构建出不同用途的Vue.js了。配置文件 在 scripts/config.js

const builds = {
'web-runtime-cjs': {
 entry: resolve('web/entry-runtime.js'),
 dest: resolve('dist/vue.runtime.common.js'),
 format:'cjs',
 banner
 },
 'web-full-cjs': {
  entry: resolve('web/entry-runtime-with-compilter.js'),
  dest:  resolve('dist/vue.common.js'),
  format: 'cjs',
  banner
 },
 'web-runtime-esm': {
  entry:resolve('web/entry-runtime.js'),
  dest: resolve('dist/vue.runtime.esm.js'),
  format:'es',
  banner
 },
 'web-full-esm': {
  entry: resolve('web/entry-runtime-with-compilter.js')
 }
}

这里列举了一些Vue.js 构建的配置 还有一些服务器渲染webpack 插件以及weex就不列举了

对于单个配置 它是遵循Rollop 的构建规则,其中entry 属性表示构建的入口JS文件地址,dess属性表示构建后的JS文件地址,fromat 属性表示构建的规则,cjs表示构建构建出来的文档遵循 CommonJS规范,es 表示构建出来的文件遵循ES Module 规范umd表示构建出来的文件遵循UMD规范

以web-runtime-cjs 配置为例 它的entryresolve('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.splice(base.length + 1))
 }else {
  return path.resolve(__dirname, '..', p)
  }
 }

这里resolve 函数实现非常简单,它先把resolve 函数传入的参数p通过/ 做了分割数组,然后取数组的第一个元素设置base 。在这个例子中参数pweb/entry-runtime.js ,那么base 则是webbase 并不是实际路径,它的真实路径借助了别名的配置,我们来看一下配置别名的代码,在scripts/alias 中

const path = require('path')

module.exports = {
 vue: path.resole(__dirname,'../src/platforms/web/entry-runtime-with-compiler'),
 compiler: path.resolve(__dirname,'../src/compiler'),
 core: path.resolve(__dirname,'../scr/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源码目录下面的entry-runtime.js 。因此 web-runtime-cjs 配置对应的文件入口就找到了

它经过Rollup 的构建打包后,最终会在dist 目录先生成 vue.runtime.common.js

Runtime Only VS Runtime+ Compiler

通常我们利用vue-cli 去初始化我们的Vue.js 项目的时候会询问我们用 Runtime Only 版本还是Runtime+Compiler 版本,下面我们来对比这两个版本

  • Runtime Only

我们在使用Runtime Only 版本的Vue.js 的时候,通常需要借助webpack的vue-loader工具把.vue文件编译成 JavaScript , 因为在编译阶段做的,所以它只包运行时的Vue.js 代码, 因此代码体积更轻

  • Runtime+Compiler
    我们如果没有对代码进行预编译,但又使用了Vue的teplate 并传入了一个字符产,则需要在客户端编译模版 如下所示:
// 需要编译器的模版
new Vue({
 template:'
{ {h1}}
'
}) // 这种情况不需要 new Vue({ render (h) { return h('div',this.hi) } })

因为在Vue.js 2.0 中最终渲染都是通过render 函数 ,如果写tempalte 属性, 则需要编译成render 函数,那么这个编译过程发生运行时,所以需要带有编译器的版本

很显然,这个编译过程对性能又一定损耗,所以通常我们更推荐 Runtime-Only 的Vue.js

zon

你可能感兴趣的:(vue)