Диагностика изменения размера пакета между версиями: как найти причину роста

Размер собранного фронтенд-приложения или npm-пакета почти никогда не растёт сам по себе. Если после обновления версии бандл стал заметно тяжелее, у этого есть конкретная причина: новая зависимость, случайно попавший в сборку модуль, изменение настроек минификации или дублирование кода. Задача диагностики — быстро перейти от факта «стало больше» к конкретному коммиту, пакету или модулю, который за это отвечает.

Главный принцип такой работы: сравнивать нужно одинаковые вещи при одинаковых условиях. Размер сборки зависит от режима (development или production), настроек минификации, source map, целевых браузеров и даже порядка плагинов. Если собрать старую версию с одними настройками, а новую — с другими, вы будете искать проблему там, где её нет.

Что именно измерять: три разных «размера»

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

  • Размер исходников в репозитории — вес каталога пакета без node_modules. Влияет на скорость установки из git и клонирования, но почти ничего не говорит о том, что получит пользователь.
  • Размер опубликованного пакета — то, что скачивает менеджер пакетов. Включает файлы, перечисленные в поле files манифеста, и исключает то, что указано в .npmignore. Здесь важны и вес tarball, и распакованный размер, потому что от последнего зависит место на диске у потребителей.
  • Размер бандла приложения — итоговые JS/CSS-файлы после сборки, которые загружает браузер. Именно этот показатель сильнее всего влияет на время загрузки страницы и обычно является предметом беспокойства.

Рост может затронуть только одну из этих метрик. Например, пакет стал тяжелее в реестре из-за добавленных типов или документации, но бандл приложения не изменился ни на килобайт — потому что эти файлы не попадают в сборку. И наоборот: новый импорт в коде раздувает бандл, оставляя вес самого пакета прежним.

Первый шаг: воспроизвести разницу в контролируемых условиях

Не начинайте с догадок. Соберите обе версии одинаково и получите точные числа.

  1. Зафиксируйте конфигурацию сборки. Убедитесь, что обе версии собираются одним и тем же конфигом, одной версией сборщика и в production-режиме. Если конфиг менялся вместе с кодом — это уже подозреваемый номер один.
  2. Соберите предыдущую версию. Проще всего взять тег или коммит до изменения и выполнить полную чистую сборку: удалить артефакты и кеш сборщика, затем собрать заново.
  3. Соберите текущую версию тем же способом.
  4. Сравните размеры файлов по отдельности, а не только суммарно. Часто общий размер почти не изменился, но код перераспределился между чанками — например, одна страница стала легче, а другая тяжелее.
  5. Если разница есть, зафиксируйте её: какие файлы, на сколько байт или процентов, до и после сжатия gzip или brotli. Для пользователя важен именно сжатый размер, который передаётся по сети.

Полезно смотреть оба числа: несжатый размер отражает объём кода, а сжатый — реальную стоимость загрузки. Добавление большого количества похожих строк часто хорошо сжимается и почти не влияет на сеть, а добавление нового минифицированного кода с уникальными символами увеличивает оба показателя сразу.

Инструменты анализа состава бандла

Когда факт роста подтверждён, следующий вопрос — из чего он состоит. Для этого используют визуализаторы состава сборки.

  • webpack-bundle-analyzer для webpack — строит интерактивную treemap-диаграмму: каждый прямоугольник соответствует модулю, площадь пропорциональна его вкладу в размер.
  • rollup-plugin-visualizer для Rollup и Vite — аналогичная диаграмма по итоговым чанкам.
  • source-map-explorer — работает по source map и показывает, какой исходный файл сколько занимает в минифицированном результате. Подходит, когда сборщик не имеет собственного анализатора.
  • встроенные отчёты сборщиков — многие инструменты умеют выводить список чанков с размерами прямо в консоль при сборке.

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

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

Типичные причины роста и как их опознать

Новая или обновлённая зависимость

Самая частая причина. Библиотека в новой мажорной версии может тянуть дополнительные зависимости, включать полифилы или отказаться от tree shaking. Признак: в анализаторе появился крупный модуль, которого раньше не было, либо существующий модуль заметно увеличился.

Проверка: посмотрите diff файла блокировки зависимостей (package-lock.json, yarn.lock, pnpm-lock.yaml) между версиями. Изменения там напрямую объясняют большинство скачков размера. Обратите внимание не только на прямые зависимости, но и на транзитивные — они обновляются незаметно.

Потеря tree shaking

Tree shaking — исключение неиспользуемого кода из сборки. Он ломается, если библиотека переключилась с ESM на CommonJS, если в package.json пропал флаг sideEffects, если импортируется весь модуль вместо нужной функции или если сборщик видит побочные эффекты в модуле. Признак: в бандле оказались функции, которые вы нигде не вызываете.

Быстрая проверка: найдите в собранном файле имя функции или строки, которых в вашем коде нет. Если они там — неиспользуемый код попал в сборку.

Дублирование зависимостей

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

В webpack для этого есть встроенная проверка дубликатов через статистику сборки, в pnpm — команда вывода дерева зависимостей. Решение обычно сводится к выравниванию версий через overrides/resolutions в манифесте.

