【TypeScript】esModuleInteropを有効にすると、'* as module'でインポートしたExpressやmoment.jsでエラーが発生する

## 概要

`tsconfig.json`の`esModuleInterop`有効時に、このようなコードのExpressやmoment.jsで以下のようなエラーが発生する場合、

```ts
import * as express from 'express'
express()
```

```
This expression is not callable.
  Type 'typeof e' has no call signatures.(2349)
input.tsx(1, 1): Type originates at this import. 
A namespace-style import cannot be called or constructed, 
and will cause a failure at runtime. 
Consider using a default import or import require here instead.
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110860/a0678080-2a7a-bb6f-c54a-dff97f52e5ff.png)

import方法を以下のように変更する必要があります。

```ts
// Namespace importから
import * as express from 'express'
express()
// ↓
// Default importに変更
import express from 'express'
express()
```

もしくは、defaultプロパティを呼び出します。
```ts
import * as express from 'express'
express.default()
```

サンプルコード: [Playground](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAKjgQwM5wKYA8xQ69AMyghDgHJtd9VyAoUSWRFdEUjAO3mNIvZBcY9OlTwEAFAEo6AodKA)

## 詳細

### `esModuleInterop`について
`esModuleInterop`はES6モジュールとCommonJSモジュールの互換性を確保するためのオプションです。
こちらを有効にすることで、CommonJSモジュールを、ES6モジュールの仕様に準拠した上でインポートできるようになります。

実際には、トランスパイル時に以下のようなヘルパー関数が挿入されます。

```元々の.ts
import * as express from 'express';
express()
```

```esModuleInteropがfalse.js
"use strict";
exports.__esModule = true;
var express = require("express");
express();
```

```esModuleInteropがtrue.js
"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;
};
exports.__esModule = true;
var express = __importStar(require("express"));
express();
```

### なぜ`Namespace import`されたExpressやmoment.jsの挙動が変わるのか

上記の`__importStar`の通り、`esModuleInterop`を有効にすると`Namespace import`の返り値がオブジェクトとなります。
(`__importStar`は[「Namespace importは、モジュールをオブジェクトとしてインポートする」というESモジュールの仕様](https://exploringjs.com/es6/ch_modules.html#_importing-styles)に準拠するためのヘルパー関数となります)

この関数により、`Namespace import`した`express`を直接実行するとエラーになってしまいます。
```元々の.ts
import * as express from 'express';
// TypeError: express is not a function
express()
```

これを修正するためには、細書に記載した通り、Import方法を`Namespace import`から`Default import`に変更するか、defaultプロパティを使用する必要があります。

#### Import方法を`Namespace import`から`Default import`に変更する

```ts
import express from 'express';
express();
```
をトランスパイルすると以下のようなヘルパー関数が挿入されます。
```js
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
exports.__esModule = true;
var express_1 = __importDefault(require("express"));
express_1["default"]();
```

インポートするモジュールがCommonJSモジュールの場合、元々`require("express")`で読み込まれた関数をdefaultプロパティに詰めて、それを実行するようになります。
そのため、エラー無く実行できます。

#### defaultプロパティを使用する

上の`esModuleInteropがtrue.js`の`__setModuleDefault`関数の通り、元々`require("express")`で読み込まれた関数は、返却されるオブジェクトのdefaultプロパティに収まっています。
そのため、`express.default()`とすれば変更前と同じ関数を実行できます。
```ts
import * as express from 'express'
express.default()
```

## まとめ

`Express`や`moment.js`といった以下の条件を満たすモジュールを使用している場合、`esModuleInterop`を有効にするとエラーが発生するので、注意しましょう。
1. CommonJSモジュール
1. `module.exports = 関数orクラス(≒呼び出し可能なオブジェクト)`


## 参考

- [TypeScript: TSConfig Reference - Docs on every TSConfig option](https://www.typescriptlang.org/tsconfig#esModuleInterop)
- [import - JavaScript | MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import)
- [16. Modules](https://exploringjs.com/es6/ch_modules.html#_importing-styles)
- [Typescript + Express : Type 'typeof e' has no compatible call signatures](https://stackoverflow.com/questions/53142239/typescript-express-type-typeof-e-has-no-compatible-call-signatures)
- [よく分からなかったのでCommonJSとTypeScriptのES Modulesinteropについて調べてみた](https://omochizo.netlify.app/posts/2020/08/commonjs-esm/)
- [esModuleInterop オプションの必要性について](https://qiita.com/karak/items/29ff148788f5abb15331)