CommonJSとES Modulesのどちらを使うべきか?
問題
JavaScript には歴史的事情により、CommonJS と ES Modules の2つのモジュール仕様がある。
本当は AMD, UMD というのもあるが、手で書くことはほぼないはずなので除外。
背景
CommonJS の仕様はサーバーサイド JavaScript (Node.js) の事情により作られた草の根的仕様になる。
文法は変更せず、module.exports 変数と require 関数でモジュールを動的にインポートする仕組み。
ES Modules は、JavaScript (ECMA Script) の仕様の再検討により作られた公式仕様になる。
文法に import/export 構文(静的インポート)を導入。import 関数(動的インポート)もある。
ES Modules でも、バックで CommonJS に変換して処理する方式は Faux ESM や Fake ESMと呼ばれる。ES Modules のまま処理する方式は Native ESM と呼ばれる。
違い
ES Modules の方が先進的。
ES Modules では動作しないプログラムが結構ある。
トランスパイルが必要になることがある。
CommonJS から ES Modules のモジュールを呼び出すのは難しい。(多くの場合、ES Modules の方を CommonJS にトランスパイルしている。)
ES Modules を CommonJS にトランスパイルしても、互換性は完全ではない。
ES Modules を CommonJS にトランスパイルしたものを ES Modules で呼び出すとその違いが出てしまう。
Tree Shaking は ES Modules でなければ動作しない。
*.js という拡張子では CommonJS か ES Modules か判別できない。
*.cjs が CommonJS
*.mjs が ES Modules
package.json に "type": "module" がある場合、*.js は ES Modules とみなされる。
ES Modules では import 時の拡張子の指定は必須。CommonJS での require では拡張子はなければ補填される。
Fake ESM の場合も拡張子が補填される。
CommonJS であることと、ES6 の各種構文が使えるかどうかは実は別。
ライブラリで、ES Modules と CommonJS の両方を用意しているのは Dual package と呼ばれる。Nativ ESM のみのものは Pure ESM と呼ばれる。今後は Pure ESM のライブラリが増えてくる。
HTML の script 要素では ES Modules の場合には type="module" と書く必要がある。(*.mjs なら不要?)
ホットリロードをする場合はモジュールで分割されている方が合理的。
見解
可能な限り ES Modules を使う。
ES Module のままバンドルして minify できるのが望ましい。
ライブラリで対応できる対象を増やしたい場合はトランスパイルして CommonJS 方式にする。(飽くまでおまけ)
参考
Node.jsのネイティブES Modulesサポートが抱える問題を解決するBabelプラグインを書いた
実践 Node.js Native ESM — Wantedlyでのアプリケーション移行事例
アプリケーションコードに変更を加えないNode.js Native ESMへの移行
Native ESM時代とはなにか