默认类实例化会导致 TypeError(ESM/CJS 互操作)。

huangapple go评论72阅读模式
英文:

Default class instantiation results in TypeError (ESM/CJS interop)

问题

问题摘要

嗨,

我有一个TypeScript项目,我试图实例化一个不同包的默认导出的类。我正在使用ESM语法编写我的项目,而它依赖的包具有CJS输出。我遇到的问题是,在运行时,当流达到类实例化点时,我得到以下错误 -

new TestClass({ arg1: "Hello, World!" });
^

TypeError: TestClass is not a constructor

代码

//我的 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"
  }
}
//我的 index.ts
import TestClass from "testpackage";

new TestClass({ arg1: "Hello, World!" });
//我的 tsconfig.json
{
	"include": ["src"],
	"compilerOptions": {
		"outDir": "dist",
		"lib": ["es2023"],
		"target": "es2022",
		"moduleResolution": "node"
	}
}

//依赖的 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"
	}
}
//依赖的 testFile.ts
export default class TestClass {
	constructor({ arg1 }: { arg1: string }) {
		console.log(arg1);
	}
}
//依赖的 tsconfig.json
{
	"include": ["src"],
	"compilerOptions": {
		"declaration": true,
		"lib": ["es2023"],
		"target": "es6",
		"module": "CommonJS",
		"outDir": "dist"
	}
}
//依赖的 testFile.js 输出
"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
英文:

Issue Summary

Hi,

I have a TypeScript project where I am trying to instantiate a class which was the default export of a different package. I am writing my project in ESM syntax, whereas the package it's dependent upon has CJS output. The issue I am running into is that at runtime, when the flow reaches the point of class instantiation I am getting the following error -

new TestClass({ arg1: &quot;Hello, World!&quot; });
^

TypeError: TestClass is not a constructor

Code

//My package.json
{
  &quot;name&quot;: &quot;myproject&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;main&quot;: &quot;dist/index.js&quot;,
  &quot;scripts&quot;: {
    &quot;build&quot;: &quot;tsc&quot;,
    &quot;start&quot;: &quot;node dist/index.js&quot;
  },
  &quot;type&quot;: &quot;module&quot;,
  &quot;dependencies&quot;: {
    &quot;testpackage&quot;: &quot;^1.0.0&quot;,
    &quot;typescript&quot;: &quot;^5.0.4&quot;
  },
  &quot;devDependencies&quot;: {
    &quot;@types/node&quot;: &quot;^20.2.5&quot;
  }
}

//My index.ts
import TestClass from &quot;testpackage&quot;;

new TestClass({ arg1: &quot;Hello, World!&quot; });
//My tsconfig.json
{
	&quot;include&quot;: [&quot;src&quot;],
	&quot;compilerOptions&quot;: {
		&quot;outDir&quot;: &quot;dist&quot;,
		&quot;lib&quot;: [&quot;es2023&quot;],
		&quot;target&quot;: &quot;es2022&quot;,
		&quot;moduleResolution&quot;: &quot;node&quot;
	}
}

//Dependency&#39;s package.json
{
	&quot;name&quot;: &quot;testpackage&quot;,
	&quot;version&quot;: &quot;1.0.0&quot;,
	&quot;description&quot;: &quot;TestPackage&quot;,
	&quot;main&quot;: &quot;./dist/testFile.js&quot;,
	&quot;exports&quot;: &quot;./dist/testFile.js&quot;,
	&quot;scripts&quot;: {
		&quot;build&quot;: &quot;tsc&quot;
	},
	&quot;files&quot;: [&quot;dist&quot;],
	&quot;devDependencies&quot;: {
		&quot;@types/node&quot;: &quot;^20.2.5&quot;,
		&quot;typescript&quot;: &quot;^5.0.4&quot;
	}
}
//Dependency&#39;s testFile.ts
export default class TestClass {
	constructor({ arg1 }: { arg1: string }) {
		console.log(arg1);
	}
}
//Dependency&#39;s tsconfig.json
{
	&quot;include&quot;: [&quot;src&quot;],
	&quot;compilerOptions&quot;: {
		&quot;declaration&quot;: true,
		&quot;lib&quot;: [&quot;es2023&quot;],
		&quot;target&quot;: &quot;es6&quot;,
		&quot;module&quot;: &quot;CommonJS&quot;,
		&quot;outDir&quot;: &quot;dist&quot;
	}
}
//Dependency&#39;s testFile.js output
&quot;use strict&quot;;
Object.defineProperty(exports, &quot;__esModule&quot;, { value: true });
class TestClass {
    constructor({ arg1 }) {
        console.log(arg1);
    }
}
exports.default = TestClass;

Things work fine if I remove &quot;type&quot;: &quot;module&quot; from my package.json. They also work fine if the class is a named export instead of a default export in the dependency's code. Is this a known incompatibility when trying to import CJS into ESM or am I doing something incorrectly here?


Note - If I set &quot;moduleResolution&quot;: &quot;nodenext&quot; in my tsconfig.json then the error is generated at compile time itself -

src/index.ts:3:5 - error TS2351: This expression is not constructable.
  Type &#39;typeof import(&quot;&lt;project_dir&gt;/node_modules/testpackage/dist/testFile&quot;)&#39; has no construct signatures.

3 new TestClass({ arg1: &quot;Hello, World!&quot; });
      ~~~~~~~~~


Found 1 error in src/index.ts:3

答案1

得分: 2

在CommonJS(CJS)和ECMAScript模块(ESM)之间存在已知的兼容性问题。在ESM中,CJS模块的默认导出被包装在默认属性中,而不是直接暴露出来。另一方面,命名导出不受影响,可以直接导入。

如果您指定"type":
在package.json中指定"type": "module"会使Node.js将.js文件视为ESM。因此,您必须使用ESM导入语句导入模块。然而,如果您尝试导入的模块以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');
英文:

There are known compatibility issues between CommonJS (CJS) and ECMAScript modules (ESM). In ESM, default exports of CJS modules are wrapped in default properties instead of being exposed directly. On the other hand, named exports are unaffected and can be imported directly.

If you specify "type":
Specifying "module" in package.json makes Node.js treat the .js file as her ESM. Therefore, you must import the module using the ESM import statement. However, if the module you are trying to import is in CJS format, you will run into compatibility issues.

There are several options to fix this.

  1. Access the class through the default property as described above.
import Test from &#39;package-name&#39;;
const TestClass = Test.default;

  1. To avoid problems caused by mixing the two module formats, convert all code to use either ESM or CJS.

  2. Load the CJS module using the Node.js createRequire function.

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

huangapple
  • 本文由 发表于 2023年6月1日 20:24:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/76381829.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定