Closed2
package.jsonとtsconfig.json
✅ 改めて整理すると
package.json
の"type": "module"
は Node.js ランタイムへの指示
tsconfig.json
のmodule
は TypeScript の出力形式の指示
この2つが 一致していないと、実行時にエラーが起きる 可能性が高まります。
🔍 具体的な食い違いの例(今回のケース)
設定項目 | 実際の値 | 意味 |
---|---|---|
package.json の "type"
|
"module" |
Node.js は .js を ESM として扱う |
tsconfig.json の module
|
"CommonJS" (または指定なし) |
TypeScript が CommonJS 形式の JS を出力する |
結果 |
exports is not defined エラー |
ESM の中に CJS 構文があるためランタイムが破綻 |
✅ まとめると:
-
tsconfig.json
のmodule
は 「どういう形式で JavaScript に出力するか」 -
package.json
の"type"
は 「Node.js が .js をどう解釈するか」 - 両者が 一致していないと、Node.js が出力された JS を正しく解釈できずに実行時エラーになる
💡 アドバイス(今後のプロジェクト運営にも)
状況 | 安定動作のためにやるべきこと |
---|---|
"type": "module" を使うなら |
tsconfig.json でも module: "ESNext" や "ES2020" にする |
"type": "commonjs" or 未指定なら |
tsconfig.json の module: "CommonJS" にする |
✅ 他のコンパイル言語と比較して「異質」に感じる理由
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日前にクローズされました
ログインするとコメントできます