Error [can only be default-imported using the 'esModuleInterop' flag]

error TS1259: Module '...' can only be default-imported using the 'esModuleInterop' flag の直し方

FIX SUMMARY verified
Applies when
node:22.23.1-alpineTypeScript (esModuleInterop defaults to false for module: commonjs; default-importing an export= CommonJS module needs it enabled)default-importing a CommonJS (export =) module under a module: commonjs tsconfig without esModuleInterop — common in hand-written or older tsconfig where the flag is absent (tsc --init and module: nodenext enable it)

Verified: reproduced in node:22.23.1-alpine, then the can only be default-imported using the 'esModuleInterop' flag signature was gone after the fix (exit 0).

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.jsonesModuleInterop を有効にすることです。

// 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: trueallowSyntheticDefaultImports(合成 default を型として許す)も暗黙に有効にします。

なぜ esModuleInterop が無いと出るのか

境界は tsconfig.json の設定で、TypeScript の版でもコードでもありません。同じ default import でも、esModuleInterop の状態で通るか止まるかが決まります。

  • esModuleInterop を書いていない module: commonjs の tsconfigesModuleInterop の既定値は false です(modulecommonjs の場合)。手書きの最小 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 --initesModuleInterop: true を明示的に書き込んでいました。

まず tsconfig.jsonesModuleInterop があるか、module が何かを確認します。module: commonjsesModuleInterop が無ければ、この記事の設定に当たっています。

直し方:esModuleInterop を有効にする

tsconfig.jsoncompilerOptionsesModuleInterop: 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 にはなりません(ただし nodenextesModuleInterop が既定で有効なので、そもそも default import が通り、この回避自体が要りません)。
  • modulenodenext に変えたらエラーは消えたが、今度は別の設定エラーが出たnodenextmoduleResolution の一致も要求します。TS5109(module と moduleResolution の不一致) を参照してください。module を上げるのは相互運用の既定が変わるぶん影響が広いので、まずは esModuleInterop を足すだけの対処を検討します。
  • 手元では通るのに CI でだけ落ちるtsconfig が環境で違う(CI が別の設定ファイルを使っている、extends の先が違う)ことがあります。CI が読んでいる tsconfigesModuleInteropmodule を確認します。

検証環境

  • node:22.23.1-alpine[email protected])、ネットワーク有り(npm install で TypeScript を入れる)
  • 再現:module: "commonjs" / moduleResolution: "node" だけの tsconfig.jsonesModuleInterop 記載なし)で、export = を持つ CommonJS 依存を import cjs from "cjs-lib" すると、tsc --noEmitTS1259: Module '...' can only be default-imported using the 'esModuleInterop' flag で終了コード 2
  • 修正:esModuleInterop: true を足すと、同じ import・同じ依存で終了コード 0・シグネチャ消滅

再現から修正までは errfix の検証ハーネスが機械的に確認しています。reproduce と fix の差は tsconfig.jsonesModuleInterop の1行だけです。境界が TypeScript の版でなく設定であること(module: commonjsesModuleInterop の既定が falsetsc --init の出力や module: nodenext では有効)は、[email protected] で各構成を実行して確認し、verification/probes.txt に記録しています。esModuleInterop の既定と node16/nodenext/preserve での暗黙有効化は、TypeScript の公式ドキュメントに拠ります。

検証(machine-verified)

この修正は node:22.23.1-alpine のバージョン固定コンテナ内で再現し、修正後に can only be default-imported using the 'esModuleInterop' flag のシグネチャが消えることを機械で確認しています。

verify — run-case.mjs
$ node run-case.mjs node/ts1259-esmoduleinterop-required
● reproduce TS1259: Module present ✓
● apply fix exit 0
● re-run TS1259: Module gone ✓
PASS verified · node:22.23.1-alpine · signature gone