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

  1. |---- vue
  2. | |---- dist # 打包后的存放文件目录
  3. | |---- scripts # 存放构建相关的代码
  4. | | |--- alias.js
  5. | | |--- build.js
  6. | | |--- config.js # 配置文件
  7. | | |--- ..... 其他的更多
  8. | |---- src # src目录是vue核心代码库
  9. | | |--- compiler
  10. | | |--- core
  11. | | |--- platforms
  12. | | | |--- web # web平台
  13. | | | | |--- compiler
  14. | | | | |--- runtime
  15. | | | | |--- server
  16. | | | | |--- util
  17. | | | | |--- entry-runtime-with-compiler.js # 运行+模板编译的入口文件
  18. | | | |--- weex
  19. | | |--- server
  20. | | |--- sfc
  21. | | |--- shared
  22. | |---- package.json

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

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

  1. {
  2. ......
  3. "scripts": {
  4. "dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev --sourcemap",
  5. "dev:cjs": "rollup -w -c scripts/config.js --environment TARGET:web-runtime-cjs-dev",
  6. "dev:esm": "rollup -w -c scripts/config.js --environment TARGET:web-runtime-esm",
  7. "dev:test": "karma start test/unit/karma.dev.config.js",
  8. "dev:ssr": "rollup -w -c scripts/config.js --environment TARGET:web-server-renderer",
  9. "dev:compiler": "rollup -w -c scripts/config.js --environment TARGET:web-compiler ",
  10. "dev:weex": "rollup -w -c scripts/config.js --environment TARGET:weex-framework",
  11. "dev:weex:factory": "rollup -w -c scripts/config.js --environment TARGET:weex-factory",
  12. "dev:weex:compiler": "rollup -w -c scripts/config.js --environment TARGET:weex-compiler ",
  13. "build": "node scripts/build.js",
  14. "build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer",
  15. "build:weex": "npm run build -- weex",
  16. "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",
  17. "test:unit": "karma start test/unit/karma.unit.config.js",
  18. "test:cover": "karma start test/unit/karma.cover.config.js",
  19. "test:e2e": "npm run build -- web-full-prod,web-server-basic-renderer && node test/e2e/runner.js",
  20. "test:weex": "npm run build:weex && jasmine JASMINE_CONFIG_PATH=test/weex/jasmine.js",
  21. "test:ssr": "npm run build:ssr && jasmine JASMINE_CONFIG_PATH=test/ssr/jasmine.js",
  22. "test:sauce": "npm run sauce -- 0 && npm run sauce -- 1 && npm run sauce -- 2",
  23. "test:types": "tsc -p ./types/test/tsconfig.json",
  24. "lint": "eslint src scripts test",
  25. "flow": "flow check",
  26. "sauce": "karma start test/unit/karma.sauce.config.js",
  27. "bench:ssr": "npm run build:ssr && node benchmarks/ssr/renderToString.js && node benchmarks/ssr/renderToStream.js",
  28. "release": "bash scripts/release.sh",
  29. "release:weex": "bash scripts/release-weex.sh",
  30. "release:note": "node scripts/gen-release-note.js",
  31. "commit": "git-cz"
  32. },
  33. .....
  34. }

这边我们只要关注 "dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev --sourcemap", 这块就可以了,其他的命令也是类似的。如上使用的 rollup 进行打包,然后我们会看到命令中有 scripts/config.js 这个配置文件,因此我们需要把视线找到 这个 scripts/config.js 这个文件上来。

