Что такое и как выглядит lockfile?
Lockfile — описанное дерево зависимостей проекта с конкретными версиями каждого пакета: что у тебя установлено, какой версии, чего, что от чего зависит. Нужен, чтобы гарантировать одинаковое состояние и работу проекта на разных устройствах.
Lockfile генерируется yarn’ом, например во время выполнения команды yarn install. Пакетные менеджеры npm и pnpm делают то же самое, только файлы называются по-разному. В этой статье будем рассматривать все на примере yarn и генерируемого им lockfile файла — yarn.lock.
Выглядит yarn.lock вот так:
В 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 все автоматизированно, есть крайние случаи, когда требуется человеческий взгляд.
Этот инструмент самостоятельный, но когда понимаешь зачем — можно тонко настроить.
И самое главное: периодически «прочесывать» зависимости – полезная практика, если проект намеревается долго жить.
Полезные ссылки:
Наглядно посмотреть какие версии какого пакета входят в диапазон.
Инструмент для проверки зависимостей — depcheck.
Подборка от редактора блога: