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