從 CommonJS 到 ES module(上)

require/exports 與 import/export

Miau Lightouch
4 min readAug 16, 2018

自 webpack 將 import/export 列為最佳 module 語法的同時,搭配 babel 將 ES6 語法轉譯成 ES5,ES6 語法受到廣泛的推崇。

現在好像感覺用了 ES6 就能打遍天下無敵手,考試都能考一百分……

真的是這樣嗎?

接下來,我要來說說我將傳統 CommonJS module(以下簡稱 CJS)轉換到 ES module(以下簡稱 ESM)時的艱難與辛苦。

說在前頭,ESM 就算到了 nodejs 環境,目前仍然是次等公民。

尤其是對雙棲(node 與 browser)的 module 來說,更是個莫大的痛苦。

ESM 仍是試驗功能

根據 nodejs文件 指出,在執行 nodejs 時,後面還得掛上 --experimental-modules

在不做任何設定的前提下,符合 ESM 規範的程式附檔名必須為 .mjs 而非 .js。如:node --experimental-modules my-app.mjs

若透過 node --experimental-modules 啟動 REPL 模式的話,還會發現似乎不能手動輸入 import 指令。

現在導入 ESM 或許言之過早,但我當時還不知道。

開始動工

這次轉移的目標是熱門的偵錯訊息套件 debug ,不過他在 nwjs 上有點問題,我就作為練習目標 fork 了一份,依 ES6 語法重寫並改名為 debug-es

因為轉換的過程大同小異,我就拿比較複雜的 src/node.js 來舉例。

首先,在 node.js 上看到程式載入了內建的 tty, util 套件

因為是在檔案開頭的地方匯入,所以我們也可以直接將上述套件改寫成 ESM

這樣就好了嗎?錯了。

接下來是 exports ,在 CJS 上 exports 是一個模組內的全域 object,但在 ESM 上並不存在。

這時可以考慮牽涉 exports 的部分重構,或者在前面加上 const exports = {} 來維持整個代碼結構。

Require 動態模組載入 v.s Import 靜態模組載入

緊接而來的是一個包在 try-catch 內的 require。

在這邊 require 跟 import 就展現出彼此的差別。

  1. require 是 blocking,直到模組載入完之前不會進行接下來的工作。
  2. 由於 import/export 是靜態的,所以不能包裹在 if-else 等語法之內。
  3. 仍在 Stage-3 的 dynamic import 是非同步的 Promise ,不僅無法取代 require,還會使得邏輯判斷更難處理。

另外,依照原設計 supports-color 是作為選用套件,手動安裝後就可以開啟多色輸出。

若轉為 ESM 就只能考慮:

  1. 放棄 supports-color,把整段 try-catch 拔掉
  2. 保留 supports-color,並正式放入 dependencies

這邊我選擇後者,於是檔頭載入模組的部分就變成了

而原本 try-catch 地方抽掉,簡化原本的 if 判斷

導出模組

在 CJS 中的 module.exports 可以直接對應到 ES6 的 export default

一樣,看到 require 就往檔頭放 import:

小結

至此,我們已經完成了基本的轉換工作,不過畢竟從動態載入的 CJS 轉向靜態的 ESM,其他關連的模組可能會因此影響執行方式,變得不得不重新梳理結構。

(像是 index.js 中有分支模組載入,也要整個重寫成 ESM,底下的子函數都要再包一層 init function,以避免產生不必要的實例。)

所以…… 我們接下來只要把附檔名改成 mjs,並啟用 nodejs 的 ESM 就能使用了嗎?

很抱歉,本範例中相依的 supports-color 是純正的 CJS module,所以我們還得進一步使用工具將相依的模組也一起轉譯為 ESM 並打包才能使用。

下篇:從 CommonJS 到 ES module(下):轉譯相依模組並使用 rollup 打包

--

--