Node.js: __dirname ist in ES-Modulen nicht definiert
Nach type module in der package.json fehlt __dirname? ES-Module kennen die Variable nicht. Node bietet dafür import.meta.dirname an.
Aufwand:
ca. 15 Min.
Voraussetzung:
Node.js ab v20.11.0 bzw. v21.2.0 für import.meta.dirname, Ein Texteditor für package.json und Quelltext
Gilt für:
Node.js v26.12.0 (Dokumentation abgerufen am 15.10.2026); import.meta.dirname und import.meta.filename hinzugefügt in v21.2.0 und v20.11.0, nicht mehr experimentell seit v24.0.0 und v22.16.0
Das Symptom
Ein Node-Projekt soll modernisiert werden. Jemand ergänzt in der package.json das Feld
type mit dem Wert module, damit man überall import statt require schreiben kann, und
stellt die Imports um. Beim nächsten Start bricht das Programm ab: __dirname sei nicht
definiert. Die Zeile, an der es scheitert, hat sich seit Jahren nicht verändert. Sie
baut wie immer den Pfad zu einer Vorlage, einer Konfigurationsdatei oder einem Ordner mit
statischen Dateien.
Die Suche nach der Ursache führt zunächst in die falsche Richtung. Es wird vermutet, Node
sei kaputt aktualisiert worden, eine Bibliothek habe etwas überschrieben oder der
Bundler mache Ärger. Manche setzen am Dateianfang einfach selbst eine Variable __dirname
mit einem festen Pfad, damit es wieder läuft, und wundern sich später auf dem Server.
Die „Layer 8“-Wahrheit
Node kennt zwei Modulsysteme, und __dirname gehört nur zu einem davon. Die Dokumentation
für CommonJS beschreibt __dirname als eine der Annehmlichkeiten des Modulbereichs, „The
directory name of the current module“. Für ES-Module steht in der Liste der Unterschiede
zu CommonJS ein eigener Abschnitt, und er ist kurz: „These CommonJS variables are not
available in ES modules.“ Gemeint sind __filename und __dirname.
Welches der beiden Systeme gilt, entscheidet nicht der Inhalt der Datei allein, sondern
ihre Kennzeichnung. Die Paket-Dokumentation nennt die Endungen .mjs und .cjs sowie das
Feld type in der package.json als ausdrückliche Markierungen. Steht dort module, wird
jede .js-Datei darunter als ES-Modul geladen. Die eine Zeile in der package.json hat also
nicht nur die Schreibweise der Imports erlaubt, sondern stillschweigend alle Dateien in
ein anderes Modulsystem verschoben, in dem eine alte Gewohnheit nicht mehr existiert.
Der Ersatz ist seit einiger Zeit eingebaut. Die Dokumentation sagt: „__filename and
__dirname use cases can be replicated via import.meta.filename and import.meta.dirname.“
Beide Eigenschaften kamen mit v21.2.0 und v20.11.0 und gelten seit v24.0.0 und v22.16.0
nicht mehr als experimentell. Wer eine ältere Version betreibt, bestimmt den Ordner aus
import.meta.url über fileURLToPath, denn import.meta.filename ist laut Dokumentation
genau das. Und wer eine einzelne Datei beim alten Stil lassen will, gibt ihr die Endung
.cjs.
Der eigentliche Fehler ist also keiner im Code, sondern einer in der Erwartung: dass eine
Variable, die man jahrelang benutzt hat, zur Sprache gehört. Sie gehörte zum
Modulsystem. Mit dem Wechsel des Modulsystems ist sie weg, und der fest eingetragene
Pfad, der die Meldung zum Schweigen bringt, verschiebt den Fehler nur auf den nächsten
Rechner.
Die echte, verifizierte Lösung
1000 % faktenbasiert. Jeder Befehl ist real, jeder Pfad existiert. Schritt für Schritt:
1.
Feststellen, ob die Datei ein ES-Modul ist
Node behandelt laut Dokumentation Dateien mit der Endung .mjs als ES-Modul, Dateien mit .cjs als CommonJS, und .js-Dateien nach dem Feld type der nächsten übergeordneten package.json. Steht dort module, ist jede .js-Datei darunter ein ES-Modul. Die Dokumentation nennt die Endungen .mjs und .cjs sowie das Feld type ausdrücklich als „Explicit markers“. Ein Blick in die package.json klärt also, warum __dirname plötzlich fehlt.
cat package.json
2.
import.meta.dirname verwenden
In einem ES-Modul ersetzt import.meta.dirname die alte Variable __dirname und import.meta.filename die Variable __filename. Beide liefern Pfade im Dateisystem, nicht Adressen. Wer damit Pfade zu Nachbardateien baut, nimmt wie gewohnt path.join. Voraussetzung ist eine Node-Version ab v20.11.0 oder v21.2.0; seit v24.0.0 und v22.16.0 sind die Eigenschaften nicht mehr experimentell.
import path from 'node:path';
const vorlage = path.join(import.meta.dirname, 'vorlagen', 'mail.html');
3.
Für ältere Node-Versionen den Umweg über import.meta.url nehmen
Ältere Versionen kennen import.meta.dirname nicht. Dann wandelt fileURLToPath aus node:url die file:-Adresse aus import.meta.url in einen Dateipfad, und dirname aus node:path schneidet den Dateinamen ab. Die Dokumentation beschreibt import.meta.filename genau als das Ergebnis von url.fileURLToPath() auf import.meta.url. Das Ergebnis entspricht damit dem, was __dirname in CommonJS geliefert hätte.
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
const ordner = dirname(fileURLToPath(import.meta.url));
4.
Oder die Datei bewusst als CommonJS führen
Muss eine einzelne Datei beim alten Stil bleiben, etwa ein Werkzeug, das require und __dirname überall benutzt, lässt sie sich mit der Endung .cjs ausdrücklich als CommonJS kennzeichnen, auch wenn die package.json type auf module setzt. Dort sind __dirname und __filename laut Modul-Dokumentation die gewohnten Variablen. Umgekehrt braucht ein ES-Modul ein require nur selten; wenn doch, lässt es sich laut Dokumentation mit module.createRequire() erzeugen.
5.
Nicht einfach die Fehlermeldung wegdefinieren
Eine selbst gesetzte Variable namens __dirname mit einem festen Pfad lässt die Meldung verschwinden, funktioniert aber nur auf dem Rechner, auf dem der Pfad stimmt. Spätestens im Container oder auf dem Server zeigt sie ins Leere, und der Fehler kommt als fehlende Datei zurück, weit weg von seiner Ursache. Der saubere Weg ist einer der drei oben, je nach Node-Version und je nachdem, ob die Datei ein ES-Modul bleiben soll.
Häufige Fragen (FAQ)
?Warum ging es gestern noch und heute nicht mehr?
Weil sich vermutlich nicht der Code geändert hat, sondern die Art, wie Node ihn liest. Die Dokumentation zu Paketen beschreibt, dass eine Datei mit der Endung .js als ES-Modul behandelt wird, wenn die nächste übergeordnete package.json ein Feld type mit dem Wert module enthält. Wer dieses Feld ergänzt hat, etwa um import statt require zu schreiben, hat damit alle .js-Dateien darunter zu ES-Modulen gemacht. Und in ES-Modulen gibt es __dirname nicht; die Dokumentation sagt über __filename und __dirname: „These CommonJS variables are not available in ES modules.“
?Was ist der offizielle Ersatz?
import.meta.dirname und import.meta.filename. Die Node-Dokumentation schreibt: „__filename and __dirname use cases can be replicated via import.meta.filename and import.meta.dirname.“ import.meta.dirname ist laut Dokumentation „The directory name of the current module“ und entspricht path.dirname() von import.meta.filename. Beide Eigenschaften gibt es seit v21.2.0 und v20.11.0; seit v24.0.0 und v22.16.0 sind sie nicht mehr als experimentell gekennzeichnet.
?Mein Node ist älter. Gibt es einen anderen Weg?
Ja, über import.meta.url, das die Dokumentation als absolute file:-Adresse des Moduls beschreibt. Für import.meta.filename heißt es dort, es sei dasselbe wie url.fileURLToPath() von import.meta.url. Mit fileURLToPath aus node:url und dirname aus node:path lässt sich der Ordner also auch ohne die neuen Eigenschaften bestimmen. Für relative Dateien zeigt die Dokumentation zudem das Muster new URL('./datei', import.meta.url), das ganz ohne Ordnernamen auskommt.
Weil es nur für Module existiert, die als Datei geladen wurden. Die Dokumentation vermerkt bei import.meta.dirname ausdrücklich als Vorbehalt, die Eigenschaft sei „only present on file: modules.“ Bei import.meta.filename heißt es entsprechend, nur lokale Module unterstützten diese Eigenschaft, Module ohne file:-Protokoll stellten sie nicht bereit. Wer Code etwa als Zeichenkette über --eval ausführt oder aus einer anderen Quelle lädt, kann sich darauf nicht verlassen.
Belege
Diese Seite ist gegen die folgenden Primärquellen geprüft – Hersteller, Standard oder Gesetzestext. Wer es nicht glaubt, muss es nicht: Da steht, wo es nachzulesen ist.