Одной из ключевых возможностей Node.js, унаследованной от её серверной природы, является мощная модульная система. В отличие от веб-браузера, где загрузка десятков отдельных JavaScript-файлов по сети была бы медленной, Node.js работает в среде с быстрой файловой системой. Это позволяет разбивать код на множество небольших, логически организованных файлов — модулей. Такой подход делает код более структурированным, удобным для сопровождения и повторного использования.
Каждый файл с кодом JavaScript (с расширением .js, .mjs или .cjs) в Node.js считается самостоятельным модулем. Самое важное, что нужно понять — все переменные, функции, константы и классы, объявленные в модуле, являются приватными.
Представьте, что каждый файл обёрнут в собственную, невидимую функцию, которая изолирует его код от глобальной области видимости. Это предотвращает конфликты имён между разными частями вашей программы.
Чтобы поделиться частью своей функциональности с другими модулями, модуль должен её экспортировать. Соответственно, чтобы использовать код из другого модуля, ваш модуль должен его импортировать.
Node.js автоматически создает для каждого модуля специальный объект module. У этого объекта есть свойство exports (module.exports). Именно этот объект и возвращается другим модулям, когда они импортируют ваш модуль с помощью require(). Для удобства также предоставляется прямая ссылка на этот объект — переменная exports.
Вы можете добавлять новые свойства к объекту exports, чтобы экспортировать функции, объекты или значения по одному.
Пример: Модуль stats.js с утилитами для статистики
// Внутри файла stats.js
// Эти функции приватны для модуля, если не экспортировать их
const sum = (x, y) => x + y;
const square = (x) => x * x;
// Экспортируем публичные методы, добавляя их в объект exports
exports.mean = (data) => data.reduce(sum) / data.length;
// Обратите внимание: внутри мы можем использовать exports.mean
exports.stddev = function(d) {
let m = exports.mean(d);
return Math.sqrt(d.map(x => x - m).map(square).reduce(sum) / (d.length - 1));
};В этом примере модуль предоставляет наружу объект с двумя методами: mean и stddev. Внутренние функции sum и square остаются скрытыми от внешнего мира.
Часто модуль предназначен для экспорта всего одного значения: например, одного класса или функции. В этом случае принято полностью перезаписывать объект module.exports.
Важное отличие: exports — это просто ссылка на изначальный module.exports. Если вы присвоите новой переменной exports, вы разорвете эту связь. Поэтому для одиночного экспорта всегда используйте module.exports.
Пример: Экспорт одного класса
// Внутри файла bitset.js
class BitSet {
// реализация класса
}
// Мы хотим, чтобы при импорте модуль возвращал этот класс, а не объект
module.exports = BitSet; // Теперь require('./bitset.js') вернет сам класс BitSetПример: Экспорт объекта в конце файла Альтернативный и очень чистый подход — сначала объявить всё, а затем одним махом экспортировать нужные части.
// Внутри файла stats-v2.js
// 1. Сначала объявляем всё (и публичное, и приватное)
const sum = (x, y) => x + y;
const square = (x) => x * x;
const mean = (data) => data.reduce(sum) / data.length;
const stddev = (d) => {
let m = mean(d);
return Math.sqrt(d.map(x => x - m).map(square).reduce(sum) / (d.length - 1));
};
// 2. Затем создаем публичный API, экспортируя объект
module.exports = { mean, stddev }; // Используем сокращенную запись свойства из ES6Для импорта модулей в Node.js используется глобальная функция require().
Если вы импортируете модуль, встроенный в Node.js (например, fs для работы с файлами) или установленный через npm (например, express), вы просто указываете его имя.
const fs = require('fs'); // Встроенный модуль 'fs'
const express = require('express'); // Сторонний модуль, установленный via npm
const http = require('http'); // Встроенный модуль 'http'Node.js знает, где искать такие модули.
Чтобы импортировать ваш собственный файл, нужно передать в require() путь к этому файлу, относительный к текущему файлу.
// Импорт модуля из той же папки
const stats = require('./stats.js'); // .js можно опустить: require('./stats')
// Импорт модуля из дочерней папки 'utils'
const BitSet = require('./utils/bitset.js');
// Импорт модуля из родительской папки
const config = require('../config.js');Обратите внимание на префиксы ./ и ../. Они критически важны, так как говорят Node.js, что это не встроенный модуль, а локальный файл. Без них Node.js будет искать модуль с именем stats в стандартных папках и среди установленных пакетов.
То, что вы получите в переменную (stats, BitSet и т.д.), зависит от того, что модуль экспортировал через module.exports.
-
Если модуль экспортирует объект с несколькими свойствами (как
stats.js): Вы можете сохранить весь объект и обращаться к его свойствам.const stats = require('./stats.js'); let average = stats.mean([1, 2, 3, 4, 5]); let deviation = stats.stddev([1, 2, 3, 4, 5]);
Это создает четкое пространство имен (
stats), что делает код понятнее.Или вы можете использовать деструктуризацию, чтобы сразу извлечь нужные функции в отдельные переменные.
const { mean, stddev } = require('./stats.js'); let average = mean([1, 2, 3, 4, 5]); let deviation = stddev([1, 2, 3, 4, 5]);
Этот код более лаконичен, но может быть менее ясен его контекст, если импортируется много функций из разных модулей.
-
Если модуль экспортирует одно значение (как
bitset.js): Вы получаете это значение напрямую.const BitSet = require('./utils/bitset.js'); // BitSet теперь это класс const myBitSet = new BitSet();
Описанный выше синтаксис с require() и module.exports является стандартом для Node.js и известен как CommonJS (CJS).
Однако современный JavaScript (ECMAScript) имеет собственную, нативную систему модулей, которая использует ключевые слова import и export. Она часто называется ES Modules (ESM).
- Вместо
require()используетсяimport. - Вместо
module.exportsиспользуетсяexport.
Со временем ES Modules становятся все более популярными и поддерживаются в Node.js. Многие современные проекты, особенно те, которые используют сборщики (like Webpack, Vite) или предназначены и для браузера, и для сервера, предпочитают синтаксис ES Modules.
Тем не менее, огромное количество существующих проектов и библиотек в экосистеме Node.js до сих пор используют CommonJS, поэтому его понимание абсолютно необходимо для любого разработчика.
- Изоляция: Каждый файл — это отдельная вселенная. Ничто из него не «утекает» наружу без явного экспорта.
- Экспорт: Используйте
exports.myFunctionдля экспорта нескольких значений. Используйтеmodule.exports = myValueдля экспорта одного значения (класса, функции, объекта). - Импорт: Используйте
const myModule = require('./path/to/module')для импорта. - Пути: Всегда используйте
./или../для импорта своих файлов. - Ясность: Выбирайте способ импорта (весь объект или деструктуризацию) в зависимости от контекста и того, что делает код более читаемым.
Понимание модулей — это фундаментальный шаг к написанию чистой, хорошо организованной и масштабируемой кодовой базы на Node.js. Практикуйтесь в создании и импорте своих модулей, чтобы почувствовать эту мощную концепцию.