為什麼要學習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.js stdout...
const a = 1;
function main() {
console.log(a);
}
export default main;
created stdout in 26ms
複製程式碼
需要注意的是rollup必須帶有-f
引數,否則會報錯:
$ rollup src/main.js
src/main.js stdout...
[!] 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.js dist/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.js dist/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.js dist/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等,還會詳細演示各種外掛的用途及用法,敬請關注。