NPX, или Прощайте, глобальные зависимости

Гасконцам от программирования посвящается.

Когда дело касается глобальных пакетов, все говорят, что это зло. Однако через некоторое время в файле README.md странным образом обнаруживается инструкция типа:

npm install -g typescript

Самые яростные кричат: «Тысяча чертей, я же сто раз говорил не делать этого!» На что слышат невнятный ответ: «Так наш пакет не собирался локально, дебажить мы не могли». Что тут скажешь? Давайте же разберемся, являются ли глобальные NPM-пакеты вселенским злом.

Для чего вообще нужны глобальные зависимости

Первое и самое главное: глобальные пакеты вносят раздрай в стройные ряды команды. И сколько бы вы ни требовали: «Каждый день обновляйте свои глобальные пакеты на рабочих машинах перед началом работы, да и вообще перед каждым коммитом», — это только воздух сотрясать. В один прекрасный день придет QA и скажет: «Написанное вами у меня не билдится!»

Если вы решите настроить CI/CD-систему, то все восклицания: «У нас прекрасно собирается локально!» — пойдут Бобику под хвост, потому что версии у вас и на билд-машине будут совершенно разные.

Теперь немного о безопасности. Знаете ли вы, что все глобальные NPM-пакеты ставятся под root в Linux и macOS? Не знаете? Так вот, они ставятся под root. (Кстати, чтобы избежать этого, предлагаю прочесть материал по ссылкам 1 и 2 в конце статьи.)

И еще раз о «Месье, у меня работает локально». Метод require в ноде резолвит имена зависимостей следующим образом:

  • Вначале проверяет собственные node_modules проекта.
  • Потом require смотрит в глобальные модули, которые для Unix-систем и для macOS лежат по адресу /usr/local/lib/node_modules (вы можете узнать этот адрес, набрав команду npm root -g).
  • А далее, спускаясь от директории вашего юзера к вашей рабочей директории, он будет искать зависимости во всех встреченных им node_modules-папках.

А теперь рассмотрим следующий широко распространенный случай. У меня есть кусок кода, ссылающийся на модуль, который я перетер в своих локальных зависимостях (на самом деле я беру самый простой вариант — на практике они еще более изощренные). Локально все работает на ура, потому что в глобальных модулях сидит предустановленная мной когда-то старая зависимость. Но потом я буду долго мучиться и анализировать логи в поисках причины.

Казалось бы, что сразу приходит на ум после таких доводов? Так как глобальные зависимости являются злом, нужно убрать их в локальные. Этот путь имеет смысл — ваш покорный слуга даже сейчас делает так для некоторых проектов. Глобальные зависимости убираются в дев-депенденси, где тихо доживают свой век. Скрипты npm будут с радостью подхватывать их не хуже глобальных. Казалось бы, и волки сыты, и овцы целы. Однако такие действия угрожают большой перегрузкой дев-секции и, как следствие, способствуют долгому выполнению команды npm install, а также ведут к большому потреблению дискового пространства на девелоперской машине (пять проектов — пять карм, три еслинта, четыре тайпскрипта). То есть мы приходим к тому, зачем писались глобальные депенденси, — и это повторное потребление места различными пакетами для одних и тех же зависимостей.

NPX как способ уйти от глобальных пакетов

Но теперь рассмотрим новый путь. Новым путем будет использование на проекте NPX. Этот инструмент предназначен для облегчения запуска скриптов — и снижения зависимости разработчика и от глобальных, и от локальных привязок в целом. Как это работает?

NPX — это инструмент, который нужен для упрощения использования утилит и исполняемых файлов. Также он помогает использовать утилиты без run-скрипта и запускать команды с различными версиями ноды. Для тех, кто хочет больше узнать именно об NPX, я рекомендую материал по ссылке 3 в конце статьи, где в легкой форме изложены базовые принципы и возможности использования этой утилиты.

Допустим, я разработчик на Angular 8, но упорно не хочу ставить Angular CLI себе в глобальные сборки: он устареет, и мне надо будет каждый раз переставлять его. Есть выход — делать все руками. Но я ленивый программист, обладающий знаниями shell. И что я делаю в таком случае? Те программы, которые вызываются крайне редко, я буду вызывать так, как есть — через NPX. И для создания нового проекта на Angular мне нужно будет набрать следующую команду:

$npx -p @angular/cli ng new my-new-project

Определившись с тем, чего я хочу, получаю следующий проект с секцией скриптов в project.json.

  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  }

Сейчас вы скажете: «Ага, ничего не получилось». Действительно, запустить я ничего не смогу, потому что у меня нет Angular CLI на локальном устройстве. А теперь давайте задумаемся, что такое ng? А ng — это всего лишь алиас. И потому мы пишем лаконичную строку в shell (для людей, пользующихся ОС Windows, такой трюк не сработает):

$alias ng='npx -p @angular/cli ng'

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

npm list -g --depth=0

Кроме уже существующих зависимостей, не должно ничего появиться.

И как только захотим убрать этот алиас, мы делаем команду:

unalias ng

В итоге

Достоинства способа:

  1. Мы можем полностью уйти от глобальных пакетов.
  2. Мы всегда пользуемся последней версией пакета.
  3. У всей команды и у всех рабочих окружений версия пакета будет одна и та же. (Это утверждение спорное, но пускай останется для беседы с прочитавшими его).
  4. Для создания алиасов можно написать простой скрипт и запускать его на разных окружениях в качестве install-скрипта.

Недостатки способа:

  1. Тратится время на загрузку пакета (хотя, если пакет уже прогрузился, npx кеширует его).
  2. Не работает под Windows. Самый главный минус этого способа, однако если использовать разработку в контейнере, то он вместе с проблемами Windows отходит на второй план.

В качестве дополнения хотелось бы сказать, что автор не топит за какой-то отдельный фреймворк. Angular использовался только как пример. С этим способом работают любые тестовые фреймворки, бойлерплейты, такие как React, и любые CLI.

Полезные ссылки, о которых шла речь в статье:

  1. Resolving EACCES permissions errors when installing packages globally.
  2. Install npm packages globally without sudo on macOS and Linux.
  3. Представляем npx: утилиту для запуска npm-пакетов.
Похожие статьи:
Після розв’язання росією повномасштабної війни в Україні сформувалося волонтерське об’єднання понад 300 тисяч фахівців, які ведуть...
У першому кварталі 2023 року експорт IT-послуг з України знизився на 16%, у порівнянні з таким же періодом 2022 року, і склав $1,68 млрд....
У свіжому випуску новинного дайджесту DOU News розповідаємо про те, як українці вивчатимуть інформатику, дізнаємось, як же...
На нашем YouTube канале появились новые видеоролики.Чехлы Spigen для iPhone 6 Plus / iPhone 6S Plus:Обзор клавиатуры Apple Smart Keyboard для iPad...
Дайджест создан в соавторстве с Егором Папышевым. 00h > Интро Всем привет, мы вернулись после небольшого летнего...
Яндекс.Метрика