CommonJS のライブラリを import x from "..." の形(default import)で読み込むと、tsc が次のエラーで止まることがあります。
index.ts(1,8): error TS1259: Module '"/app/node_modules/cjs-lib/index"' can only be default-imported using the 'esModuleInterop' flag
終了コードは 2 です。原因は、読み込もうとしているのが export =(module.exports = ...)で書かれた CommonJS モジュールで、そこに ESM の default export が無いのに、esModuleInterop を有効にせず default import していることです。CommonJS には「default export」という概念がないので、TypeScript は esModuleInterop(または相当の書き方)でその橋渡しを求めます。直し方は、tsconfig.json で esModuleInterop を有効にすることです。
// tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"esModuleInterop": true
}
}
// これがそのまま通るようになる
import cjs from "cjs-lib";
console.log(cjs.answer);
なぜ起きるのか:CommonJS に「default export」は無い
export =(module.exports = { ... })で書かれた CommonJS モジュールは、オブジェクトを丸ごと1つ書き出します。ESM の export default とは別の仕組みで、ESM 的な default export を持っていません。そのため import cjs from "cjs-lib"(default を取り出す書き方)は、型の上では取り出せる default が無く、TS1259 になります。
esModuleInterop は、この食い違いを埋める設定です。有効にすると、export = の CommonJS モジュールを default import したとき、TypeScript が**モジュール全体を default として受け取れるように橋渡し(合成)**します。これは Babel などが以前から実行時にやっていた相互運用を、TypeScript の型と出力コードでも合わせる指定です。esModuleInterop: true は allowSyntheticDefaultImports(合成 default を型として許す)も暗黙に有効にします。
なぜ esModuleInterop が無いと出るのか
境界は tsconfig.json の設定で、TypeScript の版でもコードでもありません。同じ default import でも、esModuleInterop の状態で通るか止まるかが決まります。
esModuleInteropを書いていないmodule: commonjsの tsconfig:esModuleInteropの既定値はfalseです(moduleがcommonjsの場合)。手書きの最小tsconfig.jsonや、古いテンプレートの設定では、明示的にfalseと書いていなくてもこのエラーになります。module: "nodenext"/"node16"/"preserve"では出ない:これらのmodule設定はesModuleInteropを暗黙に有効化します。module: commonjs(やes2015などの古い指定)で、かつesModuleInteropを書いていないときに表面化します。tsc --initで作った新しい tsconfig では出ない(が、理由に注意):TypeScript 5.9 以降のtsc --initは最小構成を出力し、既定のmoduleが"nodenext"です(esModuleInteropの行そのものは書きません)。出ないのはesModuleInterop: trueが書き込まれるからではなく、nodenextが暗黙有効化するからです。したがって、その tsconfig からmodule: "commonjs"に変えるとesModuleInteropは無効に戻り、同じ default import で再びこのエラーが出ます。TypeScript 5.8 以前のtsc --initはesModuleInterop: trueを明示的に書き込んでいました。
まず tsconfig.json に esModuleInterop があるか、module が何かを確認します。module: commonjs で esModuleInterop が無ければ、この記事の設定に当たっています。
直し方:esModuleInterop を有効にする
tsconfig.json の compilerOptions に esModuleInterop: true を足します。
{
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"esModuleInterop": true
}
}
これで import cjs from "cjs-lib" がそのまま通ります。esModuleInterop はプロジェクト全体の import の解釈に影響するので、既存のコードがある場合は一度ビルドして、他の import で新しい型エラーが出ないかを確認します(合成 default が使えるようになるぶん、import * as x を default import に直す指摘が出ることがあります。次節)。
tsconfig を変えずに、その import だけ直す書き方もあります。TypeScript が CommonJS を読むための import ... = require(...) 構文です。
import cjs = require("cjs-lib");
console.log(cjs.answer);
こちらは esModuleInterop に依存せず、export = のモジュールをそのまま受け取ります。プロジェクトの設定を触れない事情があるときの局所的な回避です。ただし module: "es2015" / "esnext" のように ESM をそのまま出力する設定とは併用できません(TS1202)。module: "nodenext" はファイル単位で CJS/ESM を判定し、この構文を createRequire を使う形へ変換するため、ESM 判定のファイルでも使えます(実測で確認)。module: commonjs を出力しているなら、そのまま使えます。
切り分け(うまくいかないとき)
- エラーが実行時に
named export not foundとして出る:それはtscの型チェックではなく、Node が ESM から CommonJS を読み込む実行時の相互運用の問題です。層が違うので Named export not found(ESM から CommonJS を読む) を参照してください。本記事はコンパイル時(tsc)の型エラー、あちらは実行時の Node のエラーです。 esModuleInterop: trueにしたら別の import でエラーが増えた:esModuleInteropは import 全体の解釈を変えるので、これまでimport * as xで受けていた CommonJS をimport xに直す必要が出ることがあります(TS2497など)。エラーの案内どおり default import へ寄せるか、名前空間 import に統一します。import x = require(...)にしたらTS1202が出る:import ... = require()はmodule: "es2015"/"esnext"のように ESM をそのまま出力する設定とは併用できません。この場合はesModuleInteropを使います。module: "nodenext"は例外で、この構文をcreateRequireへ変換するのでTS1202にはなりません(ただしnodenextはesModuleInteropが既定で有効なので、そもそも default import が通り、この回避自体が要りません)。moduleをnodenextに変えたらエラーは消えたが、今度は別の設定エラーが出た:nodenextはmoduleResolutionの一致も要求します。TS5109(module と moduleResolution の不一致) を参照してください。moduleを上げるのは相互運用の既定が変わるぶん影響が広いので、まずはesModuleInteropを足すだけの対処を検討します。- 手元では通るのに CI でだけ落ちる:
tsconfigが環境で違う(CI が別の設定ファイルを使っている、extendsの先が違う)ことがあります。CI が読んでいるtsconfigのesModuleInteropとmoduleを確認します。
検証環境
node:22.23.1-alpine([email protected])、ネットワーク有り(npm installで TypeScript を入れる)- 再現:
module: "commonjs"/moduleResolution: "node"だけのtsconfig.json(esModuleInterop記載なし)で、export =を持つ CommonJS 依存をimport cjs from "cjs-lib"すると、tsc --noEmitがTS1259: Module '...' can only be default-imported using the 'esModuleInterop' flagで終了コード 2 - 修正:
esModuleInterop: trueを足すと、同じ import・同じ依存で終了コード 0・シグネチャ消滅
再現から修正までは errfix の検証ハーネスが機械的に確認しています。reproduce と fix の差は tsconfig.json の esModuleInterop の1行だけです。境界が TypeScript の版でなく設定であること(module: commonjs で esModuleInterop の既定が false、tsc --init の出力や module: nodenext では有効)は、[email protected] で各構成を実行して確認し、verification/probes.txt に記録しています。esModuleInterop の既定と node16/nodenext/preserve での暗黙有効化は、TypeScript の公式ドキュメントに拠ります。