Пакеты и версии

Что это такое

Пакет — это опубликованный каталог кода с именем и версией. Диапазон версий — правило, которое говорит, какие версии этого пакета ваш проект готов принять. Семантическое версионирование наделяет эти числа смыслом: мажор — для несовместимых изменений, минор — для добавлений, патч — для исправлений; а lockfile записывает точные установленные версии, чтобы следующая установка привела к тому же дереву.

Почему это важно

Воспроизводимость сборки держится на двух вещах: на диапазонах в манифесте и на lockfile рядом с ним. Если lockfile не закоммичен, установка через месяцы подтянет более новый минор с регрессией, и сбой всплывёт в сборке, которая ничего не меняла. Однако стоит закоммитить lockfile — и дерево зависимостей закреплено, а обновление становится осознанным шагом с диффом для проверки; в этом же слое в проект проникает проблема цепочки поставок, и рассуждать о ней невозможно, не зная, какие диапазоны вы объявили.

Ключевые идеи

Семантическое версионирование — обещание, а не гарантия

1.4.2 означает мажор 1, минор 4, патч 2. Мажорный скачок может вас сломать, минорный обязан только добавлять, патч — только исправлять. Обещание держится на авторе, поэтому диапазон, доверяющий минорным версиям, доверяет каждому сопровождающему в дереве.

{
  "dependencies": {
    "left-pad": "1.3.0",
    "date-fns": "^3.6.0",
    "lodash": "~4.17.21"
  }
}

^3.6.0 принимает любую 3.x не ниже 3.6.0; ~4.17.21 принимает 4.17.x; версия без знака закрепляет точно.

Lockfile — гарантия воспроизводимости

Lockfile записывает каждый пакет дерева, включая транзитивные, которые вы никогда не называли, вместе с разрешённой версией и хешем целостности. Коммитьте его. npm ci ставит строго по нему и падает, когда манифест и lockfile расходятся, — именно так и нужно вести себя в непрерывной интеграции.

dependencies и devDependencies — разные обещания

dependencies нужны, чтобы приложение работало; devDependencies — чтобы собрать или протестировать его. Сборщик компилирует ваш исходный код, поэтому большинство инструментов сборки живёт в devDependencies, а библиотека времени выполнения, которая попадает в бандл, — по сути зависимость сборки. Неверное разделение влияет в основном на то, что установит потребитель пакета и что подтянет производственная установка.

Транзитивные зависимости доминируют в дереве

Большая часть того, что вы устанавливаете, требуется чем-то другим. npm ls <name> и npm explain <name> показывают, какой родитель втянул пакет и почему; именно с этого стоит начать, когда в дереве оказываются две версии одной библиотеки и одна из них старая.

Одноранговые зависимости выражают требование к хосту

Плагин объявляет свой хост одноранговой зависимостью: он ожидает, что совместимую версию библиотеки предоставит приложение. Предупреждение о peer-зависимости обычно означает, что в дереве оказались две копии библиотеки, а две копии библиотеки состояния — настоящая ошибка, а не косметика.

Проверяйте и обновляйте по расписанию

Уведомления об уязвимостях приходят постоянно, и читать их во время инцидента уже поздно. Ежемесячный проход с обновлением патчей и миноров держит будущий мажорный переход небольшим, а закоммиченный lockfile делает каждый шаг проверяемым.

Практика

Докажите, что lockfile делает то, что обещает

Удалите установленные модули, но оставьте lockfile, переустановите и запишите разрешённую версию одной транзитивной зависимости. Затем вручную измените в lockfile версию этого пакета и запустите установку снова.

Готово, когда: вторая установка слушается lockfile, а не диапазона в манифесте, и вы можете назвать команду, которая его игнорирует.

Проследите одну транзитивную зависимость

Выберите пакет, который вы не устанавливали напрямую, и выясните, какой родитель его требует и в какой версии.

Готово, когда: вы можете назвать родителя, объявленный им диапазон и есть ли в дереве вторая копия того же пакета.

Частые ошибки

  • Незакоммиченный lockfile — сборки перестают быть воспроизводимыми, и регрессия появляется без изменений кода; исправить: коммитьте его и ставьте через npm ci в непрерывной интеграции.
  • latest как диапазон версий — каждая установка может быть другой программой; исправить: объявите диапазон, а результат разрешения запишите в lockfile.
  • Удаление lockfile ради исправления конфликта — конфликт исчезает вместе с воспроизводимостью; исправить: найдите дубликат через npm ls и разберитесь с ним осознанно.
  • Инструменты сборки в dependencies — потребители устанавливают весь инструментарий без всякой пользы; исправить: перенесите инструменты сборки и тестов в devDependencies.
  • Обновление только когда заставили — скачок на два года сводит все ломающие изменения в одну непроверяемую ветку; исправить: обновляйтесь по расписанию небольшими шагами.

Что читать дальше