Модули: модули CommonJS
Модули CommonJS — это первоначальный способ упаковки кода JavaScript для Node.js. Node.js также поддерживает стандарт модулей ECMAScript, используемый браузерами и другими средами выполнения JavaScript.
В Node.js каждый файл рассматривается как отдельный модуль. Например, рассмотрим файл с именем foo.js:
const circle = require('./circle.js');
console.log(`The area of a circle of radius 4 is ${circle.area(4)}`); copy В первой строке foo.js загружает модуль circle.js, который находится в том же каталоге, что и foo.js.
Вот содержимое circle.js:
const { PI } = Math;
exports.area = (r) => PI * r ** 2;
exports.circumference = (r) => 2 * PI * r; copy Модуль 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(`The area of mySquare is ${mySquare.area()}`); copy Модуль square определен в square.js:
// Assigning to exports will not modify module, must use module.exports
module.exports = class Square {
constructor(width) {
this.width = width;
}
area() {
return this.width ** 2;
}
}; copy Система модулей 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", эти файлы будут распознаваться как модули 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, и главный модуль недоступен.
Советы для менеджеров пакетов
Семантика функции 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(), сами пакеты могут находиться где угодно.
Загрузка модулей ECMAScript с помощью require()
Расширение .mjs зарезервировано для модулей ECMAScript. См. раздел Определение системы модулей для получения дополнительной информации о том, какие файлы анализируются как модули ECMAScript.
require() поддерживает загрузку только тех модулей ECMAScript, которые соответствуют следующим требованиям:
- Модуль полностью синхронный (не содержит
awaitна верхнем уровне); и - Выполняется одно из следующих условий:
- Файл имеет расширение
.mjs. - Файл имеет расширение
.js, а ближайшийpackage.jsonсодержит"type": "module" - Файл имеет расширение
.js, ближайшийpackage.jsonне содержит"type": "commonjs", и модуль содержит синтаксис модулей ES.
- Файл имеет расширение
Если загружаемый модуль ES соответствует этим требованиям, require() может загрузить его и вернуть объект пространства имен модуля. В этом случае он похож на динамический import(), но выполняется синхронно и возвращает объект пространства имен напрямую.
При наличии следующих модулей ES:
// distance.mjs
export function distance(a, b) { return Math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2); } copy // point.mjs
export default class Point {
constructor(x, y) { this.x = x; this.y = y; }
} copy Модуль 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,
// } copy Для обеспечения совместимости с существующими инструментами, преобразующими модули ES в CommonJS, которые затем могут загружать реальные модули ES через require(), возвращаемое пространство имен будет содержать свойство __esModule: true, если оно имеет экспорт default, чтобы сгенерированный инструментами вызывающий код мог распознавать экспорты по умолчанию в реальных модулях 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` is lost to CommonJS consumers of this module, unless it's
// added to `Point` as a static property.
export function distance(a, b) { return Math.sqrt((b.x - a.x) ** 2 + (b.y - a.y) ** 2); }
export { Point as 'module.exports' } copy const Point = require('./point.mjs');
console.log(Point); // [class Point]
// Named exports are lost when 'module.exports' is used
const { distance } = require('./point.mjs');
console.log(distance); // undefined copy Обратите внимание, что в приведенном выше примере при использовании имени экспорта module.exports именованные экспорты будут недоступны для потребителей CommonJS. Чтобы позволить потребителям CommonJS продолжать доступ к именованным экспортам, модуль может гарантировать, что экспорт по умолчанию является объектом с прикрепленными к нему в виде свойств именованными экспортами. Например, в случае выше distance можно прикрепить к экспорту по умолчанию — классу Point — в качестве статического метода.
export function distance(a, b) { return Math.sqrt((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' } copy const Point = require('./point.mjs');
console.log(Point); // [class Point]
const { distance } = require('./point.mjs');
console.log(distance); // [Function: distance] copy Если загружаемый с помощью require() модуль содержит await верхнего уровня или граф модулей, который он import, содержит 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) from module at path Y
1. If X is a core module,
a. return the core module
b. STOP
2. If X begins with '/'
a. set Y to the file system root
3. If X is equal to '.', or X begins with './', '/' or '../'
a. LOAD_AS_FILE(Y + X)
b. LOAD_AS_DIRECTORY(Y + X)
c. THROW "not found"
4. If X begins with '#'
a. LOAD_PACKAGE_IMPORTS(X, dirname(Y))
5. LOAD_PACKAGE_SELF(X, dirname(Y))
6. LOAD_NODE_MODULES(X, dirname(Y))
7. THROW "not found"
MAYBE_DETECT_AND_LOAD(X)
1. If X parses as a CommonJS module, load X as a CommonJS module. STOP.
2. Else, if the source code of X can be parsed as ECMAScript module using
<a href="esm.md#resolver-algorithm-specification">DETECT_MODULE_SYNTAX defined in
the ESM resolver</a>,
a. Load X as an ECMAScript module. STOP.
3. THROW the SyntaxError from attempting to parse X as CommonJS in 1. STOP.
LOAD_AS_FILE(X)
1. If X is a file, load X as its file extension format. STOP
2. If X.js is a file,
a. Find the closest package scope SCOPE to X.
b. If no scope was found
1. MAYBE_DETECT_AND_LOAD(X.js)
c. If the SCOPE/package.json contains "type" field,
1. If the "type" field is "module", load X.js as an ECMAScript module. STOP.
2. If the "type" field is "commonjs", load X.js as a CommonJS module. STOP.
d. MAYBE_DETECT_AND_LOAD(X.js)
3. If X.json is a file, load X.json to a JavaScript Object. STOP
4. If X.node is a file, load X.node as binary addon. STOP
LOAD_INDEX(X)
1. If X/index.js is a file
a. Find the closest package scope SCOPE to X.
b. If no scope was found, load X/index.js as a CommonJS module. STOP.
c. If the SCOPE/package.json contains "type" field,
1. If the "type" field is "module", load X/index.js as an ECMAScript module. STOP.
2. Else, load X/index.js as a CommonJS module. STOP.
2. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP
3. If X/index.node is a file, load X/index.node as binary addon. STOP
LOAD_AS_DIRECTORY(X)
1. If X/package.json is a file,
a. Parse X/package.json, and look for "main" field.
b. If "main" is a falsy value, GOTO 2.
c. let M = X + (json main field)
d. LOAD_AS_FILE(M)
e. LOAD_INDEX(M)
f. LOAD_INDEX(X) DEPRECATED
g. THROW "not found"
2. LOAD_INDEX(X)
LOAD_NODE_MODULES(X, START)
1. let DIRS = NODE_MODULES_PATHS(START)
2. for each DIR in DIRS:
a. LOAD_PACKAGE_EXPORTS(X, DIR)
b. LOAD_AS_FILE(DIR/X)
c. LOAD_AS_DIRECTORY(DIR/X)
NODE_MODULES_PATHS(START)
1. let PARTS = path split(START)
2. let I = count of PARTS - 1
3. let DIRS = []
4. while I >= 0,
a. if PARTS[I] = "node_modules", GOTO d.
b. DIR = path join(PARTS[0 .. I] + "node_modules")
c. DIRS = DIR + DIRS
d. let I = I - 1
5. return DIRS + GLOBAL_FOLDERS
LOAD_PACKAGE_IMPORTS(X, DIR)
1. Find the closest package scope SCOPE to DIR.
2. If no scope was found, return.
3. If the SCOPE/package.json "imports" is null or undefined, return.
4. If `--experimental-require-module` is enabled
a. let CONDITIONS = ["node", "require", "module-sync"]
b. Else, let CONDITIONS = ["node", "require"]
5. let MATCH = PACKAGE_IMPORTS_RESOLVE(X, pathToFileURL(SCOPE),
CONDITIONS) <a href="esm.md#resolver-algorithm-specification">defined in the ESM resolver</a>.
6. RESOLVE_ESM_MATCH(MATCH).
LOAD_PACKAGE_EXPORTS(X, DIR)
1. Try to interpret X as a combination of NAME and SUBPATH where the name
may have a @scope/ prefix and the subpath begins with a slash (`/`).
2. If X does not match this pattern or DIR/NAME/package.json is not a file,
return.
3. Parse DIR/NAME/package.json, and look for "exports" field.
4. If "exports" is null or undefined, return.
5. If `--experimental-require-module` is enabled
a. let CONDITIONS = ["node", "require", "module-sync"]
b. Else, let CONDITIONS = ["node", "require"]
6. let MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(DIR/NAME), "." + SUBPATH,
`package.json` "exports", CONDITIONS) <a href="esm.md#resolver-algorithm-specification">defined in the ESM resolver</a>.
7. RESOLVE_ESM_MATCH(MATCH)
LOAD_PACKAGE_SELF(X, DIR)
1. Find the closest package scope SCOPE to DIR.
2. If no scope was found, return.
3. If the SCOPE/package.json "exports" is null or undefined, return.
4. If the SCOPE/package.json "name" is not the first segment of X, return.
5. let MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(SCOPE),
"." + X.slice("name".length), `package.json` "exports", ["node", "require"])
<a href="esm.md#resolver-algorithm-specification">defined in the ESM resolver</a>.
6. RESOLVE_ESM_MATCH(MATCH)
RESOLVE_ESM_MATCH(MATCH)
1. let RESOLVED_PATH = fileURLToPath(MATCH)
2. If the file at RESOLVED_PATH exists, load RESOLVED_PATH as its extension
format. STOP
3. THROW "not found" copy Кэширование
Модули кэшируются после первой загрузки. Это означает (среди прочего), что каждый вызов 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.
Встроенные модули с обязательным префиксом node:
При загрузке с помощью require() некоторые встроенные модули должны запрашиваться с префиксом node:. Это требование существует для того, чтобы предотвратить конфликты между вновь вводимыми встроенными модулями и пакетами пользовательского пространства (userland), которые уже заняли это имя. В настоящее время встроенными модулями, требующими префикса node:, являются:
Циклические зависимости
При наличии циклических вызовов 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'); copy 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'); copy 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); copy Когда 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 copy
Требуется тщательное планирование, чтобы циклические зависимости модулей корректно работали в приложении.
Файловые модули
Если точное имя файла не найдено, 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.
Папки как модули
Существует три способа передачи папки в require() в качестве аргумента.
Первый способ — создать файл package.json в корне папки, в котором указывается модуль main. Пример файла package.json может выглядеть следующим образом:
{ "name" : "some-library",
"main" : "./lib/some-library.js" } copy Если бы он находился в папке по пути ./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' copy
Во всех трех описанных выше случаях вызов 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.js node_prefix.
Это обусловлено главным образом историческими причинами.
Настоятельно рекомендуется размещать зависимости в локальной папке node_modules. Они будут загружаться быстрее и надежнее.
Обёртка модуля
Перед выполнением кода модуля Node.js оборачивает его в функцию-обертку, которая выглядит следующим образом:
(function(exports, require, module, __filename, __dirname) {
// Module code actually lives in here
}); copy Благодаря этому Node.js решает несколько задач:
- Сохраняет область видимости переменных верхнего уровня (определенных с помощью
var,constилиlet) ограниченной модулем, а не глобальным объектом. - Помогает предоставлять переменные, выглядящие как глобальные, но фактически относящиеся к модулю, такие как:
- Объекты
moduleиexports, которые разработчик может использовать для экспорта значений из модуля. - Вспомогательные переменные
__filenameи__dirname, содержащие абсолютное имя файла модуля и путь к каталогу.
- Объекты
Область видимости модуля
__dirname
- Тип: <string>
Имя директории текущего модуля. Это то же самое, что и path.dirname() для __filename.
Пример: запуск node example.js из /Users/mjr
console.log(__dirname); // Prints: /Users/mjr console.log(path.dirname(__filename)); // Prints: /Users/mjr copy
__filename
- Тип: <string>
Имя файла текущего модуля. Это абсолютный путь к файлу текущего модуля с разрешенными символическими ссылками.
Для главной программы это не обязательно то же имя файла, которое использовалось в командной строке.
См. __dirname для получения имени директории текущего модуля.
Примеры:
Запуск node example.js из /Users/mjr
console.log(__filename); // Prints: /Users/mjr/example.js console.log(__dirname); // Prints: /Users/mjr copy
Даны два модуля: 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
- Тип: <Object>
Более короткая для написания ссылка на module.exports. Подробнее о том, когда использовать exports, а когда module.exports, см. в разделе сокращение exports.
module
- Тип: <module>
Ссылка на текущий модуль, см. раздел об объекте module. В частности, module.exports используется для определения того, что модуль экспортирует и делает доступным через require().
require(id)
Используется для импорта модулей, JSON и локальных файлов. Модули могут быть импортированы из node_modules. Локальные модули и файлы JSON могут быть импортированы с использованием относительного пути (например, ./, ./foo, ./bar/baz, ../foo), который будет разрешаться относительно директории, указанной в __dirname (если она определена), или текущей рабочей директории. Относительные пути в стиле POSIX разрешаются независимо от ОС, что означает, что приведенные выше примеры будут работать в Windows так же, как и в Unix-системах.
// Importing a local module with a path relative to the `__dirname` or current
// working directory. (On Windows, this would resolve to .\path\myLocalModule.)
const myLocalModule = require('./path/myLocalModule');
// Importing a JSON file:
const jsonData = require('./path/filename.json');
// Importing a module from node_modules or Node.js built-in module:
const crypto = require('node:crypto'); copy
require.cache
- Тип: <Object>
Модули кэшируются в этом объекте при их загрузке через require. При удалении ключа из этого объекта следующий вызов 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); copy
require.extensions
- Тип: <Object>
Инструктирует require о том, как обрабатывать определенные расширения файлов.
Обработка файлов с расширением .sjs как .js:
require.extensions['.sjs'] = require.extensions['.js']; copy
Устарело. В прошлом этот список использовался для загрузки модулей, отличных от JavaScript, в Node.js путем их компиляции по требованию. Однако на практике существуют гораздо более эффективные способы сделать это, такие как загрузка модулей через другую программу Node.js или их предварительная компиляция в JavaScript.
Избегайте использования require.extensions. Его использование может привести к трудноуловимым ошибкам, а разрешение расширений замедляется с каждым зарегистрированным расширением.
require.main
- Тип: <module> | <undefined>
Объект Module, представляющий входной скрипт, загруженный при запуске процесса Node.js, или undefined, если точка входа программы не является модулем CommonJS. См. «Доступ к главному модулю».
В скрипте entry.js:
console.log(require.main); copy
node entry.js copy
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' ] } copy
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
- Тип: <Object>
В каждом модуле свободная переменная module является ссылкой на объект, представляющий текущий модуль. Для удобства module.exports также доступен через глобальную переменную модуля exports. На самом деле module не является глобальной переменной, а скорее локальной для каждого модуля.
module.exports
- Тип: <Object>
Объект module.exports создается системой Module. Иногда это неприемлемо; многие хотят, чтобы их модуль был экземпляром некоторого класса. Для этого присвойте желаемый экспортируемый объект свойству module.exports. Присвоение желаемого объекта переменной exports просто повторно привяжет локальную переменную exports, что, вероятно, не является желаемым результатом.
Например, предположим, что мы создаем модуль с именем a.js:
const EventEmitter = require('node:events');
module.exports = new EventEmitter();
// Do some work, and after some time emit
// the 'ready' event from the module itself.
setTimeout(() => {
module.exports.emit('ready');
}, 1000); copy Затем в другом файле мы можем сделать следующее:
const a = require('./a');
a.on('ready', () => {
console.log('module "a" is ready');
}); copy Присваивание module.exports должно выполняться немедленно. Его нельзя выполнять в каких-либо функциях обратного вызова. Это не работает:
x.js:
setTimeout(() => {
module.exports = { a: 'hello' };
}, 0); copy y.js:
const x = require('./x');
console.log(x.a); copy
Сокращение exports
Переменная exports доступна в пределах файловой области видимости модуля и ей присваивается значение module.exports перед вычислением модуля.
Она позволяет использовать сокращение, благодаря чему module.exports.f = ... можно записать более кратко как exports.f = .... Однако имейте в виду, что, как и любая переменная, если переменной exports присваивается новое значение, она больше не будет привязана к module.exports:
module.exports.hello = true; // Exported from require of module
exports = { hello: false }; // Not exported, only available in the module copy Когда свойство module.exports полностью заменяется новым объектом, обычно также повторно присваивают значение exports:
module.exports = exports = function Constructor() {
// ... etc.
}; copy Чтобы проиллюстрировать поведение, представьте себе эту гипотетическую реализацию require(), которая очень похожа на то, что на самом деле делает require():
function require(/* ... */) {
const module = { exports: {} };
((module, exports) => {
// Module code here. In this example, define a function.
function someFunc() {}
exports = someFunc;
// At this point, exports is no longer a shortcut to module.exports, and
// this module will still export an empty default object.
module.exports = someFunc;
// At this point, the module will now export someFunc, instead of the
// default object.
})(module, module.exports);
return module.exports;
} copy
module.isPreloading
- Тип: <boolean>
true, если модуль выполняется на этапе предварительной загрузки (preload) Node.js.
module.parent
- Тип: <module> | <null> | <undefined>
Модуль, который первым запросил (require) данный модуль, или null, если текущий модуль является точкой входа текущего процесса, или undefined, если модуль был загружен чем-то, что не является модулем CommonJS (например, REPL или import).
module.path
- Тип: <string>
Имя директории модуля. Обычно это то же самое, что и path.dirname() для module.id.
module.require(id)
Метод module.require() предоставляет возможность загрузить модуль так, как если бы require() был вызван из исходного модуля.
Для этого необходимо получить ссылку на объект module. Поскольку require() возвращает module.exports, а module обычно доступен только внутри кода конкретного модуля, его необходимо явно экспортировать для использования.
Объект Module
Этот раздел был перемещен в Модули: встроенный модуль module.
Поддержка Source map v3
Этот раздел был перемещен в Модули: встроенный модуль module.
© Joyent, Inc. and other Node contributors
Licensed under the MIT License.
Node.js is a trademark of Joyent, Inc. and is used with its permission.
We are not endorsed by or affiliated with Joyent.
https://nodejs.org/dist/latest-v22.x/docs/api/modules.html