【读vue 源码】溯源 import Vue from 到底做了什么?

阅读资源

vue.js源码托管地址

flow 静态检查工具地址

rollup 源码构建

flow 静态检查工具

flow 是由Facebook出品的javascript静态类型检查工具。vue.js 的源码是采用flow做了静态类型检查。因为javascript是动态类型的语言。语言灵活的同时也很容易引发一些隐蔽的隐患代码。在编译的时候看上去不会报错,但运行的时候就会出现奇奇怪怪的bug。而类型检查,就是在编译期尽早发现由类型错误引起的bug,又不影响代码运行(不需要运行时动态检查类型)。一些复杂的项目使用工具的手段可以增强代码的可读性,增加项目的可维护性。

flow 类型检查分为两种方式:

  • 类型推断:通过变量使用的上下文来推断出变量类型,然后根据推断来检查类型。
  • 类型注释:注释好变量的类型,通过注释来检查类型。
1// 类型推断 2// @flow 3function split(str) { 4 return str.split(' ') 5} 6split(1) // Error! 7split('abc') // Works!

传入参数 1 的时候,flow 代码检查就会报错,因为 split()方法是字符串原型对象上的方法,它期待的参数类型是字符串,而传入的确实是数字型,所以就会报错。当传入‘abc’字符串后就可以正常执行代码了。

1// 类型注释 2// @flow 3function concat(a: string, b: string) { 4 return a + b; 5} 6 7concat("A", "B"); // Works! 8concat(1, 2); // Error!

因为加运算符,即可以执行数字的相加,也可以执行字符串的拼接。所以 concat()方法提前注释好参数的类型。接收的参数a和b都是字符串,所以调用concat(1, 2);就会报错。调用concat("A", "B")就能正常执行。

需要注意的是:当一个文件中出现注释@flow 的标记,说明该文件是需要用flow 进行类型检查的,否则不进行 flow 检查。更多内容请查看flow 静态检查工具官方文档。

vue.js源码中的flow应用

在 Vue.js 的主目录下有 .flowconfig 文件, 它是 Flow 的配置文件。如下:

1[ignore] // 忽略的文件 2.*/node_modules/.* 3.*/test/.* 4.*/scripts/.* 5.*/examples/.* 6.*/benchmarks/.* 7 8[include] 9 10[libs] // 这里 [libs] 配置的是 flow,表示指定的库定义都在 flow 文件夹内 11flow // 对应的 flow 目录 12 13[options] 14unsafe.enable_getters_and_setters=true 15module.name_mapper='^compiler/\(.*\)$' -> '<PROJECT_ROOT>/src/compiler/\1' 16module.name_mapper='^core/\(.*\)$' -> '<PROJECT_ROOT>/src/core/\1' 17module.name_mapper='^shared/\(.*\)$' -> '<PROJECT_ROOT>/src/shared/\1' 18module.name_mapper='^web/\(.*\)$' -> '<PROJECT_ROOT>/src/platforms/web/\1' 19module.name_mapper='^weex/\(.*\)$' -> '<PROJECT_ROOT>/src/platforms/weex/\1' 20module.name_mapper='^server/\(.*\)$' -> '<PROJECT_ROOT>/src/server/\1' 21module.name_mapper='^entries/\(.*\)$' -> '<PROJECT_ROOT>/src/entries/\1' 22module.name_mapper='^sfc/\(.*\)$' -> '<PROJECT_ROOT>/src/sfc/\1' 23suppress_comment= \\(.\\|\n\\)*\\$flow-disable-line

vue.js 内的flow目录说明

1flow 2├── compiler.js # 编译相关 3├── component.js # 组件数据结构 4├── global-api.js # Global API 结构 5├── modules.js # 第三方库定义 6├── options.js # 选项相关 7├── ssr.js # 服务端渲染相关 8├── vnode.js # 虚拟 node 相关

vue 源码的目录结构

Vue.js 的源码都在 src 目录下,目录结构如下:

