CommonJS: require и module.exports

Как Node делит программу на файлы-модули и связывает их через require и module.exports.

Модуль — это отдельный файл с кодом. В системе CommonJS файл экспортирует то, что хочет отдать наружу, через module.exports, а подключают его через require.

Зачем вообще модули

Держать всю программу в одном файле невозможно: код становится нечитаемым, переменные конфликтуют, переиспользовать ничего нельзя. Модули разбивают программу на части, каждая со своей зоной ответственности. Node исторически использует систему CommonJS.

Экспорт: module.exports

Создадим файл math.js, который отдаёт наружу две функции. То, что мы присвоим в module.exports, станет доступно другим файлам:

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

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

module.exports = { add, multiply };

Всё, что НЕ попало в module.exports, остаётся приватным внутри файла — снаружи к нему не подобраться. Это инкапсуляция «из коробки».

Импорт: require

Теперь подключим этот модуль в другом файле. Функция require возвращает то, что модуль экспортировал. Путь к своему файлу указывают с ./:

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

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

// или сразу разобрать на части:
const { add } = require("./math");
console.log(add(10, 1));            // 11

Запустим node app.js — Node найдёт math.js, выполнит его, заберёт module.exports и подставит в переменную.

Несколько способов экспортировать

Можно собирать экспорт по частям через exports (это короткая ссылка на module.exports):

// logger.js
exports.info = (msg) => console.log("[INFO]", msg);
exports.warn = (msg) => console.log("[WARN]", msg);

А можно экспортировать один-единственный объект или функцию, присвоив его прямо в module.exports:

// config.js
module.exports = {
  port: 3000,
  host: "localhost"
};

Важно: переприсваивать нужно именно module.exports. Если написать exports = {...}, связь потеряется, и наружу ничего не уйдёт.

Модуль выполняется один раз

Когда вы require один и тот же модуль несколько раз, его код выполняется только при первом подключении, а результат кешируется. Это удобно: общий объект-конфиг или подключение к БД создаётся единожды.

Сама идея «собрать публичный интерфейс в объект» — это чистый JavaScript. Вот аналог без файлов, который реально исполнится:

function createMathModule() {
  const add = (a, b) => a + b;
  const multiply = (a, b) => a * b;
  return { add, multiply }; // публичный интерфейс
}

const math = createMathModule();
console.log(math.add(2, 3));
console.log(math.multiply(4, 5));

Вывод:

5
20

Итог

  • Каждый файл в Node — отдельный модуль со своей областью видимости.
  • Экспорт наружу — через module.exports (или exports.имя).
  • Импорт — через require("./путь"); для своих файлов путь с ./.
  • Код модуля выполняется один раз и кешируется.
Проверьте себя
1. Как в CommonJS отдать что-то наружу из модуля?
Aexport default
Bmodule.exports = ...
Creturn
Dpublic ...
2. Как подключить свой файл math.js в CommonJS?
Aimport math from 'math'
Brequire('./math')
Cinclude('math.js')
Dload('./math')
3. Что произойдёт с функцией, которую НЕ добавили в module.exports?
AОна вызовет ошибку
BОна останется приватной внутри файла
CОна экспортируется автоматически
DОна попадёт в global
Поддержать проект