Vue.js 源码⽬录设计和Vue.js 源码构建

dist 目录放构不同版本的vue.js
   

Vue.js 的源码都在 src ⽬录下,其⽬录结构如下。

src

├── compiler

├── core

├── platforms

├── server

compiler

 

在写代码的时候大都写template 而不是render函数,那么关于template的编译逻辑都在compiler

compiler ⽬录包含 Vue.js 所有编译相关的代码。它包括把模板解析成 ast 语法树,ast 语法树优化,代码

⽣成等功能。

编译的⼯作可以在构建时做(借助 webpack、vue-loader 等辅助插件);也可以在运⾏时做,使⽤包含构建功能的 Vue.js。显然,编译是⼀项耗性能的⼯作,所以更推荐前者——离线编译。

core

core ⽬录包含了 Vue.js 的核⼼代码,包括内置组件、全局 API 封装,Vue 实例化、观察者、虚拟

DOM、⼯具函数等等。

初始化,生命周期等都在core目录下的instance目录

observer跟响应式数据相关

util目录,放工具方法

vdom 目录放虚拟dom

这⾥的代码可谓是 Vue.js 的灵魂,也是我们之后需要重点分析的地⽅。

platform

Vue.js 是⼀个跨平台的 MVVM 框架,它可以跑在 web 上,也可以配合 weex 跑在 natvie 客户端上。

platform 是 Vue.js 的⼊⼝,2 个⽬录代表 2 个主要⼊⼝,分别打包成运⾏在 web 上和 weex 上的

Vue.js。

web目录 vue可以编译出在浏览器运行的框架

我们会重点分析 web ⼊⼝打包后的 Vue.js,对于 weex ⼊⼝打包的 Vue.js,感兴趣的同学可以⾃⾏研究。

server

Vue.js 2.0 ⽀持了服务端渲染,所有服务端渲染相关的逻辑都在这个⽬录下。注意:这部分代码是跑在服务端的 Node.js,不要和跑在浏览器端的 Vue.js 混为⼀谈。

服务端渲染主要的⼯作是把组件渲染为服务器端的 HTML 字符串,将它们直接发送到浏览器,最后将静态标记"混合"为客户端上完全交互的应⽤程序。

sfc

通常我们开发 Vue.js 都会借助 webpack 构建, 然后通过 .vue 单⽂件的编写组件。这个⽬录下的代码逻辑会把 .vue ⽂件内容解析成⼀个 JavaScript 的对象。

shared

Vue.js 会定义⼀些⼯具⽅法,这⾥定义的⼯具⽅法都是会被浏览器端的 Vue.js 和服务端的 Vue.js 所共享的。

总结

从 Vue.js 的⽬录设计可以看到,作者把功能模块拆分的⾮常清楚,相关的逻辑放在⼀个独⽴的⽬录下维护,并且把复⽤的代码也抽成⼀个独⽴⽬录。

这样的⽬录设计让代码的阅读性和可维护性都变强,是⾮常值得学习和推敲的。

Vue.js 源码是基于 Rollup 构建的,它的构建相关配置都在 scripts ⽬录下。

构建脚本

通常⼀个基于 NPM 托管的项⽬都会有⼀个 package.json ⽂件,它是对项⽬的描述⽂件,它的内容实际上是⼀个标准的 JSON 对象。

我们通常会配置 script 字段作为 NPM 的执⾏脚本,Vue.js 源码构建的脚本如下:

Vue.js 源码是基于 Rollup 构建的,它的构建相关配置都在 scripts ⽬录下。

构建脚本

通常⼀个基于 NPM 托管的项⽬都会有⼀个 package.json ⽂件,它是对项⽬的描述⽂件,它的内容实际上是⼀个标准的 JSON 对象。

在package.js文件中

"main": "dist/vue.runtime.common.js",

"module": "dist/vue.runtime.esm.js",

意思是vue.js的入口文件

