我們來(lái)看一看 vue 是使用了 Rollup 進(jìn)行打包項(xiàng)目的抗斤。通過(guò) npm 創(chuàng)建一個(gè)項(xiàng)目,然后創(chuàng)建 script.js 或者 index.js 文件愈魏,來(lái)作為可執(zhí)行的腳步文件碌补,首先我們需要引入所需要庫(kù)后添。
今天我們就 vue 打包的源碼分析來(lái)看一看 rollup 在 vue 中應(yīng)用蔚约。
const fs = require('fs')
const path = require('path')
const rollup = require('rollup')
const terser = require('terser')
fs 和 path 是 nodejs 提供有關(guān)文件操作和路徑解析的包娇澎, rollup 和 terser 是用于構(gòu)建打包項(xiàng)目庫(kù)逐工,當(dāng)然這里 rollup 使我們今天主角描融。如果不存在 dist 文件夾我們就創(chuàng)建一個(gè)别智。
if (!fs.existsSync('dist')) {
fs.mkdirSync('dist')
}
接下來(lái)讀取 config 這個(gè)配置文件,
let builds = require('./config').getAllBuilds()
if (process.env.TARGET) {
module.exports = genConfig(process.env.TARGET)
} else {
exports.getBuild = genConfig
//通常我們會(huì)調(diào)用這個(gè)
exports.getAllBuilds = () => Object.keys(builds).map(genConfig)
}
這里輸出 getAllBuilds 是返回一個(gè)方法稼稿,方法Object.keys 獲取對(duì)象的屬性集合薄榛,
var foo = {name:'koo',age:23}
undefined
Object.keys(foo)
(2) ["name", "age"]
Object.keys(foo).map(function(item){ console.log(foo[item])})
VM493:1 koo
VM493:1 23
用 genConfig 為每個(gè)要打包module輸出配置
function genConfig (name) {
const opts = builds[name]
const config = {
input: opts.entry,
external: opts.external,
plugins: [
flow(),
alias(Object.assign({}, aliases, opts.alias))
].concat(opts.plugins || []),
output: {
file: opts.dest,
format: opts.format,
banner: opts.banner,
name: opts.moduleName || 'Vue'
},
onwarn: (msg, warn) => {
if (!/Circular/.test(msg)) {
warn(msg)
}
}
}
// built-in vars
// const vars = {
// __WEEX__: !!opts.weex,
// __WEEX_VERSION__: weexVersion,
// __VERSION__: version
// }
// feature flags
// Object.keys(featureFlags).forEach(key => {
// vars[`process.env.${key}`] = featureFlags[key]
// })
// build-specific env
// if (opts.env) {
// vars['process.env.NODE_ENV'] = JSON.stringify(opts.env)
// }
// config.plugins.push(replace(vars))
if (opts.transpile !== false) {
config.plugins.push(buble())
}
Object.defineProperty(config, '_name', {
enumerable: false,
value: name
})
return config
}
這里主要是返回一個(gè) rollup 在構(gòu)建項(xiàng)目所用的配置文件,簡(jiǎn)單地解釋一下 config 各個(gè)屬性让歼,
input 輸入文件
plugins 插件敞恋,vue 開(kāi)始使用 flow 來(lái)作為自己類(lèi)型系統(tǒng)的,alias 方法
-i, --input 要打包的文件(必須)
-o, --output.file 輸出的文件 (如果沒(méi)有這個(gè)參數(shù)谋右,則直接輸出到控制臺(tái))
-f, --output.format [es] 輸出的文件類(lèi)型 (amd, cjs, es, iife, umd)
-e, --external 將模塊ID的逗號(hào)分隔列表排除
-g, --globals 以module ID:Global
鍵值對(duì)的形式硬猫,用逗號(hào)分隔開(kāi)
任何定義在這里模塊ID定義添加到外部依賴(lài)
-n, --name 生成UMD模塊的名字
-m, --sourcemap 生成 sourcemap (-m inline
for inline map)
--amd.id AMD模塊的ID,默認(rèn)是個(gè)匿名函數(shù)
--amd.define 使用Function來(lái)代替define
--no-strict 在生成的包中省略"use strict";
--no-conflict 對(duì)于UMD模塊來(lái)說(shuō)改执,給全局變量生成一個(gè)無(wú)沖突的方法
--intro 在打包好的文件的塊的內(nèi)部(wrapper內(nèi)部)的最頂部插入一段內(nèi)容
--outro 在打包好的文件的塊的內(nèi)部(wrapper內(nèi)部)的最底部插入一段內(nèi)容
--banner 在打包好的文件的塊的外部(wrapper外部)的最頂部插入一段內(nèi)容
--footer 在打包好的文件的塊的外部(wrapper外部)的最底部插入一段內(nèi)容
--interop 包含公共的模塊(這個(gè)選項(xiàng)是默認(rèn)添加的用
Object.defineProperty
將 name 作為 _name 屬性寫(xiě)入到 config 對(duì)象中并且為不可遍歷啸蜜。
根據(jù) name 獲取 opts, 就是 web-runtime-cjs-dev
屬性值,opts 對(duì)象提供壓縮web-runtime-cjs-dev
基本信息辈挂。我們可以將每個(gè)屬性和上面函數(shù)進(jìn)行對(duì)應(yīng)衬横。
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/zi.runtime.common.dev.js'),
format: 'cjs',
env: 'development',
banner
}
}
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)
}
}
alias 文件
const path = require('path')
const resolve = p => path.resolve(__dirname, '../', p)
module.exports = {
web: resolve('src/platforms/web'),
}
build 函數(shù)接受 builds 作為參數(shù)通過(guò)回調(diào)中再次調(diào)用自己來(lái)完成異步執(zhí)行完所有的構(gòu)建任務(wù)。
build(builds)
function build(builds) {
let built = 0
const total = builds.length
const next = () => {
buildEntry(builds[built]).then(() => {
built++
if (built < total) {
next()
}
}).catch(logError)
}
next()
}
最后看看 buildEntry 方法终蒂,我們這里調(diào)用 rollup 成功后構(gòu)建項(xiàng)目代碼通過(guò) primise 來(lái)獲取蜂林,然后就可以對(duì)處理好的代碼進(jìn)行一些寫(xiě)入附加信息等操作。
function buildEntry(config) {
const output = config.output
const { file, banner } = output
const isProd = /(min|prod)\.js$/.test(file)
return rollup.rollup(config)
.then(bundle => bundle.generate(output))
.then(({ output: [{ code }] }) => {
if (isProd) {
const minified = (banner ? banner + '\n' : '') + terser.minify(code, {
toplevel: true,
output: {
ascii_only: true
},
compress: {
pure_funcs: ['makeMap']
}
}).code
return write(file, minified, true)
} else {
return write(file, code)
}
})
}