Zenn
Closed2

package.jsonとtsconfig.json

js4000alljs4000all

✅ 改めて整理すると

package.json"type": "module" は Node.js ランタイムへの指示
tsconfig.jsonmodule は TypeScript の出力形式の指示

この2つが 一致していないと、実行時にエラーが起きる 可能性が高まります。


🔍 具体的な食い違いの例(今回のケース)

設定項目 実際の値 意味
package.json"type" "module" Node.js は .jsESM として扱う
tsconfig.jsonmodule "CommonJS"(または指定なし) TypeScript が CommonJS 形式の JS を出力する
結果 exports is not defined エラー ESM の中に CJS 構文があるためランタイムが破綻

✅ まとめると:

  • tsconfig.jsonmodule「どういう形式で JavaScript に出力するか」
  • package.json"type"「Node.js が .js をどう解釈するか」
  • 両者が 一致していないと、Node.js が出力された JS を正しく解釈できずに実行時エラーになる

💡 アドバイス(今後のプロジェクト運営にも)

状況 安定動作のためにやるべきこと
"type": "module" を使うなら tsconfig.json でも module: "ESNext""ES2020" にする
"type": "commonjs" or 未指定なら tsconfig.jsonmodule: "CommonJS" にする
js4000alljs4000all

✅ 他のコンパイル言語と比較して「異質」に感じる理由

1. 中間表現(JS)が複数の互換性形式を持つ

  • 一般的なコンパイル言語(例:C, Rust, Go)は、単一のバイナリ or 中間コード(例えば LLVM IR)に変換し、それをランタイムやOSが解釈します。
  • しかし JavaScript の世界では、「中間成果物」であるはずの .js にさえ、異なるモジュール形式(CJS, ESM)が混在している。

= 同じ拡張子 .js なのに解釈のルールが違うという、曖昧性のある設計


2. Node.js がモジュール形式を「静的に」ではなく「ヒューリスティックに」判定している

  • "type": "module" のような プロジェクトレベルの文字列設定によって、ランタイムが「これは ESM っぽい」と判断してしまう
  • 他の言語なら、バイナリヘッダや明示的なマークで形式を決めるところですが、Node.js は 拡張子+package.json+パス解決規則の組み合わせという「柔らかい」方法で判断

= コンパイル結果の .js が正しいかどうかは、ランタイムと開発者の解釈次第という"ズレ"が起こりやすい


3. 自動判定が期待通りに働かない

  • おっしゃる通り、「ランタイムが賢ければ .js の中身を見て ESM か CJS か自動で判定してくれるはず」と思いがちです。
  • しかし Node.js は仕様上、"判定基準を明文化して手動で切り替えさせる"方針を取っており、開発者が明示的に管理することを求めています。

開発者「拡張子と package.json をちゃんと揃えておかないと・・」
ランタイム「俺はルール通りに動くだけだから」


✳ 一言で言うと:

JavaScript(特に Node.js)のモジュールシステムは、後付けの進化の結果として “きれいに一貫しない” デザインになっている


💡 裏を返せば…

このあたりを押さえておくと、他の人がハマっている原因にもすぐ気づけるようになるので、実務では大きな武器になります。

特に TypeScript + ESM + サーバーレス(Vercel, Lambda)などの組み合わせは、環境ごとの「モジュール判定の仕方」がバグの温床になりやすいです。

このスクラップは8日前にクローズされました
ログインするとコメントできます