概要
tsconfig.json
のesModuleInterop
有効時に、このようなコードのExpressやmoment.jsで以下のようなエラーが発生する場合、
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.
import方法を以下のように変更する必要があります。
// Namespace importから
import * as express from 'express'
express()
// ↓
// Default importに変更
import express from 'express'
express()
もしくは、defaultプロパティを呼び出します。
import * as express from 'express'
express.default()
サンプルコード: Playground
詳細
esModuleInterop
について
esModuleInterop
はES6モジュールとCommonJSモジュールの互換性を確保するためのオプションです。
こちらを有効にすることで、CommonJSモジュールを、ES6モジュールの仕様に準拠した上でインポートできるようになります。
実際には、トランスパイル時に以下のようなヘルパー関数が挿入されます。
import * as express from 'express';
express()
"use strict";
exports.__esModule = true;
var express = require("express");
express();
"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モジュールの仕様に準拠するためのヘルパー関数となります)
この関数により、Namespace import
したexpress
を直接実行するとエラーになってしまいます。
import * as express from 'express';
// TypeError: express is not a function
express()
これを修正するためには、細書に記載した通り、Import方法をNamespace import
からDefault import
に変更するか、defaultプロパティを使用する必要があります。
Import方法をNamespace import
からDefault import
に変更する
import express from 'express';
express();
をトランスパイルすると以下のようなヘルパー関数が挿入されます。
"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()
とすれば変更前と同じ関数を実行できます。
import * as express from 'express'
express.default()
まとめ
Express
やmoment.js
といった以下の条件を満たすモジュールを使用している場合、esModuleInterop
を有効にするとエラーが発生するので、注意しましょう。
- CommonJSモジュール
module.exports = 関数orクラス(≒呼び出し可能なオブジェクト)