Ещё один инструмент для проверки ваших npm зависимостей — wtfwith

Оригинал поста на хабре

Вы как-нибудь задумывались, сколько версий одной и той же библиотеки затягивает ваша клиентская или серверная сборка? Мне вот в какой-то момент стало интересно. Навскидку найти для этого готовый инструмент не получилось, а смотреть глазами package-lock слишком утомительно. Как мы знаем - в любой непонятной ситуации нужно писать свой npm пакет, поэтому я именно это и сделал… Дальше в посте я рассмотрю результат анализа живого проекта и сделаю пару спорных выводов.

Ну и никак нельзя обойтись без этой классической картинки:

Как работает

В общем-то никакого rocket science - берём package.json и package-lock.json и пробегаем по всем зависимостям, которые там есть, собирая идентичные модули или модули-форки. Форки, ясное дело, приходится прописывать руками, поэтому сейчас в них указан только lodash и undescore. Но вы всегда можете помочь и расширить этот список.

Как использовать

Модуль опубликован на npm, проще всего запускать при помощи npx, хотя никто не мешает вам установить его локально или даже глобально. Дальше буду рассматривать вариант с запуском через npx. Для примера возьму один свой старый проект, который по сути является монолитом на ноде (это было ещё до микросервисного бума, а распиливать его потом было лень и нецелесообразно).

1. npx wtfwith moduleName (например, npm wtfwith lodash) - проверяем количество вхождений какого-то определённого модуля, выводит результаты, если их больше двух. Результат может быть, например, такой:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
npx wtfwith lodash
npx: installed 3 in 2.047s
Searching for lodash
Checking path /web/xxx/package-lock.json
Huston, we have a problem:

30 versions of lodash:
- 2.4.2 from root, cheerio@0.18.0
- 3.10.1 from xmlbuilder@2.6.4
- 4.0.0 from twilio@3.15.0
- 4.17.4 from graphlib@2.1.1, sway@1.0.0
- 4.17.5 from aws-sdk@2.211.0, xmlbuilder@4.2.1, request-promise-core@1.1.1, requestretry@1.13.0
- lodash.assign:4.2.0 from ioredis@3.2.2
- lodash.bind:4.2.1 from ioredis@3.2.2
- lodash.clone:4.5.0 from ioredis@3.2.2
- lodash.clonedeep:4.5.0 from ioredis@3.2.2
- lodash.defaults:4.2.0 from ioredis@3.2.2
- lodash.difference:4.5.0 from ioredis@3.2.2
- lodash.flatten:4.4.0 from ioredis@3.2.2
- lodash.foreach:4.5.0 from ioredis@3.2.2
- lodash.get:4.4.2 from z-schema@3.18.4
- lodash.includes:4.3.0 from jsonwebtoken@8.2.1
- lodash.isboolean:3.0.3 from jsonwebtoken@8.2.1
- lodash.isempty:4.4.0 from ioredis@3.2.2
- lodash.isequal:4.5.0 from z-schema@3.18.4
- lodash.isinteger:4.0.4 from jsonwebtoken@8.2.1
- lodash.isnumber:3.0.3 from jsonwebtoken@8.2.1
- lodash.isplainobject:4.0.6 from jsonwebtoken@8.2.1
- lodash.isstring:4.0.1 from jsonwebtoken@8.2.1
- lodash.keys:4.2.0 from ioredis@3.2.2
- lodash.noop:3.0.1 from ioredis@3.2.2
- lodash.once:4.1.1 from jsonwebtoken@8.2.1
- lodash.partial:4.2.1 from ioredis@3.2.2
- lodash.pick:4.4.0 from ioredis@3.2.2
- lodash.sample:4.2.1 from ioredis@3.2.2
- lodash.shuffle:4.2.0 from ioredis@3.2.2
- lodash.values:4.3.0 from ioredis@3.2.2

Advice: Sometimes it is good to make a fresh start: rm ./ -rf &amp;&amp; git commit -am 'nevermore' &amp;&amp; git push origin master</code>

2. npx wtfwith everything</code> (можно просто опустить аргумент и написать npx wtfwith, но это уныло) - проверяет вообще все зависимости, которых больше, чем две версии. Может быть, например, такой результат:

