Модули: Модули CommonJS
[Стабильно: 2 - Стабильно]
Стабильно: 2 Стабильность: 2 - Стабильно
Модули CommonJS — это изначальный способ упаковки кода JavaScript для Node.js. Node.js также поддерживает стандарт модулей ECMAScript, используемый браузерами и другими средами выполнения JavaScript.
В Node.js каждый файл рассматривается как отдельный модуль. Например, рассмотрим файл с именем foo.js
:
const circle = require('./circle.js')
console.log(`Площадь круга радиусом 4 равна ${circle.area(4)}`)
В первой строке foo.js
загружает модуль circle.js
, который находится в том же каталоге, что и foo.js
.
Вот содержимое circle.js
:
const { PI } = Math
exports.area = r => PI * r ** 2
exports.circumference = r => 2 * PI * r
Модуль circle.js
экспортировал функции area()
и circumference()
. Функции и объекты добавляются в корень модуля путем указания дополнительных свойств в специальном объекте exports
.
Локальные для модуля переменные будут приватными, поскольку модуль обернут в функцию Node.js (см. обертку модуля). В этом примере переменная PI
является приватной для circle.js
.
Свойству module.exports
может быть присвоено новое значение (например, функция или объект).
В следующем коде bar.js
использует модуль square
, который экспортирует класс Square:
const Square = require('./square.js')
const mySquare = new Square(2)
console.log(`Площадь mySquare равна ${mySquare.area()}`)
Модуль square
определен в square.js
:
// Присваивание exports не изменит модуль, необходимо использовать module.exports
module.exports = class Square {
constructor(width) {
this.width = width
}
area() {
return this.width ** 2
}
}
Система модулей CommonJS реализована в основном модуле module
.
Включение
Node.js имеет две системы модулей: модули CommonJS и модули ECMAScript.
По умолчанию Node.js будет обрабатывать следующее как модули CommonJS:
- Файлы с расширением
.cjs
; - Файлы с расширением
.js
, если ближайший родительский файлpackage.json
содержит поле верхнего уровня"type"
со значением"commonjs"
. - Файлы с расширением
.js
или без расширения, если ближайший родительский файлpackage.json
не содержит поля верхнего уровня"type"
или отсутствует файлpackage.json
в любой родительской папке; если только файл не содержит синтаксис, который вызывает ошибку, если он не интерпретируется как ES-модуль. Авторы пакетов должны включать поле"type"
, даже в пакетах, где все источники являются CommonJS. Явное указание типа пакета упростит для инструментов сборки и загрузчиков определение того, как следует интерпретировать файлы в пакете. - Файлы с расширением, отличным от
.mjs
,.cjs
,.json
,.node
или.js
(если ближайший родительский файлpackage.json
содержит поле верхнего уровня"type"
со значением"module"
, эти файлы будут распознаны как модули CommonJS только если они включаются черезrequire()
, а не когда используются в качестве точки входа программы в командной строке).
Более подробную информацию см. в разделе Определение системы модулей.
Вызов 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
, и главный модуль недоступен.
Советы по работе с менеджерами пакетов
Семантика функции require()
в Node.js была разработана достаточно универсальной, чтобы поддерживать разумные структуры каталогов. Программы управления пакетами, такие как 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()
[История]
Версия | Изменения |
---|---|
v23.5.0 | Эта функция больше не выдает предупреждение об экспериментальной функции по умолчанию, хотя предупреждение все еще может быть выведено с помощью --trace-require-module . |
v23.0.0 | Эта функция больше не находится за флагом CLI --experimental-require-module . |
v23.0.0 | Поддержка экспорта 'module.exports' в require(esm) . |
v22.0.0, v20.17.0 | Добавлено в: v22.0.0, v20.17.0 |
[Стабильно: 1 - Экспериментально]
Стабильно: 1 Стабильность: 1.2 - Кандидат на выпуск
Расширение .mjs
зарезервировано для модулей ECMAScript. См. раздел Определение системы модулей для получения дополнительной информации о том, какие файлы анализируются как модули ECMAScript.
require()
поддерживает загрузку только тех модулей ECMAScript, которые соответствуют следующим требованиям:
- Модуль полностью синхронный (не содержит операторов
await
верхнего уровня); и - Выполняется одно из этих условий:
Если загружаемый ES-модуль удовлетворяет требованиям, require()
может загрузить его и вернуть объект пространства имен модуля. В этом случае он аналогичен динамическому import()
, но выполняется синхронно и возвращает объект пространства имен напрямую.
С такими ES-модулями:
// distance.mjs
export function distance(a, b) {
return (b.x - a.x) ** 2 + (b.y - a.y) ** 2
}
// point.mjs
export default class Point {
constructor(x, y) {
this.x = x
this.y = y
}
}
Модуль CommonJS может загрузить их с помощью require()
:
const distance = require('./distance.mjs')
console.log(distance)
// [Module: null prototype] {
// distance: [Function: distance]
// }
const point = require('./point.mjs')
console.log(point)
// [Module: null prototype] {
// default: [class Point],
// __esModule: true,
// }
Для обеспечения совместимости с существующими инструментами, которые преобразуют ES-модули в CommonJS, которые затем могут загружать реальные ES-модули через require()
, возвращаемое пространство имен будет содержать свойство __esModule: true
, если оно имеет экспорт по умолчанию, так что потребляющий код, сгенерированный инструментами, может распознавать экспорт по умолчанию в реальных ES-модулях. Если пространство имен уже определяет __esModule
, оно не будет добавлено. Это свойство является экспериментальным и может измениться в будущем. Оно должно использоваться только инструментами, преобразующими ES-модули в модули CommonJS, следуя существующим соглашениям экосистемы. Код, написанный непосредственно в CommonJS, должен избегать зависимости от него.
Когда ES-модуль содержит как именованные экспорты, так и экспорт по умолчанию, результат, возвращаемый require()
, представляет собой объект пространства имен модуля, который помещает экспорт по умолчанию в свойство .default
, аналогично результатам, возвращаемым import()
. Чтобы настроить то, что должно возвращаться require(esm)
напрямую, ES-модуль может экспортировать желаемое значение, используя строковое имя "module.exports"
.
// point.mjs
export default class Point {
constructor(x, y) {
this.x = x
this.y = y
}
}
// `distance` теряется для потребителей CommonJS этого модуля, если он не
// добавлен в `Point` как статическое свойство.
export function distance(a, b) {
return (b.x - a.x) ** 2 + (b.y - a.y) ** 2
}
export { Point as 'module.exports' }
const Point = require('./point.mjs')
console.log(Point) // [class Point]
// Именованные экспорты теряются при использовании 'module.exports'
const { distance } = require('./point.mjs')
console.log(distance) // undefined
Обратите внимание, что в приведенном выше примере, когда используется имя экспорта module.exports
, именованные экспорты будут потеряны для потребителей CommonJS. Чтобы позволить потребителям CommonJS продолжать получать доступ к именованным экспортам, модуль может убедиться, что экспорт по умолчанию является объектом с прикрепленными к нему именованными экспортами в качестве свойств. Например, в приведенном выше примере distance
может быть прикреплен к экспорту по умолчанию, классу Point
, как статический метод.
export function distance(a, b) {
return (b.x - a.x) ** 2 + (b.y - a.y) ** 2
}
export default class Point {
constructor(x, y) {
this.x = x
this.y = y
}
static distance = distance
}
export { Point as 'module.exports' }
const Point = require('./point.mjs')
console.log(Point) // [class Point]
const { distance } = require('./point.mjs')
console.log(distance) // [Function: distance]
Если модуль, который передается в require()
, содержит операторы await
верхнего уровня, или граф модулей, который он импортирует, содержит операторы await
верхнего уровня, будет выброшено исключение ERR_REQUIRE_ASYNC_MODULE
. В этом случае пользователи должны загружать асинхронный модуль с помощью import()
.
Если включен флаг --experimental-print-required-tla
, вместо выброса ERR_REQUIRE_ASYNC_MODULE
перед вычислением, Node.js вычислит модуль, попытается найти операторы await
верхнего уровня и распечатает их местоположение, чтобы помочь пользователям исправить их.
Поддержка загрузки ES-модулей с помощью require()
в настоящее время является экспериментальной и может быть отключена с помощью --no-experimental-require-module
. Чтобы вывести информацию о том, где используется эта функция, используйте --trace-require-module
.
Эта функция может быть обнаружена путем проверки того, является ли process.features.require_module
равным true
.
Все вместе
Чтобы получить точное имя файла, который будет загружен при вызове require()
, используйте функцию require.resolve()
.
Объединяя все вышесказанное, вот алгоритм высокого уровня в псевдокоде того, что делает require()
:
require(X) из модуля по пути Y
1. Если X — это базовый модуль,
a. вернуть базовый модуль
b. ОСТАНОВИТЬСЯ
2. Если X начинается с '/'
a. установить Y как корень файловой системы
3. Если X начинается с './' или '/' или '../'
a. LOAD_AS_FILE(Y + X)
b. LOAD_AS_DIRECTORY(Y + X)
c. ВЫБРОСИТЬ "не найдено"
4. Если X начинается с '#'
a. LOAD_PACKAGE_IMPORTS(X, dirname(Y))
5. LOAD_PACKAGE_SELF(X, dirname(Y))
6. LOAD_NODE_MODULES(X, dirname(Y))
7. ВЫБРОСИТЬ "не найдено"
MAYBE_DETECT_AND_LOAD(X)
1. Если X разбирается как модуль CommonJS, загрузить X как модуль CommonJS. ОСТАНОВИТЬСЯ.
2. Иначе, если исходный код X может быть разобран как модуль ECMAScript, используя
<a href="esm#resolver-algorithm-specification">DETECT_MODULE_SYNTAX, определённый в
решателе ESM</a>,
a. Загрузить X как модуль ECMAScript. ОСТАНОВИТЬСЯ.
3. ВЫБРОСИТЬ SyntaxError из попытки разобрать X как CommonJS в 1. ОСТАНОВИТЬСЯ.
LOAD_AS_FILE(X)
1. Если X — файл, загрузить X в формате его расширения. ОСТАНОВИТЬСЯ
2. Если X.js — файл,
a. Найти ближайшую область действия пакета SCOPE к X.
b. Если область действия не найдена
1. MAYBE_DETECT_AND_LOAD(X.js)
c. Если SCOPE/package.json содержит поле "type",
1. Если поле "type" равно "module", загрузить X.js как модуль ECMAScript. ОСТАНОВИТЬСЯ.
2. Если поле "type" равно "commonjs", загрузить X.js как модуль CommonJS. ОСТАНОВИТЬСЯ.
d. MAYBE_DETECT_AND_LOAD(X.js)
3. Если X.json — файл, загрузить X.json как JavaScript объект. ОСТАНОВИТЬСЯ
4. Если X.node — файл, загрузить X.node как бинарное дополнение. ОСТАНОВИТЬСЯ
LOAD_INDEX(X)
1. Если X/index.js — файл
a. Найти ближайшую область действия пакета SCOPE к X.
b. Если область действия не найдена, загрузить X/index.js как модуль CommonJS. ОСТАНОВИТЬСЯ.
c. Если SCOPE/package.json содержит поле "type",
1. Если поле "type" равно "module", загрузить X/index.js как модуль ECMAScript. ОСТАНОВИТЬСЯ.
2. Иначе, загрузить X/index.js как модуль CommonJS. ОСТАНОВИТЬСЯ.
2. Если X/index.json — файл, разобрать X/index.json в JavaScript объект. ОСТАНОВИТЬСЯ
3. Если X/index.node — файл, загрузить X/index.node как бинарное дополнение. ОСТАНОВИТЬСЯ
LOAD_AS_DIRECTORY(X)
1. Если X/package.json — файл,
a. Разобрать X/package.json и найти поле "main".
b. Если "main" — ложное значение, ПЕРЕЙТИ к 2.
c. пусть M = X + (поле json main)
d. LOAD_AS_FILE(M)
e. LOAD_INDEX(M)
f. LOAD_INDEX(X) УСТАРЕЛО
g. ВЫБРОСИТЬ "не найдено"
2. LOAD_INDEX(X)
LOAD_NODE_MODULES(X, START)
1. пусть DIRS = NODE_MODULES_PATHS(START)
2. для каждого DIR в DIRS:
a. LOAD_PACKAGE_EXPORTS(X, DIR)
b. LOAD_AS_FILE(DIR/X)
c. LOAD_AS_DIRECTORY(DIR/X)
NODE_MODULES_PATHS(START)
1. пусть PARTS = разделить путь(START)
2. пусть I = количество PARTS - 1
3. пусть DIRS = []
4. пока I >= 0,
a. если PARTS[I] = "node_modules", ПЕРЕЙТИ к d.
b. DIR = соединить путь(PARTS[0 .. I] + "node_modules")
c. DIRS = DIR + DIRS
d. пусть I = I - 1
5. вернуть DIRS + GLOBAL_FOLDERS
LOAD_PACKAGE_IMPORTS(X, DIR)
1. Найти ближайшую область действия пакета SCOPE к DIR.
2. Если область действия не найдена, вернуть.
3. Если SCOPE/package.json "imports" равно null или undefined, вернуть.
4. Если включён `--experimental-require-module`
a. пусть CONDITIONS = ["node", "require", "module-sync"]
b. Иначе, пусть CONDITIONS = ["node", "require"]
5. пусть MATCH = PACKAGE_IMPORTS_RESOLVE(X, pathToFileURL(SCOPE),
CONDITIONS) <a href="esm#resolver-algorithm-specification">определённый в решателе ESM</a>.
6. RESOLVE_ESM_MATCH(MATCH).
LOAD_PACKAGE_EXPORTS(X, DIR)
1. Попытаться интерпретировать X как комбинацию NAME и SUBPATH, где имя
может иметь префикс @scope/, а subpath начинается с косой черты (`/`).
2. Если X не соответствует этому шаблону или DIR/NAME/package.json не является файлом,
вернуть.
3. Разобрать DIR/NAME/package.json и найти поле "exports".
4. Если "exports" равно null или undefined, вернуть.
5. Если включён `--experimental-require-module`
a. пусть CONDITIONS = ["node", "require", "module-sync"]
b. Иначе, пусть CONDITIONS = ["node", "require"]
6. пусть MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(DIR/NAME), "." + SUBPATH,
`package.json` "exports", CONDITIONS) <a href="esm#resolver-algorithm-specification">определённый в решателе ESM</a>.
7. RESOLVE_ESM_MATCH(MATCH)
LOAD_PACKAGE_SELF(X, DIR)
1. Найти ближайшую область действия пакета SCOPE к DIR.
2. Если область действия не найдена, вернуть.
3. Если SCOPE/package.json "exports" равно null или undefined, вернуть.
4. Если SCOPE/package.json "name" не является первым сегментом X, вернуть.
5. пусть MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(SCOPE),
"." + X.slice("name".length), `package.json` "exports", ["node", "require"])
<a href="esm#resolver-algorithm-specification">определённый в решателе ESM</a>.
6. RESOLVE_ESM_MATCH(MATCH)
RESOLVE_ESM_MATCH(MATCH)
1. пусть RESOLVED_PATH = fileURLToPath(MATCH)
2. Если файл по пути RESOLVED_PATH существует, загрузить RESOLVED_PATH в формате его расширения. ОСТАНОВИТЬСЯ
3. ВЫБРОСИТЬ "не найдено"
Кэширование
Модули кэшируются после первого обращения. Это означает (помимо прочего), что каждый вызов require('foo')
будет возвращать один и тот же объект, если он ссылается на один и тот же файл.
При условии, что require.cache
не модифицируется, многократные вызовы require('foo')
не приведут к многократному выполнению кода модуля. Это важная особенность. Благодаря ей могут возвращаться "частично завершенные" объекты, что позволяет загружать транзитивные зависимости даже в тех случаях, когда они вызывают циклы.
Для многократного выполнения кода модуля экспортируйте функцию и вызывайте её.
Ограничения кэширования модулей
Модули кэшируются на основе их результирующего имени файла. Поскольку модули могут ссылаться на разные имена файлов в зависимости от местоположения вызывающего модуля (загрузка из папок node_modules
), нет гарантии, что require('foo')
всегда будет возвращать один и тот же объект, если он ссылается на разные файлы.
Кроме того, в файловых системах или операционных системах без учёта регистра разные результирующие имена файлов могут указывать на один и тот же файл, но кэш всё равно будет рассматривать их как разные модули и будет многократно перезагружать файл. Например, require('./foo')
и require('./FOO')
возвращают два разных объекта, независимо от того, являются ли ./foo
и ./FOO
одним и тем же файлом.
Встроенные модули
[История]
Версия | Изменения |
---|---|
v16.0.0, v14.18.0 | Добавлена поддержка импорта node: для require(...) . |
Node.js имеет несколько модулей, скомпилированных в бинарный файл. Эти модули более подробно описаны в других разделах этой документации.
Встроенные модули определены в исходном коде Node.js и расположены в папке lib/
.
Встроенные модули могут быть идентифицированы с помощью префикса node:
, в этом случае кэш require
обходится. Например, require('node:http')
всегда будет возвращать встроенный HTTP-модуль, даже если существует запись require.cache
с таким именем.
Некоторые встроенные модули всегда загружаются предпочтительно, если их идентификатор передаётся в require()
. Например, require('http')
всегда будет возвращать встроенный HTTP-модуль, даже если существует файл с таким именем. Список встроенных модулей, которые могут быть загружены без использования префикса node:
, доступен в module.builtinModules
, перечисленные без префикса.
Встроенные модули с обязательным префиксом node:
При загрузке с помощью require()
некоторые встроенные модули должны запрашиваться с префиксом node:
. Это требование существует для предотвращения конфликтов вновь введенных встроенных модулей с пакетами пользовательского пространства, которые уже заняли это имя. В настоящее время встроенные модули, требующие префикса node:
, это:
Список этих модулей представлен в module.builtinModules
, включая префикс.
Циклы
Когда существуют циклические вызовы require()
, модуль может быть не завершен к моменту его возврата.
Рассмотрим следующую ситуацию:
a.js
:
console.log('a starting')
exports.done = false
const b = require('./b.js')
console.log('in a, b.done = %j', b.done)
exports.done = true
console.log('a done')
b.js
:
console.log('b starting')
exports.done = false
const a = require('./a.js')
console.log('in b, a.done = %j', a.done)
exports.done = true
console.log('b done')
main.js
:
console.log('main starting')
const a = require('./a.js')
const b = require('./b.js')
console.log('in main, a.done = %j, b.done = %j', a.done, b.done)
Когда main.js
загружает a.js
, затем a.js
в свою очередь загружает b.js
. В этот момент b.js
пытается загрузить a.js
. Чтобы предотвратить бесконечный цикл, незавершенная копия объекта экспорта a.js
возвращается модулю b.js
. Затем b.js
завершает загрузку, и его объект exports
предоставляется модулю a.js
.
К тому времени, когда main.js
загрузит оба модуля, они оба будут завершены. Таким образом, вывод этой программы будет:
$ node main.js
main starting
a starting
b starting
in b, a.done = false
b done
in a, b.done = true
a done
in main, a.done = true, b.done = true
Тщательное планирование необходимо для обеспечения правильной работы циклических зависимостей модулей в приложении.
Модули файлов
Если точное имя файла не найдено, 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 - Устарело]
Стабильно: 3 Стабильность: 3 - Устарело: Используйте экспорт подпутей или импорт подпутей вместо этого.
Существует три способа передачи папки в require()
в качестве аргумента.
Первый — создать файл package.json
в корне папки, который указывает основной модуль. Пример файла package.json
может выглядеть так:
{ "name": "some-library", "main": "./lib/some-library.js" }
Если это находится в папке ./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 сообщит о том, что весь модуль отсутствует, с ошибкой по умолчанию:
Error: Cannot find module 'some-library'
Во всех трех случаях выше вызов import('./some-library')
приведет к ошибке ERR_UNSUPPORTED_DIR_IMPORT
. Использование экспорта подпутей или импорта подпутей может обеспечить те же преимущества организации, что и папки в качестве модулей, и работать как для require
, так и для import
.
Загрузка из папок node_modules
Если идентификатор модуля, переданный в require()
, не является встроенным модулем и не начинается с '/'
, '../'
или './'
, то 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_prefix
Node.js.
Это в основном по историческим причинам.
Настоятельно рекомендуется размещать зависимости в локальной папке node_modules
. Они будут загружаться быстрее и надёжнее.
Обёртка модуля
Перед выполнением кода модуля Node.js оборачивает его в функцию-обёртку, которая выглядит следующим образом:
;(function (exports, require, module, __filename, __dirname) {
// Код модуля находится здесь
})
Таким образом, Node.js достигает нескольких целей:
- Переменные верхнего уровня (определённые с помощью
var
,const
илиlet
) остаются в области видимости модуля, а не глобального объекта. - Предоставляет некоторые глобально выглядящие переменные, которые на самом деле специфичны для модуля, такие как:
- Объекты
module
иexports
, которые разработчик может использовать для экспорта значений из модуля. - Удобные переменные
__filename
и__dirname
, содержащие абсолютное имя файла и путь к каталогу модуля.
- Объекты
Область видимости модуля
__dirname
Добавлен в: v0.1.27
Имя каталога текущего модуля. Это то же самое, что и path.dirname()
от __filename
.
Пример: запуск node example.js
из /Users/mjr
console.log(__dirname)
// Выводит: /Users/mjr
console.log(path.dirname(__filename))
// Выводит: /Users/mjr
__filename
Добавлен в: v0.0.1
Имя файла текущего модуля. Это абсолютный путь к файлу текущего модуля с разрешёнными символическими ссылками.
Для основной программы это не обязательно то же самое, что и имя файла, используемое в командной строке.
См. __dirname
для имени каталога текущего модуля.
Примеры:
Запуск node example.js
из /Users/mjr
console.log(__filename)
// Выводит: /Users/mjr/example.js
console.log(__dirname)
// Выводит: /Users/mjr
Учитывая два модуля: 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
Добавлено в: v0.1.12
Ссылка на module.exports
, которая короче для набора текста. См. раздел о сокращении exports для получения подробной информации о том, когда использовать exports
и когда использовать module.exports
.
module
Добавлено в: v0.1.16
Ссылка на текущий модуль, см. раздел о объекте module. В частности, module.exports
используется для определения того, что экспортирует модуль и делает доступным через require()
.
require(id)
Добавлено в: v0.1.13
Используется для импорта модулей, JSON
и локальных файлов. Модули могут быть импортированы из node_modules
. Локальные модули и JSON-файлы могут быть импортированы с использованием относительного пути (например, ./
, ./foo
, ./bar/baz
, ../foo
), который будет разрешен относительно каталога, указанного в __dirname
(если определен), или текущего рабочего каталога. Относительные пути в стиле POSIX разрешаются независимо от операционной системы, а это значит, что приведенные выше примеры будут работать в Windows так же, как и в Unix-системах.
// Импорт локального модуля с путем, относительным к `__dirname` или текущему
// рабочему каталогу. (В Windows это будет соответствовать .\path\myLocalModule.)
const myLocalModule = require('./path/myLocalModule')
// Импорт JSON-файла:
const jsonData = require('./path/filename.json')
// Импорт модуля из node_modules или встроенного модуля Node.js:
const crypto = require('node:crypto')
require.cache
Добавлено в: v0.3.0
Модули кэшируются в этом объекте при их запросе. Удалив пару «ключ-значение» из этого объекта, следующий require
перезагрузит модуль. Это не относится к нативным надстройкам, для которых перезагрузка приведет к ошибке.
Также возможно добавление или замена записей. Этот кэш проверяется перед встроенными модулями, и если в кэш добавляется имя, соответствующее встроенному модулю, только вызовы require
с префиксом node:
будут получать встроенный модуль. Используйте с осторожностью!
const assert = require('node:assert')
const realFs = require('node:fs')
const fakeFs = {}
require.cache.fs = { exports: fakeFs }
assert.strictEqual(require('fs'), fakeFs)
assert.strictEqual(require('node:fs'), realFs)
require.extensions
Добавлен в: v0.3.0
Устарел с: v0.10.6
[Стабильность: 0 - Устарело]
Стабильность: 0 Стабильность: 0 - Устарело
Инструктирует require
о том, как обрабатывать определённые расширения файлов.
Обработка файлов с расширением .sjs
как .js
:
require.extensions['.sjs'] = require.extensions['.js']
Устарело. Раньше этот список использовался для загрузки модулей, отличных от JavaScript, в Node.js путём их компиляции по требованию. Однако на практике существуют гораздо лучшие способы сделать это, например, загрузка модулей через другую программу Node.js или предварительная компиляция в JavaScript.
Избегайте использования require.extensions
. Использование может привести к скрытым ошибкам, а разрешение расширений замедляется с каждым зарегистрированным расширением.
require.main
Добавлен в: v0.1.17
Объект Module
, представляющий входной скрипт, загруженный при запуске процесса Node.js, или undefined
, если точка входа программы не является модулем CommonJS. См. "Доступ к главному модулю".
В скрипте entry.js
:
console.log(require.main)
node entry.js
Module {
id: '.',
path: '/absolute/path/to',
exports: {},
filename: '/absolute/path/to/entry.js',
loaded: false,
children: [],
paths:
[ '/absolute/path/to/node_modules',
'/absolute/path/node_modules',
'/absolute/node_modules',
'/node_modules' ] }
require.resolve(request[, options])
[История]
Версия | Изменения |
---|---|
v8.9.0 | Теперь поддерживается опция paths . |
v0.3.0 | Добавлено в: 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)
Добавлено в: v8.9.0
request
<string> Путь к модулю, пути поиска которого извлекаются.- Возвращает: <string[]> | <null>
Возвращает массив, содержащий пути, которые были просмотрены во время разрешения request
, или null
, если строка request
ссылается на основной модуль, например, http
или fs
.
Объект module
Добавлено в: v0.1.16
В каждом модуле свободная переменная module
является ссылкой на объект, представляющий текущий модуль. Для удобства module.exports
также доступен через глобальную переменную модуля exports
. module
на самом деле не является глобальным, а скорее локальным для каждого модуля.
module.children
Добавлено в: v0.1.16
Объекты модулей, впервые затребованные этим модулем.
module.exports
Добавлено в: v0.1.16
Объект module.exports
создается системой Module
. Иногда это неприемлемо; многие хотят, чтобы их модуль был экземпляром некоторого класса. Для этого присвойте желаемый экспортируемый объект module.exports
. Присваивание желаемого объекта exports
просто пересвяжет локальную переменную exports
, что, вероятно, нежелательно.
Например, предположим, что мы создаем модуль под названием a.js
:
const EventEmitter = require('node:events')
module.exports = new EventEmitter()
// Выполняем некоторую работу и через некоторое время генерируем
// событие 'ready' из самого модуля.
setTimeout(() => {
module.exports.emit('ready')
}, 1000)
Затем в другом файле мы можем сделать следующее:
const a = require('./a')
a.on('ready', () => {
console.log('module "a" is ready')
})
Присваивание module.exports
должно быть выполнено немедленно. Это не может быть сделано в каких-либо обратных вызовах. Это не работает:
x.js
:
setTimeout(() => {
module.exports = { a: 'hello' }
}, 0)
y.js
:
const x = require('./x')
console.log(x.a)
Сокращение exports
Добавлено в: v0.1.16
Переменная exports
доступна в области видимости файла модуля и получает значение module.exports
до вычисления модуля.
Это позволяет использовать сокращение, так что module.exports.f = ...
можно записать более кратко как exports.f = ...
. Однако имейте в виду, что как и любая переменная, если exports
присваивается новое значение, она больше не связана с module.exports
:
module.exports.hello = true // Экспортируется из require модуля
exports = { hello: false } // Не экспортируется, доступно только в модуле
Когда свойство module.exports
полностью заменяется новым объектом, обычно также переприсваивается exports
:
module.exports = exports = function Constructor() {
// ... и так далее
}
Чтобы проиллюстрировать поведение, представьте себе эту гипотетическую реализацию require()
, которая очень похожа на то, что на самом деле делает require()
:
function require(/* ... */) {
const module = { exports: {} }
;((module, exports) => {
// Код модуля здесь. В этом примере определяется функция.
function someFunc() {}
exports = someFunc
// На этом этапе exports больше не является сокращением для module.exports, и
// этот модуль будет по-прежнему экспортировать пустой объект по умолчанию.
module.exports = someFunc
// На этом этапе модуль будет теперь экспортировать someFunc, а не
// объект по умолчанию.
})(module, module.exports)
return module.exports
}
module.filename
Добавлено в: v0.1.16
Полное разрешенное имя файла модуля.
module.id
Добавлено в: v0.1.16
Идентификатор модуля. Обычно это полное разрешенное имя файла.
module.isPreloading
Добавлено в: v15.4.0, v14.17.0
- Тип: <булево>
true
, если модуль выполняется во время фазы предварительной загрузки Node.js.
module.loaded
Добавлен в: v0.1.16
Завершена ли загрузка модуля или она находится в процессе.
module.parent
Добавлен в: v0.1.16
Устарел с: v14.6.0, v12.19.0
[Стабильность: 0 - Устарел]
Стабильность: 0 Стабильность: 0 - Устарел: Пожалуйста, используйте require.main
и module.children
вместо этого.
Модуль, который первым потребовал этот, или null
, если текущий модуль является точкой входа текущего процесса, или undefined
, если модуль был загружен чем-то, что не является модулем CommonJS (например: REPL или import
).
module.path
Добавлен в: v11.14.0
Имя каталога модуля. Обычно это то же самое, что и path.dirname()
от module.id
.
module.paths
Добавлен в: v0.4.0
Пути поиска для модуля.
module.require(id)
Добавлен в: v0.5.1
Метод module.require()
предоставляет способ загрузки модуля, как если бы require()
был вызван из исходного модуля.
Для этого необходимо получить ссылку на объект module
. Так как require()
возвращает module.exports
, а module
обычно доступен только внутри кода определенного модуля, он должен быть явно экспортирован для использования.
Объект Module
Этот раздел перемещен в Модули: основной модуль module
.
Поддержка Source Map v3
Этот раздел перемещен в Модули: основной модуль module
.