Зарегистрируйтесь, чтобы продолжить обучение

Lock-файл JS: Настройка окружения

Поговорим об обновлении зависимостей. Для обновления всех зависимостей нужно выполнить команду npm update. Чтобы выполнить обновление конкретной зависимости — npm update name, где name — имя библиотеки. А как будет происходить обновление, зависит от того, что написано в package.json.

Рассмотрим все доступные варианты:

dependencies {
  "package1": "*",
  "package2": "1.3.5",
  "package3": "~2.3.4",
  "package4": "^2.3.4",
}
  • * означает, что можно ставить любую версию библиотеки. После выполнения команды обновления в папке node_modules окажется последняя доступная версия package1
  • 1.3.5 — конкретный номер. Если версия библиотеки жестко зафиксирована, никакая команда не сможет обновить ее

Самый интересный сценарий происходит в случае использования тильды (~).

В семантическом версионировании считается, что patch, последняя цифра в версии, изменяется только в случае исправления ошибок, значит, обратная совместимость не должна теряться. При этом на практике это не всегда так. Код может работать с учетом ошибок в зависимостях.

Как правило, в проектах десятки, а то и сотни зависимостей, причем обновляются они часто. С одной стороны, можно всегда писать *, но тогда обновления нередко могут ломать систему из-за мажорных обновлений библиотек. С другой стороны, можно зафиксировать все версии, и тогда обновлять все придется вручную.

Поэтому появился третий вариант. Добавление тильды приводит к тому, что в автоматическом режиме обновляются только патчи. Предположим что после добавления зависимости в проект, версия была установлена в ~2.10.3. Если после нее в npm репозитории появилась 2.10.5, то она будет установлена командой обновления.

То же самое произойдет, если потом будет выпущена версия 2.10.15. Но если создатель библиотеки опубликует изменения в мажорной и минорной версии, например, 2.11.5 или 3.0.0, то npm их проигнорирует.

Примерно то же самое происходит и при использовании ^. Только в отличие от тильды она фиксирует мажорную версию, а минорная обновляется наравне с патчем.

Lock-файл

На предыдущем шаге каждая новая установка зависимостей приводила сначала к созданию, а потом — к обновлению файла package-lock.json.

В package.json указываются зависимости. Мы уже рассмотрели, как их устанавливать и обновлять. Но еще мы помним, что у каждой зависимости могут быть свои зависимости, которые так же обновляются, и так до бесконечности.

Зависимости зависимостей называются транзитивными, и с ними не все так просто. Настолько не просто, что существует понятие dependency hell — ад зависимостей.

Проблема заключается в том, что мы не фиксируем версии транзитивных зависимостей. Предположим, что в нашем пакете есть зависимость A с зафиксированной версией 1.3.2, у которой в зависимостях стоит пакет B с версией *. В такой ситуации и npm install поставил бы версию зависимости A, указанной версии, но так нельзя сказать про пакет B. Npm поставит последнюю доступную версию из репозитория.

Такое поведение не детерминировано. Если создатель обновит B и нарушится обратная совместимость, проект сломается, так как перестанет работать A.

Если бы мы полгода не заходили в проект, а затем зашли и поставили зависимости заново (удалив папку node_modules или выполнив новое клонирование), то с почти 100% вероятностью ничего не заработает. Как правило, пакеты обновляются часто и какой-нибудь из них обязательно изменит мажорную версию за такой большой срок.

Очевидный, но не рабочий выход из данной ситуации — отслеживать зависимости всех зависимостей руками и явно прописывать их версии в package.json. Такой способ сработает, но даже в проекте, который содержит, например, пять зависимостей, транзитивных зависимостей будет сотни.

И не стоит забывать, что пакеты обновляются и меняются. Такую ситуацию невозможно контролировать, и в итоге зависимости перестанут обновляться.

Другой выход — требовать, чтобы создатели всех библиотек всегда жестко указывали версии. Из-за человеческих факторов это не сработает. При этом автоматизация такого процесса привела бы к полному параличу системы пакетов, библиотек и разработки.

В таком случае нас спасает lock-файл — автоматизированное решение первого способа. Его содержимое выглядит примерно так:

{
  "name": "hexlet-co",
  "version": "0.1.4",
  "lockfileVersion": 1,
  "requires": true,
  "dependencies": {
    "JSONStream": {
      "version": "1.3.1",
      "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz",
      "integrity": "sha1-cH92HgHa6eFvG8+TcDt4xwlmV5o=",
      "requires": {
        "jsonparse": "1.3.1",
        "through": "2.3.8"
      }
    },
    "abab": {
      "version": "1.0.4",
      "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz",
      "integrity": "sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4=",
      "dev": true
    },
    "acorn": {
      "version": "4.0.13",
      "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz",
      "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c="
    },
    ...
}

Первый запуск установки зависимостей формирует этот файл. Туда записываются все установленные зависимости, в том числе транзитивные со своими версиями. При дальнейших запусках npm install всегда ставится то, что указано в lock-файле, даже если стереть папку node_modules, а в npm репозитории добавятся новые версии пакетов. Повторный запуск через любой промежуток времени приведет к тому же результату.

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

Наличие lock-файла не влияет на поведение команды update для прямых зависимостей. Если пакет, указанный в package.json, обновился и может быть обновлен в соответствии с тем, как указана его версия, то загрузится новая версия, а файл lock обновится автоматически. После чего нужно не забыть залить его в репозиторий.

transitive dependencies tree

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

Как и любой автоматически генерируемый файл, lock-файл не может правиться руками, потому что наши изменения сразу будут сброшены при следующей генерации.


Самостоятельная работа

  • Клонируйте репозиторий nodejs-package, а затем выполните внутри него команду npm update. Изучите вывод команды git diff
  • Попробуйте изменить версию любого пакета в package.json, при этом указывайте точную версию без использования ^, и выполнить npm install. Доступные версии пакета можно посмотреть командой: npm info <packagename>, например, npm info eslint

Для полного доступа к курсу нужен базовый план

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

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff