Introduction : Pourquoi esModuleInterop n'est pas qu'une simple case à cocher dans tsconfig
\n\nSi vous avez déjà écrit du TypeScript et importé une bibliothèque comme express ou lodash, vous avez sûrement rencontré le moment où le compilateur râle sur import * as express from 'express'. Ou, à l'inverse, vous utilisez tranquillement import express from 'express', mais dans le code JavaScript généré, vous voyez une étrange construction module.exports.default. Quelle est cette magie ? Et bien, la magie (ou son absence) est le flag esModuleInterop dans votre tsconfig.json.
Ce paramètre est l'une des questions les plus populaires sur Stack Overflow (des milliers de vues !), et ce n'est pas surprenant : sans le comprendre, il est facile de tomber dans le piège de l'incompatibilité des modules. Dans ce tutoriel, nous n'expliquerons pas seulement la théorie, mais nous suivrons le chemin du problème à la solution avec des exemples concrets. C'est parti !
\n\nLe problème : Conflit entre deux mondes de modules
\n\nTypeScript est une surcouche de JavaScript, et JavaScript a historiquement deux systèmes de modules principaux :
\n- \n
- CommonJS (CJS) — le standard de Node.js, utilise
require()etmodule.exports. Exemple :const express = require('express');\n - ES Modules (ESM) — le standard moderne, utilise
importetexport. Exemple :import express from 'express';\n
Le problème est que ces systèmes gèrent différemment l'export par défaut (default export). En ESM, un module peut avoir un seul export default, et il est importé comme import name from 'module'. En CommonJS, il n'y a pas de notion d'"export par défaut" — il y a seulement l'objet module.exports, qui peut être n'importe quoi.
Lorsque TypeScript compile votre code en CommonJS (ce qui est souvent nécessaire pour Node.js), il doit décider : comment représenter import x from 'y' sous forme de require() ? Sans esModuleInterop, TypeScript le fait littéralement : il crée module.exports.default, ce qui casse l'import des bibliothèques écrites pour CommonJS.
Qu'est-ce que esModuleInterop ?
\n\nesModuleInterop est un flag dans tsconfig.json qui active une compatibilité supplémentaire entre CommonJS et ES Modules. Lorsqu'il est défini sur true, TypeScript génère un code auxiliaire qui permet d'écrire import foo from 'bar' même pour les modules CommonJS qui n'ont pas d'export default.
Par défaut dans TypeScript 4.x et versions ultérieures, ce flag est true (si vous utilisez --init), mais dans les anciens projets, il était souvent désactivé. Pourquoi ? Parce qu'il modifie le comportement de l'import, et sans compréhension, cela entraîne des bugs.
À quoi cela ressemble dans tsconfig.json
\n{\n "compilerOptions": {\n "esModuleInterop": true,\n // autres paramètres...\n }\n}\n\n\nL'activation de ce flag active également automatiquement allowSyntheticDefaultImports, qui autorise l'import par défaut depuis des modules sans véritable export default au niveau des types.
Tutoriel étape par étape : du problème à la solution
\n\nAnalysons la situation avec un exemple concret. Supposons que nous ayons une simple bibliothèque en CommonJS :
\n\n// myLib.js (CommonJS)\nmodule.exports = function greet(name) {\n return `Bonjour, ${name}!`;\n};\n\n\nNous voulons l'utiliser dans un projet TypeScript. Créons un fichier index.ts :
// index.ts\nimport greet from './myLib';\nconsole.log(greet('Monde'));\n\n\nVoyons maintenant ce qui se passe lors de la compilation avec différents réglages de esModuleInterop.
Étape 1 : Sans esModuleInterop (false)
\n\nLançons la compilation avec "esModuleInterop": false. TypeScript générera une erreur :
Le module '"myLib"' n'a pas d'export par défaut.