LoginSignup
6
1

More than 1 year has passed since last update.

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

Posted at

概要

tsconfig.jsonesModuleInterop有効時に、このようなコードの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.

image.png

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モジュールの仕様に準拠した上でインポートできるようになります。

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

元々の.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モジュールの仕様に準拠するためのヘルパー関数となります)

この関数により、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に変更する

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()

まとめ

Expressmoment.jsといった以下の条件を満たすモジュールを使用している場合、esModuleInteropを有効にするとエラーが発生するので、注意しましょう。

  1. CommonJSモジュール
  2. module.exports = 関数orクラス(≒呼び出し可能なオブジェクト)

参考

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1