Начало любого JavaScript проекта сопровождается амбициозным намерением— использовать как можно меньше npm пакетов в процессе разработки. Но сколько бы усилий мы не предпринимали, рано или поздно пакеты начинают накапливаться. Со временем строк в package.json
становится всё больше, а благодаря package-lock.json
пул реквесты приобретают все более устрашающий вид со всеми своими дополнениями или удалениями в процессе добавления зависимостей.
“И нормально”, — скажет лидер команды разработчиков, а все остальные только кивнут в ответ. А что еще остается делать в такой ситуации? Ведь у нас есть счастливая возможность наблюдать, как оживает и процветает экосистема JavaScript. Нам не нужно каждый раз изобретать колесо и ломать голову над вопросами, которые уже давно решены сообществом открытого ПО.
Хорошая новость: вышла первая версия AppSignal для Node.js. Это приложение предполагает интеграцию с Express и включает интерфейс для автоматических модулей Node.js.
Предположим, вы захотели создать блог и выбрали для этого Gatsby.js. Попробуйте установить и сохранить его в число ваших зависимостей. Поздравляю! Вместе с этим фреймворком вы только что получили 19000 дополнительных зависимостей. Как вам такой подарок? До какой же степени может разрастаться дерево зависимостей JavaScript? Как же мы оказываемся в аду зависимостей? Давайте копнем поглубже и выясним.
npm, менеджер пакетов, входящий в состав Node.js, содержит самый полный реестр пакетов JavaScript в мире! Он больше, чем RubyGems, PyPi и Maven вместе взятые! Данные приведены согласно исследованиям веб-сайта Module Counts, который отслеживает количество пакетов самых популярных реестров.
“Ничего себе сколько кода”, — подумали вы. Так и есть. Чтобы фрагмент вашего кода стал npm пакетом, в проекте нужно использовать package.json
. Именно так код становится пакетом, который вы можете отправить в npm реестр.
Согласно определению package.json:
Для полной картины просто представьте README на стероидах. Вы можете определять зависимости пакета, писать сборку и тестировать скрипты, а также версионировать пакет по усмотрению и описывать его функционал. Для нас же наибольший интерес представляет возможность определять зависимости внутри package.json
.
Пока звучит немного хаотично. Представьте себе бесконечную череду пакетов, зависящих друг от друга. Вот почему при установке одного пакета Gatsby вы получили 19 тысяч дополнительных зависимостей.
Чтобы прояснить вопрос накопления зависимостей с течением времени, рассмотрим разные типы зависимостей проекта. В пакете package.json встречаются несколько из них:
dependencies
— основные зависимости, которые вы можете использовать и вызывать в коде проекта. devDependencies
— зависимости разработки, например библиотека Prettier для форматирования кода. peerDependencies
— равноправные зависимости, при включении которых в package.json, вы сообщаете человеку, устанавливающему ваш пакет, что ему нужна та же зависимость с указанной версией. optionalDependencies
— это необязательные зависимости. Если во время установки с ними возникнут какие-то проблемы, то это не повлияет на удачное завершение всего установочного процесса. bundledDependencies
— это массив пакетов, которые объединяются с вашим пакетом. Они пригодятся, если вы захотите использовать стороннюю библиотеку, не входящую в npm, или включить некоторые проекты в качестве модулей. Всем известен тот самый файл, который получает много дополнений и удалений в пул реквестах, и это принимается как должное. package-lock.json
автоматически создается каждый раз при изменении файла package.json или директории node_modules. Он сохраняет в неизменном виде дерево зависимостей, созданное при установке, чтобы все последующие зависимости могли создавать идентичное дерево. Это решает проблему, при которой у меня одна зависимость, а у вас другая.
Рассмотрим проект, имеющий среди своих зависимостей React. Если вы перейдете в package-lock.json
, то увидите:
"react": {
"version": "16.13.0",
"resolved": "https://registry.npmjs.org/react/-/react-16.13.0.tgz",
"integrity": "sha512-TSavZz2iSLkq5/oiE7gnFzmURKZMltmi193rm5HEoUDAXpzT9Kzw6oNZnGoai/4+fUnm7FqS5dwgUL34TujcWQ==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2"
}
}
package-lock.json
является длинным списком зависимостей в проекте. Он указывает их версию, положение модуля (URI), хэш, отображающий взаимодействие модулей и необходимых для них пакетов. Продолжив чтение списка, вы найдете каждую запись для каждого пакета, необходимого для React и т.д. Вот тут-то и находится настоящий ад зависимостей. Он определяет все, что нужно проекту.
Итак, как же нам выйти из ситуации, в которой при установке одной зависимости мы получили в нагрузку 19 000? Ответ — зависимости зависимостей. Вот что происходит при установке Gatsby.js:
$ npm install --save gatsby
...
+ [email protected]
added 1 package from 1 contributor, removed 9 packages, updated 10 packages and audited 19001 packages in 40.382s
В package.json можно увидеть только одну зависимость. Но присмотревшись к package-lock.json, нельзя не заметить новорожденного монстра, раскинувшего свои 14 тысяч строк. Более детальную информацию можно получить в package.json, расположенном в GitHub репозитории Gatbsy.js. По подсчетам npm число прямых зависимостей составляет 136. А теперь представьте, что каждая из этих зависимостей имеет еще одну зависимость, и в итоге вы получаете 272 зависимости. И это я еще преуменьшил! В действительности у каждой зависимости может быть больше одной зависимости, так что их список продолжит пополняться.
Например, посмотрим, сколько библиотек требует lodash
.
$ npm ls lodash
[email protected]
└─┬ [email protected]
├─┬ @babel/[email protected]
│ ├─┬ @babel/[email protected]
│ │ └── [email protected] deduped
│ ├─┬ @babel/[email protected]
│ │ └── [email protected] deduped
│ └── [email protected] deduped
├─┬ @babel/[email protected]
│ └── [email protected] deduped
├─┬ @typescript-eslint/[email protected]
│ └─┬ @typescript-eslint/[email protected]
│ └── [email protected] deduped
├─┬ [email protected]
│ └─┬ @babel/[email protected]
│ ├─┬ @babel/[email protected]
│ │ └── [email protected] deduped
│ ├─┬ @babel/[email protected]
│ │ └─┬ @babel/[email protected]
│ │ └── [email protected] deduped
│ ├─┬ @babel/[email protected]
│ │ └─┬ @babel/[email protected]
│ │ └── [email protected] deduped
│ └─┬ @babel/[email protected]
│ └─┬ @babel/[email protected]
│ └── [email protected] deduped
...
К счастью, большинство из них используют одну и ту же версию lodash
, которая требует установки лишь одной библиотеки lodash
в node_modules
. Но в реальных проектах продакшена ситуация не такая оптимистичная: иногда разные пакеты требуют разных версий других пакетов. В связи с этим как только не шутили по поводу тяжелого веса директории node_modules
! В нашем же случае не все так плохо:
$ du -sh node_modules
200M node_modules
200 мегабайт, как я уже говорил, совсем неплохой результат. Бывали случаи, когда размер директории мог легко перевалить за 700 мегабайт. Если вам интересно, какие модули занимают большую часть памяти, можете выполнить следующую команду:
$ du -sh ./node_modules/* | sort -nr | grep '\dM.*'
17M ./node_modules/rxjs
8.4M ./node_modules/@types
7.4M ./node_modules/core-js
6.8M ./node_modules/@babel
5.4M ./node_modules/gatsby
5.2M ./node_modules/eslint
4.8M ./node_modules/lodash
3.6M ./node_modules/graphql-compose
3.6M ./node_modules/@typescript-eslint
3.5M ./node_modules/webpack
3.4M ./node_modules/moment
3.3M ./node_modules/webpack-dev-server
3.2M ./node_modules/caniuse-lite
3.1M ./node_modules/graphql
...
Ага, rxjs, ну и хитрая же ты штучка. Есть одна простая команда, которая поможет вам с размером node_modules
и уменьшением дублирования зависимостей — npm dedup
:
$ npm dedup
moved 1 package and audited 18701 packages in 4.622s
51 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
Действие дедупликации призвано упростить структуру дерева зависимостей путем поиска общих пакетов между ними и их перемещением для последующего переиспользования. Как раз то, что происходит в нашем примере с lodash
. Большинство пакетов останавливаются на [email protected]
, поэтому нет других версий lodash
для установки. Мы добились этого результата в самом начале, так как только что установили наши зависимости, но если вы в течение какого-то времени добавляли зависимости в package.json
, то лучше выполнить npm dedup
. В случае использования yarn
можете запустить yarn dedupe
, хотя в этом нет необходимости, так как этот процесс уже запускается при yarn install
.
Если вам интересно, как выглядят зависимости вашего проекта, можете воспользоваться парочкой инструментов. В моем арсенале есть несколько таких, которые показывают зависимости совсем иначе.
npm.anvaka.comНа изображении можно увидеть взаимодействие пакетов друг с другом, и в целом оно напоминает гигантскую паутину. Такое большое количество зависимостей Gatsby.js почти обрушило мой браузер. Здесь вы увидите, как взаимодействуют зависимости Gatsby.js. Там же можно увидеть их в 3D режиме.
npm.broofa.comДанный способ изображения зависимостей напоминает блок-схему. В случае с Gatsby.js он быстро оказался довольно замысловатым, но если любопытно, то смотрите здесь. На npms.io можно поставить оценку каждой зависимости по нескольким критериям, и, согласно полученным результатам, они будут выделены разными цветами. Вы можете также загрузить туда свой package.json и получить его визуальное отображение.
Package PhobiaЭто превосходный инструмент для предварительной проверки размера пакета перед запуском npm install
. Он показывает размер публикации в реестре npm и размер на диске после его установки в проекте.
Подводя итоги, без преувеличения скажу, что JavaScript и npm просто супер, а возможность гибкого подхода при выборе из океана зависимостей — еще лучше. Сущий пустяк — выполнить npm install
для сохранения пары строк кода, но иногда мы почему-то забываем, что скрывается за этим действием.
Теперь, прочитав всю статью, вы сможете лучше разбираться в дереве зависимостей JavaScript. Устанавливаете ли вы слишком большую библиотеку или просто выясняете насколько сложные зависимости у проекта, моя статья станет вашим руководством по анализу зависимостей разной степени сложности.
Перевод статьи Nikola Đuza: Ride Down Into JavaScript Dependency Hell
Комментарии