Последние года три-четыре я при программировании на PHP использовал composer
для управления зависимостями приложения. Сейчас появилась потребность перейти на nodejs
и, как следствие, настроить привычную для себя среду разработки. Благо, что я использую IDE PhpStorm, который позволяет работать и с PHP, и с JS. Особенностью проектов, в которых я участвую, является многомодульность. Функциональность разделяется между модулями не столько для повторного использования, сколько для уменьшения итоговой сложности приложения за счёт декомпозиции на слабосвязанные компоненты. В общем, для этих проектов нормально, когда в рамках решения одной задачи изменения вносятся в несколько модулей и коммитятся в несколько репозиториев.
При настройке nodejs
-проекта я столкнулся с некоторыми особенностями, которые осложняют многомодульную разработку. Данная публикация родилась в процессе попытки разобраться с этими особенностями. Под катом взгляд PHP'шника на развёртывание nodejs
-проекта.
Структура демо-проекта
Проект состоит из 3 модулей:
- приложение: головной модуль, подключающий зависимости;
- функциональный модуль: содержит функции, вызываемые из головного модуля;
- базовый модуль: содержит функции, вызываемые из функционального модуля;
Код каждого модуля располагается на github'е:
Дескрипторы модуля для соответствующих менеджеров зависимостей (composer.json
и package.json
) находятся в каждом модуле, т.е., каждый модуль можно разворачивать и как php-модуль, и как js-модуль. PHP-код и JS-код в модулях просто размещаются рядом, не пересекаясь друг с другом.
Запуск приложения на выполнение:
$ php index.php
$ nodejs index.js
Результат работы в обоих случаях:
This is application.
This is func module.
This is base module.
Цели
Проект в рабочем окружении должен позволять:
- отслеживать через IDE изменения в каждом модуле, участвующем в разработке;
- при помощи IDE коммитить изменения в разные репозитории в рамках одного действия;
- использовать отладчик для трассировки выполнения кода модулей;
Развёртывание через composer
Здесь всё привычно. В дескрипторе развёртывания (composer.json) приложения указываем адреса репозиториев с модулями и прописываем master-ветки модулей в качестве зависимостей с желаемой версией:
{
"require": {
"flancer64/habr-cvsn-mod-base": "dev-master as 0.1.0",
"flancer64/habr-cvsn-mod-func": "dev-master as 0.1.0"
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/flancer64/habr-cvsn-mod-base"
},
{
"type": "vcs",
"url": "https://github.com/flancer64/habr-cvsn-mod-func"
}
]
}
После выполнения команды:
$ composer install
В каталоге ./vendor
появляются подкаталоги с модулями, в свою очередь содержащие .git
-каталоги:
./vendor/
./flancer64/
./habr-cvsn-mod-base/
./.git/
./habr-cvsn-mod-base/
./.git/
Т.е., composer
сразу разворачивает зависимости в виде, пригодном для разработки (контроля версий). Остаётся только настроить IDE PhpStorm (поставить зависимые модули под контроль версий):
и можно отслеживать изменения во всех разрабатываемых модулях:
и одновременно коммитить все изменения в локальные репозитории:
и push'ить в удалённые:
С отладкой также нет проблем. Можем ставить точки останова (breakpoints) на любой строке кода базового модуля — после запуска приложения под отладчиком останов происходит там, где нужно:
В общем, привычное окружение, удобное, как домашние тапочки.
Обычное развёртывание через npm
В отличие от composer
'а npm
не предполагает, что в проекте какие-то модули из каталога node_modules
могут находиться под контролем версий. Мы можем в дескрипторе развёртывания package.json указать, что модуль нужно подгружать из внешнего репозитория (например, с githib'а):
{
"dependencies": {
"habr-cvsn-mod-base": "github:flancer64/habr-cvsn-mod-base",
"habr-cvsn-mod-func": "github:flancer64/habr-cvsn-mod-func"
}
}
но у нас нет опции, чтобы npm
при этом создал локальный репозиторий для загруженного модуля (подкаталог .git
).
После выполнения команды:
$ npm install
зависимости загружаются и устанавливаются локально без возможности использования контроля версий:
(отсутствует подкаталог ./.git/
в ./node_modules/habr-cvsn-mod-base/
)
Зато отладчик останавливается в базовом модуле без проблем:
Развёртывание через npm с использованием опции link
Чтобы модули из ./node_modules/
можно было держать под контролем версий, разработчики npm
предлагают использовать опцию 'link'. Если коротко, то суть подхода заключается в том, что модули, которые нужно версионировать, клонируются в произвольное место на диске разработчика (допустим, в /home/alex/work/habr/
), а затем линкуются в /usr/lib/node_modules/
при помощи команды:
# npm link
(мне понадобились права root'а для выполнения)
После чего уже в проекте можно использовать команды:
$ npm link habr-cvsn-mod-base
$ npm link habr-cvsn-mod-func
npm
найдёт соответствующие модули в /usr/lib/node_modules/
и замкнёт на них соответствующие подкаталоги из ./node_modules/
проекта:
$ ls -lh ./node_modules/
total 0
lrwxrwxrwx 1 alex alex 57 jūl 2 16:18 habr-cvsn-mod-base -> ../../../../../../usr/lib/node_modules/habr-cvsn-mod-base
lrwxrwxrwx 1 alex alex 57 jūl 2 16:18 habr-cvsn-mod-func -> ../../../../../../usr/lib/node_modules/habr-cvsn-mod-func
Модули в /usr/lib/node_modules/
сами, в свою очередь, являются ссылками на оригинальное местонахождение модулей:
$ ls -lh /usr/lib/node_modules/
...
lrwxrwxrwx 1 root root 39 jūl 2 16:18 habr-cvsn-mod-base -> /home/alex/work/habr/habr-cvsn-mod-base
lrwxrwxrwx 1 root root 39 jūl 2 16:18 habr-cvsn-mod-func -> /home/alex/work/habr/habr-cvsn-mod-func
...
И по своему постоянному месту "прописки" содержат локальный репозиторий:
$ ls -lha /home/alex/work/habr/habr-cvsn-mod-base
...
drwxrwxr-x 8 alex alex 4,0K jūl 2 16:18 .git
...
Таким образом, мы можем настроить IDE на контроль изменений в зависимостях проекта:
Проблемы начинаются при попытке запустить приложение:
$ nodejs index.js
internal/modules/cjs/loader.js:670
throw err;
^
Error: Cannot find module 'habr-cvsn-mod-base'
at Function.Module._resolveFilename (internal/modules/cjs/loader.js:668:15)
at Function.Module._load (internal/modules/cjs/loader.js:591:27)
at Module.require (internal/modules/cjs/loader.js:723:19)
at require (internal/modules/cjs/helpers.js:14:16)
at Object.<anonymous> (/home/alex/work/habr/habr-cvsn-mod-func/src/index.js:3:14)
...
Приложение видит залинкованный функциональный модуль, но сам функциональный модуль не видит залинкованный базовый. Для выхода из положения предназначен ключ '--preserve-symlinks' для nodejs
:
$ nodejs --preserve-symlinks index.js
Добавляем ключ в команду запуска проекта в IDE:
Теперь запуск проходит, но есть проблемы с отладкой — не отрабатывают точки останова в зависимостях. Можно остановиться в головном модуле и спуститься по шагам в исходники зависимостей, но самой точки останова IDE PhpStorm в runtime не видит, хотя и отображает:
Разработчики IDE говорят, что должно работать, но не работает (кэш сбрасывал, IDE перезапускал).
В общем-то, целью этой публикации был опрос js-коллег, как они выкручиваются в подобной ситуации, но в процессе написания статьи вырисовалась ещё одна комбинация для развёртывания проекта:
Размещение исходников во внутренних каталогах npm-проекта
Оказалось, что если для линковки использовать клоны модулей с github'а, созданные composer
'ом в подкаталоге ./vendor/
, а не завязываться на внешние по отношению к проекту каталоги, то js-скрипты запускаются без ключа --preserve-symlinks
и, что для меня более важно, IDE PhpStorm видит точки останова внутри модулей. Т.к. нет смысла использовать composer
лишь для клонирования модулей проекта, я использовал обычный git
и склонировал исходники модулей в подкаталог ./own_modules
. После чего повторил манипуляции из предыдущего пункта:
- связал модули в подкаталоге
./own_modules/...
с системной библиотекой/usr/lib/node_modules/
; - связал модули в системной библиотеке с проектом;
- настроил IDE PhpStorm на работу с локальными репозиториями в подкаталоге
./own_modules/
;
Не знаю, в чём причина, но когда исходники зависимых модулей лежат внутри проекта, то конечный результат сборки значимо отличается от того, когда исходники зависимых модулей лежат в каталоге, внешнем по отношению к проекту.
Резюме
Сравнив два подхода к сборке многомодульных приложений (PHP с composer
'ом и JS с npm
), могу сделать вывод, что composer
более дружелюбен к разработчикам, чем npm
. Возможно, что разработчики composer
(первый релиз в 2012) учли опыт разработчиков npm
(первый релиз в 2010). Тем не менее, при некоторых дополнительных усилиях, npm
также предоставляет возможность разрабатывать многомодульные приложения в довольно комфортных условиях.
Командные скрипты для развёртывания проекта в различных режимах: