10分鐘快速進階rollup.js
上一篇教程中,為大家介紹了rollup.js的入門技巧,沒有讀過的小夥伴可以點選這裡,本次我們將繼續對rollup.js的進階技巧進行探討,想直接看結論的小夥伴可以直接看最後一章。
rollup.js外掛
rollup.js的外掛採用可拔插設計,它幫助我們增強了rollup.js的基礎功能,下面我將重點講解四個rollup.js最常用的外掛。
resolve外掛
為什麼需要resolve外掛?
上一篇教程中,我們打包的物件是本地的js程式碼和庫,但實際開發中,不太可能所有的庫都位於本地,我們會通過npm下載遠端的庫。這裡我專門準備了一些測試庫,供大家學習rollup.js使用,首先下載測試庫:
npm i -S sam-test-data 複製程式碼
sam-test-data庫預設提供了一個UMD模組,對外暴露了兩個變數a和b以及一個random函式,a是0到9之間的一個隨機整數,b是0到99之間的一個隨機整數,random函式的引數是一個整數,如傳入100,則返回一個0到99之間的隨機整數,在本地建立測試外掛程式碼的資料夾:
mkdir src/plugin 複製程式碼
建立測試程式碼:
touch src/plugin/main.js 複製程式碼
寫入以下程式碼:
import * as test from 'sam-test-data' console.log(test) export default test.random 複製程式碼
先不使用rollup.js打包,直接通過babel-node嘗試執行程式碼:
babel-node > require('./src/plugin/main.js') { a: 1, b: 17, random: [Function: random] } { default: [Function: random] } > require('./src/plugin/main.js').default(100) 41 複製程式碼
可以看到程式碼可以正常執行,下面我們嘗試通過rollup.js打包程式碼,新增一個新的配置檔案:
touch rollup.plugin.config.js 複製程式碼
寫入以下內容:
import { comment } from './comment-helper-es' export default { input: './src/plugin/main.js', output: [{ file: './dist/index-plugin-cjs.js', format: 'cjs', banner: comment('welcome to imooc.com', 'this is a rollup test project'), footer: comment('powered by sam', 'copyright 2018') }, { file: './dist/index-plugin-es.js', format: 'es', banner: comment('welcome to imooc.com', 'this is a rollup test project'), footer: comment('powered by sam', 'copyright 2018') }] } 複製程式碼
這裡我提供了一個comment-helper-es模組,暴露了一個comment方法,自動讀取我們的引數,並幫助生成註釋,同時會在註釋上方和下方新增等長的分隔符,感興趣的小夥伴可以直接拿去用:
export function comment() { if (arguments.length === 0) { return // 如果引數為0直接返回 } let maxlength = 0 for (let i = 0; i < arguments.length; i++) { const length = arguments[i].toString().length maxlength = length > maxlength ? length : maxlength // 獲取最長的引數 } maxlength = maxlength === 0 ? maxlength : maxlength + 1 // 在最長引數長度上再加1,為了美觀 let seperator = '' for (let i = 0; i < maxlength; i++) { seperator += '=' // 根據引數長度生成分隔符 } const c = [] c.push('/**\n') // 添加註釋頭 c.push(' * ' + seperator + '\n') // 添加註釋分隔符 for (let i = 0; i < arguments.length; i++) { c.push(' * ' + arguments[i] + '\n') // 加入引數內容 } c.push(' * ' + seperator + '\n') // 添加註釋分隔符 c.push(' **/') // 添加註釋尾 return c.join('') // 合併引數為字串 } 複製程式碼
通過rollup.js打包:
$ rollup -c rollup.plugin.config.js ./src/plugin/main.js → ./dist/index-plugin-cjs.js, ./dist/index-plugin-es.js... (!) Unresolved dependencies https://rollupjs.org/guide/en#warning-treating-module-as-external-dependency sam-test-data (imported by src/plugin/main.js) created ./dist/index-plugin-cjs.js, ./dist/index-plugin-es.js in 13ms 複製程式碼
可以看到程式碼生成成功了,但是sam-test-data被當做一個外部的模組被引用,我們檢視dist/index-plugin-es.js原始碼:
/** * ============================== * welcome to imooc.com * this is a rollup test project * ============================== **/ import * as test from 'sam-test-data'; import { random } from 'sam-test-data'; console.log(test); var main = random; export default main; /** * =============== * powered by sam * copyright 2018 * =============== **/ 複製程式碼
和我們原本寫的程式碼幾乎沒有區別,只是通過es6的解構賦值將random函式單獨從sam-test-data獲取,然後賦給變數main並暴露出來。大家試想,如果我們正在編寫一個Javascript類庫,使用者在引用我們庫的時候,還需要手動去下載這個庫所有的依賴,這是多麼糟糕的體驗。為了解決這個問題,將我們編寫的原始碼與依賴的第三方庫進行合併,rollup.js為我們提供了resolve外掛。
resolve外掛的使用方法
首先,安裝resolve外掛:
npm i -D rollup-plugin-node-resolve 複製程式碼
修改配置檔案rollup.plugin.config.js:
import resolve from 'rollup-plugin-node-resolve' export default { input: './src/plugin/main.js', output: [{ file: './dist/index-plugin-cjs.js', format: 'cjs' }, { file: './dist/index-plugin-es.js', format: 'es' }], plugins: [ resolve() ] } 複製程式碼
重新打包:
$ rollup -c rollup.plugin.config.js ./src/plugin/main.js → ./dist/index-plugin-cjs.js, ./dist/index-plugin-es.js... created ./dist/index-plugin-cjs.js, ./dist/index-plugin-es.js in 28ms 複製程式碼
可以看到警告消除了,我們重新檢視dist/index-plugin-es.js原始碼:
const a = Math.floor(Math.random() * 10); const b = Math.floor(Math.random() * 100); function random(base) { if (base && base % 1 === 0) { return Math.floor(Math.random() * base) } else { return 0 } } var test = /*#__PURE__*/Object.freeze({ a: a, b: b, random: random }); console.log(test); var main = random; export default main; 複製程式碼
很明顯sam-test-data庫的原始碼已經與我們的原始碼集成了。
tree-shaking
下面我們修改src/plugin/main.js的原始碼:
import * as test from 'sam-test-data' export default test.random 複製程式碼
原始碼中去掉了console.log(test)
,重新打包:
rollup -c rollup.plugin.config.js 複製程式碼
再次檢視dist/index-plugin-es.js原始碼:
function random(base) { if (base && base % 1 === 0) { return Math.floor(Math.random() * base) } else { return 0 } } var main = random; export default main; 複製程式碼
我們發現關於變數a和b的定義沒有了,因為原始碼中並沒有用到這兩個變數。這就是ES模組著名的tree-shaking 機制,它動態地清除沒有被使用過的程式碼,使得程式碼更加精簡,從而可以使得我們的類庫獲得更快的載入速度(容量小了,自然載入速度變快)。
external屬性
有些場景下,雖然我們使用了resolve外掛,但我們仍然某些庫保持外部引用狀態,這時我們就需要使用external屬性,告訴rollup.js哪些是外部的類庫,修改rollup.js的配置檔案:
import resolve from 'rollup-plugin-node-resolve' export default { input: './src/plugin/main.js', output: [{ file: './dist/index-plugin-cjs.js', format: 'cjs' }, { file: './dist/index-plugin-es.js', format: 'es' }], plugins: [ resolve() ], external: ['sam-test-data'] } 複製程式碼
重新打包:
rollup -c rollup.plugin.config.js 複製程式碼
檢視dist/index-plugin-es.js原始碼:
import { random } from 'sam-test-data'; var main = random; export default main; 複製程式碼
可以看到雖然使用了resolve外掛,sam-test-data庫仍被當做外部庫處理
commonjs外掛
為什麼需要commonjs外掛?
rollup.js預設不支援CommonJS模組,這裡我編寫了一個CommonJS模組用於測試,該模組的內容與sam-test-data完全一致,差異僅僅是前者採用了CommonJS規範,先安裝這個模組:
npm i -S sam-test-data-cjs 複製程式碼
新建一個程式碼檔案:
touch src/plugin/main-cjs.js 複製程式碼
寫入如下程式碼:
import test from 'sam-test-data-cjs' console.log(test) export default test.random 複製程式碼
這段程式碼非常簡單,接下來修改rollup.js的配置檔案:
import resolve from 'rollup-plugin-node-resolve' export default { input: './src/plugin/main-cjs.js', output: [{ file: './dist/index-plugin-cjs.js', format: 'cjs' }, { file: './dist/index-plugin-es.js', format: 'es' }], plugins: [ resolve() ] } 複製程式碼
執行打包:
rollup -c rollup.plugin.config.js ./src/plugin/main-cjs.js → ./dist/index-plugin-cjs.js, ./dist/index-plugin-es.js... [!] Error: 'default' is not exported by node_modules/[email protected]@sam-test-data-cjs/index.js https://rollupjs.org/guide/en#error-name-is-not-exported-by-module- src/plugin/main-cjs.js (1:7) 1: import test from 'sam-test-data-cjs' ^ 複製程式碼
可以看到預設情況下,rollup.js是無法識別CommonJS模組的,此時我們需要藉助commonjs外掛來解決這個問題。
commonjs外掛的使用方法
首先安裝commonjs外掛:
npm i -D rollup-plugin-commonjs 複製程式碼
修改rollup.js的配置檔案:
import resolve from 'rollup-plugin-node-resolve' import commonjs from 'rollup-plugin-commonjs' export default { input: './src/plugin/main-cjs.js', output: [{ file: './dist/index-plugin-cjs.js', format: 'cjs' }, { file: './dist/index-plugin-es.js', format: 'es' }], plugins: [ resolve(), commonjs() ] } 複製程式碼
重新執行打包:
rollup -c rollup.plugin.config.js 複製程式碼
打包成功後,我們檢視dist/index-plugin-es.js原始碼:
const a = Math.floor(Math.random() * 10); const b = Math.floor(Math.random() * 100); function random(base) { if (base && base % 1 === 0) { return Math.floor(Math.random() * base) } else { return 0 } } var _samTestDataCjs_0_0_1_samTestDataCjs = { a, b, random }; console.log(_samTestDataCjs_0_0_1_samTestDataCjs); var mainCjs = _samTestDataCjs_0_0_1_samTestDataCjs.random; export default mainCjs; 複製程式碼
可以看到CommonJS模組被整合到程式碼中了,通過babel-node嘗試執行打包後的程式碼:
babel-node > require('./dist/index-plugin-es') { a: 7, b: 45, random: [Function: random] } { default: [Function: random] } > require('./dist/index-plugin-es').default(1000) 838 複製程式碼
程式碼執行成功,說明我們的程式碼打包成功了。
CommonJS與tree-shaking
我們修改src/plugin/main-cjs.js的原始碼,驗證一下CommonJS模組是否支援tree-shaking特性:
import test from 'sam-test-data-cjs' export default test.random 複製程式碼
與resolve中tree-shaking的案例一樣,我們去掉console.log(test)
,重新執行打包後,再檢視打包原始碼:
const a = Math.floor(Math.random() * 10); const b = Math.floor(Math.random() * 100); function random(base) { if (base && base % 1 === 0) { return Math.floor(Math.random() * base) } else { return 0 } } var _samTestDataCjs_0_0_1_samTestDataCjs = { a, b, random }; var mainCjs = _samTestDataCjs_0_0_1_samTestDataCjs.random; export default mainCjs; 複製程式碼
可以看到原始碼中仍然定義了變數a和b,說明CommonJS模組不能支援tree-shaking特性,所以建議大家使用rollup.js打包時,儘量使用ES模組,以獲得更精簡的程式碼。
UMD與tree-shaking
UMD模組與CommonJS類似,也是不能夠支援tree-shaking特性的,這裡我提供了一個UMD測試模組sam-test-data-umd,感興趣的小夥伴可以自己驗證一下。有的小夥伴可能會問,sam-test-data也是一個UMD模組,為什麼它能夠支援tree-shaking?我們開啟sam-test-data的package.json一探究竟:
{ "name": "sam-test-data", "version": "0.0.4", "description": "provide test data", "main": "dist/sam-test-data.js", "module": "dist/sam-test-data-es.js" } 複製程式碼
可以看到main屬性指向dist/sam-test-data.js,這是一個UMD模組,但是module屬性指向dist/sam-test-data-es.js,這是一個ES模組,rollup.js預設情況下會優先尋找並載入module屬性指向的模組。所以sam-test-data的ES模組被優先載入,從而能夠支援tree-shaking特性。我們看一下rollup.js官方的說明:
在 package.json 檔案的 main 屬性中指向當前編譯的版本。如果你的 package.json 也具有 module 欄位,像 Rollup 和 webpack 2 這樣的 ES6 感知工具(ES6-aware tools)將會直接匯入 ES6 模組版本。
babel外掛
為什麼需要babel外掛?
在src/plugin目錄下建立一個新檔案main-es.js:
touch src/plugin/main-es.js 複製程式碼
寫入如下程式碼:
import { a, b, random } from 'sam-test-data-es' console.log(a, b, random) export default (base) => { return random(base) } 複製程式碼
程式碼中採用了ES6的新特性:箭頭函式,修改配置檔案:
import resolve from 'rollup-plugin-node-resolve' import commonjs from 'rollup-plugin-commonjs' export default { input: './src/plugin/main-es.js', output: [{ file: './dist/index-plugin-cjs.js', format: 'cjs' }, { file: './dist/index-plugin-es.js', format: 'es' }], plugins: [ resolve(), commonjs() ] } 複製程式碼
重新執行打包:
rollup -c rollup.plugin.config.js 複製程式碼
檢視dist/index-plugin-es.js原始碼:
var mainEs = (base) => { return random(base) }; export default mainEs; 複製程式碼
可以看到箭頭函式被保留下來,這樣的程式碼在不支援ES6的環境下將無法執行。我們期望在rollup.js打包的過程中就能使用babel完成程式碼轉換,因此我們需要babel外掛。
babel外掛的使用方法
首先安裝babel外掛:
npm i -D rollup-plugin-babel 複製程式碼
修改配置檔案,增加babel外掛的引用:
import resolve from 'rollup-plugin-node-resolve' import commonjs from 'rollup-plugin-commonjs' import babel from 'rollup-plugin-babel' export default { input: './src/plugin/main-es.js', output: [{ file: './dist/index-plugin-cjs.js', format: 'cjs' }, { file: './dist/index-plugin-es.js', format: 'es' }], plugins: [ resolve(), commonjs(), babel() ] } 複製程式碼
重新打包:
rollup -c rollup.plugin.config.js 複製程式碼
再次檢視dist/index-plugin-es.js原始碼:
var mainEs = (function (base) { return random(base); }); export default mainEs; 複製程式碼
可以看到箭頭函式被轉換為了function,babel外掛正常工作。
json外掛
為什麼需要json外掛
touch src/plugin/main-json.js 複製程式碼
import json from '../../package.json' console.log(json.name, json.main) 複製程式碼
$ babel-node src/plugin/main-json.js rollup-test index.js 複製程式碼
import resolve from 'rollup-plugin-node-resolve' import commonjs from 'rollup-plugin-commonjs' import babel from 'rollup-plugin-babel' export default { input: './src/plugin/main-json.js', output: [{ file: './dist/index-plugin-cjs.js', format: 'cjs' }, { file: './dist/index-plugin-es.js', format: 'es' }], plugins: [ resolve(), commonjs(), babel() ] } 複製程式碼
$ rollup -c rollup.plugin.config.js ./src/plugin/main-json.js → ./dist/index-plugin-cjs.js, ./dist/index-plugin-es.js... [!] Error: Unexpected token (Note that you need rollup-plugin-json to import JSON files) 複製程式碼
json外掛的使用方法
cnpm i -D rollup-plugin-json 複製程式碼
import resolve from 'rollup-plugin-node-resolve' import commonjs from 'rollup-plugin-commonjs' import babel from 'rollup-plugin-babel' import json from 'rollup-plugin-json' export default { input: './src/plugin/main-json.js', output: [{ file: './dist/index-plugin-cjs.js', format: 'cjs' }, { file: './dist/index-plugin-es.js', format: 'es' }], plugins: [ resolve(), commonjs(), babel(), json() ] } 複製程式碼
rollup -c rollup.plugin.config.js 複製程式碼
var name = "rollup-test"; var version = "1.0.0"; var description = ""; var main = "index.js"; var scripts = { test: "echo \"Error: no test specified\" && exit 1" }; var author = ""; var license = "ISC"; var devDependencies = { "@babel/core": "^7.1.6", "@babel/plugin-external-helpers": "^7.0.0", "@babel/preset-env": "^7.1.6", rollup: "^0.67.3", "rollup-plugin-babel": "^4.0.3", "rollup-plugin-commonjs": "^9.2.0", "rollup-plugin-json": "^3.1.0", "rollup-plugin-node-resolve": "^3.4.0" }; var dependencies = { epubjs: "^0.3.80", loadsh: "^0.0.3", "sam-test-data": "^0.0.4", "sam-test-data-cjs": "^0.0.1", "sam-test-data-es": "^0.0.1", "sam-test-data-umd": "^0.0.1" }; var json = { name: name, version: version, description: description, main: main, scripts: scripts, author: author, license: license, devDependencies: devDependencies, dependencies: dependencies }; console.log(json.name, json.main); 複製程式碼
uglify外掛
uglify外掛可以幫助我們進一步壓縮程式碼的體積,首先安裝外掛:
npm i -D rollup-plugin-uglify 複製程式碼
修改rollup.js的配置檔案:
import resolve from 'rollup-plugin-node-resolve' import commonjs from 'rollup-plugin-commonjs' import babel from 'rollup-plugin-babel' import json from 'rollup-plugin-json' import { uglify } from 'rollup-plugin-uglify' export default { input: './src/plugin/main.js', output: [{ file: './dist/index-plugin-cjs.js', format: 'cjs' }], plugins: [ resolve(), commonjs(), babel(), json(), uglify() ] } 複製程式碼
這裡要注意的是uglify外掛不支援ES模組和ES6語法,所以只能打包成非ES格式的程式碼,如果碰到ES6語法則會出現報錯:
$ rollup -c rollup.plugin.config.js ./src/plugin/main.js → ./dist/index-plugin-cjs.js, ./dist/index-plugin-es.js... 19 | var main = random; 20 | > 21 | export default main; |^ Unexpected token: keyword (default) [!] (uglify plugin) Error: Unexpected token: keyword (default) 複製程式碼
所以這裡我們採用sam-test-data進行測試,因為這個模組採用了babel進行編譯,其他幾個模組uglify都不支援(因為其他幾個模組使用了const,const也是ES6特性,uglify不能支援),所以大家在自己編寫類庫的時候要注意使用babel外掛進行編譯。配置完成後重新打包:
$ rollup -c rollup.plugin.config.js ./src/plugin/main.js → ./dist/index-plugin-cjs.js... created ./dist/index-plugin-cjs.js in 679ms 複製程式碼
檢視dist/index-plugin-cjs.js原始碼:
"use strict";var a=Math.floor(10*Math.random()),b=Math.floor(100*Math.random());function random(a){return a&&a%1==0?Math.floor(Math.random()*a):0}var test=Object.freeze({a:a,b:b,random:random});console.log(test);var main=random;module.exports=main; 複製程式碼
可以看到程式碼被最小化了,體積也減小了不少。
rollup.js watch
命令列模式
rollup.js的watch模式支援監聽程式碼變化,一旦修改程式碼後將自動執行打包,非常方便,使用方法是在打包指令後新增--watch
即可:
$ rollup -c rollup.plugin.config.js--watch rollup v0.67.1 bundles ./src/plugin/main-json.js → dist/index-plugin-cjs.js, dist/index-plugin-es.js... created dist/index-plugin-cjs.js, dist/index-plugin-es.js in 24ms [2018-11-20 22:26:24] waiting for changes... 複製程式碼
API模式
rollup.js支援我們通過API來啟動watch模式,在專案根目錄下建立以下檔案:
- rollup-watch-input-options.js:輸入配置
- rollup-watch-output-options.js:輸出配置
- rollup-watch-options.js:監聽配置
- rollup-watch.js:呼叫rollup.js的API啟動watch模式
為了讓node能夠執行我們的程式,所以採用CommonJS規範,rollup-watch-input-options.js程式碼如下:
const json = require('rollup-plugin-json') const resolve = require('rollup-plugin-node-resolve') const commonjs = require('rollup-plugin-commonjs') const babel = require('rollup-plugin-babel') const uglify = require('rollup-plugin-uglify').uglify module.exports = { input: './src/plugin/main.js', plugins: [ json(), resolve({ customResolveOptions: { moduleDirectory: 'node_modules' // 僅處理node_modules內的庫 } }), babel({ exclude: 'node_modules/**' // 排除node_modules }), commonjs(), uglify() // 程式碼壓縮 ] } 複製程式碼
rollup-watch-output-options.js程式碼如下:
module.exports = [{ file: './dist/index-cjs.js', format: 'cjs', name: 'sam-cjs' }] 複製程式碼
rollup-watch-options.js程式碼如下:
module.exports = { include: 'src/**', // 監聽的資料夾 exclude: 'node_modules/**' // 排除監聽的資料夾 } 複製程式碼
rollup-watch.js程式碼如下:
const rollup = require('rollup') const inputOptions = require('./rollup-watch-input-options') const outputOptions = require('./rollup-watch-output-options') const watchOptions = require('./rollup-watch-options') const options = { ...inputOptions, output: outputOptions, watchOptions } // 生成rollup的options const watcher = rollup.watch(options) // 呼叫rollup的api啟動監聽 watcher.on('event', event => { console.log('重新打包中...', event.code) }) // 處理監聽事件 // watcher.close() // 手動關閉監聽 複製程式碼
通過node直接啟動監聽:
$ node rollup-watch.js 重新打包中... START 重新打包中... BUNDLE_START 重新打包中... BUNDLE_END 重新打包中... END 複製程式碼
之後我們再修改src/plugin/main.js的原始碼,rollup.js就會自動對程式碼進行打包。
總結
本教程詳細講解了rollup.js的外掛、tree-shaking機制和watch模式,涉及知識點整理如下:
-
rollup.js外掛
- resolve外掛:整合外部模組
- commonjs外掛:支援CommonJS模組
- babel外掛:編譯ES6語法,使低版本瀏覽器可以識別
- json外掛:支援json模組
- uglify:程式碼最小化打包(不支援ES模組)
- tree-shaking:只有ES模組才支援,大幅精簡程式碼量
- watch模式:支援命令列和API模式,實時監聽程式碼變更