Существует много webpack пакетов находящих дубли в бандле, самый популярный из них duplicate-package-checker-webpack-plugin, но он требует пересборки проекта, а так как стояла задача автоматизировать подбор оптимальной версии пакетов, то и вовсе получилось свое альтернативное решение.


Ну или моя история как получилось уменьшить бандл на 15%, за несколько секунд.


боль


Как и во многих крупных команиях у которых есть огромная кодовая база, много единой логики, как следствие используем общие компоненты, публикуемые в свой npm репозиторий. Публикуются они через lerna, соответственно перед каждой установкой или обновлением общих компонентов возникает вопрос, какую версию устанавливать. lerna перепаблишивает все компоненты, которые используют публикуемый компонент (если версия до этого последняя была). Соответственно, всегда есть версии нескольких компонентов, которые лучше подходят друг к другу, так как не конкурируют зависимостями.


Из open source проектов подобным образом паблишиться nivo, вот их конфиг lerna.


Как появляются тогда дубли зависимостей? И как их устранять?


Предположим, у вас есть простой проект со следующим package.json:


{
  "name": "demo-project",
  "version": "1.0.0",
  "dependencies": {
    "@nivo/bar": "0.54.0",
    "@nivo/core": "0.53.0",
    "@nivo/pie": "0.54.0",
    "@nivo/stream": "0.54.0"
  }
}

Посмотрим, где используется @nivo/core:


npm list @nivo/core


Видим 4 копии @nivo/core (3 экземпляра 0.54.0 и 1 — 0.53.0). Но если мы поменяем минорную версию @nivo/core на 0.54.0, дубли устранятся.



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


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


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


Все это монотонно и требует внимательности, поэтому решил автоматизировать.


Хотелось бы узнать дубликаты без переустанавки зависимостей, и пересборки проекта, в идеале cli приложение выводящее варианты оптимизаций за 10 секунд и все существующие дубли в проекте.


Устранение дублей можно поделить на несколько подзадач, рассмотрим их по порядку.


Первая задача. Нужно смоделировать будующее дерево зависимостей бандла только по package.json, учитывая стандартный dedupe, быстро, не более чем за 100ms.


Решил использовать package-json для получения информации по пакетам и semver для сравнения различных версий.


В итоге получился npm пакет dependencies-tree-builder, шустро моделирующий дерево зависимостей бандла только по package.json.


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


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


Надо было как-то сравнивать качественно получившиейся деревья, и пришлось позаимтвовать идею энтропии, как количественное измерение беспорядка, взял сумму экземпляров дублей (из примера выше она равна 3).


Было бы здорово еще учитывать веса пакетов (в КБ), но к сожалению, пакета который бы быстро работал с весами я не нашел, а те что есть работают примерно по пол минуты на пакет, к примеру package-size. Так как работают по следующему принципу: создают проект с единственной зависимостью, устанавливают зависимоти, после измеряется суммарный вес папки. В итоге, другого критерия, как количество экземляров дублей не придумал.


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


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


Запускается просто указанием на package.json вашего проекта.


ostap ./package.json


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


ostap ./package.json -s


По итогу, в моем проекте суммарный вес бандлов снизился на 15%.


В репозитории есть раздел quick start.


Если используете route-splitting, может показаться, что какие-то бандлы увеличились в весе, но возможно изменилось распределение компонентов. То есть вместо копий зависимостей на каждой странице, единственная версия перешла в общий бандл для всех страниц, поэтому нужно оценивать суммарный вес бандлов по всем страницам.


Надеюсь, статья была полезной. И кому-то информация съэкономит время. Спасибо.


Еще раз ссылочки для удобства:


  • Пакет моделирующий деверо зависимостей бандла по package.json
    GutHub;
  • Оптимизатор зависимостей для устранения дублей в бандле
    GutHub.

Если есть интересные идеи пишите в issue на guthub'е, обсудим.


