在了解源码如何构建之前,我们有必要了解下 项目中一个简单的目录结构如下:

|---- 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

如上只是一个非常简单的一个目录,为了节约篇幅,只是把入口构建的相关的目录画出来。

我们看任何库相关的代码的第一步先把视线转移到 package.json 中来。然后看下 "scripts" 这个,如下:

{
......
"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 这个文件上来。

scripts/config.js 代码如下:

......

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的值变为如下:

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 函数如下:

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"); 同理其他的也一样。

我们再结合下面的 alias.js 代码

alias.js 代码如下:

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)上去看了。在后面我们会逐渐讲解挂载了哪些全局属性和原型方法的。

Vue系列---源码构建过程(四)的更多相关文章

  1. vue源码分析—Vue.js 源码构建

    Vue.js 源码是基于 Rollup 构建的,它的构建相关配置都在 scripts 目录下.(Rollup 中文网和英文网) 构建脚本 通常一个基于 NPM 托管的项目都会有一个 package.j ...

  2. Vue.js 源码构建(三)

    Vue.js 源码是基于 Rollup 构建的,它的构建相关配置都在 scripts 目录下. 构建脚本 通常一个基于 NPM 托管的项目都会有一个 package.json 文件,它是对项目的描述文 ...

  3. Vue系列---源码调试(二)

    我们要对Vue源码进行分析,首先我们需要能够对vue源码进行调式(这里的源码调式是ES6版本的,不是打包后的代码),因此首先我们要去官方github上克隆一份vue项目下来,如下具体操作: 1. cl ...

  4. Vue.js 源码分析(十四) 基础篇 组件 自定义事件详解

    我们在开发组件时有时需要和父组件沟通,此时可以用自定义事件来实现 组件的事件分为自定义事件和原生事件,前者用于子组件给父组件发送消息的,后者用于在组件的根元素上直接监听一个原生事件,区别就是绑定原生事 ...

  5. Netty 源码 ChannelHandler(四)编解码技术

    Netty 源码 ChannelHandler(四)编解码技术 Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) 一.拆包与粘 ...

  6. 【原】SDWebImage源码阅读(四)

    [原]SDWebImage源码阅读(四) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 SDWebImage中主要实现了NSURLConnectionDataDelega ...

  7. vue.js源码精析

    MVVM大比拼之vue.js源码精析 VUE 源码分析 简介 Vue 是 MVVM 框架中的新贵,如果我没记错的话作者应该毕业不久,现在在google.vue 如作者自己所说,在api设计上受到了很多 ...

  8. 从template到DOM(Vue.js源码角度看内部运行机制)

    写在前面 这篇文章算是对最近写的一系列Vue.js源码的文章(https://github.com/answershuto/learnVue)的总结吧,在阅读源码的过程中也确实受益匪浅,希望自己的这些 ...

  9. Netty 源码解析(四): Netty 的 ChannelPipeline

    今天是猿灯塔“365篇原创计划”第四篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel Netty ...

随机推荐

  1. Android远程服务AIDL开发过程中容易遇见的两个问题

    问题 一 JavaBinder: Uncaught remote exception! (Exceptions are not yet supported across processes.) jav ...

  2. AsyncLocal和Async原理解读

    AsyncLocal 的实现很简单,将AsyncLocal实例和当前线程的值以键值对的形式保存在Thread.CurrentThread.ExecutionContext.m_localValues. ...

  3. Go微服务容错与韧性(Service Resilience)

    Service Resilience是指当服务的的运行环境出现了问题,例如网络故障或服务过载或某些微服务宕机的情况下,程序仍能够提供部分或大部分服务,这时我们就说服务的韧性很强.它是微服务中很重要的一 ...

  4. Linux6.x 更换国内比较快的yum源-通用版

    ----------更换国内比较快的yum源----------- ----------163--------- cd /etc/yum.repos.d mv CentOS-Base.repo Cen ...

  5. Django序列化&django REST framework

    第一章.Django序列化操作 1.django的view实现商品列表页(基于View类) # 通过json来序列化,但手写字典key代码量较大,容易出错:还有遇到时间,图片序列化会报错 from g ...

  6. 23种设计模式之抽象工厂(Abstract Factory Pattern)

    抽象工厂 当想创建一组密不可分的对象时,工厂方法似乎就不够用了 抽象工厂是应对产品族概念的.应对产品族概念而生,增加新的产品线很容易,但是无法增加新的产品.比如,每个汽车公司可能要同时生产轿车.货车. ...

  7. 十分钟快速学会Matplotlib基本图形操作

    在学习Python的各种工具包的时候,看网上的各种教程总是感觉各种方法很多很杂,参数的种类和个数也十分的多,理解起来需要花费不少的时间. 所以我在这里通过几个例子,对方法和每个参数都进行详细的解释,这 ...

  8. springboot 集成Redis单机

    1.redis服务搭建 centos7 搭建redis服务 2.接入相关 pom文件依赖引入 <dependencies> <dependency> <groupId&g ...

  9. 【maven的使用】2使用maven与pom文件

    一.使用maven:首先需要知道的是,在我们开发人员中有一句话叫做:约定优于配置.比如,如果我们写代码有多种可选方案:硬编码形式:obj.setPath("d:/xxxx") .配 ...

  10. SpringBoot2+Netty打造通俗简版RPC通信框架

    2019-07-19:完成基本RPC通信! 2019-07-22:优化此框架,实现单一长连接! 2019-07-24:继续优化此框架:1.增加服务提供注解(带版本号),然后利用Spring框架的在启动 ...