我们通常会配置 script 字段作为 NPM 的执⾏脚本,Vue.js 源码构建的脚本如下:

这⾥总共有 3 条命令,作⽤都是构建 Vue.js,后⾯ 2 条是在第⼀条命令的基础上,添加⼀些环境参数。

当在命令⾏运⾏ npm run build 的时候,实际上就会执⾏  node scripts/build.js ,接下来我们来看看它实际是怎么构建的。

构建过程

我们对于构建过程分析是基于源码的,先打开构建的⼊⼝ JS ⽂件,在 scripts/build.js 中:

let builds = require('./config').getAllBuilds()

 

// filter builds via command line arg if (process.argv[2]) {

const filters = process.argv[2].split(',') builds = builds.filter(b => {

return filters.some(f => b.output.file.indexOf(f) > -1 || b._name.indexOf(f) >

-1)

})

} else {

// filter out weex builds by default builds = builds.filter(b => {

return b.output.file.indexOf('weex') === -1

这段代码逻辑⾮常简单,先从配置⽂件读取配置,再通过命令⾏参数对构建配置做过滤,这样就可以构建出不同⽤途的 Vue.js 了。接下来我们看⼀下配置⽂件,在 scripts/config.js 中:

const builds = {

// Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify 'web-runtime-cjs': {

entry: resolve('web/entry-runtime.js'), dest: resolve('dist/vue.runtime.common.js'), format: 'cjs',

banner

},

// Runtime+compiler CommonJS build (CommonJS) 'web-full-cjs': {

entry: resolve('web/entry-runtime-with-compiler.js'), dest: resolve('dist/vue.common.js'),

format: 'cjs',

alias: { he: './entity-decoder' }, banner

},

// Runtime only (ES Modules). Used by bundlers that support ES Modules,

// e.g. Rollup & Webpack 2 'web-runtime-esm': {

entry: resolve('web/entry-runtime.js'), dest: resolve('dist/vue.runtime.esm.js'), format: 'es',

banner

},

// Runtime+compiler CommonJS build (ES Modules) 'web-full-esm': {

entry: resolve('web/entry-runtime-with-compiler.js'), dest: resolve('dist/vue.esm.js'),

format: 'es',

alias: { he: './entity-decoder' }, banner

},

// runtime-only build (Browser) 'web-runtime-dev': {

entry: resolve('web/entry-runtime.js'), dest: resolve('dist/vue.runtime.js'), format: 'umd',

env: 'development', banner

},

// runtime-only production build (Browser) 'web-runtime-prod': {

entry: resolve('web/entry-runtime.js'), dest: resolve('dist/vue.runtime.min.js'), format: 'umd',

env: 'production', banner

},

// Runtime+compiler development build (Browser) '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

},

alias.js是真实路径的映射关系

// Runtime+compiler production build (Browser) 'web-full-prod': {

entry: resolve('web/entry-runtime-with-compiler.js'), dest: resolve('dist/vue.min.js'),

format: 'umd', env: 'production',

alias: { he: './entity-decoder' }, banner

},

// ...

}

这⾥列举了⼀些 Vue.js 构建的配置,关于还有⼀些服务端渲染 webpack 插件以及 weex 的打包配置就不列举了。

对于单个配置,它是遵循 Rollup 的构建规则的。其中 entry 属性表⽰构建的⼊⼝ JS ⽂件地

址, dest 属性表⽰构建后的 JS ⽂件地址。 format 属性表⽰构建的格式, cjs 表⽰构建出来的

⽂件遵循 CommonJS 规范, es 表⽰构建出来的⽂件遵循 ES Module 规范。 umd 表⽰构建出来的⽂件遵循 UMD 规范。

以  web-runtime-cjs 配置为例,它的  entry 是  resolve('web/entry-runtime.js') ,先来看⼀下 resolve 函数的定义。

源码⽬录: 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)

}

}

