10分鐘快速入門rollup.js
rollup.js是Javascript的ES模組打包器,我們熟知的Vue、React等諸多知名框架或類庫都通過rollup.js進行打包。與Webpack偏向於應用打包的定位不同,rollup.js更專注於Javascript類庫打包(雖然rollup.js也可以提供資源打包,但顯然這不是它的強項)。在我們學習Vue和React等框架原始碼或者自己編寫Javascript類庫時,rollup.js是一條必經之路。
rollup.js的工作原理
rollup.js可以將我們自己編寫的Javascript程式碼(通過外掛可以支援更多語言,如Tyepscript)與第三方模組打包在一起,形成一個檔案,該檔案可以是一個庫(Library)或者一個應用(App),在打包過程中可以應用各類外掛實現特定功能。下圖揭示了rollup.js的執行機制:

rollup.js預設採用ES模組標準,我們可以通過rollup-plugin-commonjs外掛使之支援CommonJS標準。
安裝rollup.js
rollup.js的安裝依賴於nodejs,之前的手記中我曾詳細介紹如何通過nvm管理nodejs版本,需要了解的小夥伴可以點選這裡
全域性安裝rollup.js
首先全域性安裝rollup:
npm i rollup -g 複製程式碼
rollup.js打包例項
安裝成功後,我們嘗試使用rollup做一個簡單的案例,建立src目錄:
mkdir src 複製程式碼
在src目錄下建立a.js:
vim src/a.js 複製程式碼
寫入如下程式碼,這個模組非常簡單,僅僅對外暴露一個變數a:
const a = 1 export default a 複製程式碼
在src目錄下再建立main.js:
vim src/main.js 複製程式碼
寫入如下程式碼,這個模組會引入模組a,並對外暴露一個function:
import a from './a.js' export default function() { console.log(a) } 複製程式碼
通過rollup指令,我們可以快速地預覽打包後的原始碼,這點和babel非常類似:
$ rollup src/main.js -f es src/main.jsstdout... const a = 1; function main() { console.log(a); } export default main; created stdout in 26ms 複製程式碼
需要注意的是rollup必須帶有 -f
引數,否則會報錯:
$ rollup src/main.js src/main.jsstdout... [!] Error: You must specify output.format, which can be one of 'amd', 'cjs', 'system', 'esm', 'iife' or 'umd' https://rollupjs.org/guide/en#output-format-f-format 複製程式碼
rollup的報錯提示非常棒,非常有利於我們定位錯誤和修復問題。通過上面的錯誤提示,我們瞭解到 -f
的值可以為'amd'、'cjs'、'system'、'esm'('es'也可以)、'iife'或'umd'中的任何一個。 -f
引數是 --format
的縮寫,它表示生成程式碼的格式,amd表示採用AMD標準,cjs為CommonJS標準,esm(或es)為ES模組標準。接著我們把這段程式碼輸出到一個檔案中:
$ rollup src/main.js -f es -o dist/bundle.js src/main.jsdist/bundle.js... created dist/bundle.js in 29ms 複製程式碼
引數 -o
指定了輸出的路徑,這裡我們將打包後的檔案輸出到dist目錄下的bundle.js,這個檔案內容與我們之前預覽的內容是完全一致的。我們再輸出一份CommonJS格式的程式碼:
$ rollup src/main.js --format cjs --output.file dist/bundle-cjs.js src/main.jsdist/bundle-cjs.js... created dist/bundle-cjs.js in 27ms 複製程式碼
引數 --output.file
是 -o
的全稱,它們是等價的,輸出後我們在dist目錄下會多一個bundle-cjs.js檔案,檢視這個檔案的內容:
'use strict'; const a = 1; function main() { console.log(a); } module.exports = main; 複製程式碼
可以看到程式碼採用CommonJS標準編寫,並且將a.js和main.js兩個檔案進行了融合。
驗證rollup.js打包結果
在打包成功後,我們嘗試執行dist/bundle-cjs.js程式碼:
$ node > const m = require('./dist/bundle-cjs.js') > m() 1 複製程式碼
我們接著嘗試執行之前輸出的ES標準程式碼dist/bundle.js,由於nodejs並不支援ES標準,直接執行會報錯:
$ node > require('./dist/bundle.js')() /Users/sam/Desktop/rollup-test/dist/bundle.js:7 export default main; ^^^^^^ SyntaxError: Unexpected token export 複製程式碼
babel為我們提供了一個工具:babel-node,它可以在執行時將ES標準的程式碼轉換為CommonJS格式,從而使得執行ES標準的程式碼成為可能,首先全域性安裝babel-node及相關工具,@babel/node包含babel-node,@babel/cli包含babel,而這兩個工具都依賴@babel/core,所以建議都安裝:
npm i @babel/core @babel/node @babel/cli -g 複製程式碼
這裡要注意的是babel 7改變了npm包的名稱,之前的babel-core和babel-cli已經被棄用,所以安裝老版本babel的同學建議先解除安裝:
npm uninstall babel-cli babel-core -g 複製程式碼
然後到程式碼的根目錄下,初始化專案:
npm init 複製程式碼
一路回車後,在程式碼根目錄下建立babel的配置檔案.babelrc,寫入如下配置
{ "presets": ["@babel/preset-env"] } 複製程式碼
完成babel配置後安裝babel的依賴:
npm i -D @babel/core @babel/preset-env 複製程式碼
嘗試通過babel編譯程式碼:
$ babel dist/bundle.js "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var a = 1; function main() { console.log(a); } var _default = main; exports.default = _default; 複製程式碼
可以看到ES模組程式碼被編譯成了CommonJS格式,下面通過babel-node執行程式碼:
$ babel-node > require('./dist/bundle.js') { default: [Function: main] } > require('./dist/bundle.js').default() 1 複製程式碼
注意babel會認為 export default function()
是一個名稱為default的函式,如果想更改這個函式名稱,可以修改main.js:
import a from './a.js' export function test() { console.log(a) } 複製程式碼
重寫打包後通過babel-node執行:
$ rollup -f es --file dist/bundle.js src/main.js src/main.jsdist/bundle.js... created dist/bundle.js in 26ms $ babel-node > require('./dist/bundle.js').test() 1 複製程式碼
注意這裡的 --file
定價於 -o
和 --output.file
,通過上述案例,我們完成了rollup打包的基本操作,並驗證了打包結果。但很多時候我們不會這樣操作,因為直接使用命令列功能單一,而且無法使用外掛,所以我們需要藉助配置檔案來操作。
rollup.js配置檔案
首先在程式碼根目錄下建立rollup.config.js檔案:
touch rollup.config.js 複製程式碼
寫入如下配置:
export default { input: './src/main.js', output: [{ file: './dist/index-cjs.js', format: 'cjs', banner: '// welcome to imooc.com', footer: '// powered by sam' }, { file: './dist/index-es.js', format: 'es', banner: '// welcome to imooc.com', footer: '// powered by sam' }] } 複製程式碼
rollup的配置檔案非常容易理解,這裡有幾點需要說明:
- rollup的配置檔案需要採用ES模組標準編寫
- input表示入口檔案的路徑(老版本為entry,已經廢棄)
- output表示輸出檔案的內容,它允許傳入一個物件或一個數組,當為陣列時,依次輸出多個檔案,它包含以下內容:
- output.file:輸出檔案的路徑(老版本為dest,已經廢棄)
- output.format:輸出檔案的格式
- output.banner:檔案頭部新增的內容
- output.footer:檔案末尾新增的內容
通過 rollup -c
指令進行打包,rollup.js會自動尋找名稱為rollup.config.js的配置檔案:
$ rollup -c ./src/main.js./dist/index-cjs.js, ./dist/index-es.js... created ./dist/index-cjs.js, ./dist/index-es.js in 13ms 複製程式碼
檢視dist/index-es.js檔案:
// welcome to imooc.com const a = 1; function test() { console.log(a); } export { test }; // powered by sam 複製程式碼
程式碼的內容與命令列生成的無異,但頭部和末尾添加了自定義的註釋資訊。接著我們修改配置檔案的名稱,並通過 -c
引數指定配置檔案進行打包:
$ mv rollup.config.js rollup.config.dev.js $ rollup -c rollup.config.dev.js ./src/main.js./dist/index-cjs.js, ./dist/index-es.js... created ./dist/index-cjs.js, ./dist/index-es.js in 13ms 複製程式碼
rollup.js api打包
編寫rollup.js配置
很多時候命令列和配置檔案的打包方式無法滿足需求,我們需要更加個性化的打包方式,這時我們可以考慮通過rollup.js的api進行打包,建立rollup-input-options.js,這是輸入配置,我們單獨封裝一個模組,提高複用性和可擴充套件性:
touch rollup-input-options.js 複製程式碼
在輸入配置檔案中加入以下內容,需要注意的是這個檔案必須為CommonJS格式,因為需要使用nodejs來執行:
module.exports = { input: './src/main.js' } 複製程式碼
再新增一個輸出配置檔案:
touch rollup-output-options.js 複製程式碼
在輸出配置檔案我們仍然使用一個數組,實現多種檔案格式的輸出,需要注意的是umd格式必須指定模組的名稱,通過name屬性來實現:
module.exports = [{ file: './dist/index-cjs.js', format: 'cjs', banner: '// welcome to imooc.com', footer: '// powered by sam' }, { file: './dist/index-es.js', format: 'es', banner: '// welcome to imooc.com', footer: '// powered by sam', }, { file: './dist/index-amd.js', format: 'amd', banner: '// welcome to imooc.com', footer: '// powered by sam', }, { file: './dist/index-umd.js', format: 'umd', name: 'sam-umd', // 指定檔名稱 banner: '// welcome to imooc.com', footer: '// powered by sam', }] 複製程式碼
編寫rollup.js build程式碼
接下來我們要在當前專案中安裝rollup庫:
npm i -D rollup 複製程式碼
建立一個rollup-build檔案,通過這個檔案來呼叫rollup的api:
touch rollup-build.js 複製程式碼
rollup-build的原始碼如下:
const rollup = require('rollup') const inputOptions = require('./rollup-input-options') const outputOptions = require('./rollup-output-options') async function rollupBuild(input, output) { const bundle = await rollup.rollup(input) // 根據input配置進行打包 console.log(`正在生成:${output.file}`) await bundle.write(output) // 根據output配置輸出檔案 console.log(`${output.file}生成成功!`) } (async function () { for (let i = 0; i < outputOptions.length; i++) { await rollupBuild(inputOptions, outputOptions[i]) } })() 複製程式碼
程式碼的核心有兩點:
rollup.rollup(input) bundle.write(output)
這裡我們還可以通過async和await實現同步操作,因為 bundle.write(output)
是非同步的,會返回Promise物件,我們可以藉助async機制實現按配置順序依次打包。執行rollup-build檔案:
$ node rollup-build.js 正在生成:./dist/index-cjs.js ./dist/index-cjs.js生成成功! 正在生成:./dist/index-es.js ./dist/index-es.js生成成功! 正在生成:./dist/index-amd.js ./dist/index-amd.js生成成功! 正在生成:./dist/index-umd.js ./dist/index-umd.js生成成功! 複製程式碼
檢視dist/index-umd.js檔案:
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (factory((global['sam-umd'] = {}))); }(this, (function (exports) { // ... } 複製程式碼
可以看到index-umd.js檔案中在global全域性變數中添加了sam-umd屬性,這就是我們之前需要在umd配置中新增name屬性的原因。
總結
本文向大家介紹了rollup.js的三種打包方式:命令列、配置檔案和API,在下一篇教程中我將繼續為大家介紹更多rollup.js的特性,如Tree-shaking、watch等,還會詳細演示各種外掛的用途及用法,敬請關注。