javascript - L'instanciation de classe par défaut entraîne une TypeError (interop ESM/CJS)
Résumé du problème
Salut,
J'ai un projet TypeScript où j'essaie d'instancier une classe qui était l'exportation par défaut d'un autre paquet. J'écris mon projet en syntaxe ESM, tandis que le paquet dont il dépend a une sortie CJS. Le problème que je rencontre est qu'à l'exécution, lorsque le flux atteint le point d'instanciation de la classe, j'obtiens l'erreur suivante -
new TestClass({ arg1: "Hello, World!" });
^
TypeError: TestClass is not a constructor
Code
//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;
Les choses fonctionnent bien si je supprime "type": "module" de mon package.json. Elles fonctionnent également bien si la classe est une exportation nommée plutôt qu'une exportation par défaut dans le code de la dépendance. S'agit-il d'une incompatibilité connue lors de l'importation de CJS dans ESM ou est-ce que je fais quelque chose de mal ici ?
Remarque - Si je définis "moduleResolution": "nodenext" dans mon tsconfig.json, alors l'erreur est générée dès la compilation -
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
Réponse #1
Il existe des problèmes de compatibilité connus entre CommonJS (CJS) et les modules ECMAScript (ESM). Dans ESM, les exportations par défaut des modules CJS sont encapsulées dans des propriétés par défaut au lieu d'être exposées directement. En revanche, les exportations nommées ne sont pas affectées et peuvent être importées directement.
Si vous spécifiez "type" : Spécifier "module" dans package.json fait en sorte que Node.js considère le fichier .js comme son ESM. Par conséquent, vous devez importer le module en utilisant l'instruction d'import ESM. Cependant, si le module que vous essayez d'importer est au format CJS, vous rencontrerez des problèmes de compatibilité.
Il y a plusieurs options pour régler cela.
- Accédez à la classe via la propriété par défaut comme décrit ci-dessus.
import Test from 'package-name';
const TestClass = Test.default;
Pour éviter les problèmes causés par le mélange des deux formats de module, convertissez tout le code pour qu'il utilise soit ESM, soit CJS.
Chargez le module CJS en utilisant la fonction createRequire de Node.js.
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const TestClass = require('package-name');
