Vue简介

Vue.js官方文档的介绍是这样的:

Vue.js是什么

Vue是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue被设计为可以自底向上逐层应用。Vue的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue也完全能够为复杂的单页应用提供驱动。

源码目录

Vue.js的源码都在./src目录下,使用tree命令查看目录结构

1
2
3
4
5
6
7
./src
├── compiler # 编译
├── core # 核心
├── platforms # 不同平台的支持
├── server # 服务端渲染
├── sfc # .vue文件解析
└── shared # 共享代码

可以看到,源码主要包含六个部分:

  • compiler

    该目录包含Vue.js编译相关的代码,包括把模板解析成AST语法树,AST语法树优化,代码生成等功能

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    ./src/compiler
    ├── codeframe.js
    ├── codegen # 把AST转换成Render函数
    │   ├── events.js
    │   └── index.js
    ├── create-compiler.js
    ├── directives
    │   ├── bind.js
    │   ├── index.js
    │   ├── model.js
    │   └── on.js
    ├── error-detector.js
    ├── helpers.js
    ├── index.js
    ├── optimizer.js
    ├── parser # 把模板解析成AST
    │   ├── entity-decoder.js
    │   ├── filter-parser.js
    │   ├── html-parser.js
    │   ├── index.js
    │   └── text-parser.js
    └── to-function.js
  • core

    该目录包含了Vue.js的核心代码,包括内置组件、全局API封装,Vue实例化、观察者、虚拟DOM、工具函数等等

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    ./src/core
    ├── components # 组件相关,主要是Keep-Alive
    │   ├── index.js
    │   └── keep-alive.js
    ├── config.js
    ├── global-api # Vue全局API
    │   ├── assets.js
    │   ├── extend.js
    │   ├── index.js
    │   ├── mixin.js
    │   └── use.js
    ├── index.js
    ├── instance # 实例化相关,生命周期、事件等
    │   ├── events.js
    │   ├── index.js
    │   ├── init.js
    │   ├── inject.js
    │   ├── lifecycle.js
    │   ├── proxy.js
    │   ├── render-helpers
    │   ├── render.js
    │   └── state.js
    ├── observer # 观察者,响应式相关
    │   ├── array.js
    │   ├── dep.js
    │   ├── index.js
    │   ├── scheduler.js
    │   ├── traverse.js
    │   └── watcher.js
    ├── util # 工具方法
    │   ├── debug.js
    │   ├── env.js
    │   ├── error.js
    │   ├── index.js
    │   ├── lang.js
    │   ├── next-tick.js
    │   ├── options.js
    │   ├── perf.js
    │   └── props.js
    └── vdom # 虚拟dom
    ├── create-component.js
    ├── create-element.js
    ├── create-functional-component.js
    ├── helpers
    ├── modules
    ├── patch.js
    └── vnode.js
  • platforms

    Vue.js是一个跨平台的MVVM框架,它可以跑在web上,也可以配合weex跑在native客户端上

    platforms是Vue.js的入口,2个目录分别代表2个主要入口,分别打包成运行在web上和weex上的Vue.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    ./src/platforms
    ├── web
    │   ├── compiler
    │   ├── entry-compiler.js
    │   ├── entry-runtime-with-compiler.js
    │   ├── entry-runtime.js
    │   ├── entry-server-basic-renderer.js
    │   ├── entry-server-renderer.js
    │   ├── runtime
    │   ├── server
    │   └── util
    └── weex
    ├── compiler
    ├── entry-compiler.js
    ├── entry-framework.js
    ├── entry-runtime-factory.js
    ├── runtime
    └── util
  • server

    Vue.js 2.0支持了服务端渲染,所有服务端渲染相关的逻辑都在这个目录下, 这部分代码是跑在服务端的Node.js

    服务端渲染主要的工作是把组件渲染为服务器端的HTML字符串,将它们直接发送到浏览器,最后将静态标记”混合”为客户端上完全交互的应用程序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    ./src/server
    ├── bundle-renderer
    │   ├── create-bundle-renderer.js
    │   ├── create-bundle-runner.js
    │   └── source-map-support.js
    ├── create-basic-renderer.js
    ├── create-renderer.js
    ├── optimizing-compiler
    │   ├── codegen.js
    │   ├── index.js
    │   ├── modules.js
    │   ├── optimizer.js
    │   └── runtime-helpers.js
    ├── render-context.js
    ├── render-stream.js
    ├── render.js
    ├── template-renderer
    │   ├── create-async-file-mapper.js
    │   ├── index.js
    │   ├── parse-template.js
    │   └── template-stream.js
    ├── util.js
    ├── webpack-plugin
    │   ├── client.js
    │   ├── server.js
    │   └── util.js
    └── write.js
  • sfc

    把*.vue文件内容解析成一个JavaScript的对象

    1
    2
    ./src/sfc
    └── parser.js
  • shared

    Vue.js会定义一些工具方法,这里定义的工具方法都是会被浏览器端的Vue.js和服务端的Vue.js所共享的

    1
    2
    3
    ./src/shared
    ├── constants.js
    └── util.js

源码构建

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

1
2
3
4
5
6
7
8
9
10
11
12
13
./scripts
├── alias.js
├── build.js
├── config.js
├── feature-flags.js
├── gen-release-note.js
├── get-weex-version.js
├── git-hooks
│   ├── commit-msg
│   └── pre-commit
├── release-weex.sh
├── release.sh
└── verify-commit-msg.js

构建脚本

通常会在./package.json中配置scripts作为npm的执行脚本

1
2
3
4
5
6
7
{
"scripts": {
"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

构建过程

打开./scripts/build.js,其中主要的部分是以下这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)

这段代码的逻辑是:先从./scripts/config.js中通过getAllBuilds()读取参数,在通过命令行参数对参数进行过滤,最后build(builds)

./scripts/config.js中,配置遵循Rollup的构建规则

其中entry属性表示构建的入口JS文件地址,dest属性表示构建后的JS文件地址,format属性表示构建的格式,cjs表示构建出来的文件遵循CommonJS规范,es表示构建出来的文件遵循ES Module规范,umd表示构建出来的文件遵循UMD规范

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
},
// more builds
}

各个配置可以通过resolve函数找到真实路径,resolve定义在./scripts/config.js

1
2
3
4
5
6
7
8
9
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)
}
}

resolve函数传入参数p,并通过/分割成数组,取数组第一个元素设置为base,然后调用path.resolve,传入aliases中键名为base的项以及pbase之后的部分

./scripts/alias.js

1
2
3
4
5
6
7
8
9
10
11
12
const resolve = p => path.resolve(__dirname, '../', p)

module.exports = {
vue: resolve('src/platforms/web/entry-runtime-with-compiler'),
compiler: resolve('src/compiler'),
core: resolve('src/core'),
shared: resolve('src/shared'),
web: resolve('src/platforms/web'),
weex: resolve('src/platforms/weex'),
server: resolve('src/server'),
sfc: resolve('src/sfc')
}

./scripts/config.js中的entry: resolve('web/entry-runtime.js')来看

  1. 参数p在分割后生成的baseweb,在./scripts/alias.jsweb对应的真实路径是path.resolve(__dirname, '../', 'src/platforms/web)

  2. 通过path.resolve(aliases[base], p.slice(base.length + 1)),最终对应的就是./src/platforms/web目录下的entry-runtime.js