10分鐘快速入門rollup.js

sam9831發表於2019-03-03

為什麼要學習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執行機制
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等,還會詳細演示各種外掛的用途及用法,敬請關注。

相關文章