Что такое CommonJS-модуль в Node.js?

Ответ

CommonJS — это стандарт модульной системы, который был основным в Node.js до появления ES Modules. Он использует синхронную загрузку модулей и основан на функциях require() для импорта и module.exports/exports для экспорта.

Базовый пример:

// calculator.js
const add = (a, b) => a + b;
const multiply = (a, b) => a * b;

module.exports = {
  add,
  multiply
};

// или альтернативно:
exports.add = add;
exports.multiply = multiply;

// app.js
const calculator = require('./calculator');
console.log(calculator.add(5, 3)); // 8
console.log(calculator.multiply(4, 2)); // 8

Ключевые особенности CommonJS:

  • Синхронная загрузка: модули загружаются синхронно при первом вызове require()
  • Кэширование: модуль загружается только один раз, последующие вызовы require() возвращают кэшированный экземпляр
  • Циклические зависимости: поддерживаются, но могут вести себя неочевидно
  • module.exports vs exports: exports — это ссылка на module.exports, перезапись exports новой ссылкой нарушит экспорт

Пример циклической зависимости в CommonJS:

// a.js
console.log('a starting');
exports.done = false;
const b = require('./b.js');
console.log('in a, b.done =', b.done);
exports.done = true;
console.log('a done');

// b.js
console.log('b starting');
exports.done = false;
const a = require('./a.js');
console.log('in b, a.done =', a.done);
exports.done = true;
console.log('b done');

В современных Node.js-проектах я часто использую смешанный подход: CommonJS для конфигурационных файлов и утилит, а ES Modules для основного кода приложения.

Ответ 18+ 🔞

Давай разберем эту тему, а то у некоторых от этих модулей уже глаза на лоб лезут, как у совы на дискотеке.

CommonJS — это, по сути, старый добрый дедушка модульной системы в Node.js, который всех кормил и поил, пока не появились эти ваши модные ES Modules. Работает всё через require(), чтобы подтянуть модуль, и module.exports, чтобы его наружу выдать. Всё синхронно, чинно-благородно, как в советской очереди за колбасой.

Смотри, как это выглядит на практике:

// calculator.js
const add = (a, b) => a + b;
const multiply = (a, b) => a * b;

module.exports = {
  add,
  multiply
};

// или можно так, но осторожно, тут подвох есть:
exports.add = add;
exports.multiply = multiply;

// app.js
const calculator = require('./calculator');
console.log(calculator.add(5, 3)); // 8
console.log(calculator.multiply(4, 2)); // 8

А теперь главные фишки CommonJS, без которых ты просто манда с ушами:

  • Загрузка синхронная — модуль встаёт как вкопанный при первом require(), и всё ждут, пока он не прогрузится. Никакой асинхронщины, всё по старинке.
  • Кэширование — модуль грузится один раз, а потом все ходят и пользуются одним и тем же экземпляром, как общей зубной щёткой в общаге. Экономия, бля, хоть куда.
  • Циклические зависимости — вроде как работают, но поведение иногда такое, что сам от себя охуеешь. Лучше без этого.
  • module.exports против exports — вот тут, ёпта, собака зарыта. exports — это просто ссылка, и если её переприсвоить, то всё, пиши пропало, экспорт накрылся медным тазом.

Вот тебе пример, как эти циклы могут мозг вынести:

// a.js
console.log('a starting');
exports.done = false;
const b = require('./b.js');
console.log('in a, b.done =', b.done);
exports.done = true;
console.log('a done');

// b.js
console.log('b starting');
exports.done = false;
const a = require('./a.js');
console.log('in b, a.done =', a.done);
exports.done = true;
console.log('b done');

В общем, в современных проектах часто получается такая сборная солянка: CommonJS оставляем для всяких конфигов и утилит, которые не спешат, а основную логику уже пишем на ES Modules. Главное — не путать и не устраивать в коде ебушки-воробушки из-за непонимания, что куда импортируется.