ESM项目导入CJS默认导出类实例化触发TypeError
ESM项目导入CJS默认导出类时报"TypeError: TestClass is not a constructor"的问题解析与解决
这是ESM与CJS模块互操作的已知兼容性问题,并非你的操作错误。
问题本质
当ESM(你的项目)导入CJS模块(依赖包)时,Node.js会将整个CJS模块封装为一个命名空间对象——即使CJS模块设置了__esModule: true,ESM默认导入的结果依然是这个模块对象,而非exports.default指向的类。此时你尝试new TestClass,本质是在实例化一个模块对象,自然会报"不是构造函数"的错误。
而当你移除项目的"type": "module"时,项目切换为CJS模式,Node.js的require会自动处理带有__esModule标记的模块,直接返回exports.default的内容,因此能正常运行;命名导出则不受互操作逻辑影响,所以也能正常工作。
解决方案
方案1:手动修正导入逻辑
直接在代码中显式获取CJS模块的default导出:
// 方式1:命名空间导入后取default import * as TestModule from "testpackage"; const TestClass = TestModule.default; new TestClass({ arg1: "Hello, World!" }); // 方式2:重命名导入 import { default as TestClass } from "testpackage"; new TestClass({ arg1: "Hello, World!" });
方案2:调整TypeScript配置,启用互支持
修改项目的tsconfig.json,启用合成默认导入与ES模块互操作选项,同时匹配ESM的模块解析规则:
{ "include": ["src"], "compilerOptions": { "outDir": "dist", "lib": ["es2023"], "target": "es2022", "module": "ESNext", // 匹配ESM项目类型 "moduleResolution": "nodenext", "allowSyntheticDefaultImports": true, // 允许对CJS模块使用默认导入语法 "esModuleInterop": true // 启用ES与CJS互操作适配 } }
此配置会让TypeScript在编译时自动处理CJS模块的默认导出,既解决编译报错,也能保证运行时的正确性。
方案3:修改依赖包(若有权限)
如果可以修改依赖包的代码或配置,有两种优化方向:
- 改用命名导出:将依赖包的默认导出改为命名导出,彻底规避互操作问题
项目中导入方式改为:// 依赖包testFile.ts export class TestClass { constructor({ arg1 }: { arg1: string }) { console.log(arg1); } }import { TestClass } from "testpackage"; - 双格式输出:让依赖包同时生成ESM和CJS版本,在
package.json中配置exports字段做条件导出:
同时调整依赖包的// 依赖包package.json "exports": { ".": { "import": "./dist/esm/testFile.js", "require": "./dist/cjs/testFile.js" } }tsconfig,通过多配置文件生成两种格式的输出。
内容的提问来源于stack exchange,提问作者DashwoodIce9
相关产品推荐
相关产品推荐

