Vue.js 源码构建过程分析

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

Vue.js 为什么选用Rollup,而不是webpack?

  1. Tree-shaking 的优化:Rollup 在 tree-shaking 方面表现得更出色。Tree-shaking 是一种在打包时去除未引用代码的技术,可以帮助减少最终打包文件的大小。Rollup 的静态分析能够更准确地识别和移除未使用的代码,而 webpack 在这方面可能不如 Rollup。
  2. ES6 模块支持:Rollup 原生支持 ES6 模块,而 Vue.js 的源码也是使用 ES6 模块编写的。这种兼容性使得 Rollup 更适合用于打包 Vue.js 的源码。
  3. 简洁和高效:Rollup 的配置相对简洁,没有 webpack 那样的复杂插件系统。这使得 Rollup 在构建过程中更加高效,减少了不必要的复杂性和性能开销。
  4. 生成代码更小: Rollup 在打包库时具有更好的性能和优化能力,能够生成更小、更高效的代码包。

构建脚本

通常一个基于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, 第二三条命令加了不同的参数,分别去构建web版,ssr版,weex版的Vue。

当在命令行运行npm run build时,实际上就会执行 node scripts/build.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)

这段代码中先从config文件中拿到builds配置,然后根据命令行输入的 npm run build 后面所带的的参数,过滤出要执行的构建配置,这样就可以构建出不同版本的Vue.js。
接下来我们来看一下配置文件,在config.js 中:

const builds = {
// Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify
'web-runtime-cjs-dev': {
entry: resolve('web/entry-runtime.js'),
dest: resolve('dist/vue.runtime.common.dev.js'),
format: 'cjs',
env: 'development',
banner
},
'web-runtime-cjs-prod': {
entry: resolve('web/entry-runtime.js'),
dest: resolve('dist/vue.runtime.common.prod.js'),
format: 'cjs',
env: 'production',
banner
},
// Runtime+compiler CommonJS build (CommonJS)
'web-full-cjs-dev': {
entry: resolve('web/entry-runtime-with-compiler.js'),
dest: resolve('dist/vue.common.dev.js'),
format: 'cjs',
env: 'development',
alias: { he: './entity-decoder' },
banner
},
'web-full-cjs-prod': {
entry: resolve('web/entry-runtime-with-compiler.js'),
dest: resolve('dist/vue.common.prod.js'),
format: 'cjs',
env: 'production',
alias: { he: './entity-decoder' },
banner
},
// Runtime only ES modules build (for bundlers)
'web-runtime-esm': {
entry: resolve('web/entry-runtime.js'),
dest: resolve('dist/vue.runtime.esm.js'),
format: 'es',
banner
},
// Runtime+compiler ES modules build (for bundlers)
'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+compiler ES modules build (for direct import in browser)
'web-full-esm-browser-dev': {
entry: resolve('web/entry-runtime-with-compiler.js'),
dest: resolve('dist/vue.esm.browser.js'),
format: 'es',
transpile: false,
env: 'development',
alias: { he: './entity-decoder' },
banner
},
// Runtime+compiler ES modules build (for direct import in browser)
'web-full-esm-browser-prod': {
entry: resolve('web/entry-runtime-with-compiler.js'),
dest: resolve('dist/vue.esm.browser.min.js'),
format: 'es',
transpile: false,
env: 'production',
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
},
// 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构建配置。
对于单个的配置,它要遵循Roullp的构建。其中entry表示入口文件,dest表示构建后的js文件地址,format表示构建的格式,cjs表示构建文件要遵循CommonJS规范,es表示构建文件要遵循ES Module规范,umd表示构建文件要遵循UMD规范。

web-runtime-cjs-dev配置为例,它的entryresolve('web/entry-runtime-with-compiler.js'), 先来看下reslove的定义。
在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传入的参数p,以/分割成数组,base是数组的的第一个元素。在我们的例子中,参数pweb/entry-runtime.js, 那么 base 则为 webbase并不一个真实的路径,它的真是路径借助了别名配置,我们来看下它的别名配置。在script/alias中:

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')
}

很显然,这里web对应的真实路径是 resolve('src/platforms/web'),这个路径就找到了源码的web路径。resolve通过path.resolve(aliases[base], p.slice(base.length + 1))找到了它的最终路径。它就在Vue.js 源码中src/platforms/web目录下的entry-runtime.js文件。这样我们就找到了'web-runtime-cjs-dev配置对应的入口文件就找到了。

它经过Roullp打包,最终会在dist目录下生成vue.runtime.common.dev.js文件。

总结

通过Vue.js 源码构建过程分析,我们了解到了不同作用,不同功能的Vue对应的入口文件及最终生成的JS
文件。

原文链接:https://juejin.cn/post/7314491883883003945 作者:可乐不加冰_ltg

(0)
上一篇 2023年12月20日 下午4:58
下一篇 2023年12月20日 下午5:08

相关推荐

发表回复

登录后才能评论