Modules


Стабильность: 3 – Закрыто

Node.js имеет простую систему загрузки модулей. В Node.js, файлы и модули однозначны ("один к одному") (каждый файл обрабатывается, как отдельный модуль). В качестве примера, foo.js загружает модуль circle.js в том же каталоге.

Содержимое foo.js:

const circle = require('./circle.js');
console.log(`The area of a circle of radius 4 is ${circle.area(4)}`);

Содержимое 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.

Если вы хотите чтобы экспорт вашего модуля был функцией (например, как конструктор (прим. конструктор - специальный метод класса (class method), обеспечивающий создание объекта данного класса или инициализацию состояния объекта), или если вы хотите экспортировать полный объект в одном назначении, а не строить ему одно свойство за раз, присвойте его 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, если это запущено через foo.js, но неверно, если запущено с помощью ( './ Foo').
Поскольку module  предоставляет свойство filename (обычно эквивалентно __filename), точка входа текущего приложения может быть получена путем проверки require.main.filename.

Приложения: Советы по Диспетчеру пакетов

Семантика Функции Node.js require() были разработаны так, чтобы быть достаточно общими, чтобы поддерживать ряд корректных структур каталогов.

Программы диспетчера пакетов, таких как dpkgrpm, и npm  скорее всего найдут возможность строить собственные пакеты из модулей Node.js без изменений.

Ниже мы приводим предлагаемую структуру каталогов, которая могла бы работать:

Давайте предположим, что мы хотели иметь папку в /usr/lib/node/<some-package>/<some-version> для хранения содержимого конкретной версии пакета.

Пакеты могут зависеть друг от друга. Для того чтобы установить пакет foo, возможно, придется установить определенную версию пакета bar. Пакет bar сам по себе может быть зависимым, а в некоторых случаях эти зависимости могут даже вступать в конфликты или образовывать циклы (наименьший повторяющийся отрезок какой-либо последовательности (сигналов, действий, команд и т. п.).

Так как Node.js сканирует realpath на наличие каких-либо модулей, которые он (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(), пакеты сами по себе могут быть где угодно.

All Together...

Для того, чтобы получить точное имя файла, который будет загружен, когда вызывается 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') -- ссылающийся на один и тот же файл -- всегда вернет одинаковый объект.