logo
Разработка Web-приложения с использованием JavaScript каркаса Node.js

3.1 Как Node ищет модули, затребованные в require(module)?

В Node модули хранятся в файлах, в каждом файле по одному модулю. Есть несколько подходов к именованию модулей и к размещению их в файловой системе. В целом получается весьма гибкая система, особенно в сочетании с npm, стандартным менеджером пакетов для Node.

Идентификаторы модулей и пути

Вообще говоря, имя модуля - это путь, но без расширения имени файла. Таким образом, когда мы пишем require(./simple), Node знает, что необходимо добавить к имени файла расширение .js и загрузить файл simple.js.

Естественно, ожидается, что файлы, имена которых заканчиваются на .js, содержат код, написанный на JavaScript. Node поддерживает также модули в виде двоичных платформенных библиотек. В таком случае имя файла должно оканчиваться расширением .node.

Некоторые модули Node не являются файлами в файловой системе, а «зашиты» в исполняемый файл Node. Это модули ядра (Core), документированные на сайте nodejs.org. Изначально они существуют в виде файлов в дереве исходного кода Node, но в ходе сборки прикомпилируются к исполняемому файлу.

Существуют три типа идентификаторов модулей: относительные, абсолютные и верхнего уровня.

Относительные идентификаторы модулей начинаются строкой "./" или "../", а абсолютные - строкой "/". Здесь имеется полная аналогия с семантикой файловой системы, совместимой с POSIX, в которой пути записываются относительно исполняемого файла.

Понятно, что абсолютные идентификаторы модулей записываются относительно корня файловой системы.

В начале идентификатора модуля верхнего уровня нет ни ".", ни "..", ни "/", это просто имя модуля. Такие модули хранятся в одном из нескольких предопределенных каталогов, например node_modules, или в каталогах, перечисленных в массиве require.paths.

3.2 Локальные модули внутри приложения

Все множество мыслимых модулей можно разбить на две категории: являющиеся и не являющиеся частью приложения. Модули, не являющиеся частью конкретного приложения, написаны, имея в виду какую-то обобщенную цель. Начнем с реализации модулей, используемых только в вашем приложении.

В типичном приложении модули распределены по нескольким каталогам. Они хранятся в системе управления версиями и впоследствии копируются на серверы. Эти модули знают относительные пути к своим «братьям» и могут использовать эту информацию, чтобы ссылаться друг на друга, но относительным идентификаторам.

Чтобы лучше понять, как это организовано, возьмем в качестве примера структуру одного из пакетов для Node, каркаса разработки веб-приложений Express. Он состоит из нескольких модулей, организованных в виде иерархии, которую разработчики Express находят полезной. Подобные иерархии имеет смысл создавать, когда приложение достигает определенного уровня сложности, оправдывающего его разбиение на части, большие, чем модуль, но меньшие, чем приложение. К сожалению, специального термина для таких структурных компонентов в Node нет, поэтому приходится употреблять корявую фразу «разбивать на части, большие, чем модуль». Каждая такая часть представляется каталогом с несколькими модулями.

3.3 Комплектация приложения с внешними зависимостями

Для включения модулей, находящихся в каталоге node_modules, употребляется идентификатор верхнего уровня:

var express = require(express);

Node производит поиск модулей во всех каталогах node_modules, а их существует несколько. Алгоритм начинает поиск в каталоге текущего модуля, потом добавляет в путь node_modules и ищет там. Если модуль не найден в каталоге node_modules, то Node переходит к родительскому каталогу и пробует еще раз - и так до тех пор, пока не дойдет до корня файловой системы.

Но что, если мы захотим использовать каркас Express в своем приложении? Ничего сложного, просто создаем каталог node_modules внутри дерева своего приложения и устанавливаем туда Express:

Здесь показано гипотетическое приложение drawapp. Если каталог node_modules расположен так, как показано на рисунке, то любой модуль внутри drawapp может получить доступ к express следующим образом:

var express = require( express);

Однако те же самые модули не смогут добраться до модуля qs, скрытого внутри каталога node_modules, являющегося частью самого каркаса Express. Просмотр каталогов node_modules, содержащих искомый модуль, производится вверх по иерархии файловой системы, без захода в дочерние каталоги.

Аналогично: если установить модуль в каталог lib/node_modules, то он будет доступен из draw.js и svg.js, но недоступен из index.js. Как и раньше, поиск происходит вверх от текущего каталога, а не вглубь него.

