`esModuleInterop`: TypeScriptの「モジュールの不一致」という悪夢を終わらせる魔法
フロントエンドの現場でTypeScriptを触っていると、一度は必ず遭遇する「謎の型エラー」がありますよね。
「`module ‘xxx’ has no default export`」
CommonJS形式で書かれた古いライブラリをインポートしようとした途端、TypeScriptが急に冷たく拒絶してくるあの現象です。新人エンジニアがここで頭を抱え、`import as …` で無理やり切り抜けようとする姿を何度見てきたことか。
今日は、そんな泥臭いモジュール解決の苦労を根底から解消する、`tsconfig.json` の隠れた英雄 `esModuleInterop` について深掘りします。
—
なぜこの設定が必要なのか?:歴史的背景とブラウザの限界
まず、なぜこんな問題が起きるのかを理解しましょう。
JavaScriptの歴史には、長らく「CommonJS(Node.js由来の `require`)」と「ES Modules(`import/export`)」という二つの勢力が共存してきました。ブラウザや最近のNode.jsはES Modules(ESM)をネイティブでサポートしていますが、npmに転がっているライブラリの多くは、依然としてCommonJSのままです。
TypeScriptのコンパイラは、元々「厳格な仕様準拠」を求めていました。
- ESM: `import { foo } from ‘bar’`
- CJS: `const foo = require(‘bar’)`
TypeScriptにとって、CJSの `module.exports` は「単なるオブジェクト」であり、ESMの「デフォルトエクスポート」とは別物だと見なされます。そのため、CJSのライブラリを `import x from ‘y’` と書こうとすると、「デフォルトエクスポートなんて存在しないぞ」と怒られていたわけです。
`esModuleInterop: true` が裏側で行っていること
この設定を有効にすると、TypeScriptは「互換性のためのブリッジ」を自動的に生成します。具体的には、コンパイル時に以下のようなヘルパー関数を注入してくれます。
1. 名前空間のインポート: `import as React from ‘react’` と書かなくても、`import React from ‘react’` と書けるようにする。
2. デフォルト値の注入: もしインポート先のモジュールが `default` プロパティを持っていない場合、モジュール全体を `default` として割り当てる(`module.exports` を `default` に見立てる)。
これによって、私たちは「モジュールがどっちの形式で書かれているか」をいちいち気にせず、現代的な `import` 文を統一的に使えるようになるのです。
—
実践:現場で使えるベストプラクティス
現代のプロジェクトにおいて、`esModuleInterop` を `false` にする合理的な理由はほぼありません。プロジェクトを作ったら、迷わず以下のように設定してください。
tsconfig.json の設定例
{
“compilerOptions”: {
// これを有効にすることで、CJSとESMの垣根が消滅します
“esModuleInterop”: true,
// esModuleInteropとセットで推奨される設定
// これにより、CJSからデフォルトエクスポートをインポートする際の
// 型安全性が確保されます
“allowSyntheticDefaultImports”: true,
// その他、モダンなプロジェクトの標準設定
“module”: “NodeNext”, // もしくは “ESNext”
“moduleResolution”: “NodeNext”
}
}
コードでの挙動の違い
設定を有効にすると、インポートの書き方が劇的にスッキリします。
// ————————————————–
// esModuleInterop: true の世界
// ————————————————–
// 以前は import as React from ‘react’; と書く必要があったが、
// これだけでNode.jsのモジュール解決と整合性が取れるようになる。
import React from ‘react’;
import { useState } from ‘react’;
// よくあるCJSライブラリのインポートも、
// 違和感なくESMのスタイルで書ける。
import express from ‘express’;
const app = express();
もし `esModuleInterop: false` だったら、これらは全て型エラーになるか、あるいは `import as express from ‘express’` のような、少し趣の異なる書き方を強制されることになります。
—
シニアからのアドバイス:これだけは覚えておけ
現場でこの設定をいじるとき、一つだけ注意点があります。
`esModuleInterop` を `true` にすると、コンパイル後のJavaScriptコードには、互換性を保つためのヘルパー関数が含まれるようになります。 小規模なライブラリ開発などで、「配布物のサイズを1バイトでも削りたい」という超シビアな要件がある場合は検討の余地がありますが、業務アプリ開発においては「迷わず有効化」が正解です。
なぜなら、モジュール解決の齟齬で時間を溶かすのは、フロントエンドエンジニアにとって最も生産性の低い作業だからです。
技術というのは、本来「やりたいこと」を最短距離で実現するためにあるべきです。`esModuleInterop` は、言語仕様の古傷を埋めてくれる素晴らしいツールです。皆さんのプロジェクトの `tsconfig.json` を開いて、まだ `false` になっているなら、今すぐ `true` に書き換えてみてください。
その瞬間、IDEから真っ赤な波線が消え去るはずです。その時こそ、エンジニアとしてのコードに対する「信頼」が一つ積み上がる瞬間だと思ってください。

コメント