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.
- Введение
- 1. Что такое NODE?
- 1.1 Что позволяет делать Node?
- 1.2 Почему имеет смысл использовать Node?
- Архитектура: потоки или асинхронный ввод/вывод с управлением по событиям.
- 1.3 Производительность и использование процессора
- 1.4 Использование серверов, экономия затрат и экологичный Интернет
- 2. Характеристики NODE
- 2.1 Системные требования
- 2.2 Запуск Node-серверов на этапе инициализации системы
- 2.3 Использование всех процессорных ядер в многоядерной системе
- 3. Модули Node
- 3.1 Как Node ищет модули, затребованные в require(module)?
- Менеджер пакетов для Node (npm)
- 4. Хранение и выборка данных
- 4.1 Движки сохранения данных для Node
- 4.2 SQLite3 - облегченная встраиваемая база данных на основе SQL
- 4.3 Mongoose - интерфейс между Node и MongoDB
- 5. Практический пример на основе продолжительных вычислений (числа Фибоначчи)
- Глава 2 Описание языков для разработки web-приложений
- Выбор среды разработки, языка программирования и инструментальных средств разработки
- 16. Языки и средства создания Web-приложений
- JavaScript-приложения
- JavaScript
- Разработка Web - приложений с использованием uml
- JavaScript и Вы
- Средства создания Web –приложений.