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

關於 rollupjs 的教程已經很是多了,但是較少看到比較完整的工程樣板,因此分享下我本身搭建的,已經在公司內部使用的樣板工程。javascript

先認識下目錄結構

如今的前端爲了打包一個插件,差很少下面的配置文件都是必須的;html

哪怕代碼僅僅一百多行,爲了保證質量,少了誰都不能少了下面的配置文件;前端

若是是 typescript 寫的,那就還要再加幾個,懷念幾年前 Happy Coding 的日子😀。java

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
複製代碼

1、選擇輸出文件格式

由於要支持 nodejs 與瀏覽器,因此須要輸出多種格式的文件,常見的輸出格式是 cjs, esmumd 三種格式,若是有必要也能夠在加上 iife 的格式。node

  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 三種格式的文件webpack

2、 選擇 rollupjs 插件

1. 選擇代碼轉換插件(瀏覽器兼容用)

推薦 babelbuble,具體選擇看我的選擇,這裏給出我選擇的理由git

babel 的使用場景es6

  1. 在項目中使用,須要兼容的瀏覽器種類比較多
  2. 用到最新的 ECMAScript 語法,好比 async/await 等

buble 的使用場景github

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

目前兩種都在用, babel 用於項目,buble 用於插件,由於插件能夠進行後編譯處理,免去代碼冗餘的問題web

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'
複製代碼

3、編寫開發與構建配置

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
複製代碼

4、編寫測試配置

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
複製代碼

5、添加 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. 查看效果

6、非開發配置

還能夠作的事情有:

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

這些都是須要人工去配置的,當所有配置完畢後,一個擁有完善功能的項目也就配置完畢,這樣的項目才能夠被認爲是一個工程了吧。

最後獻上個人 github 地址,歡迎 fork

github.com/zhengxsFE/s…

相關文章
相關標籤/搜索