Размер собранного фронтенд-приложения или npm-пакета почти никогда не растёт сам по себе. Если после обновления версии бандл стал заметно тяжелее, у этого есть конкретная причина: новая зависимость, случайно попавший в сборку модуль, изменение настроек минификации или дублирование кода. Задача диагностики — быстро перейти от факта «стало больше» к конкретному коммиту, пакету или модулю, который за это отвечает.
Главный принцип такой работы: сравнивать нужно одинаковые вещи при одинаковых условиях. Размер сборки зависит от режима (development или production), настроек минификации, source map, целевых браузеров и даже порядка плагинов. Если собрать старую версию с одними настройками, а новую — с другими, вы будете искать проблему там, где её нет.
- Что именно измерять: три разных «размера»
- Первый шаг: воспроизвести разницу в контролируемых условиях
- Инструменты анализа состава бандла
- Типичные причины роста и как их опознать
- Новая или обновлённая зависимость
- Потеря tree shaking
- Дублирование зависимостей
- Изменение настроек сборки
- Случайно попавший в сборку код
- Поиск коммита, который всё изменил
- Как отличить оправданный рост от проблемы
- Профилактика: контроль размера как часть процесса
- Частые ошибки при диагностике
- Сценарии действий
- Что делать дальше
Что именно измерять: три разных «размера»
Прежде чем что-то сравнивать, определитесь, какой размер вас интересует. Смешение этих понятий — самая частая ошибка на старте диагностики.
- Размер исходников в репозитории — вес каталога пакета без node_modules. Влияет на скорость установки из git и клонирования, но почти ничего не говорит о том, что получит пользователь.
- Размер опубликованного пакета — то, что скачивает менеджер пакетов. Включает файлы, перечисленные в поле files манифеста, и исключает то, что указано в .npmignore. Здесь важны и вес tarball, и распакованный размер, потому что от последнего зависит место на диске у потребителей.
- Размер бандла приложения — итоговые JS/CSS-файлы после сборки, которые загружает браузер. Именно этот показатель сильнее всего влияет на время загрузки страницы и обычно является предметом беспокойства.
Рост может затронуть только одну из этих метрик. Например, пакет стал тяжелее в реестре из-за добавленных типов или документации, но бандл приложения не изменился ни на килобайт — потому что эти файлы не попадают в сборку. И наоборот: новый импорт в коде раздувает бандл, оставляя вес самого пакета прежним.
Первый шаг: воспроизвести разницу в контролируемых условиях
Не начинайте с догадок. Соберите обе версии одинаково и получите точные числа.
- Зафиксируйте конфигурацию сборки. Убедитесь, что обе версии собираются одним и тем же конфигом, одной версией сборщика и в production-режиме. Если конфиг менялся вместе с кодом — это уже подозреваемый номер один.
- Соберите предыдущую версию. Проще всего взять тег или коммит до изменения и выполнить полную чистую сборку: удалить артефакты и кеш сборщика, затем собрать заново.
- Соберите текущую версию тем же способом.
- Сравните размеры файлов по отдельности, а не только суммарно. Часто общий размер почти не изменился, но код перераспределился между чанками — например, одна страница стала легче, а другая тяжелее.
- Если разница есть, зафиксируйте её: какие файлы, на сколько байт или процентов, до и после сжатия 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.
Поиск коммита, который всё изменил
Если диапазон версий большой, полезно локализовать изменение точнее, чем «между релизами». Здесь помогает бинарный поиск по истории.
- Выберите коммит, где размер был ещё нормальным, и текущий коммит, где он вырос.
- Возьмите средний коммит между ними, соберите и измерьте.
- Если размер уже вырос — граница левее, если нет — правее.
- Повторяйте, пока не останется один подозрительный коммит.
Процедура механическая, поэтому её стоит автоматизировать: скрипт, который собирает проект и выводит суммарный размер выбранных файлов, позволяет прогнать поиск за несколько минут вместо ручных сборок. Многие команды встраивают такую проверку в CI, чтобы рост ловился сразу в пулреквесте, а не после релиза.
Важно: во время поиска держите зависимости зафиксированными. Если между коммитами обновлялся lock-файл, вы можете «найти» коммит, который лишь подтянул новые пакеты, — тогда виноват на самом деле апдейт зависимостей, и это тоже полезный результат.
Как отличить оправданный рост от проблемы
Не всякий рост размера — дефект. Новая функциональность закономерно добавляет код. Вопрос в том, соразмерна ли прибавка пользе и можно ли её уменьшить без потери возможностей.
| Признак | Оправданный рост | Проблема |
|---|---|---|
| Источник прибавки | Код новой функции, которую вы запрашивали | Модули, которые не используются |
| Характер роста | Пропорционален объёму изменений | Скачок в разы при небольшой правке |
| Дубликаты | Отсутствуют | Библиотека встречается в нескольких копиях |
| Зависимости | Lock-файл стабилен | Транзитивные пакеты обновились неожиданно |
| Динамика | Единичное увеличение | Устойчивый рост от релиза к релизу |
Последняя строка заслуживает внимания: медленный, но постоянный рост — так называемое разбухание — опаснее одиночного скачка, потому что никто его не замечает. Лекарство от него одно — автоматический контроль размера в процессе разработки.
Профилактика: контроль размера как часть процесса
Разовая диагностика решает текущую проблему, но без системного контроля ситуация повторится. Минимальный набор мер:
- Замер размера в CI на каждый пулреквест с публикацией дельты в комментарии. Существуют готовые действия и боты для GitHub и других платформ, которые сравнивают размер бандла с базовой веткой.
- Бюджеты производительности — пороги в конфигурации сборщика (например, performance.budgets в angular.json или аналогичные механизмы), при превышении которых сборка падает или предупреждает.
- Правило осознанных импортов: подключать подпути вроде «библиотека/функция» вместо корневого импорта, если пакет это поддерживает, и проверять в анализаторе, что импорт действительно сузился.
- Фиксация и ревью lock-файла: изменения зависимостей должны быть видимыми в пулреквестах, а не происходить молча при установке.
- Периодический аудит состава бандла даже без видимых проблем — раз в несколько релизов, чтобы ловить накопившийся мусор.
Для публикуемых пакетов дополнительно полезно следить за содержимым архива: команда упаковки с выводом списка файлов показывает, что именно уйдёт в реестр, и помогает не публиковать тесты, исходники демо и тяжёлые ассеты, которые потребителю не нужны.
Частые ошибки при диагностике
- Сравнение разных режимов сборки. Development-сборка старой версии против production-сборки новой даёт бессмысленную разницу. Всегда выравнивайте условия.
- Игнорирование кеша. Инкрементальная сборка с тёплым кешем может дать другие результаты, чем чистая. Для замеров используйте чистую сборку.
- Оценка только несжатого размера. Решения о оптимизации стоит принимать по сжатому размеру, который реально передаётся по сети, — иначе вы будете бороться с кодом, который сжимается почти бесплатно.
- Поиск виновника в коде, когда дело в конфиге. Если вместе с кодом менялись настройки сборщика, окружение или версия Node, их нужно проверить первыми.
- Оптимизация без замера эффекта. Каждое изменение (замена импорта, вынос чанка, отказ от зависимости) нужно проверять повторным замером — интуиция здесь регулярно ошибается.
Сценарии действий
Размер вырос после обновления одной библиотеки. Посмотрите changelog и diff lock-файла, проверьте в анализаторе, что именно добавилось. Если библиотека потеряла tree shaking — попробуйте импортировать подпути или остаться на предыдущей версии до исправления.
Рост обнаружен в проде, источник неизвестен. Соберите обе версии локально в одинаковых условиях, постройте две диаграммы состава, найдите новые и выросшие модули, затем бинарным поиском найдите коммит.
Размер стабильно ползёт вверх от релиза к релизу. Внедрите бюджет размера и CI-проверку дельты, проведите разовый аудит на дубликаты и неиспользуемые зависимости.
Пакет потяжелел в реестре, но бандл приложений не изменился. Проверьте поле files и содержимое публикуемого архива: вероятно, в пакет попали лишние файлы. Это влияет на установку, но не на пользователей приложения.
Что делать дальше
Начните с простого: соберите предыдущую и текущую версию в идентичных условиях, сравните размеры по файлам и постройте две диаграммы состава. В большинстве случаев этого достаточно, чтобы увидеть виновника — новый импорт, обновившуюся зависимость или сломанный tree shaking. Если картина неочевидна, подключайте бинарный поиск по коммитам. А чтобы проблема не возвращалась, добавьте автоматическую проверку дельты размера в пулреквесты: она превращает диагностику из аварийной процедуры в рутинную строчку в описании изменений.
Материал носит информационный характер. Конкретные инструменты, настройки и пороги размеров зависят от вашего сборщика, стека и требований проекта — перед внедрением проверяйте актуальную документацию используемых инструментов.
