使用 rollup 打包 JavaScript SDK

吾輩的博客原文地址: https://blog.rxliuli.com/p/c3...
吾輩已經寫了一個 TypeScript/JavaScript Cli 工具 liuli-cli,若有須要可使用這個 Cli 直接生成一個開箱即用 SDK 項目,而後就能夠直接開始寫本身的代碼,不須要太過關心下面的內容了 -- 由於,它們都已然集成了。

場景

爲何要使用打包工具

若是咱們想要寫一個 JavaScript SDK,那麼就不太可能將全部的代碼都寫到同一個 js 文件中。固然了,想作的話的確能夠作到,但隨着 JavaScript SDK 內容的增長,一個 js 文件容易形成開發衝突,以及測試上的困難,這也是現代前端基本上都依賴於打包工具的緣由。前端

爲何打包工具是 rollup

現今最流行的打包工具是 webpack,然而事實上對於單純的打包 JavaScript SDK 而言 webpack 顯得有些過重了。webpack 終究是用來整合多種類型的資源而產生的(ReactJS/VueJS/Babel/TypeScript/Stylus),對於純 JavaScript 庫而言其實並無必要使用如此 強大 的工具。而 rollup 就顯得小巧精緻,少量配置就能馬上打包了。node

步驟

該記錄的代碼被吾輩放到了 GitHub,有須要的話能夠看下。

前置要求

開始以前,咱們必需要對如下內容有所瞭解jquery

  • [x] JavaScript
  • [x] npm
  • [ ] babel
  • [ ] uglify
  • [ ] eslint

須要打包的代碼

// src/wait.js
/**
 * 等待指定的時間/等待指定表達式成立
 * @param {Number|Function} param 等待時間/等待條件
 * @returns {Promise} Promise 對象
 */
function wait(param) {
  return new Promise(resolve => {
    if (typeof param === 'number') {
      setTimeout(resolve, param)
    } else if (typeof param === 'function') {
      var timer = setInterval(() => {
        if (param()) {
          clearInterval(timer)
          resolve()
        }
      }, 100)
    } else {
      resolve()
    }
  })
}

export default wait

// src/fetchTimeout.js
/**
 * 爲 fetch 請求添加超時選項
 * 注:超時選項並不是真正意義上的超時即取消請求,請求依舊正常執行完成,但會提早返回 reject 結果
 * @param {Promise} fetchPromise fetch 請求的 Promise
 * @param {Number} timeout 超時時間
 * @returns {Promise} 若是超時就提早返回 reject, 不然正常返回 fetch 結果
 */
function fetchTimeout(fetchPromise, timeout) {
  var abortFn = null
  //這是一個能夠被 reject 的 Promise
  var abortPromise = new Promise(function(resolve, reject) {
    abortFn = function() {
      reject('abort promise')
    }
  })
  // 有一個 Promise 完成就馬上結束
  var abortablePromise = Promise.race([fetchPromise, abortPromise])
  setTimeout(function() {
    abortFn()
  }, timeout)
  return abortablePromise
}

export default fetchTimeout

// src/main.js
import wait from './wait'
import fetchTimeout from './fetchTimeout'

/**
 * 限制併發請求數量的 fetch 封裝
 */
class FetchLimiting {
  constructor({ timeout = 10000, limit = 10 }) {
    this.timeout = timeout
    this.limit = limit
    this.execCount = 0
    // 等待隊列
    this.waitArr = []
  }

  /**
   * 執行一個請求
   * 若是到達最大併發限制時就進行等待
   * 注:該方法的請求順序是無序的,與代碼裏的順序無關
   * @param {RequestInfo} url 請求 url 信息
   * @param {RequestInit} init 請求的其餘可選項
   * @returns {Promise} 若是超時就提早返回 reject, 不然正常返回 fetch 結果
   */
  async _fetch(url, init) {
    const _innerFetch = async () => {
      console.log(
        `執行 execCount: ${this.execCount}, waitArr length: ${
          this.waitArr.length
        }, index: ${JSON.stringify(this.waitArr[0])}`,
      )
      this.execCount++
      const args = this.waitArr.shift(0)
      try {
        return await fetchTimeout(fetch(...args), this.timeout)
      } finally {
        this.execCount--
      }
    }
    this.waitArr.push(arguments)
    await wait(() => this.execCount < this.limit)
    // 嘗試啓動等待隊列
    return _innerFetch()
  }
}

