開發一個適用於 nodejs 與瀏覽器的 npm 包 - 基於 rollupjs

打雜小廝發表於2019-05-02

關於 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, esmumd 三種格式,如果有必要也可以在加上 iife 的格式。

  1. cjsnodejs 風格的檔案,主要是為了給 node 端使用,屬於 commonjs 規範
function foo () {}

exports.foo = foo
// or 
module.exports = { foo }
複製程式碼
  1. esm 搭配 pkg.module 欄位 主要是構建工具(webpack, rollupjs)在用,屬於 es module 規範
export function foo () {}

// or
export { foo }
複製程式碼
  1. 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 () {};
}));
複製程式碼
  1. iife 格式就是自執行檔案,以前比較常見,主要是為了進行閉包和隔離程式碼的作用域,不知道是什麼規範
(function () {
    // 程式碼寫這裡
}())
複製程式碼

推薦輸出:cjs, esm 和 umd 三種格式的檔案

二、 選擇 rollupjs 外掛

1. 選擇程式碼轉換外掛(瀏覽器相容用)

推薦 babelbuble,具體選擇看個人選擇,這裡給出我選擇的理由

babel 的使用場景

  1. 在專案中使用,需要相容的瀏覽器種類比較多
  2. 用到最新的 ECMAScript 語法,比如 async/await 等

buble 的使用場景

  1. 環境可控的情況下,搭配構建工具使用
  2. 不需要考慮最新的語法,(截止 2019/05/02,不支援 async/await 和 class properties 語法轉換)
  3. 期望編譯後的程式碼較少(主要是助手函式程式碼)

目前兩種都在用, 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
複製程式碼

開發一個適用於 nodejs 與瀏覽器的 npm 包 - 基於 rollupjs

# 執行構建命令並且生成 html 報告
$ npm run build --report
複製程式碼

開發一個適用於 nodejs 與瀏覽器的 npm 包 - 基於 rollupjs

四、編寫測試配置

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
複製程式碼

開發一個適用於 nodejs 與瀏覽器的 npm 包 - 基於 rollupjs

# 檢視測試報告
$ npm run report
複製程式碼

開發一個適用於 nodejs 與瀏覽器的 npm 包 - 基於 rollupjs

五、新增 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. 寫註釋

開發一個適用於 nodejs 與瀏覽器的 npm 包 - 基於 rollupjs

4. 檢視效果

開發一個適用於 nodejs 與瀏覽器的 npm 包 - 基於 rollupjs

開發一個適用於 nodejs 與瀏覽器的 npm 包 - 基於 rollupjs

六、非開發配置

還可以做的事情有:

  1. 版本號更新的前後處理
  2. git 提交前的程式碼檢查
  3. changelog 自動生成

這些都是需要人工去配置的,當全部配置完畢後,一個擁有完善功能的專案也就配置完畢,這樣的專案才可以被認為是一個工程了吧。

開發一個適用於 nodejs 與瀏覽器的 npm 包 - 基於 rollupjs

最後獻上我的 github 地址,歡迎 fork

github.com/zhengxsFE/s…

相關文章