Последние года три-четыре я при программировании на 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-funcnpm найдёт соответствующие модули в /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 также предоставляет возможность разрабатывать многомодульные приложения в довольно комфортных условиях.
Командные скрипты для развёртывания проекта в различных режимах:
