javascript - デフォルトクラスのインスタンス化が TypeError を引き起こす (ESM/CJS の相互運用)
問題の概要
こんにちは、
私はTypeScriptのプロジェクトを持っており、別のパッケージのデフォルトエクスポートであるクラスをインスタンス化しようとしています。私のプロジェクトはESM構文で書かれていますが、依存しているパッケージはCJS出力です。実行時に、クラスのインスタンス化のポイントに到達したときに次のエラーが発生するという問題に直面しています -
new TestClass({ arg1: "Hello, World!" });
^
TypeError: TestClass is not a constructor
コード
//My package.json
{
"name": "myproject",
"version": "1.0.0",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"start": "node dist/index.js"
},
"type": "module",
"dependencies": {
"testpackage": "^1.0.0",
"typescript": "^5.0.4"
},
"devDependencies": {
"@types/node": "^20.2.5"
}
}
//My index.ts
import TestClass from "testpackage";
new TestClass({ arg1: "Hello, World!" });
//My tsconfig.json
{
"include": ["src"],
"compilerOptions": {
"outDir": "dist",
"lib": ["es2023"],
"target": "es2022",
"moduleResolution": "node"
}
}
//Dependency's package.json
{
"name": "testpackage",
"version": "1.0.0",
"description": "TestPackage",
"main": "./dist/testFile.js",
"exports": "./dist/testFile.js",
"scripts": {
"build": "tsc"
},
"files": ["dist"],
"devDependencies": {
"@types/node": "^20.2.5",
"typescript": "^5.0.4"
}
}
//Dependency's testFile.ts
export default class TestClass {
constructor({ arg1 }: { arg1: string }) {
console.log(arg1);
}
}
//Dependency's tsconfig.json
{
"include": ["src"],
"compilerOptions": {
"declaration": true,
"lib": ["es2023"],
"target": "es6",
"module": "CommonJS",
"outDir": "dist"
}
}
//Dependency's testFile.js output
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
class TestClass {
constructor({ arg1 }) {
console.log(arg1);
}
}
exports.default = TestClass;
package.jsonから"type": "module"を削除すれば、うまく動作します。また、依存関係のコードでクラスがデフォルトエクスポートではなく名前付きエクスポートの場合も、問題なく動作します。CJSをESMにインポートしようとするときの既知の非互換性でしょうか、それとも私が何か間違ったことをしているのでしょうか?
注意 - もし私が tsconfig.json で "moduleResolution": "nodenext" を設定すると、コンパイル時にそのエラーが発生します -
src/index.ts:3:5 - error TS2351: This expression is not constructable.
Type 'typeof import("<project_dir>/node_modules/testpackage/dist/testFile")' has no construct signatures.
3 new TestClass({ arg1: "Hello, World!" });
~~~~~~~~~
Found 1 error in src/index.ts:3
回答 #1
CommonJS (CJS) と ECMAScript モジュール (ESM) の間には既知の互換性の問題があります。ESM では、CJS モジュールのデフォルトエクスポートは直接公開されるのではなく、default プロパティでラップされます。一方で、名前付きエクスポートには影響がなく、直接インポートすることができます。
「type」を指定した場合: package.jsonで「module」を指定すると、Node.jsは.jsファイルをESMとして扱います。したがって、そのモジュールはESMのimport文を使用してインポートする必要があります。しかし、インポートしようとしているモジュールがCJS形式の場合、互換性の問題が発生します。
これを修正する方法はいくつかあります。
- 上記の通り、デフォルトプロパティを通じてクラスにアクセスします。
import Test from 'package-name';
const TestClass = Test.default;
2つのモジュール形式を混在させることによる問題を避けるために、すべてのコードをESMかCJSのいずれかを使用するように変換してください。
Node.js の createRequire 関数を使って CJS モジュールを読み込みます。
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const TestClass = require('package-name');
