Что такое и как выглядит lockfile?

Lockfile — описанное дерево зависимостей проекта с конкретными версиями каждого пакета: что у тебя установлено, какой версии, чего, что от чего зависит. Нужен, чтобы гарантировать одинаковое состояние и работу проекта на разных устройствах.

Lockfile генерируется yarn’ом, например во время выполнения команды yarn install. Пакетные менеджеры npm и pnpm делают то же самое, только файлы называются по-разному. В этой статье будем рассматривать все на примере yarn и генерируемого им lockfile файла — yarn.lock.

Выглядит yarn.lock вот так:

В этом примере записи в yarn.lock указывается, что в проекте должен быть установлен пакет is-odd версии 0.4.0.

В lockfile содержится набор информации:

  • version — фактически установленная версия пакета;

  • resolved — путь для скачивания этой версии пакета;

  • integrity — SHA хэш нужен для сравнения содержимого библиотеки (так yarn может проверить, было ли изменено содержимое файлов пакета);

  • dependencies — зависимости, которые необходимы для работы пакета is-odd (встретятся в lockfile и node_modules, даже если их нет в проекте напрямую).

Сами зависимости указаны в package.json, в:

  • dependencies — зависимости, необходимые для работы приложения в проде;

  • devDependencies — зависимости, которые нужны для разработки и тестирования (не попадут в прод);

  • peerDependencies — зависимости, необходимые для работы приложения, но не устанавливаемые напрямую в пакете (обычно используют разработчики библиотек).

Команда yarn install запустит установку dependencies и devDependencies, ориентируясь на версии пакетов, указанные в yarn.lock. Для корректной работы библиотеки с peerDependencies нужно «продублировать» peer зависимости в своем package.json. Если yarn.lock нет, то версии установятся согласно указанным в package.json. 

Важное примечание. Не всегда! В ходе оптимизаций пакетный менеджер может выбрать и другую версию (так yarn пытается оптимизировать зависимости). Поэтому стоит использовать resolutions, peerDependencies, указывать диапазоны версий и др. инструменты.

«Лайфхак»: внутри node_modules появится файл .yarn-integrity, который описывает текущее состояние папки node_modules. Для больших проектов вместо rm -rf node_modules && yarn install можно использовать просто rm -rf node_modules/.yarn-integrity && yarn install. Так yarn не будет скачивать уже имеющиеся пакеты, но построит дерево заново, и переустановка модулей произойдет в разы быстрее.

Коротко о семантическом версионировании

Семантическое версионирование — это система, которая помогает справиться с хаосом зависимостей (dependency hell) и версионным несоответствием. Семантическое версионирование — набор правил и требований установки версий.

Общие правила для установки версии (их больше, это основные):

Учитывая номер версии МАЖОРНАЯ.МИНОРНАЯ.ПАТЧ, следует увеличивать: МАЖОРНУЮ версию, когда сделаны обратно несовместимые изменения API.МИНОРНУЮ версию, когда вы добавляете новую функциональность, не нарушая обратной совместимости.ПАТЧ-версию, когда вы делаете обратно совместимые исправления.

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

Если нам подходит любая минорная версия, начиная с 1.2.3 (т.е. диапазон с 1.2.3 до 1.3.0 (не включая)), то мы можем указать "is-odd": "~1.2.3"

Подсказка, как читать версии пакета.

Наглядно посмотреть какие версии какого пакета входят в диапазон.

Проблемы с зависимостями

В ходе работы над проектом добавляются, удаляются и обновляются пакеты, решаются конфликты в зависимостях, в библиотеках находят уязвимости и lockfile может содержать ошибочно старые версии или дубликаты. А могут быть даже никем не используемые пакеты. Выходят новые версии библиотек, содержащие уязвимости (так бывает), все это нужно решать.

Избавимся от лишнего.

Самое простое, что мы можем проверить – это используется ли библиотека. Проверять использование можно вручную, а можно с помощью depcheck, он сообщит о том, как используется зависимость, есть ли неиспользуемые пакеты и тд. 

Например, команда мигрировала с final-form на formik, весь код уже переписали – а final-form забыли удалить. Final-form есть в зависимостях, но не используется. Если в коде осталось буквально пару мест использования старой библиотеки — можно их дорефакторить и со спокойной душой убирать зависимость.

Решим уязвимости.

Иногда достаточно просто обновить версию или перейти с deprecated пакета на новый. Команда yarn audit подсветит все уязвимости, распределив и по 5 уровням критичности, укажет в какой версии пакета это наблюдается, какой версией решается. Эти данные также доступны на npm.js.com. Но фиксить придется руками.

npm audit --fix умеет автоматически фиксить уязвимости. Однако, если проект большой — это стоит делать очень осторожно.

Примечание. Yarn не имеет такой команды, но можно воспользоваться костылём подобным инструментом.

yarn upgrade <package_name> — принудительный апгрейд пакета. Если не указывать версию, yarn подберет последнюю, указанную в package.json. Upgrade может принимать версию и в качестве параметра, но тогда он никак не опирается на package.json.

Решим дубликаты.

Конечно, yarn пытается оптимизировать всё, но нет идеального алгоритма поиска дубликатов пакетов: пакеты is-odd@1.2.2 и is-odd@1.2.3 могут быть дважды установлены, хоть и практически не отличаются между собой.

Способ 1. Удалить lockfile (опасно).

Очищаем node_modules, удаляем lockfile и запускаем yarn install, пакетный менеджер попробует построить дерево зависимостей с нуля, подтянет все новейшие версии в рамках semver. Все же так когда-нибудь делали?

Так мы получим новое дерево зависимостей, но есть высокая вероятность сломать поведение. Где-то могли произойти breaking changes, из-за возможного обновления библиотек (т.к. yarn.lock файла с указанными версиями больше нет), которые потребуют правок в коде. А что ещё хуже, мы могли притянуть новые версии, которые уже не совместимы с нашим проектом, например обновить библиотеку, зависимую от старого react, на версию, которая полностью зависит от нового.

Способ 2. Yarn dedupe.

В yarn2 есть команда dedupe, которая позволяет автоматически убирать дубликаты. Можно выбрать стратегию избавления от дубликатов.

Способ 3. Резолюции.

В package.json мы можем указать какие версии зависимостей хотим видеть в нашем проекте.

Это бывает удобно, когда сторонняя библиотека использует подзависимости, которые мы хотим обновить. Например, пакет icons-classic, зависит от browserlist (который, к примеру, содержит уязвимости). Дабы не ждать разработчиков icons-classic, мы можем сами поднять/опустить версию browserlist до необходимой.

Заключение

  • Если указываешь какую-то версию зависимостей своего пакета, то не обязательно будет стоят она. Это важно, потому что часто порождает нежелательное поведение.

  • Зависимости надо оптимизировать самостоятельно, хоть в yarn все автоматизированно, есть крайние случаи, когда требуется человеческий взгляд.

  • Этот инструмент самостоятельный, но когда понимаешь зачем — можно тонко настроить. 

  • И самое главное: периодически «прочесывать» зависимости – полезная практика, если проект намеревается долго жить.

Полезные ссылки:


Подборка от редактора блога: