Точка входа в Node.js — require.main и import.meta
Точка входа — это главный, стартовый файл приложения или скрипта, с которого начинается выполнение всей программы, и который служит корневым узлом в графе зависимостей, инициируя загрузку всех остальных необходимых модулей, конфигураций и сервисов. В контексте Node.js точкой входа обычно является файл, указанный в поле "main" файла package.json, или файл с именем index.js, index.mjs или index.cjs в корне проекта, и именно этот файл передается интерпретатору node при запуске команды, например, node app.js. Точка входа несет ответственность за первоначальную настройку окружения, создание экземпляров ключевых компонентов, запуск сервера, подключение к базам данных и определение глобальных обработчиков ошибок, а также за импорт всех необходимых модулей, без которых приложение не сможет функционировать. В сложных проектах точка входа часто сводится к минимальному коду, который лишь делегирует запуск более специализированным файлам или фреймворкам, таким как Express или NestJS, позволяя сохранить чистоту архитектуры и упростить тестирование. Выбор правильной точки входа критически важен для организации структуры проекта, поскольку она задает контекст выполнения, определяет относительные пути для импортов и влияет на то, как сборщики или упаковщики, такие как Webpack или Rollup, обрабатывают код при подготовке к production. Кроме того, точка входа может различаться для разных сред, например, отдельный файл для разработки с горячей перезагрузкой и отдельный для продакшена с оптимизированными настройками, что позволяет гибко управлять поведением приложения в зависимости от окружения.
В Python есть if __name__ == "__main__":. В Node.js тот же вопрос звучит так: файл запустили командой node file.js или подключили через require / import?
См. также: Первая программа на Node.js · модули Node · ESM в браузере · Python — if name == "main" · карта Node.js.
Запуск файла и подключение как модуль
Запуск файла — это процесс передачи интерпретатору Node.js конкретного JavaScript-файла для немедленного выполнения, в результате чего код этого файла читается, компилируется движком V8 и исполняется строка за строкой в порядке их следования, при этом все синхронные операции выполняются последовательно, а асинхронные — планируются через event loop. Когда разработчик вводит команду node filename.js в терминале, операционная система запускает процесс Node.js, загружает указанный файл, оборачивает его в специальную функцию-обертку, которая предоставляет доступ к объектам require, module, exports, __filename и __dirname, и затем выполняет эту функцию, создавая уникальную область видимости для данного модуля. Запуск файла может быть как основным, когда файл является точкой входа всего приложения, так и второстепенным, когда разработчик запускает отдельный скрипт для выполнения утилитарных задач, например, миграции базы данных, генерации документации или запуска тестов. В процессе запуска файла Node.js также обрабатывает аргументы командной строки через объект process.argv, что позволяет передавать в скрипт дополнительные параметры и флаги, меняющие его поведение без изменения исходного кода. Важно понимать, что запуск файла в Node.js всегда однопоточен с точки зрения выполнения пользовательского JavaScript-кода, но тяжелые операции ввода-вывода делегируются libuv и выполняются асинхронно, не блокируя основной поток выполнения. Также стоит отметить, что при запуске файла Node.js инициализирует глобальные объекты, настраивает цикл событий и после выполнения всего синхронного кода переходит в режим ожидания для обработки асинхронных колбэков, таймеров и сетевых запросов, завершая процесс только тогда, когда не остается ни одной активной задачи.
Подключение как модуль — это процесс загрузки одного JavaScript-файла внутрь другого с целью использования его экспортированных функций, объектов, классов или переменных, причем этот файл не выполняется как самостоятельное приложение, а становится частью более крупной системы, дополняя её функциональность. В экосистеме Node.js подключение как модуль означает, что код из подключаемого файла выполняется в контексте вызывающего модуля, но при этом сохраняет свою собственную область видимости, и единственное, что становится доступным снаружи, — это то, что было явно объявлено через экспорт, будь то через module.exports или exports в CommonJS, либо через ключевое слово export в ESM. Когда один файл подключает другой как модуль, Node.js загружает этот файл, кеширует его результат после первого выполнения, чтобы при повторных подключениях не выполнять код заново, и возвращает ссылку на экспортированный объект, которую можно использовать в вызывающем коде. Это принципиально отличает подключение как модуль от запуска файла: в первом случае файл становится библиотекой или зависимостью, во втором — он становится главным исполняемым процессом, управляющим жизненным циклом приложения. Подключение как модуль лежит в основе архитектурного паттерна композиции, позволяя разбивать огромные системы на множество маленьких, переиспользуемых кусочков, каждый из которых отвечает за свою узкую область ответственности, что значительно упрощает поддержку и масштабирование кодовой базы. Кроме того, механизм подключения как модуля обеспечивает инкапсуляцию, скрывая внутренние детали реализации и предоставляя наружу только четко определенный контракт интерфейса, что защищает от случайного изменения состояния извне и способствует слабой связанности компонентов.
Файл calculator.js экспортирует функции и внизу демонстрирует их:
function add(a, b) {
return a + b;
}
console.log(add(5, 3));
module.exports = { add };
Команда node -e "require('./calculator')" выведет 8, хотя нужна была только функция add. Демо-код стоит выполнять только при прямом запуске.
CommonJS — require.main === module
Require — это синхронная функция, встроенная в систему CommonJS в Node.js, которая используется для импорта модулей, загружая их содержимое, выполняя их код в момент вызова и возвращая объект экспорта, который затем может быть присвоен переменной и использован в текущем файле. Функция require принимает строковый аргумент, который может быть именем встроенного модуля (например, 'fs', 'http', 'path'), относительным или абсолютным путем к пользовательскому файлу (например, './utils', '../config'), либо именем пакета из директории node_modules (например, 'express', 'lodash'), и Node.js выполняет сложный алгоритм разрешения пути, начиная с текущей директории и поднимаясь вверх по иерархии файловой системы в поисках запрашиваемого модуля. При первом вызове require для конкретного модуля Node.js загружает файл, оборачивает его в функцию-обертку, выполняет и кеширует результат в объекте require.cache, поэтому все последующие вызовы require для того же модуля возвращают уже закешированный объект экспорта, что гарантирует, что код модуля выполняется только один раз за время работы приложения. Важной особенностью require является её синхронность, что означает, что выполнение программы приостанавливается до тех пор, пока модуль не будет полностью загружен и выполнен, что делает эту функцию отличным выбором для серверной среды, где файлы читаются с локального диска с высокой скоростью, но абсолютно неприемлемо для браузеров или сетевых сред. Кроме того, require поддерживает динамическую загрузку, позволяя импортировать модули условно внутри функций, циклов или блоков if, что дает невероятную гибкость при создании плагинов, реализаций фич-флагов или ленивой инициализации тяжелых зависимостей. Функция require также умеет автоматически обрабатывать расширения файлов, подставляя .js, .json и .node, если расширение не указано явно, и предоставляет доступ к специальным объектам __dirname и __filename, которые содержат информацию о расположении текущего файла.
В модулях CommonJS (require, module.exports) Node задаёт:
| Объект | Смысл |
|---|---|
module | Текущий модуль |
require.main | Модуль, с которого начался процесс |
require.main === module | true, если этот файл — точка входа |
// calculator.cjs
function add(a, b) {
return a + b;
}
module.exports = { add };
if (require.main === module) {
console.log(add(5, 3));
}
| Способ | Поведение |
|---|---|
node calculator.cjs | Показывает демо |
require('./calculator.cjs') | Только экспорт, без console.log |
Часто логику выносят в main() и вызывают её внутри проверки — как в Python.
ESM — import.meta.url и process.argv[1]
Import — это ключевое слово и синтаксическая конструкция, введенная в спецификации ECMAScript 2015, представляющая собой официальный стандарт импорта модулей в современном JavaScript, которая работает асинхронно, статически анализируется на этапе компиляции и позволяет загружать зависимости строго декларативным способом в начале файла. В отличие от синхронной функции require, import является частью языка, а не функцией среды выполнения, и он может использоваться в двух основных формах: статический импорт с фиксированным синтаксисом import ... from 'module', который анализируется парсером до выполнения кода и не может быть вызван динамически, и динамический импорт через функцию import(), возвращающую промис, которая позволяет загружать модули асинхронно и условно в любой точке программы. Статический импорт обладает неоспоримыми преимуществами, такими как возможность проведения tree-shaking, то есть удаления неиспользуемых экспортов из итогового бандла, а также обеспечения более быстрой загрузки благодаря предварительному анализу графа зависимостей до начала исполнения, что особенно важно для браузерных приложений. В Node.js поддержка import была добавлена значительно позже, начиная с версии 12, и требует либо использования расширения .mjs для файлов, либо настройки "type": "module" в package.json, причем важно понимать, что import и require не могут быть смешаны в одном файле без специальных обходных путей, поскольку они относятся к разным системам модульности с различными моделями выполнения и разрешения путей. Импорт поддерживает множество вариаций синтаксиса, включая импорт всего пространства имен объекта (import * as Module), импорт конкретных именованных экспортов (import { named }), импорт экспорта по умолчанию (import anyName from), а также их комбинации, что позволяет разработчику гибко управлять тем, какие именно сущности попадают в текущую область видимости. Переход от require к import является глобальным трендом в экосистеме JavaScript, поскольку import предлагает более строгую, предсказуемую и оптимизируемую модель загрузки, соответствующую современным стандартам веб-разработки и поддерживаемую как браузерами, так и серверными средами, хотя этот переход и сопровождается определенными сложностями из-за различий в поведении, особенно в отношении циклических зависимостей и разрешения путей.
В ES-модулях ("type": "module" в package.json или расширение .mjs) переменной require.main нет. Сравнивают путь запущенного файла с URL текущего модуля:
// calculator.mjs
import { fileURLToPath } from 'node:url';
export function add(a, b) {
return a + b;
}
const isMain =
process.argv[1] === fileURLToPath(import.meta.url);
if (isMain) {
console.log(add(5, 3));
}
import.meta.url— URL текущего файла; по нему в ESM понимают, какой модуль выполняется.process.argv[1]— путь, который передали вnode ….
В браузере скрипт подключают через <script type="module">; отдельной точки входа в стиле CLI там нет. Паттерн с require.main и проверкой import.meta.url нужен в Node.js, утилитах и тестах.
Когда писать проверку
| Ситуация | Нужна ли |
|---|---|
Одноразовый скрипт в npm run | По желанию |
| Модуль с функциями + локальное демо | Да |
Express-приложение в app.js, импортируемое в тестах | Да — listen только при прямом запуске файла |
Только export без кода верхнего уровня | Часто достаточно без обёртки |
Сравнение с Python
| Python | Node.js (CJS) | Node.js (ESM) | |
|---|---|---|---|
| Проверка | __name__ == "__main__" | require.main === module | process.argv[1] и import.meta.url |
| Импорт выполняет код верхнего уровня | Да | Да | Да |
Общий обзор по языкам — в Python — if name == "main".
Связанные материалы
| Тема | Статья |
|---|---|
npm, package.json | Первая программа на Node.js — Первая программа |
| CommonJS и ESM подробнее | Node.js - серверный JavaScript — Node.js |
import.meta в браузере | 40 — ESM |
| Карта блока Node | intro |