scripts/config.js 代码如下:

  1. ......
  2.  
  3. const aliases = require('./alias')
  4. const resolve = p => {
  5. const base = p.split('/')[0]
  6. if (aliases[base]) {
  7. return path.resolve(aliases[base], p.slice(base.length + 1))
  8. } else {
  9. return path.resolve(__dirname, '../', p)
  10. }
  11. }
  12.  
  13. const builds = {
  14. .....
  15. 'web-full-dev': {
  16. entry: resolve('web/entry-runtime-with-compiler.js'),
  17. dest: resolve('dist/vue.js'),
  18. format: 'umd',
  19. env: 'development',
  20. alias: { he: './entity-decoder' },
  21. banner
  22. },
  23. .....
  24. };
  25.  
  26. function genConfig (name) {
  27. const opts = builds[name]
  28. const config = {
  29. input: opts.entry,
  30. external: opts.external,
  31. plugins: [
  32. flow(),
  33. alias(Object.assign({}, aliases, opts.alias))
  34. ].concat(opts.plugins || []),
  35. output: {
  36. file: opts.dest,
  37. format: opts.format,
  38. banner: opts.banner,
  39. name: opts.moduleName || 'Vue'
  40. },
  41. onwarn: (msg, warn) => {
  42. if (!/Circular/.test(msg)) {
  43. warn(msg)
  44. }
  45. }
  46. }
  47.  
  48. ....
  49.  
  50. return config
  51. }
  52. if (process.env.TARGET) {
  53. module.exports = genConfig(process.env.TARGET)
  54. } else {
  55. exports.getBuild = genConfig
  56. exports.getAllBuilds = () => Object.keys(builds).map(genConfig)
  57. }

然后把视线移到最后的代码,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的值变为如下:

  1. const opts = {
  2. entry: resolve('web/entry-runtime-with-compiler.js'),
  3. dest: resolve('dist/vue.js'),
  4. format: 'umd',
  5. env: 'development',
  6. alias: { he: './entity-decoder' },
  7. banner
  8. }

再看看 resolve 函数如下:

  1. const aliases = require('./alias')
  2. const resolve = p => {
  3. const base = p.split('/')[0]
  4. if (aliases[base]) {
  5. return path.resolve(aliases[base], p.slice(base.length + 1))
  6. } else {
  7. return path.resolve(__dirname, '../', p)
  8. }
  9. }

如上 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 代码如下:

  1. const path = require('path');
  2.  
  3. const resolve = p => path.resolve(__dirname, '../', p); // 到项目的根目录下
  4.  
  5. module.exports = {
  6. vue: resolve('src/platforms/web/entry-runtime-with-compiler'),
  7. compiler: resolve('src/compiler'),
  8. core: resolve('src/core'),
  9. shared: resolve('src/shared'),
  10. web: resolve('src/platforms/web'),
  11. weex: resolve('src/platforms/weex'),
  12. server: resolve('src/server'),
  13. sfc: resolve('src/sfc')
  14. };

由代码可知:alias.js 代码可以理解为如下:

  1. module.exports = {
  2. vue: '项目的根目录' + '/src/platforms/web/entry-runtime-with-compiler',
  3. compiler: '项目的根目录' + '/src/compiler',
  4. core: '项目的根目录' + '/src/core',
  5. shared: '项目的根目录' + '/src/shared',
  6. web: '项目的根目录' + '/src/platforms/web',
  7. weex: '项目的根目录' + '/src/platforms/weex',
  8. server: '项目的根目录' + '/src/server',
  9. sfc: '项目的根目录' + '/src/sfc'
  10. };

分析可知最后的opts对象变为如下:

  1. const opts = {
  2. entry: '项目的根目录' + '/src/platforms/web/entry-runtime-with-compiler.js',
  3. dest: '项目的根目录' + '/dist/vue.js',
  4. format: 'umd',
  5. env: 'development',
  6. alias: { he: './entity-decoder' },
  7. banner
  8. };

因此 genConfig 函数内的config对象值变为如下:

  1. const config = {
  2. input: '项目的根目录' + '/src/platforms/web/entry-runtime-with-compiler.js',
  3. external: '',
  4. plugins: [
  5. flow(),
  6. alias(Object.assign({}, aliases, opts.alias))
  7. ].concat(opts.plugins || []),
  8. output: {
  9. file: '项目的根目录' + '/dist/vue.js',
  10. format: 'umd',
  11. banner: '',
  12. name: opts.moduleName || 'Vue'
  13. },
  14. onwarn: (msg, warn) => {
  15. if (!/Circular/.test(msg)) {
  16. warn(msg)
  17. }
  18. }
  19. };

如上代码打包的含义可以理解为如下:

