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進行編譯,然後生成對應的檔案。