Modernisierung von Node.js-Projekten mit ES-Modulen
Olivia Novak
Dev Intern · Leapcell

Einleitung
Jahrelang verließen sich Node.js-Entwickler hauptsächlich auf CommonJS für das Modulmanagement und nutzten require() und module.exports, um Code zu organisieren und zu teilen. Dieses System hat uns gut gedient und ein robustes Ökosystem gefördert. Mit der Standardisierung von ECMAScript-Modulen (ESM) in JavaScript selbst begann jedoch ein unausweichlicher Wandel. ESM bietet Vorteile bei der statischen Analyse, bessere Tree-Shaking-Fähigkeiten und richtet Node.js am Modul-Laden auf der Browser-Seite aus, was den Weg für eine einheitlichere JavaScript-Entwicklung ebnet. Viele moderne Bibliotheken und Frameworks übernehmen standardmäßig ESM, wodurch Projekte, die noch auf CommonJS basieren, etwas veraltet wirken oder Kompatibilitätsprobleme haben. Diese Entwicklung wirft für viele bestehende Anwendungen eine wichtige Frage auf: Wie können wir unsere etablierten CommonJS Node.js-Projekte auf das zukunftssichere ES-Modulsystem umstellen? Die Antwort liegt oft in einer einfachen, aber mächtigen Konfiguration in unserer package.json-Datei: "type": "module". Dieser Artikel führt Sie durch das Verständnis und die Durchführung dieser Migration und stellt sicher, dass Ihre Node.js-Projekte das moderne Modulsystem nutzen.
Das Modul-Update verstehen
Bevor wir uns mit der Migration befassen, klären wir einige grundlegende Konzepte, die für diese Diskussion zentral sind.
Kernterminologie
-
CommonJS (CJS): Dies ist das ursprüngliche und Standard-Modulsystem von Node.js. Es verwendet
require(), um Module zu importieren, undmodule.exportsoderexports, um sie bereitzustellen. Es ist synchron, d. h. Module werden nacheinander geladen.// CommonJS-Beispiel const express = require('express'); function greet(name) { return `Hello, ${name}!`; } module.exports = greet; -
ECMAScript Modules (ESM): Der offizielle Standard für Module in JavaScript, der nativ von Browsern und Node.js unterstützt wird. Es verwendet
import- undexport-Anweisungen. ESM ist von Natur aus asynchron und ermöglicht bessere Leistungsoptimierungen.// ESM-Beispiel import express from 'express'; export function greet(name) { return `Hello, ${name}!`; } -
package.jsontype-Feld: Dieses Feld wurde in Node.js 13.2 eingeführt und ermöglicht es Entwicklern, das Modulsystem für alle.js-Dateien innerhalb eines Pakets anzugeben."type": "commonjs": (Standard, wenn nicht angegeben) Alle.js-Dateien werden als CommonJS behandelt."type": "module": Alle.js-Dateien werden als ES-Module behandelt.
Der Mechanismus von "type": "module"
Wenn Sie "type": "module" in Ihrer package.json festlegen, ändert Node.js seinen Standard-Parser für .js-Dateien innerhalb dieses Pakets. Anstatt sie als ES-Module zu interpretieren, behandelt es sie nun als ES-Module. Dies hat mehrere kaskadierende Auswirkungen:
- Umkehrung des Standard-Modulsystems: Alle
.js-Dateien in Ihrem Projekt (und Unterverzeichnissen), die versuchen,require()odermodule.exportszu verwenden, werden nun Fehler auslösen und dieimport- undexport-Syntax verlangen. - Überschreibungen von Dateierweiterungen: Sie können weiterhin CommonJS-Dateien innerhalb eines ESM-Projekts (oder ESM-Dateien innerhalb eines CommonJS-Projekts) verwenden, indem Sie explizit die Dateierweiterungen
.cjsoder.mjsverwenden.- Eine
.cjs-Datei wird immer als CommonJS behandelt, unabhängig vomtype-Feld derpackage.json. - Eine
.mjs-Datei wird immer als ES-Modul behandelt, unabhängig vomtype-Feld derpackage.json. Dies bietet Flexibilität während der Migration und ermöglicht einen schrittweisen Übergang.
- Eine
Praktische Migrationsschritte
Lassen Sie uns die Migration eines einfachen CommonJS Node.js-Projekts zu ES-Modulen durchgehen.
Ursprüngliche CommonJS Projektstruktur
Betrachten Sie ein Projekt mit app.js und einem Utility-Modul utils.js.
package.json (initial):
{ "name": "cjs-project", "version": "1.0.0", "main": "app.js", "scripts": { "start": "node app.js" } }
utils.js (CommonJS):
// utils.js function add(a, b) { return a + b; } function subtract(a, b) { return a - b; } module.exports = { add, subtract };
app.js (CommonJS):
// app.js const express = require('express'); // Angenommen, express ist installiert const math = require('./utils'); const app = express(); const port = 3000; console.log('Addition:', math.add(5, 3)); // Ausgabe: Addition: 8 console.log('Subtraction:', math.subtract(10, 4)); // Ausgabe: Subtraction: 6 app.get('/', (req, res) => { res.send('Hello from CommonJS app!'); }); app.listen(port, () => { console.log(`CommonJS app listening at http://localhost:${port}`); });
Um dies auszuführen, stellen Sie sicher, dass express installiert ist (npm i express) und führen Sie dann npm start aus.
Schritt 1: package.json aktualisieren
Der erste und wichtigste Schritt ist, "type": "module" zu Ihrer package.json hinzuzufügen.
package.json (modifiziert):
{ "name": "cjs-project", "version": "1.0.0", "main": "app.js", "type": "module", <-- DIESE ZEILE HINZUFÜGEN "scripts": { "start": "node app.js" } }
Wenn Sie jetzt npm start ausführen, treten wahrscheinlich Fehler wie SyntaxError: Unexpected token 'export' oder ReferenceError: require is not defined auf, da Node.js nun versucht, Ihre alten CommonJS-Dateien als ES-Module zu parsen.
Schritt 2: CommonJS-Syntax in ES-Modul-Syntax konvertieren
Sie müssen Ihre CommonJS-Dateien durchgehen und require()/module.exports durch import/export ersetzen.
utils.js (ESM konvertiert):
// utils.js export function add(a, b) { return a + b; } export function subtract(a, b) { return a - b; }
Hinweis: Für module.exports = { add, subtract } könnten Sie auch export default { add, subtract } verwenden und dann import math from './utils.js' in app.js. Benannte Exporte werden jedoch generell bevorzugt.
app.js (ESM konvertiert):
// app.js import express from 'express'; // Normaler Import für npm-Pakete import { add, subtract } from './utils.js'; // Beachten Sie die .js-Erweiterung! const app = express(); const port = 3000; console.log('Addition:', add(5, 3)); console.log('Subtraction:', subtract(10, 4)); app.get('/', (req, res) => { res.send('Hello from ESM app!'); }); app.listen(port, () => { console.log(`ESM app listening at http://localhost:${port}`); });
Wichtige Überlegungen während der Konvertierung:
- Dateierweiterungen: Beim Importieren lokaler ES-Module müssen Sie die vollständige Dateierweiterung angeben (z. B.
./utils.js). Dies ist ein entscheidender Unterschied zu CommonJS, wo Erweiterungen oft weggelassen wurden. Node.js löst leere Bezeichner für installierte Pakete (wieexpress), aber nicht für relative oder absolute Dateipfade. __dirnameund__filename: Diese CommonJS-spezifischen globalen Variablen sind in ES-Modulen nicht verfügbar. Sie können ihre Funktionalität mitimport.meta.urlund den Node.js-ModulenpathundfileURLToPathnachbilden.import path from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); console.log('Current directory:', __dirname); console.log('Current file:', __filename);- JSON-Importe: In CommonJS konnten Sie
require('./data.json'). In ESM sind direkte JSON-Importe in einigen Node.js-Versionen noch experimentell (mitimport data from './data.json' with { type: 'json' }). Für breitere Kompatibilität müssen Sie JSON-Dateien möglicherweise mithilfe vonfs.readFileundJSON.parselesen. requirefür dynamische Importe / Interoperabilität: Wenn Sie unbedingt ein CommonJS-Modul aus einer ESM-Dateirequire()müssen (z. B. eine Legacy-Bibliothek, die keine ESM-Version veröffentlicht hat), können Sieimport()für dynamische Importe verwenden. Direkterequire()ist jedoch nicht verfügbar. Für einfachere Interoperabilität ohne dynamisches Laden hat das Paket selbst oft die ESM-Kompatibilität bereitgestellt, oder Sie benötigen möglicherweise eine Wrapper-Funktion.
Schritt 3: Das migrierte Projekt ausführen
Nachdem Sie alle relevanten Dateien konvertiert haben, führen Sie Ihr Projekt erneut aus:
npm start
Ihre Node.js-Anwendung sollte nun vollständig mit ES-Modulen ausgeführt werden, die neuen Meldungen protokollieren und die korrekte HTTP-Antwort liefern.
Behandlung gemischter Modulumgebungen
Was ist, wenn Sie ein großes Projekt haben und nicht alles auf einmal konvertieren können? Oder Sie sind von einem Modul abhängig, das hartnäckig nur CommonJS verwendet (obwohl die meisten jetzt dual veröffentlicht oder ESM-kompatibel sind)? Hier sind die Erweiterungen .cjs und .mjs von unschätzbarem Wert.
Beispiel: Eine CommonJS-Datei in einem ESM-Projekt beibehalten
Stellen Sie sich legacy.cjs (eine CommonJS-Datei) vor, die Sie in Ihrem type: "module"-Projekt nicht sofort konvertieren können.
legacy.cjs:
// legacy.cjs module.exports = function () { return "This is a legacy CommonJS function!"; };
Sie können dies aus einer ESM-Datei importieren:
app.js (modifiziert, um .cjs zu importieren):
import express from 'express'; import { add, subtract } from './utils.js'; import legacyFunc from './legacy.cjs'; // Node.js erkennt dies dank der .cjs-Erweiterung als CJS const app = express(); const port = 3000; console.log('Addition:', add(5, 3)); console.log('Subtraction:', subtract(10, 4)); console.log('Legacy:', legacyFunc()); // Ausgabe: Legacy: This is a legacy CommonJS function! app.get('/', (req, res) => { res.send('Hello from ESM app!'); }); app.listen(port, () => { console.log(`ESM app listening at http://localhost:${port}`); });
Dies zeigt, wie type: "module" den Standard festlegt, aber .cjs und .mjs entscheidende Fluchtmöglichkeiten bieten, um eine gemischte Codebasis während der Übergangsphase oder für bestimmte Anwendungsfälle zu verwalten.
Fazit
Die Migration eines Node.js-Projekts von CommonJS zu ES-Modulen durch Festlegen von "type": "module" in der package.json ist ein wichtiger Schritt zur Modernisierung Ihrer JavaScript-Codebasis. Obwohl dies eine systematische Konvertierung von require/module.exports zu import/export und eine sorgfältige Behandlung von Dateierweiterungen, __dirname und __filename erfordert, sind die Vorteile der Ausrichtung auf das breitere JavaScript-Ökosystem, verbesserte Tools und zukünftige Kompatibilität die Mühe wert. Nutzen Sie ES-Module für sauberere, besser wartbare und moderne Node.js-Anwendungen. Diese kleine package.json-Änderung eröffnet die Zukunft des modularen JavaScript in Node.js.