Для управления зависимостями в проекте, node.js, как и многие другие платформы, предоставляет собственный пакетный менеджер — npm. И несмотря на то, что он внешне похож, например, на Ruby Gems, и вроде бы выполняет те же самые функции, npm обладает некоторыми особенностями, которые стоит учитывать при разработке приложений на node.js. Одна из таких особенностей — это способ хранения директории node_modules в проекте. Многие, по аналогии с другими системами, оставляют в проекте только package.json с зафиксированными версиями модулей, а node_modules добавляют в .gitignore. Такая стратегия не всегда верна, если мы обратимся в FAQ на npmjs.org, то увидим там следующее:
Q: Стоит ли хранить node_modules в git?
A: Mikeal Rogers очень хорошо ответил на этот вопрос:
http://www.mikealrogers.com/posts/nodemodules-in-git.html
tl;dr
- Храните node_modules в git для проектов, которые требуется разворачивать, таких как вебсайты и приложения.
- Добавляйте node_modules в .gitignore для библиотек и повторно используемых модулей.
- Используйте npm для управления зависимостями в dev окружении, но не в скриптах используемых для деплоя.
Под катом перевод статьи Mikeal Rogers, в котором подробно описывается, с чем связан такой непривычный подход.
Одна из проблем, потребовавших переосмысления в мире node.js — это вопрос управления зависимостями приложения.
Одним из больших изменений, которые пришли вместе с node 0.4.0, стала поддержка node_modules. Это изменение имело серьезные последствия. Оно подняло приоритет локальных модулей в локальной директории над модулями установленными глобально. Вместе с тем, npm изменил установку по умолчанию с глобальной на локальную и мы увидели практически единогласный переход на локальные модули.
Переход с глобальных модулей на локальные — это то, что отличает node от платформ предыдущих поколений. Ruby и Python полностью провалились в этой области, и тот факт, что стандартной практикой стала разработка и деплой в изолированной песочнице для всей платформы (virtualenv, rvm) — это признание провала.
Но ситуация улучшается. Поддержка локальных модулей в node позволяет достичь того, что ни одна, известная мне, платформа еще не сделала, она позволяет двум зависимостям подключить абсолютно разные версии одного и того же модуля, без каких-либо конфликтов и непредвиденных последствий. Это потребовало некоторой продвинутой логики в ядре, для того чтобы разрулить имена модулей локально и рекурсивно. Но самым важным условием для такой поддержки было то, что никто, никогда не должен рассчитывать на то, что зависимости на уровне модуля будут глобальными для всего процесса ноды, хотя такой подход навязывался сообществом какое-то время.
Теперь мы уже привыкли к локальным модулям, освоились, несмотря на некоторое нытье на ранней стадии. И все же мы держимся за старые привычки, которые остались у нас со старых глобальных деньков.
Когда мы имели дело с глобальными инсталляциями, и в особенности с преимуществом глобальных модулей над локальными при разрешении конфликтов имен, хранение зависимостей в системе контроля версий было очень плохой идеей. Это плохо в основном из-за того, что это открытое вранье. Наличие кода зависимости не означает того, что он будет использоваться в случае, если тот же модуль установлен глобально. Мы разработали тяжелые инструменты для деплоя, чтобы убедиться в том, что когда код развернут в одном месте, а неделю спустя тот же код развернут уже в другом месте, обе инсталляции получат один и тот же набор зависимостей. Все эти инструменты — корявые, потому что проблема сама по себе — корявая.
Но теперь это уже не Ruby или Python, это node.js, и мы сделали модули гораздо более лучшими. Если у вас есть приложение, которое вы разворачиваете, поместите все зависимости в директории node_modules в систему контроля версий. Если вы хотите использовать npm для деплоя, подключите эти модули только в bundleDependencies. Если у вас есть зависимости, требующие компиляции, все равно закоммитте их код и просто запустите npm rebuild при деплое.
Все, кому я это рассказывал, говорили мне, что я идиот, а затем, через пару недель, говорили, что я был прав, и что размещение node_modules в git было благословением и для деплоя и для разработки. Это объективно лучше, но вот несколько вопросов/претензий, которые возникают:
Почему бы мне просто не фиксировать версии, чтобы гарантировать, что все инсталляции получат одинаковые зависимости?
Фиксация версий может зафиксировать версии только у зависимостей верхнего уровня. Вы фиксируете express конкретной версии и разворачиваетесь на машине три недели спустя, npm будет разрешать зависимости express-а снова и может получить новую версию connect-а с незначительными изменениями, которые поломают ваше приложение самым неприятным и сложным для отладки способом, так как вы узнаете об этом только когда запросы пойдут на новую машину. Это станет кошмаром, не делайте так.
Почему бы нам не поощрять мэйнтейнеров библиотек тоже фиксировать версии своих зависимостей?
На сегодняшний день в реестре npm примерно 5,500 пакетов (Примечание переводчика: данные на декабрь 2011, сейчас их уже почти 34 тысячи.). За прошлый месяц прибавилось более 600 новых пакетов и более 5,000 было обновлено. Для некоторых пакетов было выпущено несколько обновлений в течение недели. Как сообщество, мы должны распределять между собой часть интеграционного тестирования. Это невозможно для большинства мэйнтейнеров — сидеть и тестировать свой пакет со всеми обновлениями, которые приходят для зависимостей пакета. Вот почему разработчики библиотек не должны фиксировать версии и не должны коммитить свои зависимости в репозиторий. Нам нужны новые люди для обновления зависимостей локально и отправки баг-репортов. Нам нужно поддерживать движение сообщества вперед и держаться на острие последних версий и новых пакетов.
Только приложения, требующие развертывания, должны хранить node_modules в репозитории. Мэйнтейнеры пакетов должны продолжать сами определять приемлемые границы версий для своих зависимостей. Только так можно поддерживать сообщество в форме с тем количеством изменений и улучшений, которое мы видим в node.js.
Не создает ли хранение node_modules в репозитории слишком много, не имеющего отношения к моему приложению, мусора в дереве исходников?
Нет, вы ошибаетесь, этот код используется вашим приложением, он — часть вашего приложения, если будете делать вид, что это не так, это приведет к проблемам. Вы зависите от кода других людей, а они делают ошибки и плодят новые баги не реже вас, может быть даже чаще. Хранение всего кода в репозитории дает вам возможность следить за каждой строчкой, которая когда-либо менялась в вашем приложении. Это позволит вам использовать git bisect локально и быть уверенным, что результат будет аналогичным в production и что все машины в production — идентичны. Нет больше нужды следить за неведомыми изменениями в зависимостях, все изменения, в каждой строчке, доступны через систему контроля версий.
Ok, отлично, что же нам теперь делать?
Резюмируем.
- Храните node_modules в репозитории для приложений, которые вы разворачиваете, но не для библиотек, которые вы поддерживаете.
- Все компилируемые зависимости должны хранить в репозитории исходники, а не результат компиляции и должны собираться с помощью npm rebuild при деплое.
Вы все, кто добавили node_modules в ваш .gitignore, удаляйте этот отстой, сегодня. Это пережиток эры, которую мы слишком счастливы оставить позади. Эра глобальных модулей мертва.