npm run build发生了什么?
最近总是感觉对vue的一些用法和语句还是不理解,于是决定撸一下源码,用于加深自己对vue的理解,同时vue主要是通过rollup进行打包编译,因为它相比webpack更加轻量,行了,废话不多说了,开始了!
如上图所示,当我们执行npm runbuild命令的时候,首先package.json会将其解析为node build/build.js,执行这个目录,我们看看这个目录是什么!
代码如果理解起来比较吃力,在文件代码下面会有梳理!
直接进入到build /build.js贴代码!
const fs = require('fs')
const path = require('path')
const zlib = require('zlib')
const rollup = require('rollup')
const uglify = require('uglify-js')
if (!fs.existsSync('dist')) { // 判断是否存在dist文件夹,如果没有则创建一个
fs.mkdirSync('dist') // 这也是为什么,当我们执行完build命令后,会出现一个dist文件夹
}
let builds = require('./config').getAllBuilds() // 引入./config中的文件,然后执行这个文件下的getAllBuilds()方法
//process.argv获得附加的命令行参数 如: node app 127.0.0.1 7001 我们将会得到127.0.0.1
if (process.argv[2]) { // 主要针对build:ssr和week形式的
const filters = process.argv[2].split(',') // 通过逗号分隔成数组
builds = builds.filter(b => { // 过滤所以.output.file和._name包含filters内容的
return filters.some(f => b.output.file.indexOf(f) > -1 || b._name.indexOf(f) > -1) // 检测是否又符合条件的,有则返回true没有则是false
})
} else { // 说明这块执行的就是单纯的 npm run build
// filter out weex builds by default //但也对weex的进行过滤
builds = builds.filter(b => { // 过滤输出文件中不包含weex的
return b.output.file.indexOf('weex') === -1
})
} // 综上所述,主要是对builds中的值进行过滤操作
build(builds)
function build (builds) { // 对拿到的builds进行一个简单的遍历
let built = 0
const total = builds.length
const next = () => {
buildEntry(builds[built]).then(() => { // builds数组从0到最后一个元素执行buildEntry方法
built++
if (built < total) {
next()
}
}).catch(logError)
}
next()
}
function buildEntry (config) { // 真正开始通过rollup对其进行编译
const output = config.output
const { file, banner } = output
const isProd = /min\.js$/.test(file) // 匹配min.js结尾的文件
return rollup.rollup(config)
.then(bundle => bundle.generate(output))
.then(({ code }) => {
if (isProd) {
var minified = (banner ? banner + '\n' : '') + uglify.minify(code, { // 判断生成的js是否需要压缩
output: {
ascii_only: true
},
compress: {
pure_funcs: ['makeMap']
}
}).code
return write(file, minified, true)
} else {
return write(file, code)
}
})
}
function write (dest, code, zip) {
return new Promise((resolve, reject) => {
function report (extra) { // 必要的时候,在文件中加入console.log
console.log(blue(path.relative(process.cwd(), dest)) + ' ' + getSize(code) + (extra || ''))
resolve()
}
fs.writeFile(dest, code, err => { // 写文件操作
if (err) return reject(err)
if (zip) {
zlib.gzip(code, (err, zipped) => {
if (err) return reject(err)
report(' (gzipped: ' + getSize(zipped) + ')')
})
} else {
report()
}
})
})
}
function getSize (code) {
return (code.length / 1024).toFixed(2) + 'kb'
}
function logError (e) {
console.log(e)
}
function blue (str) {
return '\x1b[1m\x1b[34m' + str + '\x1b[39m\x1b[22m'
}
复制代码
下面开始对代码进行解析
前两行主要是引入了一些模块,以及对dist文件的判断,相信通过注释,大家一定能看得懂。
let builds = require('./config').getAllBuilds()
下面我们看一下这个builds到底是什么,首先我们先看一下config文件夹下的代码
JavaScript 示例:
const path = require('path')
const buble = require('rollup-plugin-buble')
const alias = require('rollup-plugin-alias')
const cjs = require('rollup-plugin-commonjs')
const replace = require('rollup-plugin-replace')
const node = require('rollup-plugin-node-resolve')
const flow = require('rollup-plugin-flow-no-whitespace')
const version = process.env.VERSION || require('../package.json').version
const weexVersion = process.env.WEEX_VERSION || require('../packages/weex-vue-framework/package.json').version
// 对版本号的一个注释
const banner =
'/*!\n' +
' * Vue.js v' + version + '\n' +
' * (c) 2014-' + new Date().getFullYear() + ' Evan You\n' +
' * Released under the MIT License.\n' +
' */'
const weexFactoryPlugin = {
intro () {
return 'module.exports = function weexFactory (exports, document) {'
},
outro () {
return '}'
}
}
const aliases = require('./alias') // alias是对文件真实路径的一个映射
const resolve = p => {
const base = p.split('/')[0] // 获取第一个/前的名字
if (aliases[base]) { // 判断aliases中是否有这个名字,同时获取它的映射路径
return path.resolve(aliases[base], p.slice(base.length + 1)) // p.slice(base.length + 1) 为 ‘/’ 后的名字
} else {
return path.resolve(__dirname, '../', p) // 即dist目录下
}
}
// entry为入口,对应rollup的input dest为出口,对应rollup的output format为格式 banner上面有过解释,为版本信息注释
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
},
// 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
},
// Web compiler (CommonJS).
'web-compiler': {
entry: resolve('web/entry-compiler.js'),
dest: resolve('packages/vue-template-compiler/build.js'),
format: 'cjs',
external: Object.keys(require('../packages/vue-template-compiler/package.json').dependencies)
},
// Web compiler (UMD for in-browser use).
'web-compiler-browser': {
entry: resolve('web/entry-compiler.js'),
dest: resolve('packages/vue-template-compiler/browser.js'),
format: 'umd',
env: 'development',
moduleName: 'VueTemplateCompiler',
plugins: [node(), cjs()]
},
// Web server renderer (CommonJS).
'web-server-renderer': {
entry: resolve('web/entry-server-renderer.js'),
dest: resolve('packages/vue-server-renderer/build.js'),
format: 'cjs',
external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies)
},
'web-server-basic-renderer': {
entry: resolve('web/entry-server-basic-renderer.js'),
dest: resolve('packages/vue-server-renderer/basic.js'),
format: 'umd',
env: 'development',
moduleName: 'renderVueComponentToString',
plugins: [node(), cjs()]
},
'web-server-renderer-webpack-server-plugin': {
entry: resolve('server/webpack-plugin/server.js'),
dest: resolve('packages/vue-server-renderer/server-plugin.js'),
format: 'cjs',
external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies)
},
'web-server-renderer-webpack-client-plugin': {
entry: resolve('server/webpack-plugin/client.js'),
dest: resolve('packages/vue-server-renderer/client-plugin.js'),
format: 'cjs',
external: Object.keys(require('../packages/vue-server-renderer/package.json').dependencies)
},
// Weex runtime factory
'weex-factory': {
weex: true,
entry: resolve('weex/entry-runtime-factory.js'),
dest: resolve('packages/weex-vue-framework/factory.js'),
format: 'cjs',
plugins: [weexFactoryPlugin]
},
// Weex runtime framework (CommonJS).
'weex-framework': {
weex: true,
entry: resolve('weex/entry-framework.js'),
dest: resolve('packages/weex-vue-framework/index.js'),
format: 'cjs'
},
// Weex compiler (CommonJS). Used by Weex's Webpack loader.
'weex-compiler': {
weex: true,
entry: resolve('weex/entry-compiler.js'),
dest: resolve('packages/weex-template-compiler/build.js'),
format: 'cjs',
external: Object.keys(require('../packages/weex-template-compiler/package.json').dependencies)
}
}
function genConfig (name) { /// 主要是通过我们上面的builds对应到rollup格式的一个转换,如把entry转换为input
const opts = builds[name]
const config = {
input: opts.entry,
external: opts.external,
plugins: [
replace({
__WEEX__: !!opts.weex,
__WEEX_VERSION__: weexVersion,
__VERSION__: version
}),
flow(),
buble(),
alias(Object.assign({}, aliases, opts.alias))
].concat(opts.plugins || []),
output: {
file: opts.dest,
format: opts.format,
banner: opts.banner,
name: opts.moduleName || 'Vue'
}
}
if (opts.env) {
config.plugins.push(replace({
'process.env.NODE_ENV': JSON.stringify(opts.env)
}))
}
Object.defineProperty(config, '_name', {
enumerable: false,
value: name
})
return config
}
if (process.env.TARGET) { // 拿到用户环境信息中的TARGET
module.exports = genConfig(process.env.TARGET)
} else {
exports.getBuild = genConfig
exports.getAllBuilds = () => Object.keys(builds).map(genConfig)
// 取到builds下的所有索引,然后遍历执行genConfig方法
}
复制代码
下面我们开始最整个打包编译进行梳理
当我们执行build文件时,在引入模块之后,会在./config中拿到builds,builds是什么呢?
在config文件下的尾部有这样一段代码
我们可以清晰的看到该在最后一行对builds拿到了所以的keys进行了一个遍历,执行genConfig方法
通过上图可以看出来,builds是含有一个个文件信息的对象,相当于是对rollup参数的一个映射,其中entry为入口,对应rollup的input,dest为出口,对应rollup的output ,format为格式,banner为版本注释
这个对象通过key传给了genConfig方法,genConfig又是什么?我们看一下:
不难看出,genConfig方法就是一个将builds对象转化为rollup使用的格式的方法,就这样含有rollup信息的格式,吐给了builds文件中的变量builds,也就是我们最开始提到的。
然后我们接着看build文件的代码
接下来通过拿到process.argv[2],进行了一个过滤,过滤掉不需要编译的文件,注释说的已经够详细了,不想在过多解释了。
接下来会执行build()方法,可以看的出来,build方法是一个循环,循环执行buildEntry()方法
buildEntry则是真正的开始执行rollup,对返回的builds进行编译,然后生成对应的文件。