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

|---- 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. wepy框架开发小程序遇到的node-sass问题解决方案

    一.报错图 二.解决方案 主要是windows平台缺少编译环境, 1.先运行: npm install -g node-gyp 2.然后运行cmd:(右键点击:以管理员身份运行) 输入:npm ins ...

  2. JavaScript阻止冒泡事件

    事件兼容 event = event ? event : window.event; js停止冒泡· window.event? window.event.cancelBubble = true : ...

  3. JAVA设计模式-单例模式(Singleton)线程安全与效率

    一,前言 单例模式详细大家都已经非常熟悉了,在文章单例模式的八种写法比较中,对单例模式的概念以及使用场景都做了很不错的说明.请在阅读本文之前,阅读一下这篇文章,因为本文就是按照这篇文章中的八种单例模式 ...

  4. LeetCode 230.二叉树中第k小的元素

    题目: 给定一个二叉搜索树,编写一个函数 kthSmallest 来查找其中第 k 个最小的元素. 说明:你可以假设 k 总是有效的,1 ≤ k ≤ 二叉搜索树元素个数. 这道题在leetCode上难 ...

  5. elastic集群单节点停机维护

    为了elastic时时提供服务,需要elastic至少状态维持在yellow状态.所有,维护时需要依次对elastic单个节点进行维护. 操作步骤如下: 1.停止elastic的自动分配功能 curl ...

  6. 自定义构建基于.net core 的基础镜像

    先说一个问题 首先记录一个问题,今天在用 Jenkins 构建项目的时候突然出现包源的错误: /usr/share/dotnet/sdk/2.2.104/NuGet.targets(114,5): e ...

  7. Spring boot 官网学习笔记 - logging

    commons-logging和slf4j是java中的日志门面,即它们提供了一套通用的接口,具体的实现可以由开发者自由选择.log4j和logback则是具体的日志实现方案. 比较常用的搭配是com ...

  8. Event Hub小白入门指南

    Event Hub事件中心 本文的目的在于用最白的大白话,让你从“完全不懂”开始,理解什么是分布式大数据流平台Event Hub,并且理解它的关键概念,并且初步理解其收发数据API. 定义,Event ...

  9. 夯实Java基础系列16:一文读懂Java IO流和常见面试题

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

  10. docker 更新后出现 error during connect

    docker更新后出现 error during connect: Get http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.39/containers/json: o ...