Пример

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
npx wtfwith lodash
npx: installed 3 in 1.813s
Searching all strange things...
Checking path /web/xxx/package-lock.json
Huston, we have a problem:

30 versions of lodash:
// ну, это вы уже видели

4 versions of punycode:
- 1.2.4 from dkim-signer@0.1.2
- 1.3.2 from url@0.10.3
- 1.4.1 from tough-cookie@2.3.3
- 2.1.0 from uri-js@3.0.2

4 versions of xmlbuilder:
- 0.4.2 from nodemailer@0.7.1, aws-sdk@2.0.5
- 2.6.4 from root, xml2js@0.4.8
- 4.2.1 from aws-sdk@2.211.0, xml2js@0.4.17
- 9.0.1 from twilio@3.15.0

3 versions of xmldom:
- 0.1.19 from xml-crypto@0.10.1
- 0.1.27 from pdf2json@0.6.2
- 0.1.7 from ws.js@2.0.22

3 versions of mime:
- 1.2.11 from mailcomposer@0.2.12
- 1.4.1 from superagent@3.8.0
- 2.3.1 from root

3 versions of sax:
- 0.4.2 from xml2js@0.2.6
- 0.6.1 from xml2js@0.4.8
- 1.2.1 from aws-sdk@2.211.0, xml2js@0.4.17

3 versions of uuid:
- 1.4.2 from root
- 3.1.0 from aws-sdk@2.211.0
- 3.2.1 from request@2.83.0, request@2.85.0, requestretry@1.13.0, twilio@3.15.0

3 versions of xml2js:
- 0.2.6 from nodemailer@0.7.1, aws-sdk@2.0.5
- 0.4.17 from aws-sdk@2.211.0
- 0.4.8 from root

3 versions of moment:
- 2.19.3 from twilio@3.15.0
- 2.20.1 from moment-timezone@0.5.14
- 2.22.1 from root, joi@4.9.0, moment-range@1.0.9, moment-timezone@0.4.0, moment-timezone@0.5.15

3 versions of readable-stream:
- 1.0.34 from match-stream@0.0.2, pullstream@0.4.1, slice-stream@1.0.0, unzip@0.1.11
- 1.1.14 from dicer@0.2.5, ftp@0.3.10, htmlparser2@3.8.3, nodemailer@0.7.1
- 2.3.3 from mysql@2.14.1, superagent@3.8.0

Advice: What about lunch?

3. Так же можно указать опцию --dev - в случае, если вы, например, собираете бандл из дев зависимостей. Хотя для этого лучше использовать фронтовые инструменты для анализа бандлов. Пример использования показывать не буду, команда выглядит как npx wtfwith everything --dev, и вы можете себе примерно представить, какой там выходит ад.

Реальные примеры

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

1. express - как ни удивительно, ничего интересного не нашлось.

2. gulp - нашлись всякие мелочи:

смотрим?

1
2
3
4
5
6
7
8
9
10
4 versions of kind-of:
- 3.2.2 from is-accessor-descriptor@0.1.6, is-data-descriptor@0.1.4, is-number@3.0.0, make-iterator@1.0.0, object-copy@0.1.0, snapdragon-util@3.0.1, to-object-path@0.3.0
- 4.0.0 from has-values@1.0.0
- 5.1.0 from array-sort@1.0.0, class-utils@0.3.6, is-descriptor@0.1.6, default-compare@1.0.0, expand-brackets@2.1.4, snapdragon@0.8.2, static-extend@0.1.2
- 6.0.2 from braces@2.3.1, is-accessor-descriptor@1.0.0, is-data-descriptor@1.0.0, is-descriptor@1.0.2, micromatch@3.1.10, nanomatch@1.2.9, use@3.1.0

3 versions of define-property:
- 0.2.5 from class-utils@0.3.6, expand-brackets@2.1.4, object-copy@0.1.0, snapdragon@0.8.2, static-extend@0.1.2
- 1.0.0 from base@0.11.2, braces@2.3.1, extglob@2.0.4, snapdragon-node@2.1.1
- 2.0.2 from micromatch@3.1.10, nanomatch@1.2.9, to-regex@3.0.2</code>

