package lock json для чего
Lock-файлы npm
Всем привет! В прошлом посте мы рассмотрели экосистему npm в качестве источника хаоса в нашем проекте, и научились с умом подходить к выбору зависимостей, чтобы минимизировать наши риски. Сегодня мы пойдем дальше и рассмотрим lock-файлы npm, которые помогают повысить стабильность проекта в процессе работы над ним.
Когда манифеста мало
Также разные версии npm могут иметь различные алгоритмы установки зависимостей, и структура файлов будет отличаться.
Вы помните, что список зависимостей в манифесте проекта содержит диапазон версий semver, что позволяет обновлять зависимости? Получается, что установка зависимостей в разное время будет приводить к разным результатам, потому что содержимое npm registry постоянно меняется, регулярно выходят новые пакеты. Кроме того, поскольку мы имеем дело с деревом зависимостей, то транзитивные зависимости (зависимости ваших зависимостей) тоже будут меняться во времени.
А еще может возникнуть ситуация, когда содержимое пакета, лежащего в npm registry, было заменено без обновления версии. Это называется мутацией пакетов и запрещено в официальном npm registry, однако в частных реестрах вполне возможно. А что, если содержимое пакета было заменено злоумышленником, чтобы внести в код какие-то закладки?
Учитывая все эти факторы, становится очевидно, что структура данных в директории node_modules очень нестабильна и может меняться во времени, даже если ваш манифест при этом остается нетронутым.
Наивно было бы попытаться зафиксировать зависимости, прописывая строгие версии в манифесте проекта (вместо диапазонов semver): как мы рассмотрели выше, это не даст существенных результатов, потому что транзитивные зависимости всё равно будут обновляться. Да и другие факторы не перестанут влиять на установку зависимостей. Кроме того, если вы заморозите ваши прямые зависимости, то получится ситуация, когда более старые версии будут работать с более свежими версиями транзитивных зависимостей, и потенциально это повышает вероятность проблем с интеграцией.
А теперь представьте, что у нас в проекте есть конвейер CI/CD и специальный сервер, который собирает, тестирует и выкатывает приложения в разные среды выполнения. Как правило, такие решения привязываются к ID коммита в Git (или к Git-тегам), и на каждый коммит система генерирует готовый к выкатке артефакт (архив с готовыми для выполнения файлами). Таким образом, на вход конвейера поступает код из Git-репозитория, версионированный через ID коммита, а на выходе вы получаете протестированный и готовый к выкатке артефакт. В идеале, это должно работать как чистая функция (pure function): если вы пересоберёте коммит, созданный несколько месяцев назад, то должны получить на выходе тот же самый артефакт. Однако мы не можем хранить содержимое node_modules в Git, и получается, что после клонирования репозитория нам необходимо вызывать установку зависимостей из реестра npm. А, как мы уже выяснили, этот процесс довольно нестабилен и привязан к глобальному состоянию экосистемы (содержимому npm registry, версиям npm и т. д.). Получается, что npm вносит хаос в наш конвейер CI/CD и мы уже не можем получить одинаковую сборку по ID коммита.
Lock-файлы приходят на помощь
Чтобы использовать преимущества lock-файла, его необходимо добавить в систему контроля версий. Таким образом, вы строго привяжете полное дерево зависимостей к коммитам в Git. Это будет гарантировать стабильное воспроизводство сборок в вашей системе CI/CD и позволит надежно «путешествовать во времени».
Кроме того, каждый разработчик, который склонирует Git-репозиторий к себе на машину, получит точно такое же дерево зависимостей, как и у вас. Это устранит известную проблему из разряда «странно, а у меня всё работает» (“it works on my machine”).
Структура package-lock.json
Npm генерирует lock-файл полностью автоматически на основе данных из манифеста проекта, глобального состояния npm registry и алгоритма установки зависимостей npm. Однако содержимое файла вполне читаемо человеком и может быть использовано даже на этапе code review. Diff lock-файла покажет, какие зависимости в дереве были обновлены, какие были удалены, а какие добавлены. Наверное, нет смысла изучать изменения этого файла при каждом обновлении, но при обнаружении каких-то деградаций это может сильно помочь в поиске виновного пакета и сэкономить вам кучу времени. Но чтобы это работал эффективнее и размер изменений был минимальным, я рекомендую обновлять зависимости как можно чаще (гораздо проще выявить проблему, если у вас обновилось три пакета в дереве зависимостей, а не сотня).
Оригинальный файл содержит почти 400 строк, но чтобы пример получился достаточно читабельным и наглядным, я показал только часть дерева зависимостей.
package-lock.json
Итак, давайте разбираться. Начнем с основных корневых свойств:
Дескриптор каждой отдельно взятой зависимости выглядит следующим образом:
Дублирующиеся зависимости
Подробнее про дублирование зависимостей мы поговорим в будущих статьях.
Ручные правки
Хотя структура lock-файла хорошо читается и довольно понятна, нельзя забывать, что этот файл генерируется автоматически. Не пытайтесь вносить в него правки, их будет невозможно сохранить. После очередного обновления lock-файла они будут потеряны.
Конфликт в lock-файлах npm
Если несколько разработчиков трудятся в одной ветке и используют lock-файлы, то в какой-то момент может возникнуть merge-конфликт в Git. В этом случае достаточно просто устранить конфликты в файле манифеста (если они есть), а потом выполнить npm install : менеджер пакетов автоматически исправит конфликты в lock-файле.
Установить merge-драйвер для npm можно следующим образом:
При возникновении конфликта при вызове команд Git в консоли будет выведено:
Обновление lock-файла
Установка зависимостей в CI/CD
Как я сказал выше, если npm обнаружит отставание lock-файла от манифеста, то это приведет к обновлению lock-файла и установке зависимостей из манифеста. Такое поведение очень удобно при разработке, но совершенно непригодно, и даже опасно в контексте конвейера CI/CD, потому что может привести к неожиданным результатам из-за слетевшей блокировки зависимостей.
По этой причине никогда не следует использовать команду npm install в рамках конвейера CI/CD, обязательно используйте npm ci вместо нее. Идите и проверьте это прямо сейчас! (я подожду).
Разные типы проектов
Давайте теперь поговорим про особенности использования lock-файлов в проектах разных типов. Первое, о чём стоит сказать: файл package-lock.json не публикуется в npm registry. Это означает, что если вы публикуете свой пакет в реестр npm (библиотека), то содержимое вашего lock-файла не будет оказывать влияния на дерево зависимостей при установке вашего пакета в чей-то проект. В этом случае играет роль только содержимое вашего манифеста. Это и хорошо: если бы каждая библиотека замораживала свои зависимости, то дерево зависимостей в конечном проекте было бы ещё больше (куда уж больше?) и содержало огромное количество дублей. Адекватно управлять зависимостями стало бы невозможно.
Shrinkwrap
Тестирование пакета-библиотеки
По этой причине вы не можете гарантировать среду, однако можете протестировать работу библиотеки со свежими версиями всех зависимостей, чтобы убедиться, что в новых и свежих проектах ничего не сломается. Однако, если вы будете использовать lock-файл в вашем проекте, то он будет постоянно оттягивать версии ваших зависимостей назад, не давая вам полноценно тестировать совместимость с самыми свежими версиями всех зависимостей (которые наверняка будут у ваших пользователей). Либо вам придется постоянно вызывать глубокий npm update перед каждым тестом.
Руководствуясь этим, некоторые разработчики советует вообще не использовать lock-файлы для проектов библиотек. Однако, я считаю это слишком радикальным советом. Дело в том, что помимо продуктовых runtime-зависимостей вашего пакета существуют еще и dev-зависимости. Если вы откажетесь от lock-файла, то ваши dev-зависимости выйдут из-под контроля, то есть вы потеряете некий островок стабильности.
Более подходящим решением, на мой взгляд, была бы реорганизация конвейера CI/CD таким образом, чтобы код библиотеки тестировался в проекте без использования lock-файла, путем установки самых свежих доступных зависимостей. Собирать же пакет (если требуется) можно и с участием lock-файла (для этого можно использовать два разных этапа в вашем CI/CD конвейере).
С глаз долой…
Надеюсь, теперь, лучше понимая, как работают эти механизмы, вы сможете эффективнее наладить управление зависимостями в вашем проекте и повысить его надежность и безопасность.
Обновляйтесь чаще!
Помните, задача lock-файлов заключается не в том, чтобы навсегда заблокировать все зависимости и больше никогда их не обновлять, а в том, чтобы обеспечить повторяемость их установки и прозрачность их обновления.
Залог высокого качества, поддерживаемости и удобства разработки вашего проекта заключается в частом обновлении зависимостей. Согласитесь, гораздо проще обновлять зависимости маленькими порциями по мере их выхода. Diff lock-файла всегда покажет, что именно обновилось. Если он будет небольшим, то вы легко сможете его прочитать. Если же после обновления возникнет проблема, то, скорее всего, она будет одна, и её будет несложно обнаружить и изолировать.
С другой стороны, если вы не будете полгода обновлять зависимости, то потом сделать это будет очень сложно, потому что крупное обновление очень сложно разделить на маленькие части. Вы получите огромный ворох изменений разной степени тяжести, а в случае возникновения проблемы (а она, скорее всего, придет не одна) выявить точное место будет гораздо сложнее (diff здесь уже будет бесполезен в силу его гигантского размера).
Поэтому не запускайте ваши зависимости, обновляйтесь регулярно, возьмите это в привычку, тогда издержки на поддержку кодовой базы будут минимальны. Переход же на новые мажорные версии библиотек будет более планомерным: гораздо проще обновиться на одну мажорную версию библиотеки, чем на несколько сразу.
Продолжение следует
Мы рассмотрели столь важный инструмент повышения надежности и безопасности управления зависимостями, как lock-файлы npm. Использование этого механизма позволит вам существенно повысить стабильность работы вашего проекта и улучшить качество и удобство разработки.
В следующем посте я еще дальше углублюсь в вопросы безопасности npm.
Если вам понравился материал, то, пожалуйста, ставьте лайки, подписывайтесь на наш блог и делитесь ссылками с коллегами. Так мы будем понимать, что наша работа востребована, и будем продолжать радовать вас новыми полезными материалами.
Если у вас есть вопросы или желание что-то добавить по теме, то не бойтесь оставлять комментарии, я с радостью приму участие в обсуждении и учту ваши пожелания в следующих постах.
Зачем нужен package lock json и package json
В данной статье мы хотим познакомить вас с package lock и package json файлами. Разберемся, для чего необходимы lock файлы, при работе с npm.
Что такое пакет в JavaScript?
Пакетом называется один или несколько файлов в JS, в виде инструмента или библиотеки. Npm хранит в себе самый большой набор пакетов. Npm необходим для скачивания пакетов и загрузки их, благодаря этому можно хорошо ускорить своё написание кода.
Что такое package.json?
Типы зависимостей в package.json
Давайте разберем несколько основных зависимостей проекта, чтобы лучше понимать что в него входит и что можно редактировать.
• dependencies — главные зависимости которые находятся в вашем проекте. Их можно применять и запускать в коде.
• devDependencies — взаимосвязи разработки.
• peerDependencies — равные взаимосвязи, при добавлении которых, мы даем понять какую версию для взаимосвязи следует установить
• optionalDependencies — второстепенные зависимости. Если при установке произойдет сбой, на основную сборку это не повлияет.
• bundledDependencies — в нём содержится массив пакетов. Данный массив нужен, если вам требуется добавить новую зависимость, которой нет в npm.
Назначение package-lock.json
package-lock.json если коротко, то предназначен для блокировки зависимостей от определенного номера версии. В package-lock.json файле перечислены зависимости вашего приложения и зависимости всех его зависимостей. Другими словами, он описывает, какую версию каждого отдельного пакета вы установили. Вот почему это намного дольше, чем package.json. Когда вы используете package-lock.json для установки зависимостей, вы получаете точно такие же пакеты, неважно, устанавливаете ли вы их сейчас или через несколько лет, когда мы, надеюсь, больше не будем использовать клавиатуры.
Чтобы установить зависимости на основе текущего package-lock.json файла, вы должны использовать npm ci вместо npm install.
Используя, npm ci вы сможете избежать проблем, которые возникают, когда приложение работает на вашем компьютере, но не работает на чужом, потому что они в конечном итоге загрузили различные зависимости из-за использования npm install.
Вот почему при запуске ваших сборок на сервере непрерывной интеграции вам следует устанавливать зависимости, используя npm ci вместо npm install. Это приведет к тому, что сборка CI-сервера будет работать так же, как на вашем компьютере.
Назначение package.json
Package специализирован не только лишь на взаимосвязях, но и для определения свойств проекта:
Чтоб отключить автоматическое создания файла следует написать в npmrc package-lock=false. Наличие package-lock.json в проекте необязательно.
Разбивка параметров
Менеджеры версий
Что из себя представляет расширение JSON
Как создать package.json?
Из-за того, что package.json довольно глобальный и состоит из множества свойств, то в ручную его писать трудно и очень долго. Для быстроты решения задачи, npm сделали команду init. Откройте консоль в области текущей папки и напишите команду: npm init. Данная команда позволяет создать файл package.json. После этого вы получаете вот такой ответ:
Наш package.json создан!
Установка модулей
Для начала в своём проекте в консоли напишите команду
npm install
Если вам нужны еще библиотеки просто перечисляете их, отделяя отступом. Но не забывайте добавлять аргумент —save, для сохранения библиотеки как зависимость самого проекта.
По итогу у нас получились созданные файлы: package.json, package-lock.json, node_modules.
Сгенерированная папка node_modules хранит в себе все модули вашего проекта. Данную папку в Git репозиторий мы не добавляем! Так как в ней хранится множество зависимостей и они будут только добавляться, вы будете очень долго ждать загрузки. Загружать нужно только 2 файла package.json, package-lock.json, даже после того, как другой разработчик сделает копию вашего проекта, он сможет установить нужные зависимости сохраненные в package.json.
Не забудьте добавить файл node_modules в gitignore.
Заключение
По итогу, в данной статье мы разобрали базу package и package lock, рассказали о самых распространенных и часто встречающихся ошибках, которые следует избегать.
Углубились в структуру файлов. Показали как установить node_modules, а также научились устанавливать дополнительные библиотеки.
В конечном итоге помните, что на git node_modules лучше не добавлять. И теперь любой скопированный проект вы сможете настроить сами, так, чтобы он работал.
Добро пожаловать в ад…зависимостей JavaScript
Начало любого JavaScript проекта сопровождается амбициозным намерением— использовать как можно меньше npm пакетов в процессе разработки. Но сколько бы усилий мы не предпринимали, рано или поздно пакеты начинают накапливаться. Со временем строк в package.json становится всё больше, а благодаря package-lock.json пул реквесты приобретают все более устрашающий вид со всеми своими дополнениями или удалениями в процессе добавления зависимостей.
“И нормально”, — скажет лидер команды разработчиков, а все остальные только кивнут в ответ. А что еще остается делать в такой ситуации? Ведь у нас есть счастливая возможность наблюдать, как оживает и процветает экосистема JavaScript. Нам не нужно каждый раз изобретать колесо и ломать голову над вопросами, которые уже давно решены сообществом открытого ПО.
Хорошая новость: вышла первая версия AppSignal для Node.js. Это приложение предполагает интеграцию с Express и включает интерфейс для автоматических модулей Node.js.
Предположим, вы захотели создать блог и выбрали для этого Gatsby.js. Попробуйте установить и сохранить его в число ваших зависимостей. Поздравляю! Вместе с этим фреймворком вы только что получили 19000 дополнительных зависимостей. Как вам такой подарок? До какой же степени может разрастаться дерево зависимостей JavaScript? Как же мы оказываемся в аду зависимостей? Давайте копнем поглубже и выясним.
Что же такое пакет JavaScript?
npm, менеджер пакетов, входящий в состав Node.js, содержит самый полный реестр пакетов JavaScript в мире! Он больше, чем RubyGems, PyPi и Maven вместе взятые! Данные приведены согласно исследованиям веб-сайта Module Counts, который отслеживает количество пакетов самых популярных реестров.
Что такое package.json?
Согласно определению package.json:
Пока звучит немного хаотично. Представьте себе бесконечную череду пакетов, зависящих друг от друга. Вот почему при установке одного пакета Gatsby вы получили 19 тысяч дополнительных зависимостей.
Типы зависимостей в package.json
Чтобы прояснить вопрос накопления зависимостей с течением времени, рассмотрим разные типы зависимостей проекта. В пакете package.json встречаются несколько из них:
Назначение package-lock.json
Всем известен тот самый файл, который получает много дополнений и удалений в пул реквестах, и это принимается как должное. package-lock.json автоматически создается каждый раз при изменении файла package.json или директории node_modules. Он сохраняет в неизменном виде дерево зависимостей, созданное при установке, чтобы все последующие зависимости могли создавать идентичное дерево. Это решает проблему, при которой у меня одна зависимость, а у вас другая.
package-lock.json является длинным списком зависимостей в проекте. Он указывает их версию, положение модуля (URI), хэш, отображающий взаимодействие модулей и необходимых для них пакетов. Продолжив чтение списка, вы найдете каждую запись для каждого пакета, необходимого для React и т.д. Вот тут-то и находится настоящий ад зависимостей. Он определяет все, что нужно проекту.
Разбираемся с зависимостями Gatsby.js
Итак, как же нам выйти из ситуации, в которой при установке одной зависимости мы получили в нагрузку 19 000? Ответ — зависимости зависимостей. Вот что происходит при установке Gatsby.js:
В package.json можно увидеть только одну зависимость. Но присмотревшись к package-lock.json, нельзя не заметить новорожденного монстра, раскинувшего свои 14 тысяч строк. Более детальную информацию можно получить в package.json, расположенном в GitHub репозитории Gatbsy.js. По подсчетам npm число прямых зависимостей составляет 136. А теперь представьте, что каждая из этих зависимостей имеет еще одну зависимость, и в итоге вы получаете 272 зависимости. И это я еще преуменьшил! В действительности у каждой зависимости может быть больше одной зависимости, так что их список продолжит пополняться.
200 мегабайт, как я уже говорил, совсем неплохой результат. Бывали случаи, когда размер директории мог легко перевалить за 700 мегабайт. Если вам интересно, какие модули занимают большую часть памяти, можете выполнить следующую команду:
Ага, rxjs, ну и хитрая же ты штучка. Есть одна простая команда, которая поможет вам с размером node_modules и уменьшением дублирования зависимостей — npm dedup :
Визуализация зависимостей
Если вам интересно, как выглядят зависимости вашего проекта, можете воспользоваться парочкой инструментов. В моем арсенале есть несколько таких, которые показывают зависимости совсем иначе.
npm.anvaka.com
На изображении можно увидеть взаимодействие пакетов друг с другом, и в целом оно напоминает гигантскую паутину. Такое большое количество зависимостей Gatsby.js почти обрушило мой браузер. Здесь вы увидите, как взаимодействуют зависимости Gatsby.js. Там же можно увидеть их в 3D режиме.
npm.broofa.com
Данный способ изображения зависимостей напоминает блок-схему. В случае с Gatsby.js он быстро оказался довольно замысловатым, но если любопытно, то смотрите здесь. На npms.io можно поставить оценку каждой зависимости по нескольким критериям, и, согласно полученным результатам, они будут выделены разными цветами. Вы можете также загрузить туда свой package.json и получить его визуальное отображение.
Package Phobia
Чем больше сила, тем больше ответственность
Подводя итоги, без преувеличения скажу, что JavaScript и npm просто супер, а возможность гибкого подхода при выборе из океана зависимостей — еще лучше. Сущий пустяк — выполнить npm install для сохранения пары строк кода, но иногда мы почему-то забываем, что скрывается за этим действием.
Теперь, прочитав всю статью, вы сможете лучше разбираться в дереве зависимостей JavaScript. Устанавливаете ли вы слишком большую библиотеку или просто выясняете насколько сложные зависимости у проекта, моя статья станет вашим руководством по анализу зависимостей разной степени сложности.
Разбираемся с lock-файлами в NPM 5
В отличие от предыдущей версии, lock-файл теперь включает в себя поле целостности ( integrity), использующее Subresource Integrity (SRI) для проверки того, что установливаемый пакет не был подменён или иным образом недействителен. В настоящее время он поддерживает SHA-1 для пакетов, опубликованных с помощью NPM предыдущей версии, и SHA-512 для текущей.
Два lock-файла
Я уже упоминал, что сейчас на самом деле имеется больше одного lock-файла. Теперь NPM автоматически генерирует lock-файл с именем package-lock.json всякий раз, когда устанавливается новая зависимость или файл еще не существует. Как уже упоминалось в начале, lock-файл представляет собой моментальный слепок текущего дерева зависимостей и позволяет воспроизводить сборки между машинами разработчиков. Поэтому рекомендуется добавить его в свою систему контроля версий.
Это здорово, но как выбрать: использовать новый lock-файл вместо старого доброго shrinkwrap или наоборот? Обычно это зависит от типа пакета, над которым вы работаете.
При разработке библиотеки
Если вы работаете с библиотекой (то есть пакетом, от которого будут зависеть другие пакеты), вы должны использовать новый lock-файл. Альтернативой является использование shrinkwrap, но убедитесь, что он никогда не будет опубликован вместе с пакетом (новый lock-файл защищён от публикации автоматически). Почему бы не опубликовать shrinkwrap? Это связано с тем, что NPM обрабатывает shrinkwrap-файлы, которые он находит в пакетах, и поскольку shrinkwrap всегда указывает на конкретные версию отдельных пакетов, вы не сможете воспользоваться тем фактом, что NPM может использовать один и тот же пакет для разрешения зависимостей нескольких пакетов, если диапазон semver позволяет это. Другими словами, не заставляя NPM устанавливать определенные версии, вы позволяете NPM эффективнее повторно использовать пакеты, что в результате приводит к более компактному дереву зависимостей и упрощает сборку.
К сожалению, вы не сможете заметить этого до тех пор, пока не появится отчет об ошибке. Без каких-либо lock-файлов в репозитории ваша сборка завершится неудачно, по крайней мере, в CI, поскольку она всегда будет устанавливать последние версии зависимостей и, таким образом, запускать тесты с новой сломанной версией (при условии, что сборка выполняется периодически, а не только для PR). Однако при наличии lock-файла CI-сервер всегда будет устанавливать рабочую заблокированную версию.
Есть несколько вариантов решения этой проблемы. Во-первых, вы можете пожертвовать точной воспроизводимостью и не добавлять lock-файл в свою систему контроля версий. Во-вторых, вы можете создать отдельную конфигурацию сборки, которая будет запускать npm update перед запуском тестов. В-третьих, вы просто удаляете lock-файл перед запуском тестов. Как на самом деле поступать с сломанной зависимостью, когда она была обнаружена, это отдельная тема, главным образом потому, что semver, реализованный NPM, не имеет концепции указания разрешения широкого диапазона и также не имеет черного списка конкретных версий.
Это, конечно, ставит вопрос о том, стоит ли добавлять lock-файл в систему управления версиями при работе с библиотеками. Однако следует иметь в виду, что lock-файл содержит не только runtime-зависимости, но и зависимости для разработки (dev dependencies). В этом смысле работа над библиотекой аналогична работе над приложением (смотрим следующий раздел).
При разработке приложения
Что насчет других типов приложений, например, проектов, которые вы запускаете из своего репозитория? В этом случае это не имеет большого значения. Имеют значение только установленные правильные зависимости, и оба lock-файла могут это обеспечить. Выбор за вами.