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
如上只是一个非常简单的一个目录,为了节约篇幅,只是把入口构建的相关的目录画出来。
我们看任何库相关的代码的第一步先把视线转移到 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系列---源码构建过程(四)的更多相关文章
- vue源码分析—Vue.js 源码构建
Vue.js 源码是基于 Rollup 构建的,它的构建相关配置都在 scripts 目录下.(Rollup 中文网和英文网) 构建脚本 通常一个基于 NPM 托管的项目都会有一个 package.j ...
- Vue.js 源码构建(三)
Vue.js 源码是基于 Rollup 构建的,它的构建相关配置都在 scripts 目录下. 构建脚本 通常一个基于 NPM 托管的项目都会有一个 package.json 文件,它是对项目的描述文 ...
- Vue系列---源码调试(二)
我们要对Vue源码进行分析,首先我们需要能够对vue源码进行调式(这里的源码调式是ES6版本的,不是打包后的代码),因此首先我们要去官方github上克隆一份vue项目下来,如下具体操作: 1. cl ...
- Vue.js 源码分析(十四) 基础篇 组件 自定义事件详解
我们在开发组件时有时需要和父组件沟通,此时可以用自定义事件来实现 组件的事件分为自定义事件和原生事件,前者用于子组件给父组件发送消息的,后者用于在组件的根元素上直接监听一个原生事件,区别就是绑定原生事 ...
- Netty 源码 ChannelHandler(四)编解码技术
Netty 源码 ChannelHandler(四)编解码技术 Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) 一.拆包与粘 ...
- 【原】SDWebImage源码阅读(四)
[原]SDWebImage源码阅读(四) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 SDWebImage中主要实现了NSURLConnectionDataDelega ...
- vue.js源码精析
MVVM大比拼之vue.js源码精析 VUE 源码分析 简介 Vue 是 MVVM 框架中的新贵,如果我没记错的话作者应该毕业不久,现在在google.vue 如作者自己所说,在api设计上受到了很多 ...
- 从template到DOM(Vue.js源码角度看内部运行机制)
写在前面 这篇文章算是对最近写的一系列Vue.js源码的文章(https://github.com/answershuto/learnVue)的总结吧,在阅读源码的过程中也确实受益匪浅,希望自己的这些 ...
- Netty 源码解析(四): Netty 的 ChannelPipeline
今天是猿灯塔“365篇原创计划”第四篇. 接下来的时间灯塔君持续更新Netty系列一共九篇 Netty 源码解析(一): 开始 Netty 源码解析(二): Netty 的 Channel Netty ...
随机推荐
- 百度脑图-离线版(支持Linux、Mac、Win)
免费好用的思维导图软件(在线版) 离线版:桌面版脑图是基于百度脑图的本地化版本,帮助你在没有互联网环境的情况下,依然可以使用脑图工具. 百度脑图帮助你进行思维导图,可以运用于学习.写作.沟通.演讲.管 ...
- [Code] 变态之人键合一
目的也比较单纯,选一门语言,走向人键合一. 选了两本书作为操练场:<精通Python设计模式>.<Data Structure and Algorithm in Python> ...
- [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 ...
- [C++]类的设计(2)——拷贝控制(析构和三五法则)
1.析构函数:释放对象使用的资源,并销毁对象的非static数据成员:析构函数不接受参数,因此不能被重载.对于一个给定类,有且只有一个析构函数. 2.析构函数的组成:一个函数体+一个析构部分(im ...
- java数据结构——递归(Recursion)例题持续更新中
继续学习数据结构递归,什么是递归呢?字面理解就是先递出去,然后回归,递归核心思想就是直接或间接调用本身,好比从前有座山,山里有位老和尚,在给小和尚讲故事,讲的是从前有座山,山里有位老和尚,在给小和尚讲 ...
- C# https证书通信Post/Get(解决做ssl通道时遇到“请求被中止: 未能创建 SSL/TLS 安全通道”问题)
public static string HttpPost(string url, string param = null) { HttpWebRequest request; //如果是发送HTTP ...
- 使用file_get_contents() 发送GET、POST请求
服务器端执行HTTP请求,大家经常使用的就是CURL,curl工具的确是很好的数据文件传输工具,那么除此之外还有其他的工具能实现这个功能吗? 现在为你介绍一个很常见的工具 file_get_conte ...
- 环境搭建-Hadoop集群搭建
环境搭建-Hadoop集群搭建 写在前面,前面我们快速搭建好了centos的集群环境,接下来,我们就来开始hadoop的集群的搭建工作 实验环境 Hadoop版本:CDH 5.7.0 这里,我想说一下 ...
- Shell之三剑客
目录 Shell之三剑客 参考 Grep Sed Awk Shell之三剑客
- 基于SpringBoot+WebSocket搭建一个简单的多人聊天系统
前言 今天闲来无事,就来了解一下WebSocket协议.来简单了解一下吧. WebSocket是什么 首先了解一下WebSocket是什么?WebSocket是一种在单个TCP连接上进行全双工 ...