在了解源码如何构建以前,咱们有必要了解下 项目中一个简单的目录结构以下:vue
|---- vue | |---- dist # 打包后的存放文件目录 | |---- scripts # 存放构建相关的代码 | | |--- alias.js | | |--- build.js | | |--- config.js # 配置文件 | | |--- ..... 其余的更多 | |---- src # src目录是vue核心代码库 | | |--- compiler | | |--- core | | |--- platforms | | | |--- web # web平台 | | | | |--- compiler | | | | |--- runtime | | | | |--- server | | | | |--- util | | | | |--- entry-runtime-with-compiler.js # 运行+模板编译的入口文件 | | | |--- weex | | |--- server | | |--- sfc | | |--- shared | |---- package.json
如上只是一个很是简单的一个目录,为了节约篇幅,只是把入口构建的相关的目录画出来。node
咱们看任何库相关的代码的第一步先把视线转移到 package.json 中来。而后看下 "scripts" 这个,以下:git
{ ...... "scripts": { "dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev --sourcemap", "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" }, ..... }
这边咱们只要关注 "dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev --sourcemap", 这块就能够了,其余的命令也是相似的。如上使用的 rollup 进行打包,而后咱们会看到命令中有 scripts/config.js 这个配置文件,所以咱们须要把视线找到 这个 scripts/config.js 这个文件上来。web
scripts/config.js 代码以下:npm
...... 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) } } const builds = { ..... '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 }, ..... }; function genConfig (name) { const opts = builds[name] 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) } } } .... return config } if (process.env.TARGET) { module.exports = genConfig(process.env.TARGET) } else { exports.getBuild = genConfig exports.getAllBuilds = () => Object.keys(builds).map(genConfig) }
而后把视线移到最后的代码,if条件判断 process.env.TARGET 是否存在,存在的话,就执行 getConfig(process.env.TARGET) 这个函数,最后把结果导出 module.exports = genConfig(process.env.TARGET); 从命令行中,咱们能够看到 process.env.TARGET 值为:'web-full-dev'; 所以 const opts = builds['web-full-dev']; 所以最后 opts的值变为以下:json
const opts = { entry: resolve('web/entry-runtime-with-compiler.js'), dest: resolve('dist/vue.js'), format: 'umd', env: 'development', alias: { he: './entity-decoder' }, banner }
再看看 resolve 函数以下:api
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 函数,首先会获取基路径,好比 'web/entry-runtime-with-compiler.js' 的基路径就是 'web',所以 base = 'web'; 而后判断 if (aliases[base]) {} aliases 是否有 key为web的,若是有的话,直接返回:return path.resolve(aliases[base], "entry-runtime-with-compiler.js"); 同理其余的也同样。bash
咱们再结合下面的 alias.js 代码weex
alias.js 代码以下:dom
const path = require('path'); 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') };
由代码可知:alias.js 代码能够理解为以下:
module.exports = { vue: '项目的根目录' + '/src/platforms/web/entry-runtime-with-compiler', compiler: '项目的根目录' + '/src/compiler', core: '项目的根目录' + '/src/core', shared: '项目的根目录' + '/src/shared', web: '项目的根目录' + '/src/platforms/web', weex: '项目的根目录' + '/src/platforms/weex', server: '项目的根目录' + '/src/server', sfc: '项目的根目录' + '/src/sfc' };
分析可知最后的opts对象变为以下:
const opts = { entry: '项目的根目录' + '/src/platforms/web/entry-runtime-with-compiler.js', dest: '项目的根目录' + '/dist/vue.js', format: 'umd', env: 'development', alias: { he: './entity-decoder' }, banner };
所以 genConfig 函数内的config对象值变为以下:
const config = { input: '项目的根目录' + '/src/platforms/web/entry-runtime-with-compiler.js', external: '', plugins: [ flow(), alias(Object.assign({}, aliases, opts.alias)) ].concat(opts.plugins || []), output: { file: '项目的根目录' + '/dist/vue.js', format: 'umd', banner: '', name: opts.moduleName || 'Vue' }, onwarn: (msg, warn) => { if (!/Circular/.test(msg)) { warn(msg) } } };
如上代码打包的含义能够理解为以下:
找到 '项目的根目录' + '/src/platforms/web/entry-runtime-with-compiler.js', 路径下的js文件 打包到'项目的根目录' + '/dist/vue.js',目录下的 vue.js 文件。所以咱们须要把视线转移到 '/src/platforms/web/entry-runtime-with-compiler.js' 文件内了。该文件就是咱们的vue的入口文件。
entry-runtime-with-compiler.js 基本的代码以下:
/* @flow */ import config from 'core/config' import { warn, cached } from 'core/util/index' import { mark, measure } from 'core/util/perf' import Vue from './runtime/index' import { query } from './util/index' import { compileToFunctions } from './compiler/index' import { shouldDecodeNewlines, shouldDecodeNewlinesForHref } from './util/compat' .... const mount = Vue.prototype.$mount; Vue.prototype.$mount = function() { ..... }; .... export default Vue;
如上其余的代码,咱们这边先无论,咱们先看的 import Vue from './runtime/index' 这句代码,为何要看这句代码呢,那是由于 它引入了该文件,而且直接使用 export default Vue; 导出该 Vue.所以咱们会找到 src/platforms/web/runtime/index.js 代码以下:
import Vue from 'core/index' import config from 'core/config' import { extend, noop } from 'shared/util' import { mountComponent } from 'core/instance/lifecycle' import { devtools, inBrowser } from 'core/util/index' import { query, mustUseProp, isReservedTag, isReservedAttr, getTagNamespace, isUnknownElement } from 'web/util/index' import { patch } from './patch' import platformDirectives from './directives/index' import platformComponents from './components/index' // install platform specific utils Vue.config.mustUseProp = mustUseProp Vue.config.isReservedTag = isReservedTag Vue.config.isReservedAttr = isReservedAttr Vue.config.getTagNamespace = getTagNamespace Vue.config.isUnknownElement = isUnknownElement ..... export default Vue;
该文件的代码也是同样,先引入 import Vue from 'core/index' 文件后,而后导出 export default Vue;
所以咱们继续找到 src/core/index.js 代码以下:
import Vue from './instance/index' import { initGlobalAPI } from './global-api/index' import { isServerRendering } from 'core/util/env' import { FunctionalRenderContext } from 'core/vdom/create-functional-component' initGlobalAPI(Vue) Object.defineProperty(Vue.prototype, '$isServer', { get: isServerRendering }) Object.defineProperty(Vue.prototype, '$ssrContext', { get () { /* istanbul ignore next */ return this.$vnode && this.$vnode.ssrContext } }) // expose FunctionalRenderContext for ssr runtime helper installation Object.defineProperty(Vue, 'FunctionalRenderContext', { value: FunctionalRenderContext }) Vue.version = '__VERSION__' export default Vue;
如上代码,咱们主要看 import Vue from './instance/index'; 和一些全局API import { initGlobalAPI } from './global-api/index' 的代码。
首先咱们看 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' function Vue (options) { if (process.env.NODE_ENV !== 'production' && !(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;
如上代码,咱们终于看到Vue的构造函数了,咱们在Vue页面初始化 new Vue({}); 这样调用的时候,就会调用该构造函数,而咱们传入的参数就传给了options。该函数首先会判断是否是正式环境 及 是否使用 new 来实列Vue。
最后会调用 this._init(options) 该函数。该函数在 src/core/instance/init.js 里面,也就是咱们下面的initMixin(Vue) 函数调用初始化了。它会把一些方法挂载到Vue的原型上,好比 _init()方法,以下代码:
Vue.prototype._init = function() {} 这样的。
下面咱们继续来看下该方法,在 src/core/instance/init.js 代码以下:
/* @flow */ import config from '../config' import { initProxy } from './proxy' import { initState } from './state' import { initRender } from './render' import { initEvents } from './events' import { mark, measure } from '../util/perf' import { initLifecycle, callHook } from './lifecycle' import { initProvide, initInjections } from './inject' import { extend, mergeOptions, formatComponentName } from '../util/index' export function initMixin (Vue: Class<Component>) { Vue.prototype._init = function (options?: Object) { ....... } } export function initInternalComponent (vm: Component, options: InternalComponentOptions) { ..... } export function resolveConstructorOptions (Ctor: Class<Component>) { .... }
如上就是 init.js 代码。
initGlobalAPI
下面咱们再看下 src/core/global-api/index.js, Vue在初始化过程当中,不只给他的原型prototype上扩展方法,还会给Vue这个对象自己扩展不少全局的静态方法。那么扩展的全局的静态方法和属性就是在该函数内作的。在 src/core/global-api 其实有以下js文件
|--- vue | |--- src | | |--- core | | | |--- global-api | | | | |--- assets.js | | | | |--- extends.js | | | | |--- index.js | | | | |--- mixin.js | | | | |--- use.js
src/core/global-api/index.js 源码能够去看vue(v2.6.10)上去看了。在后面咱们会逐渐讲解挂载了哪些全局属性和原型方法的。