Модули
В системе модулей 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 * r;
exports.circumference = (r) => 2 * PI * r;
Модуль circle.js экспортировал функции area() и circumference(). Чтобы добавить функции и объекты в корень вашего модуля, вы можете добавить их в специальный объект exports.
Переменные, локальные для модуля, будут приватными, потому что модуль обернут в функцию Node.js (см. обёртку модуля). В этом примере переменная PI является приватной для circle.js.
Если вы хотите, чтобы корень экспорта вашего модуля был функцией (например, конструктором) или хотите экспортировать целый объект в одном присваивании вместо построения его по одному свойству за раз, назначьте его в module.exports вместо exports.
Ниже bar.js использует модуль 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 = (width) => {
return {
area: () => width * width
};
};
Система модулей реализована в модуле 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.
Дополнения: Советы по менеджерам пакетов
Семантика функции 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.
Для того, чтобы модули были доступны в интерактивной оболочке 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 = [] 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('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.
Примечание: если файл, указанный в записи 'main' в файле package.json отсутствует и не может быть разрешён, Node.js сообщит об отсутствии всего модуля с помощью стандартной ошибки:
Error: Cannot find module 'some-library'
Если в директории отсутствует файл package.json, Node.js попытается загрузить файл index.js или index.node из этой директории. Например, если в приведённом выше примере не было файла package.json, то require('./some-library') попытается загрузить:
./some-library/index.js./some-library/index.node
Загрузка из папок node_modules
Если идентификатор модуля, переданный в require(), не является модулем ядра и не начинается с '/', '../', или './', тогда Node.js начинает поиск с родительского каталога текущего модуля и добавляет /node_modules, пытаясь загрузить модуль из этого расположения. Node не будет добавлять 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 будет искать в следующих расположениях:
- 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) {
// Your module code actually lives in here
});
Таким образом, Node.js достигает нескольких целей:
- Он сохраняет переменные верхнего уровня (определённые с помощью
var,constилиlet) в области видимости модуля, а не глобального объекта. - Он помогает предоставить несколько глобально выглядящих переменных, которые на самом деле специфичны для модуля, такие как:
- Объекты
moduleиexports, которые разработчик может использовать для экспорта значений из модуля. - Удобные переменные
__filenameи__dirname, содержащие абсолютный путь к файлу и каталогу модуля.
- Объекты
Объект module
В каждом модуле свободная переменная module — ссылка на объект, представляющий текущий модуль. Для удобства module.exports также доступен через exports модульную глобальную переменную. module на самом деле не является глобальной, а локальной для каждого модуля.
module.children
Необходимые для данного объекта модули.
module.exports
Объект module.exports создаётся системой модулей. Иногда это неприемлемо; многие хотят, чтобы их модуль был экземпляром какого-либо класса. Для этого присвойте желаемый объект экспорта 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) => {
// Your 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.require(id)
Метод module.require предоставляет способ загрузки модуля так, как будто require() был вызван из исходного модуля.
Обратите внимание, что для этого необходимо получить ссылку на объект module. Поскольку require() возвращает module.exports, а module обычно только доступен внутри кода конкретного модуля, его необходимо явно экспортировать, чтобы его можно было использовать.
Объект Module
Предоставляет общие служебные методы при работе с экземплярами Module — переменной module, часто встречающейся в модульных файлах. Доступ к нему через require('module').
module.builtinModules
Список имён всех модулей, предоставляемых Node.js. Может использоваться для проверки, поддерживается ли модуль сторонним модулем или нет.
© 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-v6.x/docs/api/modules.html