Abschnitt 2 · Lektion 2
ESM
Das zweite Modulsystem heißt ESM, kurz für ECMAScript Modules. Es ist keine Erfindung von Node, sondern Teil der Sprache selbst, und es funktioniert im Browser genauso. Ab dieser Lektion schreiben wir im Kurs nur noch ESM.
Herausgeben und hereinholen
Statt module.exports steht hier das Schlüsselwort export direkt vor dem, was herausgehen soll.
Statt require gibt es import.
export const SATZ = 19;
export function brutto(netto) {
return netto * (1 + SATZ / 100);
}
export default function formatiere(betrag) {
return betrag.toFixed(2).replace(".", ",") + " EUR";
} import formatiere, { brutto, SATZ } from "./preise.mjs";
console.log("Satz:", SATZ);
console.log(formatiere(brutto(100))); Das ist dieselbe Rechnung wie in der vorigen Lektion, nur anders geschrieben. Beachte die zwei Sorten Export im Beispiel:
Benannte Exporte stehen vor einer Deklaration und behalten ihren Namen. Wer sie hereinholt, schreibt sie in geschweifte Klammern und muss den Namen genau treffen.
Der Standard-Export heißt export default und ist pro Datei nur einmal erlaubt. Er hat keinen
Namen, deshalb darf die andere Seite ihn nennen, wie sie will. Im Beispiel steht er ohne Klammern
ganz vorn im Import.
Wann nimmt man was? Eine brauchbare Faustregel: Gibt eine Datei genau ein Ding heraus, ist default
in Ordnung. Gibt sie mehrere heraus, nimm benannte Exporte. Sie sind leichter zu finden, und dein
Editor kann sie automatisch ergänzen.
Drei Wege, eine Datei zu ESM zu machen
Node muss vor dem Ausführen wissen, welches der beiden Systeme gilt. Es gibt genau drei Antworten:
- In der
package.jsondes Projekts steht"type": "module". Dann sind alle.js-Dateien Module. Das ist der Normalfall in neuen Projekten, und wir sehen ihn in Abschnitt 5. - Die Datei heißt
.mjs. Dann gilt sie als Modul, egal was drumherum steht. So machen wir es in diesem Kurs, bis diepackage.jsondazukommt. - Keins von beidem. Dann schaut Node in die Datei und entscheidet an der Syntax: Steht dort ein
import, einexportoder einawaitauf oberster Ebene, liest es sie als Modul.
Es gibt spiegelbildlich auch .cjs für den umgekehrten Fall: eine CommonJS-Datei in einem Projekt,
das sonst auf Module steht.
Der dritte Punkt ist neu und stand jahrelang anders in jedem Tutorial. Früher war ein import in
einer .js ohne Eintrag ein harter Abbruch, und die Meldung dazu lautete
SyntaxError: Cannot use import statement outside a module. Node rät seit einer Weile nicht mehr,
sondern sieht nach.
Verlass dich trotzdem nicht darauf. Liegt eine package.json daneben, in der kein "type" steht,
liest Node die Datei zweimal und schreibt eine Warnung auf den Fehlerkanal. Und "type": "commonjs"
schaltet das Nachsehen ganz ab, dann ist der alte Abbruch wieder da. Lektion 5.1 kommt darauf zurück,
wenn die package.json dazukommt.
Probier die drei Wege aus
Anlegen musst du dafür nichts: Hol das erste Beispiel dieser Lektion nach vorn, starte die Sandbox,
und preise.mjs und index.mjs liegen dort. Tipp dann im Terminal erst cp index.mjs index.js und
danach node index.js.
Dieselben Zeilen, andere Endung, kein Eintrag irgendwo, und es läuft trotzdem: Satz: 19 und
119,00 EUR. Node hat den import gesehen und die Datei als Modul gelesen.
Jetzt nimm ihm die Entscheidung ab. Tipp echo '{"type": "commonjs"}' > package.json und danach noch
einmal node index.js. Da ist der alte Fehler, wörtlich, mit dem import in der Zeile darüber und
einem Zirkumflex darunter. Ein node index.mjs läuft daneben weiter, denn die Endung schlägt den
Eintrag.
Mit rm index.js package.json räumst du hinterher wieder auf. Nötig ist das nicht, ein Wechsel zu
einem anderen Beispiel wischt das Verzeichnis ohnehin, aber es ist eine gute Gewohnheit.
Die Endung im Import ist Pflicht
Das ist der Punkt, an dem fast jeder Umsteiger einmal hängen bleibt.
export function brutto(netto) {
return netto * 1.19;
} // In ESM fehlt hier die Endung .mjs, und das ist ein Fehler.
import { brutto } from "./preise";
console.log(brutto(100)); In CommonJS durftest du require("./preise") schreiben, Node hat die Endung dazugeraten. In ESM
tut es das nicht. Der Pfad im Import ist eine Adresse, und eine Adresse ohne Endung zeigt auf nichts.
Verwirrend ist das vor allem, weil du es in React-, Vue- oder Astro-Projekten dauernd ohne Endung siehst. Dort läuft aber ein Bündler dazwischen, der die Datei für dich sucht. Node hat keinen Bündler. Was du schreibst, wird gesucht.
Lies die Fehlermeldung im Beispiel einmal in Ruhe. ERR_MODULE_NOT_FOUND und ein Pfad ohne Endung
darin sind zusammen ein ziemlich eindeutiges Zeichen.
Was jedes System besser kann
ESM kann await auf oberster Ebene, also außerhalb jeder Funktion.
const antwort = await Promise.resolve("Das geht nur in ESM");
console.log(antwort);
console.log("require gibt es hier nicht:", typeof require); Das ist praktischer, als es klingt: Konfiguration einlesen, eine Verbindung aufbauen, einen Wert
holen, bevor der Rest startet. In CommonJS bräuchtest du dafür eine async-Funktion, die du sofort
selbst aufrufst.
Im selben Beispiel siehst du auch, warum require in der vorigen Lektion nicht in der Namensliste
stand: In einem Modul gibt es das Wort gar nicht.
CommonJS kann dafür etwas anderes: mitten im Code laden. Ein require darf in einer Bedingung
stehen und erst dann ausgeführt werden, wenn es wirklich gebraucht wird. Ein import darf das nicht,
es gehört auf die oberste Ebene der Datei und wird immer ausgeführt. Wer wirklich erst später laden
will, nimmt die Funktionsform import(), die ein Promise zurückgibt und überall stehen darf.
Und was heißt das für dich?
Neue Projekte schreibst du in ESM. Es ist der Standard der Sprache, es läuft im Browser und in Node, und alle Werkzeuge unterstützen es. CommonJS liest du, wenn du in ältere Projekte kommst.
Zwei Systeme im selben Projekt zu mischen geht übrigens auch, ist aber ein eigenes kleines Thema voller Sonderfälle. Solange du eins von beidem konsequent durchziehst, begegnet es dir nicht.
Zum Mitnehmen
Die Dateiendung im Import ist in ESM Pflicht. Genau daran scheitert fast jeder Umsteiger einmal.
Jetzt du
Basis Konto, kostenlosZu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.
Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.
In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.
Was in dieser Lektion steckt
-
Artikel mit 3 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe, dein Code läuft auf einem Server
Öffnet sich mit dem Basis Konto.