这⾥的  resolve 函数实现⾮常简单,它先把  resolve 函数传⼊的参数  p 通过   / 做了分割成数组,然后取数组第⼀个元素设置为 base 。在我们这个例⼦中,参数 p 是 web/entry-

runtime.js ,那么 base 则为 web 。 base 并不是实际的路径,它的真实路径借助了别名的配置,我们来看⼀下别名配置的代码,在 scripts/alias 中:

 

const path = require('path')

 

module.exports = {

vue: path.resolve( dirname, '../src/platforms/web/entry-runtime-with-compiler'), compiler: path.resolve( dirname, '../src/compiler'),

core: path.resolve(  dirname,  '../src/core'), shared: path.resolve(  dirname, '../src/shared'), web: path.resolve(  dirname, '../src/platforms/web'),

weex: path.resolve( dirname, '../src/platforms/weex'), server: path.resolve( dirname, '../src/server'), entries: path.resolve(  dirname, '../src/entries'), sfc: path.resolve( dirname, '../src/sfc')

}

很显然,这⾥   web 对应的真实的路径是   path.resolve( dirname, '../src/platforms/web') , 这个路径就找到了 Vue.js 源码的 web ⽬录。然后 resolve 函数通过

  path.resolve(aliases[base], p.slice(base.length + 1)) 找到了最终路径,它就是 Vue.js 源码

web ⽬录下的 entry-runtime.js 。因此, web-runtime-cjs 配置对应的⼊⼝⽂件就找到了。

它经过 Rollup 的构建打包后,最终会在 dist ⽬录下⽣成 vue.runtime.common.js 。

 

Runtime Only VS Runtime+Compiler

通常我们利⽤ vue-cli 去初始化我们的 Vue.js 项⽬的时候会询问我们⽤ Runtime Only 版本的还是

Runtime+Compiler 版本。下⾯我们来对⽐这两个版本。

Runtime Only

我们在使⽤ Runtime Only 版本的 Vue.js 的时候,通常需要借助如 webpack 的 vue-loader ⼯具把 .vue ⽂件编译成 JavaScript,因为是在编译阶段做的,所以它只包含运⾏时的 Vue.js 代码,因此代码体积也会更轻量。

Runtime+Compiler

 

我们如果没有对代码做预编译,但⼜使⽤了 Vue 的 template 属性并传⼊⼀个字符串,则需要在客户端编译模板,如下所⽰:

new Vue({

template: '<div>{{ hi }}</div>'

})

 

// 这种情况不需要

new Vue({ render (h) {

return h('div', this.hi)

}

因为在 Vue.js 2.0 中,最终渲染都是通过 render 函数,如果写 template 属性,则需要编译成

  render 函数,那么这个编译过程会发⽣运⾏时,所以需要带有编译器的版本。

很显然,这个编译过程对性能会有⼀定损耗,所以通常我们更推荐使⽤ Runtime-Only 的 Vue.js。

总结

通过这⼀节的分析,我们可以了解到 Vue.js 的构建打包过程,也知道了不同作⽤和功能的 Vue.js 它们对应的⼊⼝以及最终编译⽣成的 JS ⽂件。尽管在实际开发过程中我们会⽤ Runtime Only 版本开发⽐较多,但为了分析 Vue 的编译过程,我们这门课重点分析的源码是 Runtime+Compiler 的 Vue.js。

 

 

 

在build.js文件里

if (process.argv[2]) {

const filters = process.argv[2].split(',')

builds = builds.filter(b => {

return filters.some(f => b.output.file.indexOf(f) > -1 || b._name.indexOf(f) > -1)

})

process.argv[2]是指package.json文件里的script命令

builds.filter把不需要打包的给过滤掉

import { initMixin } from './init'

initMixin(Vue)

就是给原型链vue添加方法

 
   

 

猜你喜欢

转载自blog.csdn.net/qq_41153478/article/details/82291613
今日推荐