Avatar ·

Зачем нужен module.exports в Node.js и как его правильно использовать?

Всем привет! Я только начал изучать Node.js и никак не могу понять, что такое module.exports и зачем он нужен. Вроде бы я читал документацию, но всё равно путаюсь. Допустим, у меня есть файл `math.js`, где я пишу простые функции для сложения и умножения: ```javascript function add(a, b) { return a + b; } function multiply(a, b) { return a * b; } ``` И в другом файле `app.js` я хочу использовать эти функции: ```javascript console.log(add(2, 3)); // Ошибка: add is not defined ``` Но получаю ошибку. Я понимаю, что нужно как-то "экспортировать" функции, но не понимаю разницу между `module.exports` и `exports`, и почему нельзя просто использовать глобальные переменные. Также я видел, что некоторые пишут `module.exports = { ... }`, а другие `exports.functionName = ...`. Какой способ правильный? И что будет, если я напишу `module.exports = function() { ... }`? Объясните, пожалуйста, на простых примерах, как это работает и когда какой способ использовать. Заранее спасибо! <<>>

Что такое module.exports и как работает система модулей в Node.js

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

Простейший пример экспорта

Исправим ваш math.js:

// math.js
function add(a, b) {
    return a + b;
}

function multiply(a, b) {
    return a * b;
}

// Экспортируем объект с функциями
module.exports = {
    add: add,
    multiply: multiply
};

Теперь в app.js:

// app.js
const math = require('./math.js');

console.log(math.add(2, 3));      // 5
console.log(math.multiply(2, 3)); // 6

require('./math.js') возвращает то, что было присвоено module.exports. В нашем случае — объект с двумя методами.

Разница между module.exports и exports

Изначально exports — это просто ссылка на module.exports. То есть: exports = module.exports. Поэтому можно писать:

// math.js (альтернативный синтаксис)
exports.add = function(a, b) {
    return a + b;
};

exports.multiply = function(a, b) {
    return a * b;
};

Это работает, потому что мы добавляем свойства к объекту, на который ссылается exports. Однако важно: нельзя переопределять exports целиком!

❌ Неправильно:

exports = {
    add: add,
    multiply: multiply
}; // module.exports останется пустым объектом!

✅ Правильно: используйте module.exports, если хотите присвоить новое значение (например, функцию или другой объект).

Когда использовать каждый подход

  • Экспорт нескольких функций/значений: используйте module.exports = { ... } или exports.key = value. Первый способ нагляднее, если экспортируете много сущностей.
  • Экспорт одной функции или класса: используйте module.exports = function() { ... }. Это позволяет импортировать модуль как функцию:
// logger.js
module.exports = function(message) {
    console.log(`[LOG]: ${message}`);
};

// app.js
const log = require('./logger.js');
log('Hello!'); // [LOG]: Hello!
  • Экспорт класса:
// user.js
class User {
    constructor(name) {
        this.name = name;
    }
    greet() {
        return `Hi, I'm ${this.name}`;
    }
}

module.exports = User;

// app.js
const User = require('./user.js');
const user = new User('Alice');
console.log(user.greet()); // Hi, I'm Alice

Почему не использовать глобальные переменные?

Глобальные переменные в Node.js — плохая практика, потому что:

  1. Они загрязняют глобальное пространство имён, что может привести к конфликтам.
  2. Усложняется отладка и тестирование кода.
  3. Нарушается принцип модульности — каждый модуль должен быть независимым.

Важное замечание про пути

В require() пути указываются относительно текущего файла. Для локальных модулей используйте:

  • ./file — файл в той же папке
  • ../folder/file — файл в родительской папке

Без префикса ./ или ../ Node.js будет искать встроенный модуль или пакет из node_modules.

<<>>

Дополнительный взгляд: типы экспорта и особенности

Кроме базового использования, стоит знать несколько тонкостей, которые помогут избежать ошибок.

Экспорт примитивов

Можно экспортировать не только объекты и функции, но и примитивные значения:

// config.js
module.exports = {
    port: 3000,
    dbUrl: 'mongodb://localhost:27017'
};

// Или просто число/строку
module.exports = 42;

Но обычно экспортируют объекты или функции для гибкости.

Проблема с циклическими зависимостями

Если модуль A требует модуль B, а модуль B требует модуль A, Node.js вернёт неполный объект. В таких случаях лучше реструктурировать код или отложить require внутрь функций.

Сравнение с ES-модулями (import/export)

Современный JavaScript (ES6+) использует синтаксис import/export. В Node.js он поддерживается с версии 12 (с флагом --experimental-modules) или в файлах с расширением .mjs. CommonJS (module.exports/require) остаётся стандартом для большинства проектов.

Практический совет

Для простоты придерживайтесь правила: если экспортируете одну сущность — используйте module.exports = ..., если несколько — exports.имя = ... или объект. И никогда не переопределяйте exports целиком, если не уверены в последствиях.