找到 '项目的根目录' + '/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 基本的代码如下:

  1. /* @flow */
  2.  
  3. import config from 'core/config'
  4. import { warn, cached } from 'core/util/index'
  5. import { mark, measure } from 'core/util/perf'
  6.  
  7. import Vue from './runtime/index'
  8. import { query } from './util/index'
  9. import { compileToFunctions } from './compiler/index'
  10. import { shouldDecodeNewlines, shouldDecodeNewlinesForHref } from './util/compat'
  11.  
  12. ....
  13.  
  14. const mount = Vue.prototype.$mount;
  15.  
  16. Vue.prototype.$mount = function() {
  17. .....
  18. };
  19.  
  20. ....
  21.  
  22. export default Vue;

如上其他的代码,我们这边先不管,我们先看的 import Vue from './runtime/index' 这句代码,为什么要看这句代码呢,那是因为 它引入了该文件,并且直接使用 export default Vue; 导出该 Vue.因此我们会找到 src/platforms/web/runtime/index.js 代码如下:

  1. import Vue from 'core/index'
  2. import config from 'core/config'
  3. import { extend, noop } from 'shared/util'
  4. import { mountComponent } from 'core/instance/lifecycle'
  5. import { devtools, inBrowser } from 'core/util/index'
  6.  
  7. import {
  8. query,
  9. mustUseProp,
  10. isReservedTag,
  11. isReservedAttr,
  12. getTagNamespace,
  13. isUnknownElement
  14. } from 'web/util/index'
  15.  
  16. import { patch } from './patch'
  17. import platformDirectives from './directives/index'
  18. import platformComponents from './components/index'
  19.  
  20. // install platform specific utils
  21. Vue.config.mustUseProp = mustUseProp
  22. Vue.config.isReservedTag = isReservedTag
  23. Vue.config.isReservedAttr = isReservedAttr
  24. Vue.config.getTagNamespace = getTagNamespace
  25. Vue.config.isUnknownElement = isUnknownElement
  26.  
  27. .....
  28.  
  29. export default Vue;

该文件的代码也是一样,先引入 import Vue from 'core/index' 文件后,然后导出 export default Vue;

因此我们继续找到 src/core/index.js 代码如下:

  1. import Vue from './instance/index'
  2. import { initGlobalAPI } from './global-api/index'
  3. import { isServerRendering } from 'core/util/env'
  4. import { FunctionalRenderContext } from 'core/vdom/create-functional-component'
  5.  
  6. initGlobalAPI(Vue)
  7.  
  8. Object.defineProperty(Vue.prototype, '$isServer', {
  9. get: isServerRendering
  10. })
  11.  
  12. Object.defineProperty(Vue.prototype, '$ssrContext', {
  13. get () {
  14. /* istanbul ignore next */
  15. return this.$vnode && this.$vnode.ssrContext
  16. }
  17. })
  18.  
  19. // expose FunctionalRenderContext for ssr runtime helper installation
  20. Object.defineProperty(Vue, 'FunctionalRenderContext', {
  21. value: FunctionalRenderContext
  22. })
  23.  
  24. Vue.version = '__VERSION__'
  25.  
  26. export default Vue;

如上代码,我们主要看 import Vue from './instance/index'; 和一些全局API import { initGlobalAPI } from './global-api/index' 的代码。

首先我们看 src/core/instance/index.js 代码如下:

  1. import { initMixin } from './init'
  2. import { stateMixin } from './state'
  3. import { renderMixin } from './render'
  4. import { eventsMixin } from './events'
  5. import { lifecycleMixin } from './lifecycle'
  6. import { warn } from '../util/index'
  7.  
  8. function Vue (options) {
  9. if (process.env.NODE_ENV !== 'production' &&
  10. !(this instanceof Vue)
  11. ) {
  12. warn('Vue is a constructor and should be called with the `new` keyword')
  13. }
  14. this._init(options)
  15. }
  16.  
  17. initMixin(Vue)
  18. stateMixin(Vue)
  19. eventsMixin(Vue)
  20. lifecycleMixin(Vue)
  21. renderMixin(Vue)
  22.  
  23. 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 代码如下:

  1. /* @flow */
  2.  
  3. import config from '../config'
  4. import { initProxy } from './proxy'
  5. import { initState } from './state'
  6. import { initRender } from './render'
  7. import { initEvents } from './events'
  8. import { mark, measure } from '../util/perf'
  9. import { initLifecycle, callHook } from './lifecycle'
  10. import { initProvide, initInjections } from './inject'
  11. import { extend, mergeOptions, formatComponentName } from '../util/index'
  12.  
  13. export function initMixin (Vue: Class<Component>) {
  14. Vue.prototype._init = function (options?: Object) {
  15. .......
  16. }
  17. }
  18.  
  19. export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
  20. .....
  21. }
  22.  
  23. export function resolveConstructorOptions (Ctor: Class<Component>) {
  24. ....
  25. }

