關於 rollupjs 的教程已經非常多了,可是較少看到比較完整的工程樣板,所以分享下我自己搭建的,已經在公司內部使用的樣板工程。
先認識下目錄結構
現在的前端為了打包一個外掛,差不多下面的配置檔案都是必須的;
哪怕程式碼僅僅一百多行,為了保證質量,少了誰都不能少了下面的配置檔案;
如果是 typescript 寫的,那就還要再加幾個,懷念幾年前 Happy Coding 的日子?。
Project/
├── README.md
├── package.json
├── rollup.config.js
├── babel.config.js
├── bundle-analyzer-report.html 構建分析報告
├── jsdoc.json 自動生成 api 文件
├── .gitignore
├── .eslintrc.js 程式碼檢查
├── .eslintignore
├── .editorconfig 統一編輯器風格用的配置檔案
├── coverage/ 測試覆蓋率
├── dist/ 輸出目錄
├── dist-docs/ 文件輸出目錄
├── .vscode/ vscode 編輯器配置目錄
├── src/
| ├── index.esm.js esm 輸出用
| └── index.js cjs 和 umd 輸出用
└── test/
├── fixtures/
├── unit/
└── .eslintrc.js
複製程式碼
一、選擇輸出檔案格式
因為要支援 nodejs
與瀏覽器,所以需要輸出多種格式的檔案,常見的輸出格式是 cjs
, esm
和 umd
三種格式,如果有必要也可以在加上 iife
的格式。
cjs
是nodejs
風格的檔案,主要是為了給 node 端使用,屬於 commonjs 規範
function foo () {}
exports.foo = foo
// or
module.exports = { foo }
複製程式碼
esm
搭配pkg.module
欄位 主要是構建工具(webpack, rollupjs)在用,屬於 es module 規範
export function foo () {}
// or
export { foo }
複製程式碼
- umd 就是個萬金油,不管是瀏覽器和 nodejs,有或沒有模組載入器,都可以正常使用,屬於 umd 規範
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'b'], function (exports, b) {
factory((root.commonJsStrictGlobal = exports), b);
});
} else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
// CommonJS
factory(exports, require('b'));
} else {
// Browser globals
factory((root.commonJsStrictGlobal = {}), root.b);
}
}(typeof self !== 'undefined' ? self : this, function (exports, b) {
// Use b in some fashion.
// attach properties to the exports object to define
// the exported module properties.
exports.action = function () {};
}));
複製程式碼
- iife 格式就是自執行檔案,以前比較常見,主要是為了進行閉包和隔離程式碼的作用域,不知道是什麼規範
(function () {
// 程式碼寫這裡
}())
複製程式碼
推薦輸出:cjs, esm 和 umd 三種格式的檔案
二、 選擇 rollupjs 外掛
1. 選擇程式碼轉換外掛(瀏覽器相容用)
推薦 babel 和 buble,具體選擇看個人選擇,這裡給出我選擇的理由
babel 的使用場景
- 在專案中使用,需要相容的瀏覽器種類比較多
- 用到最新的 ECMAScript 語法,比如 async/await 等
buble 的使用場景
- 環境可控的情況下,搭配構建工具使用
- 不需要考慮最新的語法,(截止 2019/05/02,不支援 async/await 和 class properties 語法轉換)
- 期望編譯後的程式碼較少(主要是助手函式程式碼)
目前兩種都在用, babel 用於專案,buble 用於外掛,因為外掛可以進行後編譯處理,免去程式碼冗餘的問題
2. 選擇測試框架
推薦一:
斷言工具用 nodejs 自帶的 assert 或其他的都可以
推薦二:
真心好用,可惜還沒有去深入瞭解,正在入手中
沒有 e2e
的推薦,因為我也不熟
3. 其他外掛
// 清理檔案
import clear from 'rollup-plugin-clear'
// 執行進度(可選)
import progress from 'rollup-plugin-progress'
// 程式碼檢查
import { eslint } from 'rollup-plugin-eslint'
// 去除不需要打包的外部依賴
import externals from 'rollup-plugin-node-externals'
// 字串替換,類似 webpack 的 DefinePlugin
import replace from 'rollup-plugin-replace'
// 模組引用
import commonjs from 'rollup-plugin-commonjs'
import resolve from 'rollup-plugin-node-resolve'
// json 檔案處理(可選)
import json from 'rollup-plugin-json'
// 程式碼壓縮
import { terser } from 'rollup-plugin-terser'
// 檢視構建後的檔案大小
import filesize from 'rollup-plugin-filesize'
// 用於分析構建後的程式碼
import visualizer from 'rollup-plugin-visualizer'
複製程式碼
三、編寫開發與構建配置
1. 配置 package.json
{
// nodejs 入口
"main": "./dist/lib.commonjs.js",
// webpack,rollupjs 入口
"module": "./dist/lib.esm.js",
"scripts": {
// 用 cross-env 解決不同作業系統之間環境變數設定方式不一致的問題
"dev": "cross-env NODE_ENV=development rollup -cw rollup.config.js",
"build": "cross-env NODE_ENV=production rollup -c rollup.config.js"
}
}
複製程式碼
2. 配置 rollup.config.js
rollupjs 支援輸出物件或陣列形式的配置,所以不需要拆分成多個配置檔案.
const isProd = process.env.NODE_ENV === 'production'
// 配置輸出格式
export default mergeConfig(baseConfig, [
{
input: 'src/index.esm.js',
output: {
banner,
file: 'dist/lib.esm.js',
format: 'es'
}
},
{
input: 'src/index.js',
output: {
file: 'dist/lib.commonjs.js',
format: 'cjs'
}
},
{
input: './src/index.js',
output: {
file: `./dist/lib${isProd ? '.min' : ''}.js`,
format: 'umd'
},
plugins: [
isProd && terser(),
process.env.npm_config_report && visualizer({
title: `${pkg.name} - ${pkg.author.name}`,
filename: 'bundle-analyzer-report.html'
})
]
}
])
複製程式碼
3. 配置 babel.config.js
module.exports = {
presets: [
['@babel/preset-env',{
// rollupjs 會處理模組,所以設定成 false
modules: false
}]
],
plugins: [
// 避免 babel 將 async/await 轉成 Generator
// 這樣相容性更好
'transform-async-to-promises'
]
}
複製程式碼
4. 配置 .eslintrc.js
module.exports = {
root: true,
env: {
// 用於跳過各自環境的全域性變數,也可以分開使用 node 和 browser 屬性
'shared-node-browser': true,
es6: true
},
rules: {
// 構建時避免 console 和 debugger 被一起構建上去
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
},
// 為了語法解析
parserOptions: {
parser: 'babel-eslint',
sourceType: 'module'
}
}
複製程式碼
5. 檢視效果
# 執行構建命令
$ npm run build --report
複製程式碼
# 執行構建命令並且生成 html 報告
$ npm run build --report
複製程式碼
四、編寫測試配置
1. 配置 package.json
{
"scripts": {
// test 還可能包含測試資料檔案,所以不能直接使用萬用字元
"test": "cross-env NODE_ENV=test nyc mocha \"test/{**/*,*}.test.js\"",
"report": "nyc report --reporter=html"
},
// 配置 nyc 外掛
"nyc": {
"require": [
// 為了能跑 es6 的程式碼
"@babel/register"
],
"reporter": [
"text-summary"
]
}
}
複製程式碼
2. 配置 test/.eslintrc
{
"env": {
// eslint 預設了 mocha 的全域性變數,所以設定為 true 就可以了
"mocha": true
}
}
複製程式碼
注意 eslint 的配置會繼承專案根目錄的 eslint 的配置資訊
3. 配置 babel.config.js
const pkg = require('./package.json')
module.exports = {
presets: [
['@babel/preset-env', {
// 測試時模組需要轉換
modules: process.env.NODE_ENV === 'test' ? 'commonjs' : false
}]
],
// 配置測試時用到的外掛
env: {
test: {
plugins: [
'istanbul',
'inline-json-import',
// 路徑別名,不然就只能用長長的路徑進行模組引用了
['module-resolver',
{
root: ['./src/'],
alias: {
[pkg.name]: './src/index.esm.js'
}
}
]
]
}
}
}
複製程式碼
4. 檢視效果
# 執行測試命令
$ npm test
複製程式碼
# 檢視測試報告
$ npm run report
複製程式碼
五、新增 API 生成工具
這裡使用的是 jsdoc 工具
1. 配置 package.json
{
"scripts": {
"build:docs": "jsdoc -c jsdoc.json"
}
}
複製程式碼
2. 配置 jsdoc.json
{
"source": {
"include": ["src"]
},
"templates": {
"cleverLinks": false,
"monospaceLinks": false
},
"tags": {
"allowUnknownTags": false
},
"opts": {
"verbose": true,
"recurse": true,
"encoding": "utf8",
"readme": "README.md",
"destination": "dist-docs",
// 建議新增,因為這樣可以一個版本一個文件
"package": "package.json"
}
}
複製程式碼
3. 寫註釋
4. 檢視效果
六、非開發配置
還可以做的事情有:
- 版本號更新的前後處理
- git 提交前的程式碼檢查
- changelog 自動生成
這些都是需要人工去配置的,當全部配置完畢後,一個擁有完善功能的專案也就配置完畢,這樣的專案才可以被認為是一個工程了吧。
最後獻上我的 github 地址,歡迎 fork