esModuleInterop
フラグを有効にすると、コンパイル時にヘルパーメソッドが生成されるようになり、モジュールシステムの相互運用性が高まる。
これにより、default
をエクスポートしていない CommonJS 形式のモジュールを、ES Modules でデフォルトインポートする、といったことが可能になる。
この記事の内容は、TypeScript のv3.9.6
と Node.js のv12.17.0
で動作確認している。
ES Modules と CommonJS の互換性
まず、検証用に以下のファイルを作成する。
// src/myModule.js const add = (a, b) => a + b; module.exports = { value: 123, add, };
// src/index.ts import {value, add} from './myModule'; console.log(value); console.log(add(3, 4));
src/myModule.js
は CommonJS 形式でエクスポートしており、それをsrc/index.ts
でインポートして使っている。
そして、tsconfig.json
を以下の内容にした上で、コンパイルする。
{ "compilerOptions": { "target": "ES2015", "module": "CommonJS", "outDir": "./dist", "strict": true, "noImplicitAny": false, "moduleResolution": "Node", "allowJs": true }, "include": ["./src/**/*"] }
そうするとdist/index.js
が生成されるので、実行してみる。
$ node dist/index.js 123 7
無事に実行される。
インポートに*
を使っても、問題ない。
import * as myModule from './myModule'; console.log(myModule.value); console.log(myModule.add(3, 4));
問題は、デフォルトインポートしたときである。
// can only be default-imported using the 'esModuleInterop' flagts(1259) import myModule from './myModule'; console.log(myModule.value); console.log(myModule.add(3, 4));
コンパイルエラーになってしまう。
以下のようにdefault
をエクスポートしていればそれを読み込めるが、CommonJS ではこの書き方はあまり一般的ではない。
module.exports = { default: 1, };
例えば、@types/node
パッケージをインストールすることで、 Node.js の標準モジュールを使ったコードを書けるようになるが、以下のコードはエラーになる。
// Module '"fs"' has no default export.ts(1192) import fs from 'fs'; console.log(fs.readFile);
サードパーティライブラリでも同様の問題が起こり得る。
例えば React で発生する。
// can only be default-imported using the 'esModuleInterop' flagts(1259) import React from 'react'; console.log(React.memo);
allowSyntheticDefaultImports
allowSyntheticDefaultImports
フラグを有効にすることで、コンパイルエラーを回避できるようになる。
{ "compilerOptions": { "target": "ES2015", "module": "CommonJS", "outDir": "./dist", "strict": true, "noImplicitAny": false, "moduleResolution": "Node", "allowJs": true, "allowSyntheticDefaultImports": true // これを追加 }, "include": ["./src/**/*"] }
これで、以下のコードもコンパイルできるようになった。
import fs from 'fs'; console.log(fs.readFile);
だが残念ながら、コンパイルしたコードを実行するとエラーになる。
TypeError: Cannot read property 'readFile' of undefined
allowSyntheticDefaultImports
は型チェックにのみ影響を与えるフラグで、生成される JavaScript には影響しない。
そのため、相変わらずimport fs from 'fs';
でfs
モジュールを読み込むことはできず、fs
はundefined
になってしまう。
esModuleInterop
実際にデフォルトインポートするためには、esModuleInterop
フラグを有効にする。
そうすることで CommonJS と相互運用可能なコードが出力されるようになり、デフォルトインポートも使えるようになる。
また、このフラグを有効にすると自動的にallowSyntheticDefaultImports
も有効になる。
{ "compilerOptions": { "target": "ES2015", "module": "CommonJS", "outDir": "./dist", "strict": true, "noImplicitAny": false, "moduleResolution": "Node", "allowJs": true, "esModuleInterop": true // これを追加 }, "include": ["./src/**/*"] }
これで、デフォルトインポートできるようになった。
import fs from 'fs'; console.log(fs.readFile); // [Function: readFile]
生成されたdist/index.js
を見てみると、__importDefault
というヘルパーメソッドが生成されており、これによってデフォルトインポートが可能になっている。
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const fs_1 = __importDefault(require("fs")); console.log(fs_1.default.readFile);
また、*
を使ったインポート文を書くと、__importStar
が生成される。
import * as fs from 'fs'; console.log(fs.readFile);
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); const fs = __importStar(require("fs")); console.log(fs.readFile);