1src 2├── compiler # 包含 Vue.js 所有编译相关的代码。 3├── core # 包含了 Vue.js 的核心代码,包括内置组件、全局 API 封装,Vue 实例化、观察者、虚拟 DOM、工具函数等等。 4├── platforms # Vue.js 是一个跨平台的 MVVM 框架,它可以跑在 web 上,也可以配合 weex 跑在 native 客户端上。 5├── server # 所有服务端渲染相关的逻辑都在这个目录下。 6├── sfc # vue.js通过 .vue 单文件来编写组件。这个目录下的代码逻辑会把 .vue 文件内容解析成一个 JavaScript 的对象。 7├── shared # 定义一些工具方法,这里定义的工具方法都是会被浏览器端的 Vue.js 和服务端的 Vue.js 所共享的。

从整个目录结构来看,作者把功能模块拆的非常的清楚,相关的逻辑都放在同一个目录下来进行维护。可复用的代码也单独成为一个文件夹。

vue.js源码构建

Vue.js 源码是基于 Rollup 构建的,它的构建相关配置都在 scripts 目录下。Rollup 是一个javascript 的模块打包工具。相比webpack更为轻量。了解更多请访问Rollup Github 地址

构建脚本

NPM 托管的项目都会有一个package.json的文件,对这个项目加以描述。script 字段用来定义NPM 的执行脚本。vue.js 的执行构建的脚本如下:

1"scripts": { 2 "build": "node scripts/build.js", 3 "build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer", 4 "build:weex": "npm run build -- weex", 5 },

也就是说,当我们执行 npm run build的时候,实际上就是执行 node scripts/build.js这条语句。也就是说 scripts/build.js就是构建入口的js文件。

构建过程

1. 从构建的入口文件开始:scripts/build.js

1let builds = require('./config').getAllBuilds() //拿到所有的配置 2 3// filter builds via command line arg 4if (process.argv[2]) { 5 const filters = process.argv[2].split(',') 6 builds = builds.filter(b => { 7 return filters.some(f => b.output.file.indexOf(f) > -1 || b._name.indexOf(f) > -1) 8 }) 9} else { 10 // filter out weex builds by default 11 builds = builds.filter(b => { 12 return b.output.file.indexOf('weex') === -1 13 }) 14} 15 16build(builds)

上面这部分代码,首先从配置文件scripts/config.js中读取配置相关的数据,在对配置进行相应的过滤,从而构建出不同用途的vue.js。

2. 查看构建的配置文件:scripts/config.js

