從 CommonJS 到 ES module(下)
轉譯相依模組並使用 rollup 打包
自上篇我們將 CJS 結構的轉寫成 ESM 格式之後,我們發現在 ESM 內無法使用 CJS 模組。
更別說還有巢狀相依的狀況,那會有一大票模組必須轉換為 ESM 格式。
這種工作量光想就累了。
難道我們就沒有工具能夠簡單快速的解決相依問題嗎?
當然,那就是 rollup。
rollup 與 webpack 同屬 Javascript 的打包工具,主要是把用到的 npm 模組與自己的程式進行打包、集合起來。
而 rollup 比 webpack 更出色的地方是,在產出模組時 rollup 可以選擇 ESM,而目前 webpack 尚未支援 ESM 輸出。
基礎設定
就像 webpack 的設定檔一樣,基礎都會有
- Entry 接入點
- Output 輸出位置
不過 rollup 的設定檔,不像 webpack 遵循 CJS 格式,而是一開始就得使用 ESM 格式撰寫。
src/index.js
並將打包結果輸出 ESM 格式到 esm/bundle.js
接著下 rollup --config
指令就會進行打包。
不過這時候 rollup 會顯示找不到 supports-color,因為還沒轉譯成 ESM。
解決 CJS 相依問題
官方文件指出,在 ESM 中使用 CJS 模組是不支援的。
於是我們需要 rollup 幫我們接管 CJS 模組的部分,並將相依模組與我們的主程式打包在一起。
除了 rollup 主程式以外,我們還需要以下 plugin:
- rollup-plugin-node-resolve:解析 npm 模組路徑
- rollup-plugin-commonjs:將 CJS 轉為 ESM
於是延續上面的設定,我們將 plugin 放入:
再執行一次 rollup --config
,哈!這不就包進來了嗎?
這時候我們就可以用個簡單的程式測試我們剛打包好的模組:
在 Webpack 碰上問題
假設我們已經把打包好的 ESM 發佈到 NPM 上。
使用者在 webpack 上搭配 babel-loader 可能就會出現問題。
- 因為 第三方 loader 預設不會處理 mjs
- webpack 會對 babel-loader 輸出的 ESM 填充一個 CJS 格式的 process polyfill 然後報錯
到這裡,若我們繼續堅持使用 .mjs 作為附檔名,那在 babel-loader 上就會出現問題。
如果不堅持使用 .mjs 作為附檔名,那麼 nodejs 將無法直接讀取 ESM 模組。
於是我決定暫時向 webpack 靠攏,畢竟 nodejs 對 ESM 的支援尚未完善:
- 將輸出的打包從 .mjs 改為 .js
- 為了更好的相容性,也同時輸出 cjs 格式的打包
於是我們的設定就變成了
現在可以根據環境變數 BABEL_ENV
的不同我們就可以輸出不同版本的打包
BABEL_ENV=cjs rollup --config
則會產出 lib/bundle.js (CJS 格式)
BABEL_ENV=esm rollup --config
則會產出 esm/bundle.js (ESM 格式)
然後讓 package.json 的 main
欄位指向 lib/bundle.js
,並導引使用者若以 ESM 撰寫原始碼的話可以考慮載入 esm/bundle.js
。
補充:Tree-shaking
如果我們把 debug-es
模組接入點 index.js 打開來看
我們打從一開始就載入了兩種實例(node 或 browser),但最終只會有其中一種實例導出。
為何不就把 browser 跟 node 分別獨立導出呢?
我們將同一個模組打包成三個分支
- index.js: 自動判斷實例
- browser.js: 瀏覽器用實例
- node.js: nodejs 用實例
再輸出成 esm, cjs 雙格式,其實總計有六個打包檔案。
這樣使用者只要載入
debug-es/lib/browser
debug-es/lib/node
debug-es/esm/browser
debug-es/esm/node
就可以直接選擇需要的實例。
結語
其實 ESM 目前仍有不少前端、後端利用上有歧異或是不完善的地方。
但不管你使用 CJS 還是 ESM 撰寫模組,最後還是希望可以輸出 CJS 模組就好。
因為現在不管是對 ESM 的管理還是 .mjs 處理問題,都還在過渡中的陣痛期。
如果你仍執意要多種格式都釋出的話,記得把打包結果放進測試專案的 node_modules 內,使用 webpack 打包看看能不能正常運作。