Menu

module.exports и exports в Node.js: разница и примеры

module.exports это значение, которое файл CommonJS отдаёт тому, кто вызывает для него require(). exports это сокращение, указывающее на тот же объект, пока вы его не переприсвоите.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

module.exports это значение, которое файл Node.js отдаёт любому другому файлу, загружающему его через require(). Сначала это пустой объект: добавьте ему свойства, чтобы экспортировать несколько вещей, или присвойте новое значение (функцию, класс), чтобы экспортировать одну. exports это переменная-сокращение, которая указывает на тот же объект, поэтому exports.add = ... работает, а exports = ... рвёт связь и ничего не экспортирует.

Программа вывела 5 20 3.14159, затем [ 'add', 'multiply', 'PI' ]. Все три строки в math.js добавили свойства одному и тому же объекту, через module.exports или через exports, и require('./math') вернул этот объект.

Экспорт функции, класса или объекта

Когда у модуля одна главная вещь, присвойте её прямо в module.exports. Тогда вызывающий код получает саму функцию, а не объект вокруг неё:

Программа вывела Hello, Ada и Grace (user). Если видите TypeError: greet is not a function, вызывающий код ждал не ту форму: пишите const { greet } = require(...) для свойства объекта и const greet = require(...) для присвоенного значения.

module.exports и exports: ловушка с переприсваиванием

Перед запуском вашего файла Node.js выполняет exports = module.exports. Оба имени указывают на один объект, а require возвращает module.exports. Присваивание нового значения в exports меняет только локальную переменную:

broken.js экспортировал {}, потому что exports теперь указывает на новый объект, который никто не возвращает. working.js экспортировал { version: 2 }.

Вы пишетеЭкспортируется?
module.exports.x = 1Да
exports.x = 1Да, тот же объект
module.exports = { x: 1 }Да, объект заменяется
exports = { x: 1 }Нет, значение теряется

Самое простое правило: всегда пишите module.exports. Это работает в любом случае, и по коду видно, что он делает.

require возвращает тот же объект из кэша

Node.js выполняет код модуля один раз, при первом require, и кэширует module.exports. Каждый следующий require того же файла возвращает тот же объект, поэтому состояние внутри модуля общее для всех файлов, которые его загружают:

counter.js is running появилось один раз, b.next() вернул 3, а a === b равно true, потому что ключ кэша это полный путь к файлу, а ./counter и ./counter.js указывают на один файл. Поэтому подключение к базе данных или объект конфигурации, созданный в одном модуле, общий для всего приложения.

module.exports и ES модули

ES модули используют export и import. Формы соответствуют друг другу так:

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

ES модуль может через import подключить файл CommonJS: его module.exports приходит как импорт по умолчанию. Начиная с Node.js 22.12, require() тоже может загрузить ES модуль, если в нём нет top-level await. Что когда выбирать, читайте в статье CommonJS и ES модули.

Частые ошибки

exports.x и module.exports = ... в одном файле. Присваивание заменяет объект, и все exports.x, записанные до него, пропадают. Выберите один стиль на файл.

Экспорт значения, которое потом меняется. module.exports = { count } копирует число в момент загрузки; когда модуль обновляет count, вызывающий код всё ещё видит старое значение. Экспортируйте функцию, которая его возвращает, как counter.js выше.

Пропущенный ./. require('math') ищет пакет в node_modules, а не math.js рядом с вашим файлом. Локальные файлы всегда начинаются с ./ или ../.

Часто задаваемые вопросы

Можно ли использовать module.exports и export в одном файле?

Нет. Файл либо CommonJS (require, module.exports), либо ES модуль (import, export). Node.js решает это по расширению (.cjs или .mjs) или по полю "type" в package.json.

Почему require возвращает пустой объект?

Обычно модуль написал exports = ... вместо module.exports = ..., или два модуля подключают друг друга. При циклическом require один из них получает module.exports другого раньше, чем тот файл выполнится до конца.

Нужно ли расширение .js в require?

Нет. require('./math') пробует ./math, затем ./math.js, ./math.json и ./math.node, затем папку ./math с index.js. В import ES модуля расширение обязательно.

Как перезагрузить модуль, который require уже закэшировал?

Удалите его запись через delete require.cache[require.resolve('./math')] и подключите заново. Это полезно в основном в тестах и инструментах разработки; код приложения не должен на это полагаться.

Иллюстрация языков программирования Coddy

Учитесь программировать с Coddy

Начать