前提:本文所有笔记是基于ustbhuangyi老师在MK上的《Vue源码全方位解析》学习后的自我总结,仅供参考,如果有错误的欢迎指正。支持正版。转载请注明出处。
1.目录设计
vue的源码大家从github上clone下来即可,它的所有源码均在src目录下,结构如下:
/src
----/compiler 编译相关代码
----/core 核心代码
----/platforms 不同平台的内容
----/server 服务端渲染
----/sfc .vue文件解析
----/shared 共享代码
- compiler目录
该目录主要内容为从template到render的相关逻辑内容。
- core目录
该目录为vue源码的核心目录,有mixin、extend、生命周期、初始化、响应式代码、虚拟DOM等等内容。
- platforms目录
该目录下有两个目录,为web还有weex,web就是我们平时浏览器应用,weex是类似React-Native的跨端应用;所以vue就可以编译为浏览器应用的框架还可以编译为跨端应用。
- server目录
该目录主要为服务端渲染相关的内容;
- sfc目录
该目录就一个文件,parser.js,主要作用就是将单vue文件,编译成Javascript对象。
- shared目录
该目录里面都是一些共享的辅助方法。
2.源码构建
++前提:VUE的源码基于Rollup构建++
2.1 Rollup是什么?
Rollup 是一个 JavaScript模块打包器,可以将小块代码编译成大块复杂的代码。与webpack不同的是,rollup更轻量,但它只针对JS文件,对于一些图片等内容不像webpack那么强大。
2.2 Rollup的两个特性
- 它使用的是ES6的模块标准;
- tree-shaking,帮助你将无用的代码从中删除掉;
2.3 正文
先看一下package.json,重点关注一下scripts字段的内容,通过执行相应的命令就可以构建相应的vue版本。
{
"name": "vue",
"version": "2.6.10",
"description": "Reactive, component-oriented view layer for modern web interfaces.",
"main": "dist/vue.runtime.common.js",
"module": "dist/vue.runtime.esm.js",
"unpkg": "dist/vue.js",
"jsdelivr": "dist/vue.js",
"typings": "types/index.d.ts",
"files": [
"src",
"dist/*.js",
"types/*.d.ts"
],
"sideEffects": false,
"scripts": {
"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev",
"dev:cjs": "rollup -w -c scripts/config.js --environment TARGET:web-runtime-cjs-dev",
"dev:esm": "rollup -w -c scripts/config.js --environment TARGET:web-runtime-esm",
"dev:test": "karma start test/unit/karma.dev.config.js",
"dev:ssr": "rollup -w -c scripts/config.js --environment TARGET:web-server-renderer",
"dev:compiler": "rollup -w -c scripts/config.js --environment TARGET:web-compiler ",
"dev:weex": "rollup -w -c scripts/config.js --environment TARGET:weex-framework",
"dev:weex:factory": "rollup -w -c scripts/config.js --environment TARGET:weex-factory",
"dev:weex:compiler": "rollup -w -c scripts/config.js --environment TARGET:weex-compiler ",
"build": "node scripts/build.js",
"build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer",
"build:weex": "npm run build -- weex",
"test": "npm run lint && flow check && npm run test:types && npm run test:cover && npm run test:e2e -- --env phantomjs && npm run test:ssr && npm run test:weex",
"test:unit": "karma start test/unit/karma.unit.config.js",
"test:cover": "karma start test/unit/karma.cover.config.js",
"test:e2e": "npm run build -- web-full-prod,web-server-basic-renderer && node test/e2e/runner.js",
"test:weex": "npm run build:weex && jasmine JASMINE_CONFIG_PATH=test/weex/jasmine.js",
"test:ssr": "npm run build:ssr && jasmine JASMINE_CONFIG_PATH=test/ssr/jasmine.js",
"test:sauce": "npm run sauce -- 0 && npm run sauce -- 1 && npm run sauce -- 2",
"test:types": "tsc -p ./types/test/tsconfig.json",
"lint": "eslint src scripts test",
"flow": "flow check",
"sauce": "karma start test/unit/karma.sauce.config.js",
"bench:ssr": "npm run build:ssr && node benchmarks/ssr/renderToString.js && node benchmarks/ssr/renderToStream.js",
"release": "bash scripts/release.sh",
"release:weex": "bash scripts/release-weex.sh",
"release:note": "node scripts/gen-release-note.js",
"commit": "git-cz"
},
"gitHooks": {
"pre-commit": "lint-staged",
"commit-msg": "node scripts/verify-commit-msg.js"
},
"lint-staged": {
"*.js": [
"eslint --fix",
"git add"
]
},
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/vue.git"
},
"keywords": [
"vue"
],
"author": "Evan You",
"license": "MIT",
"bugs": {
"url": "https://github.com/vuejs/vue/issues"
},
"homepage": "https://github.com/vuejs/vue#readme",
"devDependencies": {
... // 省略了依赖
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
}
这边重点看一下执行了“npm run build”后,具体做了什么事情。
// script/build.js
// 1.定义一些依赖的模块
const fs = require('fs')
const path = require('path')
const zlib = require('zlib')
const rollup = require('rollup')
const terser = require('terser')
// 2.如果没有dist目录,则创建dist目录
if (!fs.existsSync('dist')) {
fs.mkdirSync('dist')
}
// 3.获取构建所需的所有配置
let builds = require('./config').getAllBuilds()
// 4.对拿到的配置项过滤
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
})
}
// 5.执行构建方法
build(builds)
function build (builds) {
let built = 0
const total = builds.length
const next = () => {
buildEntry(builds[built]).then(() => {
built++
if (built < total) {
next()
}
}).catch(logError)
}
next()
}
然后我们重点看一下获取配置这块的内容,getAllBuilds()方法就是拿到所有构建信息的对象,构建出rollup认识的构建配置结构,然后根据新的构建对象基于不同的生成规则并依赖不同的入口文件生成不同的输出文件。
对于构建对象,entry表示是入口文件,dest表示输出文件,format表示输出文件的格式,banner表示生成文件头部的注释内容。
// script/config.js
...
// resolve方法,实质就是拼接文件的位置
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)
}
}
// 构建对象的配置,有入口,出口,依赖文件,生产环境还是开发环境等配置
const build = {
// 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
},
...
}
// 文件的最底下,暴露了getAllBuilds方法
if (process.env.TARGET) {
module.exports = genConfig(process.env.TARGET)
} else {
exports.getBuild = genConfig
exports.getAllBuilds = () => Object.keys(builds).map(genConfig)
}
2.4 runtime only VS runtime + compiler
我们通过vue-cli构建vue项目的时候会问你使用runtime only还是runtime + compiler哪个版本
- runtime only
runtime only比较轻,它需要webpack的vue-loader工具将浏览器不认识的vue文件编译成Javascript。
- runtime + compiler
反之,这个版本则会在客户端编译模版。
// 需要编译
new Vue({
template: '<div>{{ Hello world }}</div>'
})
// 不需要编译
new Vue({
render (h) {
return h('div', this.text)
}
})
Vue.js 2.0 中,最终渲染都是通过 render 函数,如果写 template 属性,则需要编译成 render 函数,那么这个编译过程会发生运行时,所以需要带有编译器的版本。
所以,推荐使用runtime only版本,减少性能损耗。