Как ускорить сборку проекта: ревью кода и рефакторинг
Долгая сборка редко лечится одной настройкой. Разбираем, что искать на ревью JavaScript/TypeScript-проекта, как измерять эффект и когда рефакторинг действительно сокращает время, а когда лишь усложняет код.

Если production-сборка JavaScript/TypeScript-проекта на Vite занимает 8 минут, разработчики теряют час за рабочий день на пяти запусках. Но ускорять нужно не «конфиг вообще», а конкретные этапы: чтение модулей, преобразование TypeScript, работу плагинов, минификацию и генерацию карт исходников. Ниже — схема ревью для Vite 6.x и Node.js 20 LTS, которую можно перенести на другой сборщик после замеров.
Сначала отделите проблему сборки от соседних задач
Команда часто называет сборкой весь CI-этап. В него могут входить установка зависимостей, линтинг, проверка типов, тесты, компиляция приложения, загрузка артефактов и Docker-сборка. Рефакторинг исходников повлияет только на часть этого времени.
Зафиксируйте один сценарий: чистый checkout или уже прогретый кэш, версия Node.js, менеджер пакетов, число ядер и объём памяти. Запишите продолжительность каждого этапа. Для базового замера достаточно команды, которая есть в Unix-подобных системах:
time npm ci
time npm run build
Пробел перед второй командой в примере не нужен при копировании. Для каждого варианта сделайте несколько запусков и сохраняйте не только среднее время, но и разброс. Первый запуск после очистки кэшей нельзя напрямую сравнивать с десятым прогретым запуском.
Технический термин: граф зависимостей — это набор модулей и связей между ними, который сборщик проходит от входных файлов до конечных бандлов. Чем больше узлов и преобразований в графе, тем больше работы получает сборка.
Что искать на ревью
1. Лишний объём графа зависимостей
Начните с импортов. Наиболее дорогой случай — подключить крупную библиотеку ради одной функции или импортировать весь пакет вместо отдельного модуля. Но заменять импорт вслепую нельзя: некоторые пакеты используют побочные эффекты, а их модульная структура не позволяет безопасно выкинуть неиспользуемый код.
Формулировка замечания должна быть проверяемой: «Покажите размер бандла до и после замены импорта и убедитесь, что побочные эффекты библиотеки сохранены». Не пишите «этот пакет тяжёлый», пока не видите его вклад в итоговый артефакт.
2. Работа, которая выполняется на каждом запуске
Плагин может читать каталог, запускать генератор, разбирать документацию или преобразовывать изображения при каждом старте. На ревью ищите операции с файловой системой и внешними процессами внутри обработчиков, которые вызываются для множества модулей.
Если результат зависит только от входного файла, его можно кэшировать на уровне инструмента или вынести в отдельный подготовительный шаг. Если результат меняется редко, генерация при каждом локальном запуске — плохой кандидат для основного конвейера.
3. Смешение проверки типов и транспиляции
Для TypeScript-проекта сборщик часто преобразует код, а отдельный процесс проверяет типы. Это разные задачи. Попытка заставить production-сборку каждый раз выполнять полный анализ типов может увеличить время без изменения JavaScript-артефакта.
На ревью проверьте, где запускается проверка типов: в локальном pre-commit, отдельном CI-джобе или внутри сборщика. Убирать её полностью нельзя. Цель — не потерять гарантию, а не выполнять одну и ту же работу дважды.
4. Слишком широкие входные границы
Проблема возникает, когда в обработку случайно попадают тесты, истории компонентов, примеры, локализации в исходном виде или сгенерированные файлы. Проверьте glob-шаблоны, каталоги входных файлов и список исключений. Особенно подозрительны шаблоны, начинающиеся с корня репозитория.
Замечание должно указывать на границу: «Почему сборщик читает каталог с тестовыми фикстурами, если он не попадает в production-артефакт?» Это полезнее, чем требование «сузить include» без проверки результата.
Рефакторинг, который обычно даёт измеримый эффект
| Подход | Что меняется | Цена | Когда выбирать |
|---|---|---|---|
| Удаление лишнего импорта | Сокращается граф модулей и итоговый бандл | Нужно проверить API и побочные эффекты | Один пакет заметно влияет на размер артефакта |
| Динамический импорт | Часть кода уходит из начального чанка | Появляются отдельные файлы и задержка при первом открытии функции | Раздел приложения нужен не всем пользователям |
| Вынос генерации из сборки | Повторяющаяся подготовительная работа выполняется отдельно | Нужен контроль актуальности сгенерированных файлов | Генератор меняется реже, чем исходный код |
| Разделение CI-этапов | Типы, тесты и бандлинг не дублируют друг друга | Сложнее конфигурация pipeline | Основное время уходит не на сам бандлер |
Динамический импорт: пример с реальной сменой поведения
Статический импорт загружает модуль в граф начального входа. Динамический оставляет загрузку до момента вызова функции. Для Vite 6.x и обычного JavaScript/TypeScript синтаксис выглядит так:
async function openReport(reportId) {
const { renderReport } = await import('./reports/renderReport.js');
return renderReport(reportId);
}
openReport('monthly');
Это не бесплатное ускорение. Пользователь получит дополнительный сетевой запрос, если файл не попал в нужный кэш. Поэтому измеряйте не только время сборки, но и размер начального чанка, число запросов и время открытия экрана с отчётом.
Удаление тяжёлого импорта
Не заменяйте библиотеку по одному названию. Сначала зафиксируйте её вклад через анализ итоговых файлов, затем проверьте эквивалентность результата. Иногда небольшая функция из пакета уже оптимально вырезается сборщиком, и ручной рефакторинг ничего не даёт.
Хорошее ревью-комментарий выглядит так: «Нужен замер размера production-артефакта и времени сборки до и после. Если разница меньше погрешности, оставляем более читаемый вариант». Так команда не превращает оптимизацию в соревнование по минимальному числу импортов.
Что измерять до и после
Минимальный отчёт должен содержать commit, команду, окружение и время отдельных этапов. Для сборки приложения добавьте размер каталога с артефактами и размеры крупнейших файлов. Если используете динамические чанки, отдельно посмотрите начальный набор файлов и сценарий загрузки отложенного экрана.
- Время установки зависимостей и время самой команды сборки.
- Число файлов, которые обрабатывает сборщик, если инструмент выводит этот показатель.
- Размер JavaScript и CSS после минификации.
- Время проверки типов, линтинга и тестов отдельно от бандлинга.
- Стабильность результата на чистом и прогретом запуске.
Оптимизация сборки начинается не с настройки, а с вопроса: какая именно работа повторяется зря и кто может доказать это замером.
Где рефакторинг ломается
Кэш скрывает настоящий результат
После первого быстрого запуска кажется, что изменение сработало. Но ускорился не код, а кэш файловой системы или менеджера пакетов. Сравнивайте одинаковые условия и явно отмечайте, какие кэши сохранены.
Динамический импорт ухудшает пользовательский сценарий
Разделение бандла сокращает стартовый файл, но может добавить задержку в момент открытия функции. Если экран нужен почти каждому пользователю сразу, такой рефакторинг переносит проблему, а не решает её.
Генерация вынесена, но артефакт устарел
Отдельный подготовительный шаг должен иметь понятный триггер и проверку результата. Иначе CI соберёт приложение из старой схемы, локализаций или сгенерированных типов. Скорость здесь оплачивается риском неправильной сборки.
Оптимизирован локальный запуск, но не CI
Локальная машина и runner могут иметь разные CPU, память, дисковую систему и кэш. Изменение считается полезным только после замера в том окружении, где время действительно мешает команде.
Как проводить ревью по шагам
- Зафиксируйте базовую линию. Сохраните команду, версии Node.js и npm, режим запуска и длительность каждого этапа. Проверка: другой разработчик может повторить замер по записи.
- Разделите этапы. Уберите из обсуждения установки, тесты и Docker, если вы оптимизируете только Vite-сборку. Проверка: понятно, какой процесс должен стать быстрее.
- Найдите крупнейшие источники работы. Изучите импорты, плагины, генераторы и широкие glob-шаблоны. Проверка: для каждого кандидата есть причина и способ измерить эффект.
- Сделайте одно изменение. Не совмещайте замену библиотеки, динамический импорт и перестройку CI в одном коммите. Проверка: результат можно связать с конкретным изменением.
- Проверьте функциональность. Запустите тесты, проверку типов и сценарии загрузки отложенных частей. Проверка: production-артефакт собирается, а приложение открывает все затронутые экраны.
- Сравните чистый и прогретый запуск. Запишите время и размеры после нескольких повторов. Проверка: улучшение не исчезает при смене режима кэша.
Что делать дальше
Соберите один отчёт по последнему медленному CI-запуску и выберите только один самый дорогой этап. Затем добавьте в pipeline сохранение времени сборки и размера артефактов, чтобы следующее ревью опиралось на историю, а не на ощущения. Для Vite отдельно изучите анализ production-бандла и поведение динамических импортов в официальной документации; инвалидация кэшей и оптимизация Docker-слоёв — отдельные темы.
Редактор изображений: Убрать лишнее, заменить фон, добавить объект, расширить кадр — на вашем изображении.
Спросить за 20 ₽Источники: Vite Documentation: Guide, npm Documentation: Scripts
Частые вопросы
С какого времени сборки нужно начинать оптимизацию?
Нужно ли сразу переходить на динамические импорты?
Почему после рефакторинга локально быстрее, а в CI разницы нет?
Можно ли отключить проверку типов ради быстрой сборки?
Что делать, если после динамического импорта появился 404?
Подойдут ли эти приёмы для другой версии Vite или другого сборщика?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.