Menu

module.exports vs exports w Node.js, z przykładami

module.exports to wartość, którą plik CommonJS przekazuje każdemu, kto wywoła na nim require(). exports to skrót, który wskazuje ten sam obiekt, dopóki nie przypiszesz mu nowej wartości.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

module.exports to wartość, którą plik Node.js daje każdemu innemu plikowi, który wczyta go przez require(). Na początku to pusty obiekt: dodaj do niego właściwości, żeby wyeksportować kilka rzeczy, albo przypisz mu nową wartość (funkcję, klasę), żeby wyeksportować jedną. exports to skrótowa zmienna wskazująca ten sam obiekt, więc exports.add = ... działa, ale exports = ... zrywa powiązanie i niczego nie eksportuje.

Wypisało 5 20 3.14159, a potem [ 'add', 'multiply', 'PI' ]. Wszystkie trzy linie w math.js dodały właściwości do tego samego obiektu, niezależnie od tego, czy przez module.exports, czy przez exports, a require('./math') zwróciło ten obiekt.

Eksport funkcji, klasy albo obiektu

Gdy moduł ma jedną główną rzecz, przypisz ją bezpośrednio do module.exports. Wywołujący dostaje wtedy samą funkcję, a nie obiekt wokół niej:

Wypisało Hello, Ada i Grace (user). Jeśli widzisz TypeError: greet is not a function, wywołujący użył złego kształtu: pisz const { greet } = require(...) dla właściwości obiektu i const greet = require(...) dla przypisanej wartości.

module.exports vs exports: pułapka z przypisaniem

Zanim twój plik się wykona, Node.js ustawia exports = module.exports. Obie nazwy wskazują jeden obiekt, a require zwraca module.exports. Przypisanie nowej wartości do exports zmienia tylko lokalną zmienną:

broken.js wyeksportował {}, bo exports wskazuje teraz nowy obiekt, którego nikt nie zwraca. working.js wyeksportował { version: 2 }.

PiszeszWyeksportowane?
module.exports.x = 1Tak
exports.x = 1Tak, ten sam obiekt
module.exports = { x: 1 }Tak, zastępuje obiekt
exports = { x: 1 }Nie, wartość przepada

Najprostsza zasada: zawsze pisz module.exports. Działa w każdym przypadku, a kod mówi, co robi.

require zwraca ten sam obiekt z cache

Node.js wykonuje kod modułu raz, przy pierwszym require, i zapisuje w cache module.exports. Każde kolejne require tego samego pliku zwraca ten sam obiekt, więc stan wewnątrz modułu jest wspólny dla każdego pliku, który go wczytuje:

counter.js is running pojawiło się raz, b.next() zwróciło 3, a a === b to true, bo kluczem cache jest rozwiązana ścieżka pliku, a ./counter i ./counter.js wskazują ten sam plik. Dlatego połączenie z bazą danych albo obiekt konfiguracji utworzony w jednym module jest wspólny dla całej aplikacji.

module.exports i moduły ES

Moduły ES używają zamiast tego export i import. Formy odpowiadają sobie tak:

// 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';

Moduł ES może zaimportować plik CommonJS przez import: jego module.exports przychodzi jako import domyślny. Od Node.js 22.12 require() potrafi też wczytać moduł ES bez await na najwyższym poziomie. Kiedy wybrać które, przeczytasz w CommonJS vs moduły ES.

Częste błędy

Mieszanie exports.x z module.exports = ... w jednym pliku. Przypisanie zastępuje obiekt, więc każde exports.x zapisane wcześniej przepada. Wybierz jeden styl na plik.

Eksportowanie wartości, która później się zmienia. module.exports = { count } kopiuje liczbę w chwili wczytania; gdy moduł zaktualizuje count, wywołujący nadal widzą starą wartość. Eksportuj funkcję, która ją zwraca, jak counter.js powyżej.

Pomijanie ./. require('math') szuka pakietu w node_modules, a nie pliku math.js obok twojego pliku. Lokalne pliki zawsze zaczynają się od ./ albo ../.

Najczęściej zadawane pytania

Czy mogę używać module.exports i export w tym samym pliku?

Nie. Plik to albo CommonJS (require, module.exports), albo moduł ES (import, export). Node.js decyduje na podstawie rozszerzenia (.cjs lub .mjs) albo pola "type" w package.json.

Dlaczego require zwraca pusty obiekt?

Zwykle moduł przypisał exports = ... zamiast module.exports = ... albo dwa moduły wzajemnie się wczytują. Przy cyklicznym require jeden z nich dostaje module.exports drugiego, zanim ten plik skończy się wykonywać.

Czy w require potrzebne jest rozszerzenie .js?

Nie. require('./math') próbuje ./math, potem ./math.js, ./math.json i ./math.node, a potem folderu ./math z plikiem index.js. W import w module ES rozszerzenie jest wymagane.

Jak przeładować moduł, który require już ma w cache?

Usuń jego wpis przez delete require.cache[require.resolve('./math')] i wczytaj go ponownie przez require. Przydaje się to głównie w testach i narzędziach deweloperskich; kod aplikacji nie powinien na tym polegać.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

Zacznij