Модули CommonJS¶
Стабильность: 2 – Стабильная
АПИ является удовлетворительным. Совместимость с NPM имеет высший приоритет и не будет нарушена кроме случаев явной необходимости.
Модули CommonJS - это оригинальный способ упаковки кода JavaScript для Node.js. Node.js также поддерживает стандарт ECMAScript modules, используемый браузерами и другими средами выполнения JavaScript.
В Node.js каждый файл рассматривается как отдельный модуль. Например, рассмотрим файл с именем foo.js
:
1 2 3 4 |
|
В первой строке foo.js
загружает модуль circle.js
, который находится в том же каталоге, что и foo.js
.
Вот содержимое circle.js
:
1 2 3 4 5 |
|
Модуль circle.js
экспортировал функции area()
и circumference()
. Функции и объекты добавляются в корень модуля путем указания дополнительных свойств специального объекта exports
.
Переменные, локальные для модуля, будут приватными, поскольку модуль обернут в функцию Node.js (см. module wrapper). В этом примере переменная 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
core module.
Включение¶
Node.js имеет две системы модулей: CommonJS модули и ECMAScript модули.
По умолчанию Node.js будет считать модулями CommonJS следующее:
-
Файлы с расширением
.cjs
; -
Файлы с расширением
.js
, если ближайший родительский файлpackage.json
содержит поле верхнего уровня"type"
со значением"commonjs"
. -
Файлы с расширением
.js
, если ближайший родительскийpackage.json
файл не содержит поля верхнего уровня"type"
. Авторы пакетов должны включать полетип
, даже в пакетах, где все источники являются CommonJS. Явное указаниетипа
пакета облегчит инструментам сборки и загрузчикам определение того, как следует интерпретировать файлы в пакете. -
Файлы с расширением не
.mjs
,.cjs
,.json
,.node
или.js
(если ближайший родительский файлpackage.json
содержит поле верхнего уровня"type"
со значением"module"
, эти файлы будут распознаны как модули CommonJS только в том случае, если они включаются черезrequire()
, но не при использовании в качестве точки входа программы в командной строке).
Более подробную информацию смотрите в Определение системы модулей.
Вызов require()
всегда использует загрузчик модулей CommonJS. Вызов import()
всегда использует загрузчик модулей ECMAScript.
Доступ к главному модулю¶
Когда файл запускается непосредственно из Node.js, require.main
устанавливается в его модуль
. Это означает, что можно определить, был ли файл запущен напрямую, проверив 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
.
Чтобы сделать модули доступными для Node.js REPL, может быть полезно также добавить папку /usr/lib/node_modules
в переменную окружения $NODE_PATH
. Поскольку поиск модулей с помощью папок node_modules
является относительным и основан на реальном пути к файлам, выполняющим вызовы require()
, сами пакеты могут находиться где угодно.
Расширение .mjs
¶
Из-за синхронной природы require()
невозможно использовать его для загрузки файлов модулей ECMAScript. Попытка сделать это приведет к ошибке ERR_REQUIRE_ESM
. Вместо этого используйте import()
.
Расширение .mjs
зарезервировано для Модулей ECMAScript, которые не могут быть загружены через require()
. Смотрите раздел Определение системы модулей для получения дополнительной информации о том, какие файлы разбираются как модули ECMAScript.
Все вместе¶
Чтобы получить точное имя файла, который будет загружен при вызове 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 |
|
Кэширование¶
Модули кэшируются после первой загрузки. Это означает (помимо прочего), что при каждом вызове require('foo')
будет возвращен точно такой же объект, если он будет разрешен в тот же файл.
При условии, что require.cache
не изменен, многократные вызовы require('foo')
не приведут к многократному выполнению кода модуля. Это важная особенность. С ее помощью можно возвращать "частично выполненные" объекты, что позволяет загружать переходные зависимости, даже если они могут вызвать циклы.
Чтобы модуль выполнял код несколько раз, экспортируйте функцию и вызовите ее.
Предостережения по кэшированию модулей¶
Модули кэшируются на основе их разрешенного имени файла. Поскольку модули могут разрешаться в разные имена файлов в зависимости от расположения вызывающего модуля (загрузка из папок node_modules
), это не гарантия того, что require('foo')
всегда будет возвращать точно такой же объект, если он разрешается в разные файлы.
Кроме того, в файловых системах или операционных системах, не чувствительных к регистру, разные разрешенные имена файлов могут указывать на один и тот же файл, но кэш все равно будет рассматривать их как разные модули и будет перезагружать файл несколько раз. Например, require('./foo')
и require('./FOO')
возвращают два разных объекта, независимо от того, являются ли ./foo
и ./FOO
одним и тем же файлом.
Основные модули¶
Node.js имеет несколько модулей, скомпилированных в двоичный файл. Эти модули более подробно описаны в других разделах этой документации.
Основные модули определены в исходном коде Node.js и находятся в папке lib/
.
Основные модули могут быть определены с помощью префикса node:
, в этом случае они обходят кэш require
. Например, require('node:http')
всегда будет возвращать встроенный модуль HTTP, даже если в require.cache
есть запись с таким именем.
Некоторые модули ядра всегда загружаются предпочтительно, если их идентификатор передан в require()
. Например, require('http')
всегда будет возвращать встроенный модуль HTTP, даже если существует файл с таким именем. Список основных модулей, которые могут быть загружены без использования префикса 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 5 6 7 8 |
|
Когда main.js
загружает a.js
, то a.js
в свою очередь загружает b.js
. В этот момент b.js
пытается загрузить a.js
. Чтобы предотвратить бесконечный цикл, незавершенная копия объекта экспорта a.js
возвращается в модуль b.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 – Закрыто
Принимаются только фиксы, связанные с безопасностью, производительностью или баг-фиксы. Пожалуйста, не предлагайте изменений АПИ в разделе с таким индикатором, они будут отклонены.
Вместо этого используйте subpath exports или subpath imports.
Существует три способа передачи папки в require()
в качестве аргумента.
Первый - создать в корне папки файл package.json
, который определяет главный
модуль. Пример файла package.json
может выглядеть следующим образом:
1 |
|
Если бы это находилось в папке ./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
. Использование пакетов subpath exports или subpath imports может обеспечить те же преимущества организации содержимого, что и папки, и модули, и работать как для require
, так и для import
.
Загрузка из папок node_modules
¶
Если идентификатор модуля, переданный в require()
, не является модулем core и не начинается с '/'
, '../'
или './'
, то Node.js начинает с каталога текущего модуля, добавляет /node_modules
и пытается загрузить модуль из этого места. Node.js не будет добавлять node_modules
к пути, который уже заканчивается на node_modules
.
Если модуль не найден там, то он переходит в родительский каталог, и так далее, пока не будет достигнут корень файловой системы.
Например, если файл по адресу '/home/ry/projects/foo.js'
вызывает require('bar.js')
, то Node.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.js пришла к соглашению о расположении зависимых модулей, необходимость в нем отпала. Иногда развертывания, которые полагаются на NODE_PATH
, показывают неожиданное поведение, когда люди не знают, что 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 4 5 6 7 8 9 |
|
Делая это, Node.js достигает нескольких вещей:
- Переменные верхнего уровня (определенные с помощью
var
,const
илиlet
) привязываются к модулю, а не к глобальному объекту. - Это помогает обеспечить некоторые глобальные на вид переменные, которые на самом деле специфичны для модуля, например:
- Объекты
module
иexports
, которые исполнитель может использовать для экспорта значений из модуля. - Удобные переменные
__filename
и__dirname
, содержащие абсолютное имя файла и путь к каталогу модуля.
- Объекты
Область применения модуля¶
__dirname
¶
Имя каталога текущего модуля. Это то же самое, что path.dirname()
из __filename
.
Пример: запуск node example.js
из /Users/mjr
.
1 2 3 4 |
|
__filename
¶
Имя файла текущего модуля. Это абсолютный путь к файлу текущего модуля с разрешенными симлинками.
Для основной программы это имя не обязательно совпадает с именем файла, используемым в командной строке.
Имя каталога текущего модуля смотрите в __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
.
Ссылки на __filename
в пределах b.js
вернут /Users/mjr/app/node_modules/b/b.js
, а ссылки на __filename
в пределах a.js
вернут /Users/mjr/app/a.js
.
exports
¶
Ссылка на module.exports
, который короче по типу. Смотрите раздел о ярлыке exports для подробностей о том, когда использовать exports
и когда использовать module.exports
.
module
¶
- {module}
Ссылка на текущий модуль, см. раздел об объекте module
. В частности, 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
¶
Модули кэшируются в этом объекте, когда они требуются. Если удалить значение ключа из этого объекта, то при следующем require
модуль будет перезагружен. Это не относится к родным аддонам, для которых перезагрузка приведет к ошибке.
Также возможно добавление или замена записей. Этот кэш проверяется перед встроенными модулями, и если в кэш добавлено имя, совпадающее со встроенным модулем, то только node:
-префиксные вызовы require будут получать встроенный модуль. Используйте с care!
1 2 3 4 5 6 7 8 |
|
require.extensions
¶
Стабильность: 0 – устарело или набрало много негативных отзывов
Эта фича является проблемной и ее планируют изменить. Не стоит полагаться на нее. Использование фичи может вызвать ошибки. Не стоит ожидать от нее обратной совместимости.
Указывает require
, как обрабатывать определенные расширения файлов.
Обрабатывать файлы с расширением .sjs
как .js
:
1 |
|
Удалено. В прошлом этот список использовался для загрузки в Node.js не-JavaScript модулей путем их компиляции по требованию. Однако на практике существуют гораздо лучшие способы сделать это, например, загрузить модули через какую-либо другую программу Node.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])
¶
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
¶
В каждом модуле свободная переменная module
является ссылкой на объект, представляющий текущий модуль. Для удобства, module.exports
также доступен через exports
module-global. На самом деле module
не является глобальным, а скорее локальным для каждого модуля.
module.children
¶
- {module[]}
Объекты модуля, впервые требуемые этим модулем.
module.exports
¶
Объект 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()
, которая очень похожа на то, что на самом деле делает require()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
module.filename
¶
Полностью разрешенное имя файла модуля.
module.id
¶
Идентификатор для модуля. Обычно это полностью разрешенное имя файла.
module.isPreloading
¶
- Тип:
<boolean>
true
, если модуль запущен во время фазы предварительной загрузки Node.js.
module.loaded
¶
Завершил ли модуль загрузку или находится в процессе загрузки.
module.parent
¶
Стабильность: 0 – устарело или набрало много негативных отзывов
Эта фича является проблемной и ее планируют изменить. Не стоит полагаться на нее. Использование фичи может вызвать ошибки. Не стоит ожидать от нее обратной совместимости.
Пожалуйста, используйте require.main
и module.children
вместо этого.
- {module | null | undefined}
Модуль, который первым потребовал данный модуль, или null
, если текущий модуль является точкой входа текущего процесса, или undefined
, если модуль был загружен чем-то, что не является модулем CommonJS (например: REPL или import
).
module.path
¶
Имя каталога модуля. Обычно оно совпадает с path.dirname()
из module.id
.
module.paths
¶
Пути поиска для модуля.
module.require(id)
¶
Метод module.require()
предоставляет возможность загрузить модуль так, как если бы require()
был вызван из исходного модуля.
Для этого необходимо получить ссылку на объект module
. Поскольку require()
возвращает module.exports
, а module
обычно только доступен в коде конкретного модуля, для использования он должен быть явно экспортирован.