Работа с версиями пакетов

Версии
Когда вы работаете с менеджером пакетов, вы, наверное, обращали внимание на номера версий, например: 1.2.0, 2.5.6 и так далее. Эти цифры состоят из трех частей: мажорной версии, минорной и так называемой патч-версии.
Но что они означают? Давайте разберемся! 🚀
В npm, yarn и других пакетных менеджерах используется семантическое версионирование (SemVer), которое выглядит как X.Y.Z (Мажорная.Минорная.Патч).
-
Мажорная (Major): Ломающие изменения (2.0.0), которые могут нарушить обратную совместимость. Если вы обновляетесь до мажорной версии, то код, который работал, может перестать работать, поэтому после обновления необходимо протестировать функционал.
-
Минорная (Minor): Новые функции, совместимые со старыми (1.3.0), которые не нарушает обратную совместимость. Это значит, что после обновления ваш код должен продолжить работать так же, как и работал.
-
Патч (Patch): Исправления багов (1.2.4), которые не нарушают обратную совместимость. Обновляемся - и всё работает.
НО! Советую после любого обновления библиотек всё-таки делать регресс своего функционала.
Допустим, у нас есть пакет версии 1.2.3. Если разработчик выпускает новую версию с номером 2.0.0, это означает, что в пакет были внесены существенные изменения, которые могут нарушить обратную совместимость. Однако если разработчик выпускает версию 1.3.0, это указывает на то, что в пакет были добавлены новые функции, но обратная совместимость при этом сохраняется.
Управление версиями в package.json
В файле package.json вы указываете зависимости вашего проекта и их версии. Версии могут быть зафиксированы точно, либо можно использовать символы для указания допустимых диапазонов версий.
-
Точная версия: "react": "1.0.1" - всегда будет использоваться версия 1.0.1.
-
Символ
^: "react": "^1.0.1" - будет использоваться любая MINOR или PATCH версия, начиная с 1.0.1 (например, 1.1.0 или 1.0.2). -
Символ
~: "react": "~1.0.1" - будет использоваться любая PATCH версия, начиная с 1.0.1 (например, 1.0.2), но не 1.1.0.
Запоминаем простое правило:
^= «Я хочу новые функции, но не ломайте мой код» (самый частый вариант).
~= «Только баг-фиксы, ничего не меняйте».
Как обновлять пакеты
(Далее мы будем использовать npm)
Вообще, прежде чем обновлять пакеты, необходимо посмотреть, какие из них уже устарели. Это можно сделать с помощью команды:
npm outdated
Вы увидите таблицу с колонками Current (текущая), Wanted (максимальная разрешенная символом ^ или ~) и Latest (абсолютно последняя в реестре).
| Package | Current | Wanted | Latest | Location | Depended by |
|---|---|---|---|---|---|
| react | 17.0.2 | 17.0.2 | 18.2.0 | node_modules/react | my-app |
| axios | 0.21.1 | 0.21.4 | 1.6.5 | node_modules/axios | my-app |
| lodash | 4.17.15 | 4.17.21 | 4.17.21 | node_modules/lodash | my-app |
Рассмотрим несколько вариантов обновлений:
- Безопасное обновление
Чтобы обновить пакеты до версии Wanted (то есть с учетом ваших
^и~):
npm update
- Эта команда обновит файлы в папке node_modules.
- Она также обновит файл package-lock.json.
- Важно: Она не изменит версии, прописанные в вашем package.json, если они все еще попадают под условия (например, если там написано ^1.0.0, а вы обновились до 1.5.0, текст в файле останется ^1.0.0, так как 1.5.0 подходит под это правило).
- Полное обновление
Если вы хотите обновиться с версии 1.x.x до 2.x.x (игнорируя правила в package.json), команда
npm updateэтого не сделает. Для одного пакета мы можем использовать команду:
npm install <имя_пакета>@latest
Это принудительно установит последнюю версию и перепишет package.json.
А чтобы обновить автоматически все пакеты, используйте утилиту npm-check-updates. Она проверяет latest-версии и перезаписывает ваш package.json. Запустите проверку (без установки утилиты):
npx npm-check-updates
Если хотите применить изменения в package.json, добавьте флаг -u:
npx npm-check-updates -u
После этого обязательно установите новые версии:
npm install
Выглядит всё просто, но на практике могут возникнуть проблемы, так что пробуйте!
Почему нельзя удалять package-lock.json?
Вообще, этот файл - это снимок вашего дерева зависимостей в проекте, и удалять его нельзя. Не знаю почему, но многие думают, что это копия package.json. Так зачем же он нужен?
Представьте: вы работаете в команде, и в вашем package.json есть зависимость с версией ^0.21.1. Сегодня вы установили пакет, и скачалась версия 0.21.1. Но наступает завтра, к вам приходит фронтенд-стажёр и устанавливает зависимости в проекте. И - о нет! - у него та же зависимость, но уже с версией 0.21.2, так как вышла новая версия. Всё из-за символа ^. Как итог: если в версии 0.21.2 разработчики случайно допустили баг, у вас проект будет работать, а у коллеги - нет. Хотя в package.json у вас написано одно и то же!
ЗАПОМИНАЕМ: package-lock.json фиксирует точные версии (например, строго 0.21.1) всех установленных пакетов и их собственных подзависимостей. Когда коллега напишет npm install, система посмотрит в лок-файл и установит в точности те же зависимости, что и у вас.
Главные функции package-lock.json:
-
Гарантия идентичности: На любом компьютере и на сервере (Production) установится один и тот же код.
-
Безопасность: Файл хранит integrity (хэш-суммы). Если кто-то взломает пакет в реестре npm и подменит код версии
0.21.1, npm заметит несовпадение хэша и выдаст ошибку. -
Скорость: npm не нужно каждый раз вычислять дерево зависимостей, он просто берет готовую схему из файла.
Дополнительные операторы
Есть еще операторы, но я их видел очень редко. Примеры привел в таблице:
| Символ | Правило | Пример |
|---|---|---|
> | Принимать обновления любой версии выше указанной | >0.13.0: 0.13.1, 0.14.1, 1.1.1 |
< | Принимать обновления любой версии ниже указанной | <3.0.0: 2.0.0, 2.9.0 |
>= | Принимать любую версию больше или равную указанной | >=3.0.0: 3.0.0, 4.1.0 |
<= | Принимать любую версию меньше или равную указанной | <=3.0.0: 3.0.0, 2.9.0 |
= | Принимать только указанную точную версию | =3.0.0: 3.0.0 (not 3.0.1) |
- | Принимать диапазон версий (включительно) | 1.0.0 - 1.10.10: 1.5.0 (not 1.11.0) |
|| | Комбинация версий (логическое ИЛИ) | <2.1.0 || >2.6.0: 2.0.1, 3.1.0 |
&& | Версии, удовлетворяющие обоим условиям (логическое И) | >=1.0.0 && <2.0.0: 1.5.0 (not 2.0.0) |
(space) | Неявное логическое И (аналогично &&) | >=1.0.0 <2.0.0: 1.5.0 (not 2.0.0) |
* | Принимать любую версию (wildcard / маска) | *: any version available |
x | Любая версия для данной позиции | 1.x: 1.0.0, 1.5.0 (not 2.0.0) |
X | То же, что и x (нечувствительно к регистру) | 1.X: 1.0.0, 1.5.0 (not 2.0.0) |
(none) | Принимать только указанную точную версию | 3.0.0: 3.0.0 (not 3.0.1) |
latest | Всегда устанавливать последнюю доступную версию | npm install <package>@latest |
@tag | Установить конкретный дистрибутив по тегу | npm install react@beta |
И небольшой пример package.json с такими операторами:
{
"dependencies": {
"express": "^4.18.0", // Разрешить минорные обновления: 4.18.x, 4.19.x
"lodash": "~4.17.21", // Разрешить только патч-обновления: только 4.17.x
"react": ">=18.0.0", // Любая версия 18.0.0 или выше
"typescript": "4.x", // Любая версия в рамках мажорной ветки 4.x
"eslint": "*", // Всегда устанавливать самую последнюю версию
"axios": ">=1.0.0 && <2.0.0", // Только версии ветки 1.x
"my-utils": "workspace:^1.0.0", // Зависимость из рабочего пространства (monorepo)
"local-pkg": "file:../local-package" // Зависимость из локальной папки
},
"devDependencies": {
"jest": "29.0.0 - 29.5.0", // Специфический диапазон версий
"prettier": "2.8.8", // Точная версия
"@types/node": ">=16.0.0 <21.0.0" // Логическое «И» (через пробел)
}
}
Шпаргалка по командам
| Команда | Описание |
|---|---|
npm outdated | Проверить наличие устаревших пакетов |
npm update | Обновить пакеты в пределах диапазонов semver |
npm install package@1.2.3 | Установить конкретную версию пакета |
npm install package@latest | Установить последнюю версию пакета |
npm install package@beta | Установить пре-релизную (бета) версию |
npm list | Показать дерево установленных версий пакетов |
npm view package versions --json | Показать все доступные версии пакета в формате JSON |
На этом все, спасибо за прочтение 🙏