Изменение настроек сборки

Обновление сборщика или плагинов может изменить режим минификации, целевые браузеры, обработку CSS или формат модулей. Например, расширение списка поддерживаемых браузеров заставляет транспилятор генерировать больше вспомогательного кода. Признак: рост распределён равномерно по всему бандлу, без явного «виновника» среди модулей.

Случайно попавший в сборку код

Импорт большой библиотеки ради одной маленькой функции, забытый console-модуль, тестовые данные, импортированные в продакшен-код, — всё это классика. Признак: резкий скачок на конкретную величину, совпадающий с появлением нового импорта в diff.

Поиск коммита, который всё изменил

Если диапазон версий большой, полезно локализовать изменение точнее, чем «между релизами». Здесь помогает бинарный поиск по истории.

  1. Выберите коммит, где размер был ещё нормальным, и текущий коммит, где он вырос.
  2. Возьмите средний коммит между ними, соберите и измерьте.
  3. Если размер уже вырос — граница левее, если нет — правее.
  4. Повторяйте, пока не останется один подозрительный коммит.

Процедура механическая, поэтому её стоит автоматизировать: скрипт, который собирает проект и выводит суммарный размер выбранных файлов, позволяет прогнать поиск за несколько минут вместо ручных сборок. Многие команды встраивают такую проверку в CI, чтобы рост ловился сразу в пулреквесте, а не после релиза.

Важно: во время поиска держите зависимости зафиксированными. Если между коммитами обновлялся lock-файл, вы можете «найти» коммит, который лишь подтянул новые пакеты, — тогда виноват на самом деле апдейт зависимостей, и это тоже полезный результат.

Как отличить оправданный рост от проблемы

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

Признак Оправданный рост Проблема
Источник прибавки Код новой функции, которую вы запрашивали Модули, которые не используются
Характер роста Пропорционален объёму изменений Скачок в разы при небольшой правке
Дубликаты Отсутствуют Библиотека встречается в нескольких копиях
Зависимости Lock-файл стабилен Транзитивные пакеты обновились неожиданно
Динамика Единичное увеличение Устойчивый рост от релиза к релизу

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

Профилактика: контроль размера как часть процесса

Разовая диагностика решает текущую проблему, но без системного контроля ситуация повторится. Минимальный набор мер:

  • Замер размера в CI на каждый пулреквест с публикацией дельты в комментарии. Существуют готовые действия и боты для GitHub и других платформ, которые сравнивают размер бандла с базовой веткой.
  • Бюджеты производительности — пороги в конфигурации сборщика (например, performance.budgets в angular.json или аналогичные механизмы), при превышении которых сборка падает или предупреждает.
  • Правило осознанных импортов: подключать подпути вроде «библиотека/функция» вместо корневого импорта, если пакет это поддерживает, и проверять в анализаторе, что импорт действительно сузился.
  • Фиксация и ревью lock-файла: изменения зависимостей должны быть видимыми в пулреквестах, а не происходить молча при установке.
  • Периодический аудит состава бандла даже без видимых проблем — раз в несколько релизов, чтобы ловить накопившийся мусор.

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

Частые ошибки при диагностике

  • Сравнение разных режимов сборки. Development-сборка старой версии против production-сборки новой даёт бессмысленную разницу. Всегда выравнивайте условия.
  • Игнорирование кеша. Инкрементальная сборка с тёплым кешем может дать другие результаты, чем чистая. Для замеров используйте чистую сборку.
  • Оценка только несжатого размера. Решения о оптимизации стоит принимать по сжатому размеру, который реально передаётся по сети, — иначе вы будете бороться с кодом, который сжимается почти бесплатно.
  • Поиск виновника в коде, когда дело в конфиге. Если вместе с кодом менялись настройки сборщика, окружение или версия Node, их нужно проверить первыми.
  • Оптимизация без замера эффекта. Каждое изменение (замена импорта, вынос чанка, отказ от зависимости) нужно проверять повторным замером — интуиция здесь регулярно ошибается.

Сценарии действий

Размер вырос после обновления одной библиотеки. Посмотрите changelog и diff lock-файла, проверьте в анализаторе, что именно добавилось. Если библиотека потеряла tree shaking — попробуйте импортировать подпути или остаться на предыдущей версии до исправления.

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

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

Пакет потяжелел в реестре, но бандл приложений не изменился. Проверьте поле files и содержимое публикуемого архива: вероятно, в пакет попали лишние файлы. Это влияет на установку, но не на пользователей приложения.

Что делать дальше

Начните с простого: соберите предыдущую и текущую версию в идентичных условиях, сравните размеры по файлам и постройте две диаграммы состава. В большинстве случаев этого достаточно, чтобы увидеть виновника — новый импорт, обновившуюся зависимость или сломанный tree shaking. Если картина неочевидна, подключайте бинарный поиск по коммитам. А чтобы проблема не возвращалась, добавьте автоматическую проверку дельты размера в пулреквесты: она превращает диагностику из аварийной процедуры в рутинную строчку в описании изменений.

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

PEFile.ru