При обходе каталогов node_modules Node останавливается, как только найдет искомый модуль. Так, если ссылка встречается в файле draw,js или svg.js, то будут просмотрены следующие каталоги:

/home/david/projects/drawapp/lib/node_modules

/home/david/projects/drawapp/node_modules

/home/david/projects/node_modules

/home/david/node_modules

/home/node_modules

/node_modules

Каталог node_modules играет важнейшую роль, позволяя системе управления пакетами выпутаться из лабиринта конфликтующих версий. Вместо того чтобы помещать все модули в одно место и медленно сходить с ума, пытаясь разрешить зависимости от конфликтующих номеров версий, мы можем завести несколько каталогов node_modules и при необходимости складывать конкретные версии в конкретное место. Разные версии одного модуля могут находиться в разных каталогах node_modules, и при условии, что эти каталоги расположены правильно относительно друг друга, никаких конфликтов не возникнет.

Пусть, например, мы написали приложение, в котором используется модуль forms (https://github.com/caolan/forms) для построения форм, и, когда у вас уже накопились сотни форм, авторы модуля внесли в него несовместимые изменения. Переделывать и заново тестировать все формы сразу вам не хочется, лучше делать это постепенно. Для этого надо будет создать в приложении два каталога, организовать в каждом свой подкаталог node_modules и поместить в них разные версии модуля forms. Затем, по мере перевода очередной формы на новый модуль forms, ее код перемещается в каталог, где находится новая версия.

3.4 Системные модули в каталогах, перечисленных в массиве require.paths

При поиске каталогов node_modules Node не ограничивается деревом приложения. Алгоритм доходит до корня файловой системы, поэтому можно создать каталог /node_modules и организовать в нем глобальный репозиторий модулей. Именно здесь будет завершаться поиск модуля, не найденного ни в каком другом месте.

Но Node предоставляет и еще один механизм, основанный на переменной require,paths. Это массив имен каталогов, в которых следует искать модули.

Приведем пример:

$ node

> require.paths;

["/home/david/.node_modules",/home/david/.node_libraries", "/usr/local/lib/node"]

Для заполнения массива require,paths используется переменная окружения NODE_PATH:

$ export NODE_PATH=/usr/lib/node

$ node

> require.paths;

["/usr/lib/node","/home/david/.node_libraries","/usr/local/lib/node”]

>

Раньше в программах для Node часто применялась следующая идиома для добавления новых элементов в массив require.paths:require.paths.push(__dirname). Однако теперь она не рекомендуется, потому что, как выяснилось, является источником путаницы. Хотя так делать можно и даже еще остались модули, в которых эта идиома встречается, но смотрят на ее использование с большим неодобрением. Если несколько модулей помещают каталоги в require.paths, то результаты непредсказуемы.

В большинстве случаев рекомендуется устанавливать модули в каталоги node_modules.

Составные модули - модули-каталоги

Составной модуль может включать несколько внутренних модулей, файлы данных, файлы шаблонов, документацию, тесты и прочее. Все это можно поместить в хорошо продуманную структуру каталогов, которую Node будет рассматривать как модуль, и загружать командой require(moduleName). Для этого следует добавить в каталог файл модуля index.js или файл с именем package.json. Файл package.json должен содержать данные, описывающие модуль, в формате, очень похожем на формат файла package.json, используемого менеджером пакетов npm (см. ниже). В обоих случаях для совместимости с Node достаточно очень небольшого набора полей, распознаваемых npm.

Точнее, Node распознает следующие поля в файле package.json:

{ name: "myAwesomeLibrary",

main: "./lib/awesome.js" }

При таком файле package.json команда require(myAwesomeLibrary) найдет этот каталог и загрузит файл

/path/to/node_modules/myAwesomeLibrary/lib/awesome.js

Если файла package.json нет, то Node будет вместо него искать файл index.js, то есть загрузит файл:

/path/to/node_modules/myAwesomeLibrary/index.js

В любом случае (index.js или package.json) реализовать составной модуль, содержащий внутренние модули и другие файлы, несложно. Если вернуться к рассмотренной выше структуре пакета Express, то мы увидим, что некоторые модули пользуются относительными идентификаторами для ссылки на другие модули в пакете, а для включения модулей, разработанных кем-то другим, можно воспользоваться каталогом node_modules.