一、FLow类型检测
-
安装Flow
npm install flow-bin
-
初始化flow项目
flow init
flow检查项目 flow 文件
/*@flow*/ 接下去的代码就需要flow检查了
二、Vue目录源码设计
-
Vue.js 的源码都在 src 目录下,其目录结构如下。
src ├── compiler # 编译相关 ├── core # 核心代码 ├── platforms # 不同平台的支持 ├── server # 服务端渲染 ├── sfc # .vue 文件解析 ├── shared # 共享代码
-
compiler
- Vue2.0有一个改变,添加了watch dom,watch dom生成实际执行的是render function,平时很少写render function 大部分写template ,将template转成render function,就在complier 中。编译的代码都在complier目录下
-
core
- component:内部存放了很多组件,比如keep-alive
- global-api:全局api
- instance:存放的是渲染的一些函数,事件、初始化、生命周期
- observer:跟响应式数据相关
- util:存放工具方法
- vdom:watch dom 核心代码放到这里
-
platforms
- web:浏览器程序
- complier:平台相关的编译
- runtime:平台运行代码
- server:平台相关的server render代码
- util:辅助的工具函数
- weex:混合app
- web:浏览器程序
server:跟服务端渲染的相关代码
sfc:vue的解析器,将单文件(.vue)编译成一个对象
shared:辅助的方法,比如以下常亮和工具方法
通过模块化方式,这让这些相互引用,再通过编译工具(Rollup)生成独立的js
三、源码构建
-
Vue.js 源码是基于 Rollup 构建的,它的构建相关配置都在 scripts 目录下。
- Rollup 和webpack区别:
- webpack将静态资源全部编译成js,而Rollup适合js库的编译,只处理js,不管理其他文件,更加轻量
- package.json
- mian:npm包入口,import vue,从这里查找vue文件路径
- module:webpack2以上,将module将这个当做默认入口
- scripts:
- 构建项目的执行命令有:build、build:ssr、build:weex
-
"build": "node scripts/build.js"
:编译成web相关的 -
"build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer"
:输出和renderer相关的 -
"build:weex": "npm run build -- weex"
:输出和weex相关的
-
- 构建项目的执行命令有:build、build:ssr、build:weex
- Rollup 和webpack区别:
-
构建过程:
-
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)
:执行构建
-
分析如何拿到配置文件当前目录下的config.js
-
if (process.env.TARGET) { module.exports = genConfig(process.env.TARGET) } else { exports.getBuild = genConfig exports.getAllBuilds = () => Object.keys(builds).map(genConfig) }
暴露一个方法getAllBuilds,通过Object.keys(builds)拿到builds的所有keys形成数组,map遍历
-
builds是什么:
-
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 }, // Runtime+compiler CommonJS build (CommonJS) 'web-full-cjs-dev': { entry: resolve('web/entry-runtime-with-compiler.js'), dest: resolve('dist/vue.common.dev.js'), format: 'cjs', env: 'development', alias: { he: './entity-decoder' }, banner }, 'web-full-cjs-prod': { entry: resolve('web/entry-runtime-with-compiler.js'), dest: resolve('dist/vue.common.prod.js'), format: 'cjs', env: 'production', alias: { he: './entity-decoder' }, banner }, // Runtime only ES modules build (for bundlers) 'web-runtime-esm': { entry: resolve('web/entry-runtime.js'), dest: resolve('dist/vue.runtime.esm.js'), format: 'es', banner }, // Runtime+compiler ES modules build (for bundlers) 'web-full-esm': { entry: resolve('web/entry-runtime-with-compiler.js'), dest: resolve('dist/vue.esm.js'), format: 'es', alias: { he: './entity-decoder' }, banner }, // Runtime+compiler ES modules build (for direct import in browser) 'web-full-esm-browser-dev': { entry: resolve('web/entry-runtime-with-compiler.js'), dest: resolve('dist/vue.esm.browser.js'), format: 'es', transpile: false, env: 'development', alias: { he: './entity-decoder' }, banner }, // Runtime+compiler ES modules build (for direct import in browser) 'web-full-esm-browser-prod': { entry: resolve('web/entry-runtime-with-compiler.js'), dest: resolve('dist/vue.esm.browser.min.js'), format: 'es', transpile: false, env: 'production', alias: { he: './entity-decoder' }, banner }, // runtime-only build (Browser) 'web-runtime-dev': { entry: resolve('web/entry-runtime.js'), dest: resolve('dist/vue.runtime.js'), format: 'umd', env: 'development', banner }, // runtime-only production build (Browser) 'web-runtime-prod': { entry: resolve('web/entry-runtime.js'), dest: resolve('dist/vue.runtime.min.js'), format: 'umd', env: 'production', 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 }, // Web compiler (CommonJS). 'web-compiler': { entry: resolve('web/entry-compiler.js'), dest: resolve('packages/vue-template-compiler/build.js'), format: 'cjs', external: Object.keys(require('../packages/vue-template-compiler/package.json').dependencies) }, // Web compiler (UMD for in-browser use). 'web-compiler-browser': { entry: resolve('web/entry-compiler.js'), dest: resolve('packages/vue-template-compiler/browser.js'), format: 'umd', env: 'development', moduleName: 'VueTemplateCompiler', plugins: [node(), cjs()] }, // Web server renderer (CommonJS). 'web-server-renderer-dev': { entry: resolve('web/entry-server-renderer.js'), dest: resolve('packages/vue-server-renderer/build.dev.js'), format: 'cjs', env: 'development', external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies) }, 'web-server-renderer-prod': { entry: resolve('web/entry-server-renderer.js'), dest: resolve('packages/vue-server-renderer/build.prod.js'), format: 'cjs', env: 'production', external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies) }, 'web-server-renderer-basic': { entry: resolve('web/entry-server-basic-renderer.js'), dest: resolve('packages/vue-server-renderer/basic.js'), format: 'umd', env: 'development', moduleName: 'renderVueComponentToString', plugins: [node(), cjs()] }, 'web-server-renderer-webpack-server-plugin': { entry: resolve('server/webpack-plugin/server.js'), dest: resolve('packages/vue-server-renderer/server-plugin.js'), format: 'cjs', external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies) }, 'web-server-renderer-webpack-client-plugin': { entry: resolve('server/webpack-plugin/client.js'), dest: resolve('packages/vue-server-renderer/client-plugin.js'), format: 'cjs', external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies) }, // Weex runtime factory 'weex-factory': { weex: true, entry: resolve('weex/entry-runtime-factory.js'), dest: resolve('packages/weex-vue-framework/factory.js'), format: 'cjs', plugins: [weexFactoryPlugin] }, // Weex runtime framework (CommonJS). 'weex-framework': { weex: true, entry: resolve('weex/entry-framework.js'), dest: resolve('packages/weex-vue-framework/index.js'), format: 'cjs' }, // Weex compiler (CommonJS). Used by Weex's Webpack loader. 'weex-compiler': { weex: true, entry: resolve('weex/entry-compiler.js'), dest: resolve('packages/weex-template-compiler/build.js'), format: 'cjs', external: Object.keys(require('../packages/weex-template-compiler/package.json').dependencies) } }
- builds的可以是不同版本的vuejs编译配置
- entry:表示入口文件
- dest:输出目标
- format:构建出来的文件格式
- banner:添加注释,版本,创建这之类的
- builds的可以是不同版本的vuejs编译配置
genConfig方式,将builds配置转换成Rollup的配置文件格式
最终
let builds = require('./config').getAllBuilds()
:拿到相关配置文件数组
-
-
继续返回build.js
-
// 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 }) }
- process.argv[2] 拿到的scripts配置文件的参数
- 比如:
"build:weex": "npm run build -- weex"
:process.argv[2] 拿到的是weex这个参数
- 比如:
- filter 方法,根据参数,将不要的配置给过滤掉,如果没有参数的话,将weex给过滤掉,也就是打包web平台
- process.argv[2] 拿到的scripts配置文件的参数
-
编译的方法build()
function build (builds) { // 计数器,用于取出配置文件 let built = 0 // 配置文件的个数 const total = builds.length // 循环配置文件执行编译 const next = () => { buildEntry(builds[built]).then(() => { built++ if (built < total) { next() } }).catch(logError) } next() } /** * Rollup打包构建 * @param config 配置文件 * @returns {Promise<RollupOutput | never>} */ function buildEntry (config) { // 输出文件配置 /* const config = { input: opts.entry, external: opts.external, plugins: [ flow(), alias(Object.assign({}, aliases, opts.alias)) ].concat(opts.plugins || []), output: { file: opts.dest, format: opts.format, banner: opts.banner, name: opts.moduleName || 'Vue' }, onwarn: (msg, warn) => { if (!/Circular/.test(msg)) { warn(msg) } } } */ const output = config.output // file:输出文件名和路径 banner:添加注释,版本,创建这之类的 const { file, banner } = output // 根据文件后缀名判断需不需要压缩 const isProd = /(min|prod)\.js$/.test(file) // 根据配置文件开始构建 return rollup.rollup(config) .then(bundle => bundle.generate(output)) .then(({ output: [{ code }] }) => { // 如果是需要压缩 if (isProd) { const minified = (banner ? banner + '\n' : '') + terser.minify(code, { toplevel: true, output: { ascii_only: true }, compress: { pure_funcs: ['makeMap'] } }).code // 将编译好的代码写入对应的file指定的路径并命名指定的文件名 return write(file, minified, true) } else { return write(file, code) } }) } /** * // 将编译好的代码写入对应的file指定的路径并命名指定的文件名 * @param dest 输出的路径和名称 * @param code 代码 * @param zip 是否压缩 * @returns {Promise<any>} */ function write (dest, code, zip) { return new Promise((resolve, reject) => { function report (extra) { console.log(blue(path.relative(process.cwd(), dest)) + ' ' + getSize(code) + (extra || '')) resolve() } fs.writeFile(dest, code, err => { if (err) return reject(err) if (zip) { zlib.gzip(code, (err, zipped) => { if (err) return reject(err) report(' (gzipped: ' + getSize(zipped) + ')') }) } else { report() } }) }) }
-
-
-
* Runtime Only 和Runtime + Complier比较
* Runtime Only:将template模板编译成render函数,编译后是reader函数版本,运行时候不带编译,编译在离线时候做
* Runtime + Complier:可以不对代码做预编译,不使用vue单文件方式,可以在vue在运行时候,将template编译成render函数
* ```js
// 需要编译器的版本(Runtime + Complier)
new Vue({
template: '<div>{{ hi }}</div>'
})
// 这种情况不需要编译器的版本,只需要Runtime Only就行了
new Vue({
render (h) {
return h('div', this.hi)
}
})
```
* 我们写的.vue 文件,在真正运行时候,早就编译成了javascript函数了,并且template模板已经编译成了render函数了
* 将目标编译,是很耗性能的,所以开发阶段建议使用Runtime Only
四、从入口开始 import 开始
我们研究的是Runtime + Complier 版本
入口文件 src/platforms/web/entry-runtime-with-compiler.js
-
从入口文件,按照Vue的路径,一路找到了 src/core/instance/index.js
路径:src/platforms/web/entry-runtime-with-compiler.js ➡️ src/platforms/web/runtime/index.js ➡️ src/core/index.js ➡️ src/core/instance/index.js
-
src/core/instance/index.js
-
import { initMixin } from './init' import { stateMixin } from './state' import { renderMixin } from './render' import { eventsMixin } from './events' import { lifecycleMixin } from './lifecycle' import { warn } from '../util/index' // ES5 实现的 Class function Vue (options) { if (process.env.NODE_ENV !== 'production' && // 这个就是强制实行new Vue,直接调用会提示下面的警告的 !(this instanceof Vue) ) { warn('Vue is a constructor and should be called with the `new` keyword') } this._init(options) } initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue) export default Vue
- initMixin方法:向原型上挂载_init 方法
- stateMixin方法:向原型挂载delete、$watch等方法
- 为什么Vue使用ES5实现而不是ES6实现:因为,哪些Mixin方式,实际是向Vue原型上挂在方法,如果用ES6就做不到将挂载的方法拆分出来管理,这种做法有利于代码管理,按照模块的组织关系把Vue的原型拆分到不同的文件中
-
src/core/index.js
-
initGlobalAPI(Vue)
:往Vue上挂载一些静态属性
-
-