TypeScriptのCommonJS/ESM問題にぶつかった記録

普段はRailsを使っていてTypeScriptは滅多に書きませんが、昨今TypeScriptのニーズが高いので、たまに軽微なツール作りで利用しています。ただ、たまにしか触らないとCommonJS(CJS)やES Modules(ESM)まわりで毎回混乱するので、頭の整理を兼ねて記録しておきます。

Imagemin 8でエラーが出た

画像最適化のためにImagemin 8を入れたら、こうなりました。

Error [ERR_REQUIRE_ESM]: require() of ES Module .../node_modules/imagemin/index.js not supported.

TypeScriptのコンパイル結果が require()(CJS形式)になるのに、Imagemin 8はESM専用。そこで衝突しています。

CJS/ESMの何が厄介か

CommonJS と ES Modules の関係

  • Node.jsが最初に採用したのがCommonJS、後からES Modulesが標準化された
  • 2つのモジュールシステムが共存しているので、組み合わせの問題が起きる
  • tsconfig.jsonmodule 指定、esModuleInteroppackage.jsontype: "module" — 関連する設定が多すぎる

概要はわかっても、設定の組み合わせが多くて正直手に負えません。

助けになったのが tsconfig/bases です。ランタイム環境に応じた推奨 tsconfig.json が公開されていて、ゼロから設定を考えずに済みます。

製作者側の事情

p-limitの作者が書いた Pure ESM package という文書が参考になりました。

  • 多くのnpmパッケージの作者は事実上ESMで開発している
  • CJS版はトランスパイラに頼っているだけで、作者自身がCJSの挙動を深く理解しているわけではない
  • つまり「たまたま動いている」ケースが少なくない

これを知って、「自分の設定が悪い」と思い込んでいたのが、「提供側にもCJS互換が保証されていないケースがある」と考えられるようになりました。

自分の方針

  • tsconfig.json は tsconfig/bases の推奨設定をベースにする
  • CJS/ESMの構造は概要だけ押さえ、細部に深入りしない
  • ESM専用モジュールでエラーが出たら、Dynamic Importやバージョン固定で対処
  • どうしても解決しない場合は @ts-nocheck や eslint の部分無効化も許容する

Related Posts