JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /CommonJS: require, module.exports, exports

CommonJS: require, module.exports, exports

Модуль 4: Node.js, Next.js и Angular
1 уровень , 6 лекция
Открыта

1. Зачем нужны модули и что такое CommonJS

Node.js был задуман как среда для серверного JavaScript, и сразу столкнулся с проблемой: как делить код на части и переиспользовать их? В браузере до появления ES-модулей (import/export) такого механизма не было, и всё работало через глобальные переменные (что, мягко говоря, не очень хорошо).

CommonJS — это спецификация модульной системы, которую реализовал Node.js. Она определяет, как "экспортировать" и "импортировать" код между файлами.

  • Каждый файл в Node.js — это отдельный модуль.
  • Всё, что объявлено внутри файла, по умолчанию "невидимо" для других файлов.
  • Чтобы что-то "отдать наружу" (экспортировать) — используйте module.exports или exports.
  • Чтобы что-то "забрать" из другого файла (импортировать) — используйте require().

Аналогия:
Модули — это как комнаты в большом доме: чтобы что-то вынести наружу, вы кладёте это у двери (module.exports). Чтобы занести что-то из другой комнаты, вы используете ключ (require).

2. require(): импортируем модули

Функция require() — это ваш портал в другие файлы (модули). Она синхронно загружает модуль (один раз), исполняет его код (если ещё не исполнялся), и возвращает то, что модуль экспортировал.

Синтаксис:

const модуль = require('путь_или_имя_модуля');

Примеры:

Импорт стандартного модуля:

const fs = require('fs'); // модуль для работы с файлами
const path = require('path'); // модуль для работы с путями

Импорт своего файла:

const myUtils = require('./my-utils.js'); // относительный путь, обязательно './'

Важно:
Если путь начинается с './' или '../', это ваш файл. Если без точки — это модуль из node_modules или стандартный модуль Node.js.

3. module.exports: как экспортировать из модуля

Всё, что вы хотите "отдать наружу" из файла, вы присваиваете специальному объекту module.exports.

Пример 1: экспорт одной функции

// Файл: greet.js
function greet(name) {
  return `Привет, ${name}!`;
}

module.exports = greet;
Экспорт функции из модуля
// Файл: app.js
const greet = require('./greet');
console.log(greet('Вася')); // Привет, Вася!

Пример 2: экспорт объекта

// Файл: math.js
function add(a, b) {
  return a + b;
}

function sub(a, b) {
  return a - b;
}

module.exports = {
  add,
  sub
};
Экспорт нескольких функций через объект
// Файл: app.js
const math = require('./math');
console.log(math.add(2, 3)); // 5
console.log(math.sub(5, 2)); // 3

Пример 3: экспорт класса

// Файл: User.js
class User {
  constructor(name) {
    this.name = name;
  }
  sayHello() {
    return `Привет, я ${this.name}`;
  }
}

module.exports = User;
Экспорт класса из модуля
// Файл: app.js
const User = require('./User');
const user = new User('Аня');
console.log(user.sayHello()); // Привет, я Аня

4. exports: короткая запись, но с подвохом

Node.js предоставляет ещё одну переменную — exports. Это просто "сокращение" для module.exports, но тут есть нюансы.

// Это корректно
exports.hello = function() {
  console.log('Привет!');
};

В этом случае, вы экспортируете объект с методом hello.

ВАЖНО!
Если вы присвоите что-то напрямую в exports, связь с module.exports теряется!

// ОШИБКА! Это НЕ сработает, как вы ожидаете:
exports = function() { /* ... */ };
// Теперь module.exports всё ещё указывает на старый объект, а exports — на новый.
// require() получит пустой объект!

Вывод:
Если экспортируете отдельные свойства/методы, используйте exports.что_то = ....
Если экспортируете функцию/класс/объект целиком — используйте module.exports = ....

5. Как это работает внутри? (немного магии Node.js)

Когда Node.js загружает ваш файл через require, он оборачивает его в функцию примерно так:

(function(exports, require, module, __filename, __dirname) {
  // ваш код тут
});

То есть, внутри каждого модуля уже есть exports, require, module, и даже переменные с путём к файлу и папке.

  • module.exports — это то, что реально возвращает require().
  • exports — это просто сокращение для удобства, но только пока вы не присвоили ему новое значение.

6. Практика: собираем мини-приложение

Давайте шаг за шагом сделаем простое приложение, используя модули.

Шаг 1. math.js

// math.js
function sum(a, b) {
  return a + b;
}
function mul(a, b) {
  return a * b;
}
module.exports = {
  sum,
  mul
};

Шаг 2. greet.js

// greet.js
exports.hello = function(name) {
  return `Привет, ${name}!`;
};

Шаг 3. app.js

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

console.log(greet.hello('Мир'));          // Привет, Мир!
console.log('2 + 3 =', math.sum(2, 3));   // 2 + 3 = 5
console.log('3 * 4 =', math.mul(3, 4));   // 3 * 4 = 12

Запуск

node app.js

Результат:

Привет, Мир!
2 + 3 = 5
3 * 4 = 12

Повторное использование и кеширование модулей

Node.js загружает и исполняет модуль только один раз. Если вы несколько раз делаете require('./math') в разных файлах, модуль не будет пересоздаваться — будет возвращаться тот же объект.

Это удобно для хранения состояния, но может привести к неожиданностям, если вы вдруг решите мутировать экспортируемый объект.

8. Типичные ошибки при работе с CommonJS

Ошибка №1: Неправильный путь в require

Если забыть './' при импорте своего файла, Node.js будет искать модуль в node_modules, а не рядом с вашим файлом. Например, require('math') ищет пакет, а не файл math.js в вашей папке. Всегда пишите ./math.

Ошибка №2: Перезапись exports

Если вы напишете exports = function() {...}, модуль экспортирует пустой объект! Всегда используйте либо module.exports = ..., либо добавляйте свойства к exports.

Ошибка №3: Экспорт функции и свойств одновременно

Если вы экспортируете функцию через module.exports = function() {...} и потом добавляете свойства к exports, эти свойства никто не увидит. Всё, что присвоено module.exports, "перекрывает" exports.

Ошибка №4: Циклические зависимости

Если два модуля требуют друг друга (A -> B -> A), вы получите частично инициализированный объект. Node.js не падает, но результат может быть неожиданным.

Ошибка №5: Изменение экспортируемого объекта после require

Если вы мутируете экспортируемый объект после того, как его уже импортировали где-то, изменения увидят все, кто сделал require этого модуля. Это может быть полезно (например, для синглтонов), но часто приводит к багам.

1
Задача
Модуль 4: Node.js, Next.js и Angular, 1 уровень, 6 лекция
Недоступна
Импорт стандартного модуля через require
Импорт стандартного модуля через require
1
Задача
Модуль 4: Node.js, Next.js и Angular, 1 уровень, 6 лекция
Недоступна
Экспорт функции и её импорт в другой файл
Экспорт функции и её импорт в другой файл
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