如上就是 init.js 代码。

initGlobalAPI

下面我们再看下 src/core/global-api/index.js, Vue在初始化过程中,不仅给他的原型prototype上扩展方法,还会给Vue这个对象本身扩展很多全局的静态方法。那么扩展的全局的静态方法和属性就是在该函数内做的。在 src/core/global-api 其实有如下js文件

  1. |--- vue
  2. | |--- src
  3. | | |--- core
  4. | | | |--- global-api
  5. | | | | |--- assets.js
  6. | | | | |--- extends.js
  7. | | | | |--- index.js
  8. | | | | |--- mixin.js
  9. | | | | |--- 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. 百度脑图-离线版(支持Linux、Mac、Win)

    免费好用的思维导图软件(在线版) 离线版:桌面版脑图是基于百度脑图的本地化版本,帮助你在没有互联网环境的情况下,依然可以使用脑图工具. 百度脑图帮助你进行思维导图,可以运用于学习.写作.沟通.演讲.管 ...

  2. [Code] 变态之人键合一

    目的也比较单纯,选一门语言,走向人键合一. 选了两本书作为操练场:<精通Python设计模式>.<Data Structure and Algorithm in Python> ...

  3. [Linux] Telnet提示:Unable to connect to remote host: No route to host

    出错现象: [root@localhost ~]# telnet 192.168.1.128 Trying 192.168.1.128...telnet: connect to address 192 ...

  4. [C++]类的设计(2)——拷贝控制(析构和三五法则)

    1.析构函数:释放对象使用的资源,并销毁对象的非static数据成员:析构函数不接受参数,因此不能被重载.对于一个给定类,有且只有一个析构函数.   2.析构函数的组成:一个函数体+一个析构部分(im ...

  5. java数据结构——递归(Recursion)例题持续更新中

    继续学习数据结构递归,什么是递归呢?字面理解就是先递出去,然后回归,递归核心思想就是直接或间接调用本身,好比从前有座山,山里有位老和尚,在给小和尚讲故事,讲的是从前有座山,山里有位老和尚,在给小和尚讲 ...

  6. C# https证书通信Post/Get(解决做ssl通道时遇到“请求被中止: 未能创建 SSL/TLS 安全通道”问题)

    public static string HttpPost(string url, string param = null) { HttpWebRequest request; //如果是发送HTTP ...

  7. 使用file_get_contents() 发送GET、POST请求

    服务器端执行HTTP请求,大家经常使用的就是CURL,curl工具的确是很好的数据文件传输工具,那么除此之外还有其他的工具能实现这个功能吗? 现在为你介绍一个很常见的工具 file_get_conte ...

  8. 环境搭建-Hadoop集群搭建

    环境搭建-Hadoop集群搭建 写在前面,前面我们快速搭建好了centos的集群环境,接下来,我们就来开始hadoop的集群的搭建工作 实验环境 Hadoop版本:CDH 5.7.0 这里,我想说一下 ...

  9. Shell之三剑客

    目录 Shell之三剑客 参考 Grep Sed Awk Shell之三剑客

  10. 基于SpringBoot+WebSocket搭建一个简单的多人聊天系统

    前言   今天闲来无事,就来了解一下WebSocket协议.来简单了解一下吧. WebSocket是什么   首先了解一下WebSocket是什么?WebSocket是一种在单个TCP连接上进行全双工 ...