Erstellung und Veröffentlichung eines dualen Paket-NPM-Moduls
Min-jun Kim
Dev Intern · Leapcell

Einleitung
Im sich ständig weiterentwickelnden JavaScript-Ökosystem ist das Teilen wiederverwendbarer Codes fundamental für eine effiziente Entwicklung. NPM-Pakete bilden dafür den Eckpfeiler, der es Entwicklern ermöglicht, ihre Lösungen zu modularisieren und zur breiteren Community beizutragen. Mit dem Aufkommen von ECMAScript-Modulen (ESM) neben dem langjährigen CommonJS (CJS)-Standard ist die Erstellung eines Pakets, das nahtlos in beide Umgebungen integriert werden kann, jedoch zu einer entscheidenden, aber manchmal auch herausfordernden Aufgabe geworden. Das Ignorieren eines der Standards kann die Akzeptanz Ihres Pakets einschränken und Benutzer zu komplizierten Workarounds zwingen. Dieser Artikel zielt darauf ab, den Prozess zu entmystifizieren und eine umfassende Anleitung zu geben, wie Sie Ihr eigenes NPM-Paket schreiben, testen und veröffentlichen können, um sicherzustellen, dass es sowohl ESM als auch CJS unterstützt.
Grundlegende Konzepte verstehen
Bevor wir in die Implementierung eintauchen, wollen wir ein gemeinsames Verständnis der beteiligten Schlüsselkonzepte schaffen:
- CommonJS (CJS): Dieses von Node.js popularisierte Modulsystem verwendet
require()
zum Importieren von Modulen undmodule.exports
zum Exportieren. Es ist synchron und war viele Jahre lang der Standard für serverseitiges JavaScript.// CJS Import const myModule = require('./myModule.js'); // CJS Export module.exports = { myFunction: () => console.log('Hallo von CJS!') };
- ECMAScript-Module (ESM): Der offizielle Modulstandard für JavaScript, eingeführt in ES2015. Es verwendet
import
- undexport
-Anweisungen, die inhärent asynchron sind und für Browser- und Node.js-Umgebungen konzipiert wurden.// ESM Import import { myFunction } from './myModule.js'; // ESM Export export const myFunction = () => console.log('Hallo von ESM!');
- Dual Package Hazard: Dies bezieht sich auf potenzielle Probleme, die auftreten, wenn ein Paket versucht, sowohl CJS- als auch ESM-Versionen bereitzustellen. Probleme können entstehen, wenn verschiedene Umgebungen auf unterschiedliche Modultypen aufgelöst werden oder wenn Zustände dupliziert werden.
- Conditioned Exports: Eine leistungsstarke Funktion in
package.json
, die es Ihnen ermöglicht, verschiedene Einstiegspunkte basierend auf der Umgebung zu definieren (z. B.import
für ESM,require
für CJS,browser
für Web). Dies ist der Schlüssel zur Lösung des Dual Package Hazards.// package.json Snippet für Conditioned Exports "exports": { ".": { "import": "./dist/esm/index.js", "require": "./dist/cjs/index.js" } }
type
-Feld inpackage.json
: Dieses Feld ("type": "module"
oder"type": "commonjs"
) definiert das Standard-Modulsystem für alle.js
-Dateien innerhalb eines Pakets oder Umfangs. Es spielt eine entscheidende Rolle dabei, wie Node.js Dateien interpretiert.- Transpilierung: Der Prozess der Konvertierung von Quellcode, der in einer Sprache oder Version geschrieben wurde, in eine andere. Für JavaScript bedeutet dies oft die Konvertierung neuerer Syntax (wie ESM
import/export
) in ältere Syntax (wie CJSrequire/module.exports
) für breitere Kompatibilität, typischerweise unter Verwendung von Tools wie Babel oder TypeScript.
Erstellung eines Dual-Paket-Moduls
Lassen Sie uns die Schritte zur Erstellung eines einfachen Utility-Pakets, das den Benutzer begrüßt, durchgehen und sicherstellen, dass es nahtlos mit CJS und ESM funktioniert.
1. Projekt-Setup und -Initialisierung
Erstellen Sie zunächst ein neues Verzeichnis für Ihr Paket und initialisieren Sie ein NPM-Projekt.
mkdir my-greeting-package cd my-greeting-package npm init -y
2. Quellcode
Unser Paket wird eine einzige Funktion haben, die eine Begrüßung zurückgibt. Wir schreiben dies mit moderner ESM-Syntax.
src/index.js
:
// src/index.js export function greet(name = 'World') { return `Hello, ${name}!`; }
3. Build-Konfiguration und Transpilierung
Um sowohl CJS als auch ESM zu unterstützen, müssen wir unseren Quellcode transpilieren. Wir verwenden hierfür Rollup.js, da es hochgradig konfigurierbar und hervorragend für die Bündelung von Bibliotheken geeignet ist.
npm install --save-dev rollup @rollup/plugin-terser @rollup/plugin-node-resolve
Erstellen Sie eine Datei rollup.config.js
:
// rollup.config.js import { terser } from '@rollup/plugin-terser'; import { nodeResolve } from '@rollup/plugin-node-resolve'; export default [ // CJS Build { input: 'src/index.js', output: { file: 'dist/cjs/index.js', format: 'cjs', sourcemap: true, exports: 'named', // Stellt sicher, dass benannte Exporte für CJS korrekt gehandhabt werden }, plugins: [nodeResolve(), terser()], }, // ESM Build { input: 'src/index.js', output: { file: 'dist/esm/index.js', format: 'esm', sourcemap: true, }, plugins: [nodeResolve(), terser()], }, ];
Fügen Sie ein Build-Skript zu Ihrer package.json
hinzu:
// package.json Snippet "scripts": { "build": "rollup -c", "test": "node test/test.js" // Dies fügen wir später hinzu }, "main": "dist/cjs/index.js", // Fallback für ältere Node.js oder Tools "module": "dist/esm/index.js", // Hinweis für Bundler wie Webpack/Rollup "type": "commonjs", // Standardtyp für das Paket
Führen Sie den Build aus:
npm run build
Dies erstellt dist/cjs/index.js
und dist/esm/index.js
.
4. Konfigurierung von package.json
für Dual-Paket-Unterstützung
Dies ist der wichtigste Schritt. Wir ändern package.json
, um Conditioned Exports zu verwenden und sicherzustellen, dass Umgebungen den richtigen Modultyp auswählen.
// package.json { "name": "my-greeting-package", "version": "1.0.0", "description": "Ein einfaches Paket, das den Benutzer begrüßt.", "main": "dist/cjs/index.js", "module": "dist/esm/index.js", "type": "commonjs", "exports": { ".": { "import": "./dist/esm/index.js", "require": "./dist/cjs/index.js", "default": "./dist/cjs/index.js" }, "./package.json": "./package.json" }, "files": [ "dist" ], "scripts": { "build": "rollup -c", "test": "node test/test.js" }, "keywords": [ "greeting", "esm", "cjs" ], "author": "Your Name", "license": "MIT", "devDependencies": { "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-terser": "^0.4.4", "rollup": "^4.12.0" } }
main
: Gibt den Einstiegspunkt für CJS-Umgebungen (Legacy Fallback) an.module
: Gibt den Einstiegspunkt für ESM-fähige Bundler (z. B. Webpack, Rollup) an.type: "commonjs"
: Dies teilt Node.js mit, dass.js
-Dateien in diesem Paket standardmäßig CJS sind. Wenn wir.mjs
-Dateien für ESM hätten, bräuchten wir dies nicht, oder wir könnten es auf"module"
setzen und.cjs
für CJS-Dateien verwenden. Mit unserem aktuellen Setup funktioniertdist/esm/index.js
immer noch als ESM, da Rollup ESM-Syntax ausgibt und dieexports.import
-Bedingung Vorrang hat.exports
: Hier passiert die Magie..
: Definiert den primären Einstiegspunkt des Pakets.import
: Verweist bei Verwendung vonimport
auf die ESM-Version.require
: Verweist bei Verwendung vonrequire
auf die CJS-Version.default
: Ein Fallback für Umgebungen, dieimport
- oderrequire
-Bedingungen nicht erkennen, oder zur Zukunftssicherheit."./package.json": "./package.json"
: Entscheidend, damit andere Pakete direkt auf Ihrepackage.json
zugreifen können, ohne Probleme.
files
: Gibt die Dateien an, die beim Veröffentlichen auf NPM enthalten sein sollen. Dies hält Ihr Paket schlank.
5. Testen des Pakets
Robuste Tests sind unerlässlich. Wir erstellen zwei Testdateien: eine für ESM und eine für CJS.
test/cjs-test.js
:
// test/cjs-test.js const { greet } = require('../'); // Beachten Sie, dass wir das Paket selbst anfordern if (typeof greet === 'function') { console.log('CJS Test: greet ist eine Funktion'); const result1 = greet(); console.log('CJS Test:', result1); // Erwartet: Hello, World! const result2 = greet('Alice'); console.log('CJS Test:', result2); // Erwartet: Hello, Alice! if (result1 === 'Hello, World!' && result2 === 'Hello, Alice!') { console.log('CJS Test BESTANDEN'); } else { console.error('CJS Test FEHLGESCHLAGEN'); process.exit(1); } } else { console.error('CJS Test FEHLGESCHLAGEN: greet ist keine Funktion'); process.exit(1); }
test/esm-test.mjs
:
// test/esm-test.mjs import { greet } from '../'; // Beachten Sie, dass wir das Paket selbst importieren async function runEsmTest() { if (typeof greet === 'function') { console.log('ESM Test: greet ist eine Funktion'); const result1 = greet(); console.log('ESM Test:', result1); // Erwartet: Hello, World! const result2 = greet('Bob'); console.log('ESM Test:', result2); // Erwartet: Hello, Bob! if (result1 === 'Hello, World!' && result2 === 'Hello, Bob!') { console.log('ESM Test BESTANDEN'); } else { console.error('ESM Test FEHLGESCHLAGEN'); process.exit(1); } } else { console.error('ESM Test FEHLGESCHLAGEN: greet ist keine Funktion'); process.exit(1); } } runEsmTest().catch(err => { console.error('ESM Test ist auf einen Fehler gestoßen:', err); process.exit(1); });
Aktualisieren Sie Ihre package.json
-Skripte, um beide Tests auszuführen:
// package.json Snippet "scripts": { "build": "rollup -c", "test": "node test/cjs-test.js && node test/esm-test.mjs" },
Führen Sie nun Ihre Tests aus:
npm run test
Sie sollten sehen, dass beide Tests erfolgreich sind, was bedeutet, dass Ihr Paket korrekt für beide Umgebungen (CJS und ESM) exportiert.
6. Veröffentlichung auf NPM
Stellen Sie vor der Veröffentlichung sicher:
- Sie haben ein NPM-Konto.
- Sie sind über Ihr Terminal bei NPM angemeldet (
npm login
). - Ihre
package.json
hat einen eindeutigenname
und eine passendeversion
. - Das
files
-Array listet korrekt auf, was veröffentlicht werden soll (z. B.["dist", "README.md", "LICENSE"]
).
Schließlich, um zu veröffentlichen:
npm publish
Wenn Sie Ihr Paket jemals aktualisieren müssen, erhöhen Sie die Version in package.json
und führen Sie dann erneut npm publish
aus.
Fazit
Die Erstellung eines NPM-Pakets, das sowohl ESM als auch CJS unterstützt, ist eine entscheidende Fähigkeit für moderne JavaScript-Entwickler. Durch die Nutzung von Tools wie Rollup für die Transpilierung und die sorgfältige Konfiguration von package.json
mit Conditioned Exports können Sie ein robustes, universell kompatibles Modul bereitstellen. Dieser Ansatz minimiert die Reibung für den Benutzer und maximiert die Reichweite Ihres wertvollen Codes. Die Übernahme dieser Praktiken stellt sicher, dass Ihr Paket im vielfältigen JavaScript-Umfeld relevant und zugänglich bleibt.