Поддержите пожалуйста проект звездочкой на GutHub.

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


  1. vintage
    29.03.2019 10:23

    А вы попробуйте не фиксировать версии зависимостей и жить сразу станет легче :-)


    1. Tarik02
      29.03.2019 10:35

      Или веселее не в самом хорошем смысле :-)


      1. vintage
        29.03.2019 10:55
        -1

        Можно каждое утро делать пробежку, а можно раз в месяц задыхаться от подъёма на пару этажей по лестнице. Вы сами решаете держать ли себя в форме и держать руку на пульсе, либо лежать и тухнуть, пока вас жёстко не сбросят с дивана.


        1. Tarik02
          29.03.2019 11:12

          Если это была аналогия, то намного полезнее будет делать регулярную пробежку, то-есть самому исправлять зависимости, а не упасть в рандомном месте...


    1. Drag13
      29.03.2019 11:05
      +4

      Они все равно зафиксированы в package-lock. А если вы призываете еще и от него отказаться то удачи ловить баги с прода которые не воспроизводятся ни на одной машине у девов :).


      1. itwillwork2 Автор
        29.03.2019 11:59

        с package-lock есть ньюанс, что если в нем будут конфилкты при мердже будет очень больно


        1. justboris
          29.03.2019 22:17

          А npm-merge-driver пробовали?


      1. vintage
        29.03.2019 13:03

        Зачем мне удача, если дев в любой момент может взять тот же артефакт сборки (например, докер-контейнер) и развернув у себя в точности воспроизвести продакшен окружение, а не надеяться на то, что он переключился на правильную ревизию, что результат сборки зависит исключительно от package-lock (что далеко не всегда так, например, при выгрузке свежих данных со внешнего сервиса при сборке), что никто не забыл закоммитить актуальный package-lock, что никто нигде не подменил и не удалил содержимое пакета (что особенно актуально для приватных репозиториев), что версия ноды и нпм/ярна совпадают с продом, и тд и тп?


        1. Drag13
          29.03.2019 13:25

          Затем что не у всех есть докер, не все умеют правильный докер (я — не умею), и наконец, затем что многим он не нужен в принципе. Проще проблему изначально не создавать, а не героически ее бороть докером.


          1. vintage
            29.03.2019 15:19

            Слово «например» вы проигнорировали сознательно или бессознательно? Артефакты сборки могут быть разные, но их не может не быть, иначе вам нечего выкладывать на сервер.


            1. Drag13
              29.03.2019 17:37

              Ок, у меня есть артефакты сборки. Они минифицированы (если мы говорим про js) или в бинарном виде (если про компилируемый код), они настроены на прод сервисы, они могут содержать закрытую информацию (даже если это не совсем правильно). И что вы таки предлагаете с этим делать?


              1. vintage
                29.03.2019 17:46

                Они минифицированы (если мы говорим про js)

                Сорсмапы или отсутствие минификации.


                или в бинарном виде (если про компилируемый код)

                Отладочные символы.


                они настроены на прод сервисы

                Не надо зашивать конфиги в бинарники.


                они могут содержать закрытую информацию

                В отдельный артефакт.


                1. Drag13
                  29.03.2019 21:30

                  Можно и так. А можно держать package-lock и будет вам счастье в 99,99% за куда меньшие усилия. А если вдруг (может у вас ентерпрайз) есть свой частный npm репозиторий, то и в 100% будет вам счастье.


                  1. vintage
                    29.03.2019 23:39

                    Сомнительное счастье жить с протухшими зависимостями.


                    1. Tom910
                      30.03.2019 10:38

                      На самом деле package-lock и не фиксированные версии это компромис, когда не все разваливается при малейшей проблеме, но и при необходимости все просто обновляется. У нас такой подход используется


        1. dagen
          29.03.2019 15:57

          А зачем козе баян? Всмысле зачем брать тот же артефакт сборки для отладки, если можно зафиксировать версии, что будет гарантировать, что артефакты будут собираться с нужными версиями.


          Без лока версий всех зависимостей никто не может гарантировать, что в артефакте использованы нужные версии.


          1. vintage
            29.03.2019 17:43

            зафиксировать версии, что будет гарантировать

            Не будет. Примеры я привёл в комментарии, на который вы отвечали.


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

            А это и не надо гарантировать. Гарантировать нужно лишь два инварианта:


            1. На прод попало именно то, что протестировано.
            2. Легко развернуть окружение идентичное проду.

            Артефакты это гарантируют. Исходные коды, хоть трижды обложитесь локами, — нет.


            1. dagen
              30.03.2019 22:18

              Примеры я привёл в комментарии, на который вы отвечали.
              Вам про козу, а вы про Ерёму. В моём комментарии про версии, до сборки артефактов, и я нигде не говорил про что-либо стороннее, запихиваемое в артефакты сборки.

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


  1. itwillwork2 Автор
    29.03.2019 11:04
    +3

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


    1. vintage
      29.03.2019 13:06
      -2

      CI для того и существует, чтобы сразу понимать, что что-то сломалось. Если это ваш джун, то почему у него есть право коммитить в мастер без тестирования и код ревью? Если это разработчик внешней зависимости, то это хороший повод задуматься о том, стоит ли зависеть от кода, разрабатываемого джунами.


      1. itwillwork2 Автор
        29.03.2019 13:27

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


        1. vintage
          29.03.2019 15:22

          Отлично, он получит быструю обратную связь и тут же поправит.


  1. inoyakaigor
    29.03.2019 13:21
    +3

    Напомниаю, что чтобы потестить не ставя этот пакет локально/глобально можно сделать так:

    npx ostap ./package.json -d


  1. Psychosynthesis
    30.03.2019 02:01

    У меня сейчас нет возможности проверить, скажите, пожалуйста, ostap добавлен в общий репозиторий npm? То есть, смогу ли я установить его обычным npm install -g?


    1. itwillwork2 Автор
      30.03.2019 02:15
      +1

      да, можно установить


      npm install -g ostap

      если что в репозитории есть quick start


      1. Psychosynthesis
        30.03.2019 02:37

        Спасибо! Классный инструмент!