自 webpack 將 import/export 列為最佳 module 語法的同時,搭配 babel 將 ES6 語法轉譯成 ES5,ES6 語法受到廣泛的推崇。
現在好像感覺用了 ES6 就能打遍天下無敵手,考試都能考一百分……
真的是這樣嗎?
接下來,我要來說說我將傳統 CommonJS module(以下簡稱 CJS)轉換到 ES module(以下簡稱 ESM)時的艱難與辛苦。
說在前頭,ESM 就算到了 nodejs 環境,目前仍然是次等公民。
尤其是對雙棲(node 與 browser)的 module 來說,更是個莫大的痛苦。
根據 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。
首先,在 node.js 上看到程式載入了內建的 tty, util 套件
https://gist.github.com/c7ff357bec318ab5e64b4d422e212b86
因為是在檔案開頭的地方匯入,所以我們也可以直接將上述套件改寫成 ESM
https://gist.github.com/ffd9d6d1cdf80708cdd59874b030308f
這樣就好了嗎?錯了。
https://gist.github.com/caebb99e62be293e0c72b92a056de24c
接下來是 exports ,在 CJS 上 exports 是一個模組內的全域 object,但在 ESM 上並不存在。
這時可以考慮牽涉 exports 的部分重構,或者在前面加上 const exports = {}
來維持整個代碼結構。
https://gist.github.com/ada210719310e76db8c6a0bf61cf1cad
緊接而來的是一個包在 try-catch 內的 require。
在這邊 require 跟 import 就展現出彼此的差別。
- 由於 import/export 是靜態的,所以不能包裹在 if-else 等語法之內。
- require 是 blocking,直到模組載入完之前不會進行接下來的工作。
- 仍在 Stage-3 的 dynamic import 是非同步的 Promise ,不僅無法取代 require,還會使得邏輯判斷更難處理。
依照原設計 supports-color 是作為選用套件,手動安裝後就可以開啟多色輸出。
但若轉為 ESM 就只能考慮:
- 放棄 supports-color,把整段 try-catch 拔掉
- 保留 supports-color,並正式放入 dependencies
這邊我選擇後者,於是檔頭載入模組的部分就變成了
https://gist.github.com/a8dbf05a9ab56497f49c9e6cb21a543f
而原本 try-catch 地方抽掉,簡化原本的 if 判斷
https://gist.github.com/ad25e2b3a606a9e630b40e9b87b485fe
在 CJS 中的 module.exports
可以直接對應到 ES6 的 export default
https://gist.github.com/9b5adab8197f38b71322bce22ef66c74
一樣,看到 require 就往檔頭放 import ,就會變成:
https://gist.github.com/41eaf7542356e618cacb4ec92c494827
至此,基本上已經完成了轉換工作,不過畢竟從動態載入的 CJS 轉向 靜態的 ESM,其他關連的模組可能會因此影響執行方式,變得不得重新梳理結構。(如 index.js 的分支模組載入,也要整個重寫成 ESM)