export default FetchLimiting

使用 rollup 直接打包

安裝 rollupwebpack

npm i rollup -D

在根目錄建立一個 rollup.config.js 配置文件git

export default {
  // 入口文件
  input: 'src/main.js',
  output: {
    // 打包名稱
    name: 'bundlea',
    // 打包的文件
    file: 'dist/bundle.js',
    // 打包的格式,umd 支持 commonjs/amd/life 三種方式
    format: 'umd',
  },
}

添加一個 npm scriptgithub

"scripts": {
  "build": "rollup -c"
}

而後運行 npm run build 測試打包,能夠看到 dist 目錄下已經有 bundle.js 文件了web

好了,到此爲止咱們已經簡單使用 rollup 打包 js 了,下面的內容都是可選項,若是須要能夠分節選讀。

使用 babel 轉換 ES5

然而,咱們雖然已經將 main.js 打包了,然而實際上咱們的代碼沒有發生什麼變化。即:本來是 ES6 的代碼仍然會是 ES6,而若是咱們想要儘量地支持更多的瀏覽器,目前而言仍是須要兼容到 ES5 才行。npm

因此,咱們須要 babel,它可以幫咱們把 ES6 的代碼編譯成 ES5。json

附:babel 被稱爲現代前端的 jquery。

首先,安裝 babel 須要的包promise

npm i -D rollup-plugin-babel @babel/core @babel/plugin-external-helpers @babel/preset-env

rollup.config.js 中添加 plugins

import babel from 'rollup-plugin-babel'

export default {
  plugins: [
    // 引入 babel 插件
    babel({
      exclude: 'node_modules/**',
    }),
  ],
}

添加 babel 的配置文件 .babelrc

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "modules": false
      }
    ]
  ],
  "plugins": ["@babel/plugin-external-helpers"]
}

再從新運行 npm run build,能夠看到 bundle.js 中的代碼已經被編譯成 ES5 了。

使用 uglify 壓縮生產環境代碼

那麼,生產中的代碼還須要作什麼呢?是的,壓縮,減少 js 代碼的體積是必要的。接下來,咱們還須要使用 uglify 壓縮咱們打包後的 bundle.js 代碼。

首先仍然是安裝 uglify 相關的包

npm i -D rollup-plugin-uglify

而後在 rollup.config.js 中引入插件就行了

// 注意,這裏引入須要使用 { uglify } 而非 uglify,由於 uglify 導出自身時使用的是 exports.uglify
import { uglify } from 'rollup-plugin-uglify'

export default {
  plugins: [
    // js 壓縮插件,須要在最後引入
    uglify(),
  ],
}

使用 ESLint 檢查代碼

若是咱們想要須要多人協做統一代碼風格,那麼可使用 ESLint 來強制規範。

首先,全局安裝 eslint

npm i eslint -g

而後使用 eslint cli 初始化

eslint --init

