Модули: CommonJS¶
Стабильность: 2 – Стабильная
Модули CommonJS — изначальный способ упаковки кода JavaScript для Node.js. Node.js также поддерживает стандарт модулей ECMAScript, который используют браузеры и другие среды выполнения JavaScript.
В Node.js каждый файл считается отдельным модулем. Например, рассмотрим файл с именем foo.js:
1 2 | |
В первой строке foo.js загружает модуль circle.js, который находится в том же каталоге, что и foo.js.
Содержимое circle.js:
1 2 3 4 5 | |
Модуль circle.js экспортировал функции area() и circumference(). Функции и объекты добавляются в корень модуля через дополнительные свойства специального объекта exports.
Локальные переменные модуля будут приватными, потому что модуль оборачивается в функцию Node.js (см. обёртку модуля). В этом примере переменная PI приватна для circle.js.
Свойству module.exports можно присвоить новое значение (например, функцию или объект).
В следующем коде bar.js использует модуль square, который экспортирует класс Square:
1 2 3 | |
Модуль square определён в square.js:
1 2 3 4 5 6 7 8 9 10 | |
Система модулей CommonJS реализована во встроенном модуле module.
Включение¶
У Node.js две системы модулей: CommonJS и модули ECMAScript.
По умолчанию Node.js считает модулями CommonJS следующее:
-
Файлы с расширением
.cjs. -
Файлы с расширением
.jsили без расширения, если ближайший родительский файлpackage.jsonсодержит поле верхнего уровня"type"со значением"commonjs". -
Файлы с расширением
.jsили без расширения, если ближайший родительскийpackage.jsonне содержит поля верхнего уровня"type"или в родительских папках нетpackage.json; если только файл не содержит синтаксиса, который вызовет ошибку, если его не оценивать как ES-модуль. Авторам пакетов следует указывать поле"type", даже если все исходники — CommonJS. Явное указаниеtypeпакета упрощает работу инструментов сборки и загрузчиков при определении того, как интерпретировать файлы пакета. -
Файлы с расширением, отличным от
.mjs,.cjs,.json,.nodeи.js, если ближайший родительскийpackage.jsonсодержит поле верхнего уровня"type"со значением"module".
Подробнее см. Определение системы модулей.
Вызов require() всегда использует загрузчик модулей CommonJS. Вызов import() всегда использует загрузчик модулей ECMAScript.
Доступ к главному модулю¶
Когда файл запускается напрямую из Node.js, require.main указывает на его module. Значит, можно определить, запущен ли файл напрямую, проверкой require.main === module.
Для файла foo.js это будет true, если он запущен как node foo.js, и false, если через require('./foo').
Если точка входа не является модулем CommonJS, require.main равен undefined, и главный модуль недоступен.
Советы по менеджерам пакетов¶
Семантика функции Node.js require() рассчитана на достаточно общую поддержку разумных структур каталогов. Программы менеджеров пакетов вроде dpkg, rpm и npm могут собирать нативные пакеты из модулей Node.js без изменений.
Ниже приведена предлагаемая структура каталогов:
Пусть каталог /usr/lib/node/<some-package>/<some-version> хранит содержимое конкретной версии пакета.
Пакеты могут зависеть друг от друга. Чтобы установить пакет foo, может понадобиться конкретная версия пакета bar. У bar могут быть свои зависимости, иногда они конфликтуют или образуют циклы.
Поскольку Node.js вычисляет realpath для загружаемых модулей (разрешает симлинки) и затем ищет зависимости в каталогах node_modules, ситуацию можно разрешить такой схемой:
/usr/lib/node/foo/1.2.3/: содержимое пакетаfoo, версия 1.2.3./usr/lib/node/bar/4.3.2/: содержимое пакетаbar, от которого зависитfoo./usr/lib/node/foo/1.2.3/node_modules/bar: символическая ссылка на/usr/lib/node/bar/4.3.2/./usr/lib/node/bar/4.3.2/node_modules/*: символические ссылки на пакеты, от которых зависитbar.
Таким образом, даже при цикле или конфликтах зависимостей каждый модуль получит подходящую версию своей зависимости.
Когда код пакета foo вызывает require('bar'), подставляется версия по симлинку в /usr/lib/node/foo/1.2.3/node_modules/bar. Когда код пакета bar вызывает require('quux'), подставляется версия по симлинку в /usr/lib/node/bar/4.3.2/node_modules/quux.
Чтобы ускорить поиск модулей, вместо прямого размещения в /usr/lib/node можно класть пакеты в /usr/lib/node_modules/<name>/<version>. Тогда Node.js не будет искать отсутствующие зависимости в /usr/node_modules или /node_modules.
Чтобы модули были доступны в REPL Node.js, полезно добавить каталог /usr/lib/node_modules в переменную окружения $NODE_PATH. Поиск через каталоги node_modules относительный и опирается на реальный путь файлов, вызывающих require(), поэтому сами пакеты могут находиться где угодно.
Загрузка ECMAScript-модулей через require()¶
Расширение .mjs зарезервировано для ECMAScript Modules. См. раздел Определение системы модулей, какие файлы разбираются как ECMAScript-модули.
require() поддерживает загрузку ECMAScript-модулей только при выполнении условий:
- модуль полностью синхронный (без top-level
await); и - выполняется одно из условий:
- у файла расширение
.mjs; - у файла расширение
.js, и ближайшийpackage.jsonсодержит"type": "module"; - у файла расширение
.js, ближайшийpackage.jsonне содержит"type": "commonjs", и в модуле есть синтаксис ES-модуля.
Если загружаемый ES-модуль удовлетворяет требованиям, require() может загрузить его и вернуть объект пространства имён модуля. Поведение похоже на динамический import(), но выполняется синхронно и сразу возвращает объект пространства имён.
Пример ES-модулей:
1 2 | |
1 2 3 4 | |
Их может загрузить модуль CommonJS через require():
1 2 3 4 5 6 7 8 9 10 11 12 | |
Для совместимости с инструментами, переводящими ES-модули в CommonJS и затем загружающими настоящие ES-модули через require(), в возвращаемом пространстве имён может быть свойство __esModule: true, если есть экспорт default, чтобы сгенерированный код распознавал default-экспорт. Если __esModule уже задано, оно не добавляется. Свойство экспериментальное и может измениться. Его должны использовать только инструменты конвертации ES → CommonJS по принятым в экосистеме соглашениям. Код, написанный напрямую на CommonJS, не должен от него зависеть.
Результат require() — объект пространства имён модуля: default-экспорт лежит в свойстве .default, как при import(). Чтобы задать, что именно вернёт require(esm) напрямую, ES-модуль может экспортировать нужное значение под строковым именем "module.exports".
1 2 3 4 5 6 7 8 9 | |
1 2 3 4 5 6 | |
В примере выше при использовании экспорта с именем module.exports именованные экспорты недоступны потребителям CommonJS. Чтобы они сохранили доступ к именованным экспортам, можно сделать default-экспорт объектом со свойствами-экспортами. В этом примере distance можно привязать к default-экспорту — классу Point — как статический метод.
1 2 3 4 5 6 7 8 | |
1 2 3 4 5 | |
Если в require()-ном модуле есть top-level await или в графе его import есть top-level await, выбрасывается ERR_REQUIRE_ASYNC_MODULE. В этом случае асинхронный модуль нужно загружать через import().
При включённом --experimental-print-required-tla вместо выброса ERR_REQUIRE_ASYNC_MODULE до выполнения Node.js выполнит модуль, попытается найти top-level await и выведет их расположение, чтобы упростить исправление.
Если поддержка загрузки ES-модулей через require() даёт неожиданные сбои, её можно отключить флагом --no-require-module. Чтобы вывести места использования этой возможности, используйте --trace-require-module.
Наличие возможности проверяется по process.features.require_module === true.
Всё вместе¶
Чтобы узнать точное имя файла, которое загрузит require(), используйте require.resolve().
Ниже псевдокод высокоуровневого алгоритма работы require():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | |
Кэширование¶
Модули кэшируются после первой загрузки. В частности, каждый вызов require('foo') возвращает один и тот же объект, если он разрешается в тот же файл.
Пока require.cache не меняли, повторные require('foo') не приводят к повторному выполнению кода модуля. Это важно: можно вернуть «частично готовые» объекты и загружать транзитивные зависимости даже при циклах.
Чтобы код модуля выполнялся несколько раз, экспортируйте функцию и вызывайте её.
Ограничения кэша модулей¶
Кэш привязан к разрешённому имени файла. Так как модуль может разрешаться в разные файлы в зависимости от места вызывающего модуля (загрузка из node_modules), нет гарантии, что require('foo') всегда вернёт один и тот же объект при разных файлах.
Кроме того, на файловых системах без учёта регистра разные разрешённые имена могут указывать на один файл, но кэш считает их разными модулями и перезагружает файл. Например, require('./foo') и require('./FOO') дают два разных объекта, независимо от того, один это файл или нет.
Встроенные модули¶
В Node.js несколько модулей встроено в бинарник; они подробнее описаны в других разделах документации.
Встроенные модули определены в исходниках Node.js в каталоге lib/.
Их можно запрашивать с префиксом node: — тогда обходится кэш require. Например, require('node:http') всегда возвращает встроенный HTTP-модуль, даже если в require.cache есть запись с таким именем.
Некоторые встроенные модули всегда имеют приоритет при передаче идентификатора в require(). Например, require('http') всегда даёт встроенный HTTP-модуль, даже если есть файл с таким именем.
Список всех встроенных модулей — в module.builtinModules. Имена перечислены без префикса node:, кроме модулей, для которых префикс обязателен (см. ниже).
Встроенные модули с обязательным префиксом node:¶
При загрузке через require() некоторые встроенные модули нужно запрашивать с префиксом node:. Это снижает риск конфликта с пакетами пользователя с тем же именем. Сейчас префикс node: обязателен для:
Список этих модулей есть в module.builtinModules, с префиксом.
Циклы¶
При циклических вызовах require() модуль может быть возвращён до завершения выполнения.
Пример:
a.js:
1 2 3 4 5 6 | |
b.js:
1 2 3 4 5 6 | |
main.js:
1 2 3 4 | |
Когда main.js загружает a.js, тот загружает b.js. В этот момент b.js пытается загрузить a.js. Чтобы избежать бесконечного цикла, модулю b.js возвращается незавершённая копия объекта экспорта a.js. Затем b.js догружается, и его exports передаётся модулю a.js.
К моменту, когда main.js загрузил оба модуля, они уже выполнены. Вывод программы:
1 2 3 4 5 6 7 8 9 | |
Циклические зависимости требуют продуманной организации кода.
Файлы как модули¶
Если файл с точным именем не найден, Node.js пробует добавить расширения .js, .json и наконец .node. Для другого расширения (например .cjs) нужно передать в require() полное имя включая расширение (например require('./file.cjs')).
Файлы .json разбираются как JSON, .node — как скомпилированные аддоны через process.dlopen(). С любыми другими расширениями или без расширения файл обрабатывается как JavaScript. См. Определение системы модулей, какой режим разбора применяется.
Путь, начинающийся с '/', — абсолютный путь к файлу. Например, require('/home/marco/foo.js') загрузит /home/marco/foo.js.
Путь с префиксом './' задаётся относительно файла, вызывающего require(). То есть circle.js должен лежать в том же каталоге, что и foo.js, чтобы require('./circle') его нашёл.
Без ведущего '/', './' или '../' модуль должен быть встроенным или загружаться из node_modules.
Если путь не существует, require() выбрасывает MODULE_NOT_FOUND.
Каталоги как модули¶
Стабильность: 3 – Закрыто
Используйте вместо этого экспорты подпутей или импорты подпутей.
Передать в require() каталог можно тремя способами.
Первый — создать в корне каталога package.json с полем main. Пример package.json:
1 2 | |
Если каталог — ./some-library, то require('./some-library') попытается загрузить ./some-library/lib/some-library.js.
Если в каталоге нет package.json, или поле "main" отсутствует или не разрешается, Node.js ищет index.js или index.node в этом каталоге. Например, без package.json в примере выше require('./some-library') попытается загрузить:
./some-library/index.js./some-library/index.node
Если и это не удалось, Node.js сообщает, что модуль не найден:
1 | |
Во всех трёх случаях вызов import('./some-library') даст ошибку ERR_UNSUPPORTED_DIR_IMPORT. экспорты подпутей или импорты подпутей дают схожую инкапсуляцию, как у каталогов-модулей, и работают и с require, и с import.
Загрузка из каталогов node_modules¶
Если идентификатор для require() — не встроенный модуль и не начинается с '/', '../' или './', Node.js начинает с каталога текущего модуля, добавляет /node_modules и пытается загрузить модуль оттуда. К пути, уже оканчивающемуся на node_modules, ещё один node_modules не добавляется.
Если не найдено, поиск поднимается к родительскому каталогу и так далее до корня файловой системы.
Например, если файл '/home/ry/projects/foo.js' вызывает require('bar.js'), порядок поиска:
/home/ry/projects/node_modules/bar.js/home/ry/node_modules/bar.js/home/node_modules/bar.js/node_modules/bar.js
Так зависимости можно локализовать и избежать конфликтов.
Можно подключать конкретные файлы или подмодули пакета, указав суффикс пути после имени. Например, require('example-module/path/to/file') разрешит path/to/file относительно расположения example-module. Для суффикса действуют те же правила разрешения.
Загрузка из глобальных каталогов¶
Если задана переменная окружения NODE_PATH со списком абсолютных путей через двоеточие, Node.js ищет модули и там, если не нашла раньше.
В Windows разделитель NODE_PATH — точка с запятой (;), не двоеточие.
NODE_PATH изначально добавляли для загрузки с разных путей до появления нынешнего алгоритма разрешения модулей.
NODE_PATH по-прежнему поддерживается, но реже нужен: в экосистеме принят поиск через локальные node_modules. Развёртывания на NODE_PATH иногда ведут себя неожиданно, если переменную забыли задать. Смена зависимостей может привести к загрузке другой версии при обходе NODE_PATH.
Дополнительно Node.js ищет в списке GLOBAL_FOLDERS:
- 1:
$HOME/.node_modules - 2:
$HOME/.node_libraries - 3:
$PREFIX/lib/node
Здесь $HOME — домашний каталог пользователя, $PREFIX — префикс установки Node.js (node_prefix).
В основном это наследие прошлого.
Настоятельно рекомендуется держать зависимости в локальном node_modules — так быстрее и надёжнее.
Обёртка модуля¶
Перед выполнением кода модуля Node.js оборачивает его в функцию вида:
1 2 3 | |
Так Node.js:
- ограничивает область видимости переменных верхнего уровня (
var,const,let) модулем, а не глобальным объектом; - даёт переменные, похожие на глобальные, но привязанные к модулю:
- объекты
moduleиexportsдля экспорта значений; __filenameи__dirname— абсолютный путь к файлу и к каталогу модуля.
Область видимости модуля¶
__dirname¶
- Тип:
<string>
Имя каталога текущего модуля. Совпадает с path.dirname() от __filename.
Пример: запуск node example.js из /Users/mjr
1 2 3 4 | |
__filename¶
- Тип:
<string>
Имя файла текущего модуля — абсолютный путь с разрешёнными симлинками.
Для главной программы это не обязательно то же имя, что в командной строке.
См. __dirname для каталога текущего модуля.
Примеры:
Запуск node example.js из /Users/mjr
1 2 3 4 | |
Два модуля: a и b, где b — зависимость a, структура каталогов:
/Users/mjr/app/a.js/Users/mjr/app/node_modules/b/b.js
В b.js ссылки на __filename дают /Users/mjr/app/node_modules/b/b.js, в a.js — /Users/mjr/app/a.js.
exports¶
- Тип:
<Object>
Сокращение для module.exports. См. сокращение exports, когда использовать exports, а когда module.exports.
module¶
- Тип:
<module>
Ссылка на текущий модуль, см. объект module object. module.exports задаёт, что модуль экспортирует и отдаёт через require().
require(id)¶
Импорт модулей, JSON и локальных файлов. Модули из node_modules, локальные файлы и JSON — через относительный путь (например ./, ./foo, ./bar/baz, ../foo), разрешаемый относительно __dirname (если есть) или текущего рабочего каталога. Относительные пути в стиле POSIX разрешаются одинаково на разных ОС, в том числе в Windows как в Unix.
1 2 3 4 5 6 7 8 9 | |
require.cache¶
- Тип:
<Object>
Здесь кэшируются загруженные модули. Удалив ключ, следующий require перезагрузит модуль. Не действует для нативных аддонов — повторная загрузка вызовет ошибку.
Записи можно добавлять и заменять. Кэш проверяется до встроенных модулей; если имя совпадает со встроенным, встроенный модуль получат только вызовы с префиксом node:. Пользуйтесь осторожно!
1 2 3 4 5 6 7 8 | |
require.extensions¶
Стабильность: 0 – устарело или набрало много негативных отзывов
- Тип:
<Object>
Задаёт обработку расширений файлов для require.
Обрабатывать .sjs как .js:
1 | |
Устарело. Раньше список использовали для подгрузки не-JS модулей с компиляцией на лету. Сейчас лучше загружать через отдельную программу или заранее компилировать в JavaScript.
Избегайте require.extensions — возможны тонкие ошибки, а каждое новое расширение замедляет разрешение.
require.main¶
- Тип:
<module>| undefined
Объект Module для сценария входа при запуске процесса Node.js, или undefined, если точка входа не CommonJS-модуль. См. «Доступ к главному модулю».
В скрипте entry.js:
1 | |
1 | |
1 2 3 4 5 6 7 8 9 10 11 12 | |
require.resolve(request[, options])¶
Добавлено в: v0.3.0
request<string>Путь модуля, который нужно разрешить.options<Object>paths<string[]>Каталоги для разрешения модуля. Если заданы, используются вместо путей по умолчанию, кроме GLOBAL_FOLDERS вроде$HOME/.node_modules— они всегда учитываются. Каждый путь — стартовая точка для алгоритма разрешения, то есть от неё проверяется иерархияnode_modules.- Возвращает:
<string>
Внутренний механизм require() для поиска файла модуля без его загрузки — возвращается разрешённый путь.
Если модуль не найден, выбрасывается MODULE_NOT_FOUND.
require.resolve.paths(request)¶
request<string>Путь модуля, для которого нужно получить пути поиска.- Возвращает:
<string[]>| null
Массив путей, просмотренных при разрешении request, или null, если request — встроенный модуль, например http или fs.
Объект module¶
- Тип:
<Object>
В каждом модуле свободная переменная module — ссылка на объект текущего модуля. Для удобства к module.exports есть доступ через глобальную для модуля переменную exports. Сама module не глобальна, а локальна для модуля.
module.children¶
- Тип:
<module[]>
Объекты модулей, которые этот модуль впервые подключил через require.
module.exports¶
- Тип:
<Object>
Объект module.exports создаётся системой Module. Иногда нужен не он, а, например, экземпляр класса — тогда присвойте module.exports нужный объект. Присвоение того же объекта переменной exports лишь перепривяжет локальную exports, что обычно не то, что нужно.
Пример модуля a.js:
1 2 3 4 5 6 7 8 9 | |
В другом файле:
1 2 3 4 | |
Присваивание module.exports должно быть сразу, не в колбэках. Так не сработает:
x.js:
1 2 3 | |
y.js:
1 2 | |
Сокращение exports¶
Переменная exports есть в области файла модуля и до выполнения равна module.exports.
Это сокращение: module.exports.f = ... можно писать как exports.f = .... Но при присвоении exports нового значения она перестаёт указывать на module.exports:
1 2 | |
Когда module.exports полностью заменяют новым объектом, часто одновременно переприсваивают exports:
1 2 3 | |
Иллюстративная упрощённая модель require(), близкая к реальности:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
module.filename¶
- Тип:
<string>
Полностью разрешённое имя файла модуля.
module.id¶
- Тип:
<string>
Идентификатор модуля. Обычно это полностью разрешённое имя файла.
module.isPreloading¶
- Тип:
<boolean>true, если модуль выполняется на фазе предзагрузки Node.js.
module.loaded¶
- Тип:
<boolean>
Загрузка модуля завершена или ещё идёт.
module.parent¶
Стабильность: 0 – устарело или набрало много негативных отзывов
Используйте вместо этого require.main и module.children.
- Тип:
<module>| null | undefined
Модуль, который первым подключил этот, или null, если текущий модуль — точка входа процесса, или undefined, если загрузчик не CommonJS (например REPL или import).
module.path¶
- Тип:
<string>
Каталог модуля. Обычно совпадает с path.dirname() от module.id.
module.paths¶
- Тип:
<string[]>
Пути поиска для модуля.
module.require(id)¶
module.require() загружает модуль так, как если бы require() вызвали из исходного модуля.
Нужна ссылка на объект module: require() возвращает module.exports, а сам module обычно доступен только внутри кода модуля, поэтому его иногда явно экспортируют.
Объект Module¶
Раздел перенесён в Модули: встроенный модуль module.
Поддержка карт исходного кода v3¶
Раздел перенесён в Модули: встроенный модуль module.
module.findSourceMap(path)- Class:
module.SourceMap new SourceMap(payload)sourceMap.payloadsourceMap.findEntry(lineNumber, columnNumber)