Привет, всем!

Предполагаю, многие уже знакомы с «npm» (менеджер пакетов), файлом «package.json» и командой для установки пакетов «npm install». Тема моей на следующем шаге, когда после успешно выполненной команды «npm install» появляется файл «package-lock.json». Он создается для любых операций, в которых npm изменяет дерево «node_modules» или файл «package.json». Описывает точное дерево зависимостей, которое было сгенерировано, так что последующие установки могут генерировать идентичные деревья, независимо от промежуточных обновлений зависимостей.

О том, как это работает — мой туториал.

Меня зовут Михаил. В Группу НЛМК я пришел в 2021 году и занимался ревью исходного кода одной из информационных систем, а также Frontend-разработкой на React. Работа с npm-пакетами, развертывание программного продукта и установка его зависимостей — неотъемлемая часть моей работы.

На момент написания данной статьи у меня установлена npm v8.3.0.

Я создал пустую папку на рабочем столе. Открыл ее в редакторе исходного кода (у меня это Visual Studio Code). И в терминале выполнил команду — «npm init», которая используется для настройки нового или существующего «npm»-пакета. Система предлагает ввести имя создаваемого пакета, а все остальное можно пропустить, просто нажимая «Enter», ну или заполняя при желании. На последний вопрос, о том, всё ли меня устраивает, отвечаю «yes».

После чего в папке появляется файл «package.json». Данный файл в рамках этой статьи рассматривать не буду, просто покажу его содержимое.

Теперь, с помощью терминала, установлю какую-нибудь зависимость, например, популярную библиотеку «lodash».

Выполню в терминале команду установки «npm i lodash».

После успешной установки пакета «lodash» появляется папка «node_modules», файл «package-lock.json» в корневой папке и в папке «node_modules».

Открою файл «package-lock.json» в корне проекта и посмотрю, что в нем находится.

Файл «package-lock.json» содержит «JSON»-объект со следующими свойствами:

name — название проекта из «package.json» манифеста;

version — версия проекта из «package.json» манифеста;

lockfileVersion — версия формата «lock»-файла.

  • версия не указана: старый формат файла до «npm» V5;

  • 1: используется в «npm» v5 и v6;

  • 2: используется в «npm» v7, обратно совместима с файлами блокировки v1;

  • 3: используется в «npm» v7, без возможности обратной совместимости. Возможно будет использоваться в будущей версии «npm», когда поддержка «npm» v6 перестанет быть актуальной.

packages — объект, который сопоставляет расположение пакетов с объектом, содержащим информацию об этом пакете. Корневой проект обычно указывается с ключом "", а все другие пакеты перечислены с их относительными путями от корневой папки проекта.

dependencies — это раздел с устаревшими данными для поддержки версий «npm», использующих «lockfile» версии 1. Представляет собой соотношение имени пакета и объекта со свойствами пакета и зависимостями в поле «requires». С «npm» v7 раздел полностью игнорируется, но поддерживается в актуальном состоянии, чтобы иметь возможность переключения между npm v6 и npm v7.

В папке «node_modules» есть второй файл «package-lock.json». Это так называемый «скрытый» файл блокировки, который содержит информацию о дереве зависимостей и используется «npm» начиная с 7 версии, чтобы избежать повторной обработки папки «node_modules». Но есть ограничения для его использования, так «скрытый» файл блокировки будет актуален только в том случае, если он был создан как часть самого последнего обновления дерева пакетов. Если другой интерфейс каким-либо образом изменяет дерево, это будет обнаружено, а скрытый файл блокировки будет проигнорирован.

Если при установке пакета «npm» обнаруживает файл блокировки из «npm» v6 или более ранней версии, то файл блокировки автоматически обновляется для получения недостающей информации либо из дерева «node_modules», либо из реестра «npm».

Взгляну, какие есть версии у библиотеки «lodash».

Закреплю версию «lodash» в файле «package.json» на более низкую патч версию с "^4.17.21" на "4.17.10", удалю папку «node_modules», файл «package-lock.json» и выполню команду «npm install».

Получаю следующий «lock»-файл:

Загляну в «скрытый» файл блокировки, который содержит информацию о дереве зависимостей:

А вот файл «package.json» самой библиотеки «lodash»:

Везде версия "4.17.10".

Теперь изменю версию «lodash» в файле «package.json» на более низкую патч версию, но с возможностью взять последнюю патч версию при установке, т.е. с "4.17.10" на "~4.17.1", удалю папку «node_modules» и выполню команду «npm install». При этом самая высокая доступная патч версия для 17 минорной версии это “4.17.21”. Но при этом в нашем «package-lock.json» сейчас закреплена версия "4.17.10".

Запускаю команду «npm install». Открываю «package-lock.json»:

При установке «lock»-файл был обновлен, обновилось поле об основной зависимости от "lodash": "~4.17.1", но при этом ранее добавленная блокировка на версию "4.17.10" не изменилась. Что же установилось в «node_modules»? Открою «скрытый» файл блокировки:

«Скрытый» файл блокировки показывает, что в «node_modules» установлена версия "4.17.10". Загляну в файл «package.json» установленной библиотеки "lodash":

Итак, при установке «npm» смотрит в файл «package.json» видит, что есть зависимость:

"dependencies": {

"lodash": "~4.17.1"

}