下面的三項問題選擇

  1. How would you like to configure ESLint? (Use arrow keys)
    Use a popular style guide
  2. Which style guide do you want to follow? (Use arrow keys)
    Standard (https://github.com/standard/standard)
  3. What format do you want your config file to be in? (Use arrow keys)
    JavaScript
  4. Would you like to install them now with npm?
    y

而後,咱們發現項目根目錄下多出了 .eslintrc.js,這是 eslit 的配置文件。然而,咱們須要對其稍微修改一下,否則若是咱們的代碼中出現了瀏覽器中的對象,例如 document,eslint 就會傻傻的認爲那是個錯誤!
修改後的 .eslintrc.js 配置

module.exports = {
  extends: 'standard',
  // 添加了運行環境設定,設置 browser 爲 true
  env: {
    browser: true,
  },
}

當咱們查看打包後的 bundle.js 時發現 eslint 給咱們報了一堆錯誤,因此咱們須要排除掉 dist 文件夾
添加 .eslintignore 文件

dist

添加 rollup-plugin-eslint 插件,在打包以前進行格式校驗

npm i -D rollup-plugin-eslint

而後引入它

import { eslint } from 'rollup-plugin-eslint'

export default {
  plugins: [
    // 引入 eslint 插件
    eslint(),
  ],
}

這個時候,當你運行 npm run build 的時候,eslint 可能提示你一堆代碼格式錯誤,難道咱們還要一個個的去修復麼?不,eslint 早已考慮到了這一點,咱們能夠添加一個 npm 腳本用於全局修復格式錯誤。

"scripts": {
  "lint": "eslint --fix src"
}

而後運行 npm run lint,eslint 會盡量修復格式錯誤,若是不能修復,會在控制檯打印異常文件的路徑,而後咱們手動修復就好啦

其餘 rollup 配置

添加代碼映射文件

其實很簡單,只要在 rollup.config.js 啓用一個配置就行了

export default {
  output: {
    // 啓用代碼映射,便於調試之用
    sourcemap: true,
  },
}

多環境打包

首先移除掉根目錄下的 rollup.config.js 配置文件,而後建立 build 目錄並添加下面四個文件

// build/util.js
import path from 'path'

/**
 * 根據相對路徑計算真是的路徑
 * 從當前類的文件夾開始計算,這裏是 /build
 * @param {String} relaPath 相對路徑
 * @returns {String} 絕對路徑
 */
export function calcPath(relaPath) {
  return path.resolve(__dirname, relaPath)
}
// build/rollup.config.dev.js
import { eslint } from 'rollup-plugin-eslint'
import { calcPath } from './util'
import { name } from '../package.json'

export default {
  // 入口文件
  input: calcPath('../src/main.js'),
  output: {
    // 打包名稱
    name,
    // 打包的文件
    file: calcPath(`../dist/${name}.js`),
    // 打包的格式,umd 支持 commonjs/amd/life 三種方式
    format: 'umd',
    // 啓用代碼映射,便於調試之用
    sourcemap: true,
  },
  plugins: [
    // 引入 eslint 插件,必須在 babel 以前引入,由於 babel 編譯以後的代碼未必符合 eslint 規範,eslint 僅針對咱們 [本來] 的代碼
    eslint(),
  ],
}
// build/rollup.config.prod.js
import babel from 'rollup-plugin-babel'
// 注意,這裏引入須要使用 { uglify } 而非 uglify,由於 uglify 導出自身時使用的是 exports.uglify
import { uglify } from 'rollup-plugin-uglify'
import { eslint } from 'rollup-plugin-eslint'
import { calcPath } from './util'
import dev from './rollup.config.dev'
import { name } from '../package.json'

export default [
  dev,
  {
    // 入口文件
    input: calcPath('../src/main.js'),
    output: {
      // 打包名稱
      name,
      // 打包的文件
      file: calcPath(`../dist/${name}.min.js`),
      // 打包的格式,umd 支持 commonjs/amd/life 三種方式
      format: 'umd',
    },
    plugins: [
      // 引入 eslint 插件,必須在 babel 以前引入,由於 babel 編譯以後的代碼未必符合 eslint 規範,eslint 僅針對咱們 [本來] 的代碼
      eslint(),
      // 引入 babel 插件
      babel({
        exclude: calcPath('../node_modules/**'),
      }),
      // js 壓縮插件,須要在最後引入
      uglify(),
    ],
  },
]
// build/rollup.config.js
import dev from './rollup.config.dev'
import prod from './rollup.config.prod'

// 若是當前環境時 production,則使用 prod 配置,不然使用 dev 配置
export default process.env.NODE_ENV === 'production' ? prod : dev

修改 npm 腳本

"scripts": {
  "build:dev": "rollup -c build/rollup.config.js --environment NODE_ENV:development",
  "build:prod": "rollup -c build/rollup.config.js --environment NODE_ENV:production",
  "build": "npm run build:dev && npm run build:prod",
}

那麼,關於使用 rollup 打包 JavaScript 的內容就先到這裏了,有須要的話後續吾輩還會繼續更新的!

相關文章
相關標籤/搜索