Spec-Zone.ru › Node.js 4 LTS

Модули

Стабильность: 3 — Заблокировано

В Node.js простая система загрузки модулей. В 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.PI;

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');
var mySquare = square(2);
console.log(`The area of my square 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 './' or '/' or '../'
   a. LOAD_AS_FILE(Y + X)
   b. LOAD_AS_DIRECTORY(Y + X)
3. LOAD_NODE_MODULES(X, dirname(Y))
4. 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_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)
2. If X/index.js is a file, load X/index.js as JavaScript text.  STOP
3. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP
4. If X/index.node is a file, load X/index.node as binary addon.  STOP

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.

Если модуль не будет найден там, 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 будет искать в следующих местах:

  • 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) {
// Your module code actually lives in here
});

С помощью этого Node.js достигает нескольких целей:

  • Это сохраняет переменные верхнего уровня (определённые с помощью var, const или let) в пределах модуля, а не в глобальном объекте.
  • Это помогает предоставить некоторые переменные, выглядящие как глобальные, но на самом деле специфичные для модуля, такие как:
    • Объекты module и exports, которые разработчик может использовать для экспорта значений из модуля.
    • Удобные переменные __filename и __dirname, содержащие абсолютный путь к файлу и каталогу модуля.

Объект module

Добавлен в: v0.1.16
  • <Объект>

В каждом модуле свободная переменная module — ссылка на объект, представляющий текущий модуль. Для удобства module.exports также доступен через глобальную переменную модуля exports . module фактически не является глобальной, а локальной для каждого модуля.

module.children

Добавлен в: v0.1.16
  • <Массив>

Требуемые для этого модуля объекты модулей.

module.exports

Добавлен в: v0.1.16
  • <Объект>

Объект 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

Добавлен в: v0.1.16

Переменная 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(...) {
  var module = { exports: {} };
  ((module, exports) => {
    // Your module code here. In this example, define a function.
    function some_func() {};
    exports = some_func;
    // At this point, exports is no longer a shortcut to module.exports, and
    // this module will still export an empty default object.
    module.exports = some_func;
    // At this point, the module will now export some_func, instead of the
    // default object.
  })(module, module.exports);
  return module.exports;
}

module.filename

Добавлен в: v0.1.16
  • <Строка>

Полное имя файла модуля.

module.id

Добавлен в: v0.1.16
  • <Строка>

Идентификатор модуля. Обычно это полное имя файла.

module.loaded

Добавлен в: v0.1.16
  • <Булево значение>

Загружен ли модуль или находится в процессе загрузки.

module.parent

Добавлен в: v0.1.16
  • <Объект> Объект модуля

Модуль, который сначала потребовал этот модуль.

module.require(id)

Добавлен в: v0.5.1
  • id <Строка>
  • Возвращает: <Объект> module.exports из разрешенного модуля

Метод module.require предоставляет способ загрузки модуля так, как будто require() был вызван из исходного модуля.

Обратите внимание, что для этого вы должны получить ссылку на объект module . Поскольку require() возвращает module.exports, а 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-v4.x/docs/api/modules.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API