module.exports ist der Wert, den eine Node.js-Datei jeder anderen Datei gibt, die sie mit require() lädt. Es beginnt als leeres Objekt: Füg ihm Eigenschaften hinzu, um mehrere Dinge zu exportieren, oder weis ihm einen neuen Wert zu (eine Funktion, eine Klasse), um eine Sache zu exportieren. exports ist eine Kurzform-Variable, die auf dasselbe Objekt zeigt, exports.add = ... funktioniert also, aber exports = ... kappt die Verbindung und exportiert nichts.
Es hat 5 20 3.14159 ausgegeben, dann [ 'add', 'multiply', 'PI' ]. Alle drei Zeilen in math.js haben Eigenschaften zum selben Objekt hinzugefügt, egal ob über module.exports oder exports, und require('./math') hat dieses Objekt zurückgegeben.
Eine Funktion, eine Klasse oder ein Objekt exportieren
Wenn ein Modul eine Hauptsache hat, weis sie direkt module.exports zu. Der Aufrufer bekommt dann die Funktion selbst, kein Objekt drumherum:
Es hat Hello, Ada und Grace (user) ausgegeben. Wenn du TypeError: greet is not a function siehst, hat der Aufrufer die falsche Form benutzt: Schreib const { greet } = require(...) für eine Objekteigenschaft und const greet = require(...) für einen zugewiesenen Wert.
module.exports vs. exports: die Falle bei Neuzuweisung
Bevor deine Datei läuft, setzt Node.js exports = module.exports. Beide Namen zeigen auf ein Objekt, und require gibt module.exports zurück. Ein neuer Wert für exports ändert nur die lokale Variable:
broken.js hat {} exportiert, weil exports jetzt auf ein neues Objekt zeigt, das niemand zurückgibt. working.js hat { version: 2 } exportiert.
| Du schreibst | Exportiert? |
|---|---|
module.exports.x = 1 | Ja |
exports.x = 1 | Ja, dasselbe Objekt |
module.exports = { x: 1 } | Ja, ersetzt das Objekt |
exports = { x: 1 } | Nein, der Wert geht verloren |
Die einfachste Regel: Schreib immer module.exports. Das funktioniert in jedem Fall, und der Code sagt, was er tut.
require gibt dasselbe gecachte Objekt zurück
Node.js führt den Code eines Moduls einmal aus, beim ersten require, und cacht module.exports. Jedes spätere require derselben Datei gibt genau dieses Objekt zurück, Zustand in einem Modul wird also von jeder Datei geteilt, die es lädt:
counter.js is running erschien einmal, b.next() gab 3 zurück, und a === b ist true, weil der Cache-Schlüssel der aufgelöste Dateipfad ist und ./counter und ./counter.js auf dieselbe Datei zeigen. Deshalb wird eine Datenbankverbindung oder ein Config-Objekt, das in einem Modul erstellt wird, in der ganzen App geteilt.
module.exports und ES-Module
ES-Module nutzen stattdessen export und import. Die Formen entsprechen sich so:
// CommonJS // ES modules
module.exports = greet; export default greet;
module.exports.add = add; export { add };
const greet = require('./greet'); import greet from './greet.js';
const { add } = require('./math'); import { add } from './math.js';
Ein ES-Modul kann eine CommonJS-Datei mit import laden: Ihr module.exports kommt als Default-Import an. Seit Node.js 22.12 kann require() auch ein ES-Modul ohne Top-Level-await laden. Wann du welches nimmst, steht unter CommonJS vs. ES-Module.
Häufige Fehler
exports.x und module.exports = ... in einer Datei mischen. Die Zuweisung ersetzt das Objekt, jedes exports.x, das davor steht, ist also weg. Nimm einen Stil pro Datei.
Einen Wert exportieren, der sich später ändert. module.exports = { count } kopiert die Zahl beim Laden; wenn das Modul count aktualisiert, sehen Aufrufer weiterhin den alten Wert. Exportier eine Funktion, die ihn zurückgibt, wie counter.js oben.
./ weglassen. require('math') sucht ein Paket in node_modules, nicht math.js neben deiner Datei. Lokale Dateien beginnen immer mit ./ oder ../.
Häufig gestellte Fragen
Kann ich module.exports und export in derselben Datei verwenden?
Nein. Eine Datei ist entweder CommonJS (require, module.exports) oder ein ES-Modul (import, export). Node.js entscheidet das anhand der Endung (.cjs oder .mjs) oder anhand von "type" in package.json.
Warum gibt require ein leeres Objekt zurück?
Meistens hat das Modul exports = ... statt module.exports = ... zugewiesen, oder zwei Module laden sich gegenseitig. Bei einem zirkulären require bekommt eines von beiden das module.exports des anderen, bevor diese Datei fertig gelaufen ist.
Brauche ich die Endung .js in require?
Nein. require('./math') probiert ./math, dann ./math.js, ./math.json und ./math.node, dann einen Ordner ./math mit einer index.js. Bei import in einem ES-Modul ist die Endung Pflicht.
Wie lade ich ein Modul neu, das require schon gecacht hat?
Lösch seinen Eintrag mit delete require.cache[require.resolve('./math')] und lade es noch einmal mit require. Das ist vor allem in Tests und Dev-Tools nützlich; Anwendungscode sollte sich nicht darauf verlassen.