Vue源码解读(二)---Vue源码构建

mac2022-06-30  10

Vue.js 源码构建

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

构建脚本

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

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

{ "script": { "build": "node scripts/build.js", "build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer", "build:weex": "npm run build --weex" } }

这⾥总共有 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 }) } build(builds)

这段代码逻辑⾮常简单,先从配置⽂件读取配置,再通过命令⾏参数对构建配置做过滤,这样就可以构建出不同⽤途的 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 14}, // 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 }, // 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 ⽂件地址。

output.format 生成包的格式,有如下格式:

1. amd -- 异步模块定义,用于像RequestJS这样的模块加载器。

2. cjs -- CommonJS, 适用于Node或Browserify/webpack

3. es -- 将软件包保存为ES模块文件。

4. iife -- 一个自动执行的功能,适合作为 <script>标签这样的。

5. umd -- 通用模块定义,以amd, cjs, 和 iife 为一体。

umd模块化规范解释

(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = global || self, global.Vue = factory()); }(this, function () { 'use strict';

CommonJS规范

CommonJS 有三个全局变量 module、exports 和 require。但是由于 AMD 也有 require 这个全局变量,故不使用这个变量来进行检测。

如果想要对外提供接口的话,可以将接口绑定到 exports (即 module.exports) 上

typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory()

AMD

AMD 规范中,define 函数同样有一个公有属性 define.amd。

AMD 中的参数便是这个模块的依赖。那么如何在 AMD 中提供接口呢?它是返回一个对象,这个对象就作为这个模块的接口,故我们可以这样写

typeof define === 'function' && define.amd ? define(factory)

 

以 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

最新回复(0)