Согласно правилам семантического версионирования (от англ. Semantic Versioning) или semver, позволяю установить более высокую патч версию пакета. Менеджер пакетов «npm» видит, что есть файл «package-lock.json», находит там данную зависимость, но с версией "4.17.10", что удовлетворяет требованиям по данной зависимости файла «package.json» и устанавливает версию "lodash": "4.17.10" опираясь на файл блокировки «package-lock.json».

Отлично, а что если в «package.json» установить версию пакета с патч версией выше той, что закреплена в «package-lock.json»? Удаляю папку «node_modules» и изменяю версию «lodash» в файле «package.json» с "~4.17.1" на "~4.17.11".

В файле блокировки стоит ограничение по версии "4.17.10".

Выполню команду «npm install» и загляну в файл блокировки.

При установке «lock»-файл был обновлен, т.к. в файле «package.json» зависимость:

"dependencies": {

"lodash": "~4.17.11"

}

При установке «npm» видит, что есть файл «package-lock.json», находит там данную зависимость, но с версией "4.17.10", версия не удовлетворяет требованиям по данной зависимости файла «package.json», тогда «npm» устанавливает версию "lodash" удовлетворяющую условию "~4.17.11", а на текущий момент это версия "4.17.21" и обновляет файл блокировки «package-lock.json». Загляну в файл «package.json» установленной библиотеки "lodash".

Установлена версия “lodash":"4.17.21".

Пока все логично и понятно.

В «npm» v6 появилась прекрасная команда «npm ci». Согласно официальной документации (https://docs.npmjs.com/cli/v6/commands/npm-ci) - эта команда похожа на «npm install», за исключением того, что она предназначена для использования в автоматизированных средах, таких как тестовые платформы, непрерывная интеграция и развертывание, или в любой ситуации, когда вы хотите убедиться, что выполняете чистую установку своих зависимостей.

Использования команды «npm ci» возможно при наличии у проекта файла «package-lock.json». Если зависимости в файле блокировки пакета не совпадают с зависимостями в файле «package.json», «npm ci» выведет ошибку и не произведет установку пакетов. Команда «npm ci» устанавливает только целый проекты за раз, отдельные зависимости не устанавливаются с помощью этой команды.

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

Версия «lodash» в файле «package.json» "~4.17.11". Удаляю папку «node_modules».

В файле «package-lock.json» зафиксирую версию «lodash» на "4.17.10", которая отличается от версии в файле «package.json».

Выполню команду «npm ci» и согласно документации ожидаю получить ошибку в консоли.

Но ошибки нет. Зависимости успешно установлены. Пробегусь по файлам и посмотрю, что произошло.

Согласно документации при установке зависимостей командой «npm ci» «lock»-файл не должен быть переписан:

Да, это так, он остался в исходном состоянии.

Тогда какая версия зависимости была установлена? Загляну в файл «package.json» самой библиотеки «lodash»:

Итак, при установке «npm» смотрит в файл «package.json» видит, что есть зависимость:

"dependencies": {

"lodash": "~4.17.11"

}

Находит файл «package-lock.json», видит там данную зависимость, но с версией "4.17.10", но никак на это не реагирует и устанавливает библиотеку "lodash": "4.17.21” опираясь на файла «package.json».

Как же так? А где же сообщение об ошибке в консоли о несовпадении версии зависимостей, почему файл «package-lock.json» был просто проигнорирован?

Оказывается в «npm» v8.3.0 «npm ci» попросту не работает.

Это подтверждают два заведеных «issues» для «npm» на https://github.com/npm/cli:

  1. https://github.com/npm/cli/issues/2701

  2. https://github.com/npm/cli/issues/3947

Поставлю «npm» v6.

В файле «package.json» версия библиотеки "lodash": "~4.17.11".

В файле «package-lock.json» зафиксирована версия библиотеки "lodash": "4.17.10".

Выполню команду «npm ci».

В «npm» v6 команда «npm ci» прекрасно отрабатывает и выполняет функции описанные в документации.

Подведем итог. На текущий момент в «npm» v8.3.0 файл «package-lock.json» выполняет свои функции частично, т.е. он описывает точное дерево, которое было сгенерировано, но, к сожалению, не пригоден для использования в автоматизированных средах, таких как тестовые платформы, непрерывная интеграция и развертывание, или в любой ситуации, когда вы хотите убедиться, что выполняете чистую установку своих зависимостей используя команду «npm ci».

Надеюсь статья была полезна и помогла узнать немного больше о файле «package-lock.json».

Спасибо за уделенное время и удачи!

Комментарии (3)


  1. iliazeus
    25.10.2022 13:23
    +1

    Лок-файлы починили в v8.4.1, которая вышла еще в феврале.

    Обновить npm, даже если новой версии по какой-то причине нет в репозиториях вашего дистрибутива, можно командой npm install --global npm@latest


  1. olku
    25.10.2022 13:27
    +1

    Не подскажете как именно резолвятся зависимости в npm? Когда-то писал свой package manager, брал https://github.com/pghalliday/semver-resolver Может, есть актуальная стандартная библиотека?


    1. iliazeus
      25.10.2022 13:40
      +2

      Вам нужно смотреть в сторону arborist или его зависимостей — к примеру, npm-pick-manifest для матчинга semver-диапазонов). npm пользуется именно им.

      В целом, все зависимости npm можно посмотреть на самом npmjs.com.