网站搜索

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 文件视为 ES 模块。因此,你必须使用 ES 模块的 import 语句来导入模块。然而,如果你试图导入的模块是 CJS 格式的,你将遇到兼容性问题。

有几种方法可以解决这个问题。

  1. 通过上述描述的默认属性访问类。
import Test from 'package-name';
const TestClass = Test.default;

  1. 为了避免由混合使用这两种模块格式引起的问题,请将所有代码转换为使用 ESM 或 CJS。

  2. 使用 Node.js 的 createRequire 函数加载 CJS 模块。

import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const TestClass = require('package-name');