1const builds = { 2 // Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify 3 'web-runtime-cjs-dev': { 4 entry: resolve('web/entry-runtime.js'), 5 dest: resolve('dist/vue.runtime.common.dev.js'), 6 format: 'cjs', 7 env: 'development', 8 banner 9 }, 10 'web-runtime-cjs-prod': { 11 entry: resolve('web/entry-runtime.js'), 12 dest: resolve('dist/vue.runtime.common.prod.js'), 13 format: 'cjs', 14 env: 'production', 15 banner 16 }, 17 // Runtime+compiler CommonJS build (CommonJS) 18 'web-full-cjs-dev': { 19 entry: resolve('web/entry-runtime-with-compiler.js'), 20 dest: resolve('dist/vue.common.dev.js'), 21 format: 'cjs', 22 env: 'development', 23 alias: { he: './entity-decoder' }, 24 banner 25 }, 26 'web-full-cjs-prod': { 27 entry: resolve('web/entry-runtime-with-compiler.js'), 28 dest: resolve('dist/vue.common.prod.js'), 29 format: 'cjs', 30 env: 'production', 31 alias: { he: './entity-decoder' }, 32 banner 33 }, 34 // Runtime only ES modules build (for bundlers) 35 'web-runtime-esm': { 36 entry: resolve('web/entry-runtime.js'), 37 dest: resolve('dist/vue.runtime.esm.js'), 38 format: 'es', 39 banner 40 }, 41 // Runtime+compiler ES modules build (for bundlers) 42 'web-full-esm': { 43 entry: resolve('web/entry-runtime-with-compiler.js'), 44 dest: resolve('dist/vue.esm.js'), 45 format: 'es', 46 alias: { he: './entity-decoder' }, 47 banner 48 }, 49 // Runtime+compiler ES modules build (for direct import in browser) 50 'web-full-esm-browser-dev': { 51 entry: resolve('web/entry-runtime-with-compiler.js'), 52 dest: resolve('dist/vue.esm.browser.js'), 53 format: 'es', 54 transpile: false, 55 env: 'development', 56 alias: { he: './entity-decoder' }, 57 banner 58 }, 59 // Runtime+compiler ES modules build (for direct import in browser) 60 'web-full-esm-browser-prod': { 61 entry: resolve('web/entry-runtime-with-compiler.js'), 62 dest: resolve('dist/vue.esm.browser.min.js'), 63 format: 'es', 64 transpile: false, 65 env: 'production', 66 alias: { he: './entity-decoder' }, 67 banner 68 }, 69 // runtime-only build (Browser) 70 'web-runtime-dev': { 71 entry: resolve('web/entry-runtime.js'), 72 dest: resolve('dist/vue.runtime.js'), 73 format: 'umd', 74 env: 'development', 75 banner 76 }, 77 // runtime-only production build (Browser) 78 'web-runtime-prod': { 79 entry: resolve('web/entry-runtime.js'), 80 dest: resolve('dist/vue.runtime.min.js'), 81 format: 'umd', 82 env: 'production', 83 banner 84 }, 85 // Runtime+compiler development build (Browser) 86 'web-full-dev': { 87 entry: resolve('web/entry-runtime-with-compiler.js'), 88 dest: resolve('dist/vue.js'), 89 format: 'umd', 90 env: 'development', 91 alias: { he: './entity-decoder' }, 92 banner 93 }, 94 // Runtime+compiler production build (Browser) 95 'web-full-prod': { 96 entry: resolve('web/entry-runtime-with-compiler.js'), 97 dest: resolve('dist/vue.min.js'), 98 format: 'umd', 99 env: 'production', 100 alias: { he: './entity-decoder' }, 101 banner 102 }, 103 // Web compiler (CommonJS). 104 'web-compiler': { 105 entry: resolve('web/entry-compiler.js'), 106 dest: resolve('packages/vue-template-compiler/build.js'), 107 format: 'cjs', 108 external: Object.keys(require('../packages/vue-template-compiler/package.json').dependencies) 109 }, 110 // Web compiler (UMD for in-browser use). 111 'web-compiler-browser': { 112 entry: resolve('web/entry-compiler.js'), 113 dest: resolve('packages/vue-template-compiler/browser.js'), 114 format: 'umd', 115 env: 'development', 116 moduleName: 'VueTemplateCompiler', 117 plugins: [node(), cjs()] 118 }, 119 // Web server renderer (CommonJS). 120 'web-server-renderer-dev': { 121 entry: resolve('web/entry-server-renderer.js'), 122 dest: resolve('packages/vue-server-renderer/build.dev.js'), 123 format: 'cjs', 124 env: 'development', 125 external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies) 126 }, 127 'web-server-renderer-prod': { 128 entry: resolve('web/entry-server-renderer.js'), 129 dest: resolve('packages/vue-server-renderer/build.prod.js'), 130 format: 'cjs', 131 env: 'production', 132 external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies) 133 }, 134 'web-server-renderer-basic': { 135 entry: resolve('web/entry-server-basic-renderer.js'), 136 dest: resolve('packages/vue-server-renderer/basic.js'), 137 format: 'umd', 138 env: 'development', 139 moduleName: 'renderVueComponentToString', 140 plugins: [node(), cjs()] 141 }, 142 'web-server-renderer-webpack-server-plugin': { 143 entry: resolve('server/webpack-plugin/server.js'), 144 dest: resolve('packages/vue-server-renderer/server-plugin.js'), 145 format: 'cjs', 146 external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies) 147 }, 148 'web-server-renderer-webpack-client-plugin': { 149 entry: resolve('server/webpack-plugin/client.js'), 150 dest: resolve('packages/vue-server-renderer/client-plugin.js'), 151 format: 'cjs', 152 external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies) 153 }, 154 // Weex runtime factory 155 'weex-factory': { 156 weex: true, 157 entry: resolve('weex/entry-runtime-factory.js'), 158 dest: resolve('packages/weex-vue-framework/factory.js'), 159 format: 'cjs', 160 plugins: [weexFactoryPlugin] 161 }, 162 // Weex runtime framework (CommonJS). 163 'weex-framework': { 164 weex: true, 165 entry: resolve('weex/entry-framework.js'), 166 dest: resolve('packages/weex-vue-framework/index.js'), 167 format: 'cjs' 168 }, 169 // Weex compiler (CommonJS). Used by Weex's Webpack loader. 170 'weex-compiler': { 171 weex: true, 172 entry: resolve('weex/entry-compiler.js'), 173 dest: resolve('packages/weex-template-compiler/build.js'), 174 format: 'cjs', 175 external: Object.keys(require('../packages/weex-template-compiler/package.json').dependencies) 176 } 177}

上面这部分代码是vue.js构建的配置、服务端渲染webpack插件、weex的打包配置。对于单个配置,遵循了Rollup 的构建规则。配置说明:

  • entry属性:构建入口js文件的地址。
  • dest属性:构建完成后的js文件地址
  • format属性:构建文件的格式。'cjs'表示构建出来的文件遵循 CommonJS 规范;'es' 表示构建出来的文件遵循 ES Module 规范; 'umd' 表示构建出来的文件遵循 UMD 规范。
  • banner属性:对vue.js的一个简单的描述。包含作者信息,版本号等。

3. 以一个配置为例探寻构建过程:web-runtime-cjs

1'web-runtime-cjs-dev': { 2 entry: resolve('web/entry-runtime.js'), 3 dest: resolve('dist/vue.runtime.common.dev.js'), 4 format: 'cjs', 5 env: 'development', 6 banner 7 },

从配置中可见:入口的js文件地址,与完成后的js地址,均调用了resolve() 方法。

1const aliases = require('./alias') 2const 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() 方法将传入的参数 p 调用split()方法,通过'/'分割成数组,然后取第一个元素设置为base,那么上述案例中 base即为 web。但是base 并不是真实路径,而是借助了别名的配置。别名配置的代码如下:scripts/alias

1const path = require('path') 2 3const resolve = p => path.resolve(__dirname, '../', p) 4// 到真实文件的一个映射关系 5module.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}

由上述代码可知:web 对应的知识路径是path.resolve(__dirname, '../', 'src/platforms/web')。由此找到它的入口文件是src/platforms/web/entry-runtime.js它经过 Rollup 的构建打包后,最终会在 dist/vue.runtime.common.js

Runtime Only VS Runtime + Compiler

通常我们利用 vue-cli 去初始化我们的 Vue.js 项目的时候会询问是用 Runtime Only 版本的还是 Runtime + Compiler 版本。他们的区别如下:

  • Runtime Only 通常需要借助如 webpack 的 vue-loader 工具把 .vue 文件编译成 JavaScript,将template 编译成render 函数。因为是在编译阶段做的,所以它只包含运行时的 Vue.js 代码,因此代码体积也会更轻量。

  • Runtime + Compiler
    我们如果没有对代码做预编译,但又使用了 Vue 的 template 属性并传入一个字符串,则需要在客户端编译模板,如下所示:

1// 需要编译器的版本 2new Vue({ 3 template: '<div>{{ hi }}</div>' 4}) 5 6// 这种情况不需要 7new Vue({ 8 render (h) { 9 return h('div', this.hi) 10 } 11})

综上:因为在 Vue.js 2.0 中,最终渲染都是通过 render 函数,如果写 template 属性,则需要编译成 render函数,那么这个编译过程会发生运行时,所以需要带有编译器的版本。显然,这个编译过程对性能会有一定损耗,所以推荐使用 Runtime Only

vue 的入口

当我们开发的时候import Vue from 'vue'到底做了些什么?顺着 Runtime Only 构建出来的vue.js 它的入口是在src/platforms/web/entry-runtime.js代码如下:

1/* @flow */ 2 3import Vue from './runtime/index' 4 5export default Vue

上述代码 导出一个 Vue,而这个Vue是从./runtime/index导入的。

vue静态的全局配置和原型对象上的方法

继续看./runtime/index文件。代码如下:

1/* @flow */ 2 3import Vue from 'core/index' 4import config from 'core/config' 5import { extend, noop } from 'shared/util' 6import { mountComponent } from 'core/instance/lifecycle' 7import { devtools, inBrowser } from 'core/util/index' 8 9import { 10 query, 11 mustUseProp, 12 isReservedTag, 13 isReservedAttr, 14 getTagNamespace, 15 isUnknownElement 16} from 'web/util/index' 17 18import { patch } from './patch' 19import platformDirectives from './directives/index' 20import platformComponents from './components/index' 21 22// install platform specific utils 23// 静态的全局配置 24Vue.config.mustUseProp = mustUseProp 25Vue.config.isReservedTag = isReservedTag 26Vue.config.isReservedAttr = isReservedAttr 27Vue.config.getTagNamespace = getTagNamespace 28Vue.config.isUnknownElement = isUnknownElement 29 30// install platform runtime directives & components 31extend(Vue.options.directives, platformDirectives) 32extend(Vue.options.components, platformComponents) 33 34// install platform patch function 35// 原型__patch__ 36Vue.prototype.__patch__ = inBrowser ? patch : noop 37 38// public mount method 39// 定义了原型上的$mount 方法 40Vue.prototype.$mount = function ( 41 el?: string | Element, 42 hydrating?: boolean 43): Component { 44 el = el && inBrowser ? query(el) : undefined 45 return mountComponent(this, el, hydrating) 46} 47 48// devtools global hook 49/* istanbul ignore next */ 50if (inBrowser) { 51 setTimeout(() => { 52 if (config.devtools) { 53 if (devtools) { 54 devtools.emit('init', Vue) 55 } else if ( 56 process.env.NODE_ENV !== 'production' && 57 process.env.NODE_ENV !== 'test' 58 ) { 59 console[console.info ? 'info' : 'log']( 60 'Download the Vue Devtools extension for a better development experience:\n' + 61 'https://github.com/vuejs/vue-devtools' 62 ) 63 } 64 } 65 if (process.env.NODE_ENV !== 'production' && 66 process.env.NODE_ENV !== 'test' && 67 config.productionTip !== false && 68 typeof console !== 'undefined' 69 ) { 70 console[console.info ? 'info' : 'log']( 71 `You are running Vue in development mode.\n` + 72 `Make sure to turn on production mode when deploying for production.\n` + 73 `See more tips at https://vuejs.org/guide/deployment.html` 74 ) 75 } 76 }, 0) 77} 78 79export default Vue

上述代码还是从core/index文件中导入一个Vue,最后将其导出。在该文件中定义了Vue的一些静态的全局配置,和原型对象上的方法。

通过initGlobalAPI 给 vue 添加静态方法

继续往下看core/index如何定义 Vue 的,代码如下:

1import Vue from './instance/index' 2import { initGlobalAPI } from './global-api/index' 3import { isServerRendering } from 'core/util/env' 4import { FunctionalRenderContext } from 'core/vdom/create-functional-component' 5 6initGlobalAPI(Vue) // 定义了vue 本身的静态方法 7 8Object.defineProperty(Vue.prototype, '$isServer', { 9 get: isServerRendering 10}) 11 12Object.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 20Object.defineProperty(Vue, 'FunctionalRenderContext', { 21 value: FunctionalRenderContext 22}) 23 24Vue.version = '__VERSION__' 25 26export default Vue

同样 它是从./instance/index 文件中导入 Vue ,最后将其导出。该文件通过initGlobalAPI方法 给 vue 添加静态方法。

通过 Mixin 混入往Vue 的原型上添加方法

继续往下,到./instance/index 文件,代码如下:

1import { initMixin } from './init' 2import { stateMixin } from './state' 3import { renderMixin } from './render' 4import { eventsMixin } from './events' 5import { lifecycleMixin } from './lifecycle' 6import { warn } from '../util/index' 7 8 9// 终于溯源结束了,Vue就是一个用 Function 实现的类,所以才通过 new Vue 去实例化它。 10function Vue (options) { 11 if (process.env.NODE_ENV !== 'production' && 12 !(this instanceof Vue) 13 ) { 14 warn('Vue is a constructor and should be called with the `new` keyword') 15 } 16 this._init(options) 17} 18 19// 在vue原型上挂了方法 20initMixin(Vue) 21stateMixin(Vue) 22eventsMixin(Vue) 23lifecycleMixin(Vue) 24renderMixin(Vue) 25 26export default Vue

到此,终于溯源结束了,Vue就是一个用 Function 实现的类,所以才通过 new Vue 去实例化它。该文件中通过 Mixin 混入的方法,往Vue 的原型上添加了方法。

结束

最近一段时间都会认真的去看vue.js的源码。【读vue 源码】会按照一个系列去更新。分享自己学习的同时,也希望与更多的同行交流所得,如此而已。

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )