Créer un package npm compatible avec ESM et CJS en 2024
18 avril 2024
0 minutes de lecture
Publier des packages JavaScript compatibles à la fois avec ECMAScript Modules (ESM) et CommonJS (CJS) est une compétence essentielle pour les développeurs qui souhaitent intégrer un large éventail de bibliothèques.
Cet article présente des approches pratiques et des bonnes pratiques pour maintenir la prise en charge d’ESM et de CJS. Nous examinerons les conséquences de l’omission de la déclaration ”type: module” dans les bibliothèques compatibles avec les deux formats et étudierons l’utilisation des champs main et module dans package.json pour différencier les points d’entrée.
Cet article clarifie également le rôle et l’utilisation du champ exports dans le fichier package.json, essentiel pour contrôler la résolution des modules. Par ailleurs, pour les projets TypeScript, nous explorerons l’intégration des exports du manifeste de package avec un type de module afin de garantir à la fois la compatibilité et la sécurité des types.
Nous vous invitons à suivre les exemples de code pas à pas ci-dessous. Vous trouverez également un dépôt de code GitHub nommé package-json-exports, qui contient des exemples complets à reproduire et s’appuie sur le projet proxy npm open source Verdaccio.
Évitez de définir « Type: Module » pour les bibliothèques compatibles avec ESM et CJS
Saviez-vous que si vous omettez le champ ”type” dans le fichier package.json, sa valeur est implicitement définie par défaut sur commonjs ? Par exemple : ”type”: “commonjs”.
Dès que vous ajoutez ”type”: “module” au fichier package.json, vous indiquez explicitement que la bibliothèque cible uniquement les projets ESM. Vous devez toujours définir un champ main dans le manifeste du package, qui sera alors mis à jour pour pointer vers le module exporté compatible avec ESM.
Pour illustrer cela concrètement, la définition suivante du fichier package.json ne fonctionne pas pour les consommateurs ESM en amont, même s’il s’agit d’ESM « pur » :
La définition module correspond à un module ESM et le champ type indique clairement que cette bibliothèque cible les consommateurs ESM, mais le champ main manque dans le fichier package.json. Node.js renverra une exception indiquant qu’il ne parvient pas à localiser le package, par exemple :
En conclusion, évitez d’utiliser une déclaration ”type”: “module” dans le manifeste d’un package npm.
Voyons comment utiliser les champs main et module dans le fichier package.json, et pourquoi ils constituent de meilleures directives.
Compatibilité ESM et CJS avec les champs main et module
Avant l’arrivée d’ESM, le champ main du fichier package.json servait à indiquer au runtime Node.js le point d’entrée du package. En général, les développeurs avaient un fichier index.js ou app.js à la racine du répertoire et faisaient pointer le champ main vers celui-ci, par exemple ”main”: “index.js”.
Si vous omettez le champ main du fichier package.json, le runtime Node.js tentera de trouver le point d’entrée du package en recherchant un fichier server.js à sa racine.
Pour rendre un package npm compatible à la fois avec ESM et CJS, nous pouvons suivre une convention selon laquelle le champ main pointe vers un export CJS et le champ module vers un export ESM.
Bibliothèque :
Les projets consommateurs en aval peuvent être en CJS ou en ESM. Les projets CJS utiliseront le fichier src/index.cjs et les projets ESM le fichier src/index.mjs. Aucun des deux types de projets consommateurs n’a besoin de configurer quoi que ce soit de particulier pour la dépendance math-add : elle fonctionnera tout simplement.
Comprendre le champ exports de package.json
Le champ exports du fichier package.json permet de contrôler encore plus précisément les éléments exportés par votre package npm et la manière dont ils sont utilisés.
Par exemple, vous pouvez indiquer le chemin complet vers le fichier d’entrée si un runtime Node.js tente de charger votre package npm avec require(‘math-add’), et utiliser un tout autre fichier comme point d’entrée si le runtime Node.js tente de charger le package avec import .. from ‘math-add’).
Voici un exemple de code pour un package à double mode CJS et ESM, comme décrit ci-dessus :
Exports de package.json et type de module pour un projet TypeScript
Si vous écrivez le code ESM de votre package en TypeScript et souhaitez préserver la compatibilité avec CJS, vous devrez également déclarer types et configurer la compilation TypeScript ainsi que la transpilation de la partie CJS.
Pour compiler et regrouper le code TypeScript, je vous recommande tsup. Vous devrez ensuite ajouter une étape de script build et penser à lancer cette compilation avant une tâche CI ou avant de publier manuellement le package npm.
Voici un exemple complet :
Vous remarquerez que nous utilisons également la prise en charge native de Node.js pour surveiller les modifications, grâce à l’option de ligne de commande --watch src. Auparavant, on aurait utilisé nodemon, qui est un excellent package, mais moins de dépendances, c’est toujours mieux.
Pour aller plus loin : publication et structure modernes des packages npm en 2024
Cet article court et ciblé s’adresse aux développeurs JavaScript et leur propose des conseils concrets et directement applicables pour gérer efficacement les formats de module dans leurs projets.
Pensez également à suivre les bonnes pratiques de publication des packages npm et à créer des packages npm modernes, en approfondissant la configuration de TypeScript, les tests, la CI, la sécurité et d’autres aspects.
Sécurisez vos dépendances open source
Snyk crée des pull requests de correction en un clic pour les dépendances open source vulnérables et leurs dépendances transitives.


