Модули
В системе модулей Node.js каждый файл рассматривается как отдельный модуль. Например, рассмотрим файл с именем foo.js:
const circle = require('./circle.js');
console.log(`The area of a circle of radius 4 is ${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(`The area of mySquare is ${mySquare.area()}`);
Модуль 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;
}
};
Система модулей реализована в модуле require('module').
Доступ к главному модулю
Когда файл запускается напрямую из Node.js, require.main устанавливается на его module. Это означает, что можно определить, был ли файл запущен напрямую, проверив require.main === module.
Для файла foo.js, это будет true при запуске через node foo.js, но false при запуске через require('./foo').
Поскольку module предоставляет свойство filename (обычно эквивалентное __filename), точку входа текущего приложения можно получить, проверив require.main.filename.
Дополнения: Советы по менеджеру пакетов
Семантика функции 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(), сами пакеты могут быть расположены где угодно.
Всё вместе...
Чтобы получить точное имя файла, который будет загружен при вызове require(), используйте функцию require.resolve().
Объединяя всё вышесказанное, вот псевдокод высокого уровня, описывающий алгоритм работы require.resolve():
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 be the filesystem root 3. If X begins with './' or '/' or '../' a. LOAD_AS_FILE(Y + X) b. LOAD_AS_DIRECTORY(Y + X) 4. LOAD_NODE_MODULES(X, dirname(Y)) 5. THROW "not found" LOAD_AS_FILE(X) 1. If X is a file, load X as JavaScript text. STOP 2. If X.js is a file, load X.js as JavaScript text. STOP 3. If X.json is a file, parse 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, load X/index.js as JavaScript text. 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. let M = X + (json main field) c. LOAD_AS_FILE(M) d. LOAD_INDEX(M) 2. LOAD_INDEX(X) LOAD_NODE_MODULES(X, START) 1. let DIRS = NODE_MODULES_PATHS(START) 2. for each DIR in DIRS: a. LOAD_AS_FILE(DIR/X) b. 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 = [GLOBAL_FOLDERS] 4. while I >= 0, a. if PARTS[I] = "node_modules" CONTINUE b. DIR = path join(PARTS[0 .. I] + "node_modules") c. DIRS = DIRS + DIR d. let I = I - 1 5. return DIRS
Кеширование
Модули кешируются после первого загрузки. Это означает (в том числе), что каждый вызов require('foo') вернёт ровно тот же объект, если он будет соответствовать тому же файлу.
При условии, что require.cache не модифицируется, множественные вызовы require('foo') не приведут к многократному выполнению кода модуля. Это важная функция. С её помощью могут быть возвращены "частично готовые" объекты, что позволяет загружать транзитивные зависимости даже в случаях циклов.
Для многократного выполнения кода модуля экспортируйте функцию и вызывайте её.
Ограничения кэширования модулей
Модули кэшируются на основе пути резолвированного файла. Поскольку модули могут быть резолвированы в разные имена файлов в зависимости от местоположения вызывающего модуля (загрузка из папок node_modules), нет гарантии, что require('foo') всегда вернёт тот же объект, если он будет соответствовать разным файлам.
Кроме того, в системах с регистронезависимыми файловыми системами или операционными системами разные резолвированные имена файлов могут указывать на один и тот же файл, но кэш всё равно будет рассматривать их как разные модули и перегружать файл несколько раз. Например, require('./foo') и require('./FOO') возвращают два разных объекта, независимо от того, являются ли ./foo и ./FOO одним и тем же файлом.
Ядерные модули
Node.js имеет несколько модулей, скомпилированных в двоичный файл. Эти модули описаны более подробно в других разделах этой документации.
Ядерные модули определены в исходном коде Node.js и находятся в папке lib/.
Ядерные модули всегда будут предпочтительно загружаться, если их идентификатор передаётся в require() . Например, require('http') всегда вернёт встроенный модуль HTTP, даже если существует файл с таким именем.
Циклы
При наличии циклических 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.
Файлы .js интерпретируются как текстовые файлы JavaScript, файлы .json анализируются как текстовые файлы JSON. Файлы .node интерпретируются как скомпилированные модули плагинов, загруженные с помощью dlopen.
Требуемый модуль, начинающийся с '/' , представляет собой абсолютный путь к файлу. Например, require('/home/marco/foo.js') загрузит файл по пути /home/marco/foo.js.
Требуемый модуль, начинающийся с './' , является относительным по отношению к файлу, вызывающему require() . То есть, circle.js должен находиться в той же директории, что и foo.js, для того, чтобы require('./circle') его нашёл.
Без ведущих '/', './', или '../' для обозначения файла, модуль должен быть либо ядерным модулем, либо загружен из папки node_modules.
Если указанный путь не существует, require() выбросит Error с свойством code , установленным на значение 'MODULE_NOT_FOUND'.
Папки как модули
Полезно организовывать программы и библиотеки в автономные папки, а затем предоставлять единую точку входа в эту библиотеку. Существует три способа передачи папки в require() в качестве аргумента.
Первый способ — создать файл package.json в корне папки, который указывает на модуль main . Пример файла package.json может выглядеть так:
{ "name" : "some-library",
"main" : "./lib/some-library.js" }
Если этот файл находился в папке ./some-library, то require('./some-library') попытался бы загрузить ./some-library/lib/some-library.js.
Это предел осознания Node.js файлов package.json.
Если файл 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'
Загрузка из папок node_modules
Если идентификатор модуля, переданный в require(), не является модулем ядра и не начинается с '/', '../', или './', Node.js начинает поиск в родительской директории текущего модуля, добавляет /node_modules, и пытается загрузить модуль из этого места. Node.js не будет добавлять node_modules к пути, который уже заканчивается на node_modules.
Если модуль не найден там, Node.js переходит к родительской директории и так далее, пока не достигнет корня файловой системы.
Например, если файл по адресу '/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) {
// Module code actually lives in here
});
Сделав это, Node.js добивается нескольких целей:
- Это сохраняет переменные верхнего уровня (определённые с помощью
var,constилиlet), ограниченные по области видимости модулем, а не глобальным объектом. -
Это помогает предоставлять переменные, похожие на глобальные, которые на самом деле специфичны для модуля, такие как:
- Объекты
moduleиexports, которые реализатор может использовать для экспорта значений из модуля. - Удобные переменные
__filenameи__dirname, содержащие абсолютный путь к файлу и директории модуля.
- Объекты
Область видимости модуля
__dirname
Имя директории текущего модуля. Это то же самое, что path.dirname() файла __filename.
Пример: запуск node example.js из /Users/mjr
console.log(__dirname); // Prints: /Users/mjr console.log(path.dirname(__filename)); // Prints: /Users/mjr
__filename
Имя файла текущего модуля. Это абсолютный путь к текущему файлу модуля с разрешенными символическими ссылками.
Для основной программы это не обязательно то же самое, что имя файла, используемое в командной строке.
См. __dirname для имени директории текущего модуля.
Примеры:
Запуск node example.js из /Users/mjr
console.log(__filename); // Prints: /Users/mjr/example.js console.log(__dirname); // Prints: /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
Ссылка на module.exports, которая короче для ввода. Подробности о том, когда использовать exports и когда module.exports, см. в разделе об обходном пути exports.
module
Ссылка на текущий модуль, см. раздел об объекте module объекта. В частности, module.exports используется для определения того, что экспортирует модуль и делает доступным через require().
require()
Используется для импорта модулей, JSON, и локальных файлов. Модули могут импортироваться из node_modules. Локальные модули и JSON-файлы могут импортироваться с помощью относительного пути (например, ./, ./foo, ./bar/baz, ../foo ), который будет разрешаться относительно директории, указанной в __dirname (если определена), или в текущей рабочей директории.
// Importing a local module:
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('crypto');
require.cache
Модули кешируются в этом объекте при их потреблении. Удаление пары ключ-значение из этого объекта перезагрузит модуль при следующем require. Обратите внимание, что это не относится к нативным плагинам, для которых перезагрузка приведёт к ошибке.
require.extensions
Инструктирует require о том, как обрабатывать определённые расширения файлов.
Обрабатывает файлы с расширением .sjs как .js:
require.extensions['.sjs'] = require.extensions['.js'];
Устарело В прошлом этот список использовался для загрузки в Node.js модулей, отличных от JavaScript, компилируя их по требованию. Однако на практике есть гораздо лучшие способы сделать это, например, загрузка модулей с помощью какой-либо другой программы Node.js или компиляция их в JavaScript заранее.
Поскольку система модулей заблокирована, эта функция, вероятно, никогда не исчезнет. Однако она может содержать тонкие ошибки и сложности, которые лучше оставить без изменений.
Обратите внимание, что количество операций с файловой системой, которые система модулей должна выполнить для преобразования require(...) в имя файла, пропорционально количеству зарегистрированных расширений.
Другими словами, добавление расширений замедляет загрузчик модулей и не рекомендуется.
require.main
Объект Module, представляющий скрипт входа, загруженный при запуске процесса Node.js. См. «Доступ к основному модулю».
В скрипте entry.js:
console.log(require.main);
node entry.js
Module {
id: '.',
exports: {},
parent: null,
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])
-
request<строка> Путь к модулю для разрешения. -
options<объект>-
paths<массив строк> Пути для разрешения местоположения модуля. Если присутствуют, эти пути используются вместо стандартных путей разрешения, за исключением GLOBAL_FOLDERS, таких как$HOME/.node_modules, которые всегда включаются. Обратите внимание, что каждый из этих путей используется в качестве отправной точки для алгоритма разрешения модуля, что означает, что иерархияnode_modulesпроверяется с этого места.
-
- Возвращает: <строка>
Использует внутренние механизмы require() для поиска местоположения модуля, но вместо загрузки модуля просто возвращает разрешённое имя файла.
require.resolve.paths(request)
-
request<string> Путь к модулю, пути поиска которого необходимо получить. - Возвращает: <string[]> | <null>
Возвращает массив, содержащий пути, просмотренные во время разрешения request или null, если строка request ссылается на основной модуль, например http или fs.
Объект module
В каждом модуле свободная переменная module — ссылка на объект, представляющий текущий модуль. Для удобства module.exports также доступен через глобальную переменную модуля exports . module на самом деле не является глобальной, а является локальной для каждого модуля.
module.children
Объекты модулей, которые впервые требуются этим модулем.
module.exports
Объект module.exports, созданный системой Module . Иногда это неприемлемо; многие хотят, чтобы их модуль был экземпляром некоторого класса. Для этого присвойте желаемый экспортируемый объект переменной module.exports. Обратите внимание, что присвоение желаемого объекта переменной exports просто перепривяжет локальную переменную exports, что, вероятно, не является желаемым действием.
Например, предположим, что мы создаём модуль под названием a.js:
const EventEmitter = require('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);
Затем в другом файле мы можем сделать следующее:
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
Переменная 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
Когда свойство module.exports полностью заменяется новым объектом, обычно также переопределяется exports:
module.exports = exports = function Constructor() {
// ... etc.
};
Чтобы проиллюстрировать поведение, представьте себе эту гипотетическую реализацию 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;
}
module.filename
Полное разрешённое имя файла модуля.
module.id
Идентификатор модуля. Обычно это полное разрешенное имя файла.
module.loaded
Загружен ли модуль или находится в процессе загрузки.
module.parent
Модуль, который первоначально потребовал этого.
module.paths
Пути поиска модуля.
module.require(id)
Метод module.require предоставляет способ загрузки модуля так, как будто require() был вызван из исходного модуля.
Для этого необходимо получить ссылку на объект module . Поскольку require() возвращает module.exports, а module обычно только доступен внутри кода конкретного модуля, его необходимо явно экспортировать, чтобы его можно было использовать.
Объект Module
Предоставляет общие вспомогательные методы при работе с экземплярами Module, переменной module, часто встречающейся в модулях файлов. Доступ к нему осуществляется через require('module').
module.builtinModules
Список имён всех модулей, предоставляемых Node.js. Может использоваться для проверки, поддерживается ли модуль третьей стороной или нет.
Обратите внимание, что module в данном контексте не является тем же объектом, что и предоставленный обёрткой модуля обёрткой модуля. Для доступа к нему нужно загрузить модуль Module:
const builtin = require('module').builtinModules;
module.createRequireFromPath(filename)
-
filename<string> Имя файла, используемое для построения относительной функции require. - Возвращает: {
require} Функция require
const { createRequireFromPath } = require('module');
const requireUtil = createRequireFromPath('../src/utils');
// require `../src/utils/some-tool`
requireUtil('./some-tool');
© 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-v10.x/docs/api/modules.html