3. npm - довольно забавный результат: смотрим!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
14 versions of lodash:
- 3.10.1 from cli-table2@0.2.0
- lodash._baseindexof:3.1.0 from root
- lodash._baseuniq:4.6.0 from root
- lodash._bindcallback:3.0.1 from root
- lodash._cacheindexof:3.0.2 from root
- lodash._createcache:3.1.2 from root
- lodash._createset:4.0.3 from lodash._baseuniq@4.6.0
- lodash._getnative:3.9.1 from root, lodash._createcache@3.1.2
- lodash._root:3.0.1 from lodash._baseuniq@4.6.0
- lodash.clonedeep:4.5.0 from root
- lodash.restparam:3.6.1 from root
- lodash.union:4.6.0 from root
- lodash.uniq:4.5.0 from root
- lodash.without:4.4.0 from root

3 versions of mississippi:
- 1.3.1 from make-fetch-happen@2.6.0
- 2.0.0 from cacache@10.0.4
- 3.0.0 from root, pacote@7.6.1

3 versions of pump:
- 1.0.3 from mississippi@1.3.1
- 2.0.1 from mississippi@2.0.0, pumpify@1.4.0
- 3.0.0 from mississippi@3.0.0</code>

Как минимум, забавно, что npm явно пытается минимизировать использование лодаша, но его целиком затягивает одна из его зависимостей…

Todo

Проект писался за вечер на коленке, поэтому есть некоторые количество вещей, которые там не реализованы:
  1. Нет работы с shrinkwrap файлом - но это не очень нужно, вы можете просто сгенерить package-lock для проверки;
  2. Поддерживается только node 6+. Тоже не очень критично, ведь есть nvm;
  3. Не поддерживается лок файл от yarn. Опять же не слишком критично;
  4. Наверняка есть некоторое количество багов и неточностей.

В общем, если вам актуален инструмент, то pull requests приветствуются.

Выводы

Выглядят результаты не очень красиво, но, если честно, я ожидал больше ада. Судя по всему, многие таки пользуются инструментами для анализа обновлений и защищённости зависимостей.

Из спорных рекомендаций:

  1. Я бы не рекомендовал для заведомо бэковых модулей затягивать зависимости вроде лодаша кусками - всё равно кто-то наверняка затянет к себе полную версию библиотеки. Ну и не забывайте про семантическое версионирование - конечно, без точных ограничений версий всё может в какой-то момент сломаться, но это должно решаться фиксацией зависимостей в package-lock и взаимодействием между разработчиками, а не точными ограничениям. То есть, если вы обнаружили breaking change в какой-то своей зависимости, то хорошо бы зайти к автору пакета и сказать, что он неправ - этим вы сэкономите другим много усилий и терабайты места на дисках.

Хотя вот уважаемый мной автор ioredis делает ровно наоборот.

  1. Стоит периодически пробегать по своим зависимостям и смотреть, что в них есть подозрительного, а затем отписывать issues или делать pull requests в проблемные модули.

Если вам близка проблема обновления зависимостей, то напоследок могу посоветовать несколько полезных инструментов:

  1. npm-check-updates - быстро смотрим обновления версий пакетов, быстро их ставим. Только аккуратно!
  2. snyk - проверка безопасности пакетов, про него я уже писал чуть раньше.
  3. node security project - ещё один инструмент для проверки безопасности зависимостей, который можно считать родным для экосистемы, потому что его недавно приобрёл npm.
  4. В случае, если вы работаете с фронтом, то наверняка используете что-то вроде webpack-bundle-analyzer, который вам расскажет, что именно у вас вышло в результате. Но я с фронтом не работаю, так что здесь внятных рекомендаций дать не могу.
  5. Так же полезно иметь всякие бейджики и CI интеграции, которые вам будут наглядно показывать и оповещать, если что-то устарело или стало небезопасным. Примеров бейджиков приводить не буду, их весьма много, и часть вы можете посмотреть на shields.io. Интеграций тоже много, и, если у вас opensource проект, то travis-ci может прогонять практичски любые проверки.

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