Проблема сравнения frontend-сборок обычно начинается с красивой цифры: новый инструмент собрал проект быстрее на локальной машине. Но у baseline мог быть холодный cache, другой lockfile, другой набор entry-файлов или неполный output. Цена неверного сравнения — перенос нескольких дней работы ради ускорения, которое исчезает при первом чистом запуске. Сначала фиксируем вход, потом измеряем секунды.
У сборки есть как минимум пять независимых частей: исходный граф, конфигурация, окружение, состояние cache и состав артефакта. Изменение любой части меняет смысл результата. Поэтому один замер duration не отвечает на вопрос «какой сборщик лучше». Он отвечает только на вопрос «сколько занял этот запуск при таких условиях». Таблица условий должна быть частью отчёта рядом с числом.
Сравниваем не инструменты, а одинаковую работу
Baseline и candidate должны получить один commit, одинаковый lockfile, одинаковые entry и одинаковую команду выхода. Если одна сборка делает minify, а другая — development output, секунды нельзя складывать в общий вывод. Точно так же размер файла сравним только при одинаковом составе chunks, compression и правилах публикации. Иначе оптимизация одного слоя будет выглядеть как преимущество всего pipeline.
В webpack contenthash и deterministic module ids помогают сохранить стабильные имена и локализовать изменение, но это не делает два разных toolchain эквивалентными. В esbuild metafile можно посмотреть, какие input вошли в output и сколько байт они заняли. Оба документа подсказывают важный порядок: записать конфигурацию и состав результата, а не оставлять только итоговую цифру.
| Слой | Зафиксировать | Пример различия | Почему это важно |
|---|---|---|---|
| Input | commit, lockfile, entry | feature branch вместо tag | меняется dependency graph |
| Config | mode, flags, plugins | minify включён только у candidate | числа относятся к разным работам |
| Environment | Node, OS, CPU | разные версии Node | разный runtime и cache |
| Cache | cold/warm, directory, key | candidate повторный | ускорение может быть cache effect |
| Output | chunks, source maps, compression | один bundle не учтён | размер и delivery несопоставимы |
Учебное сравнение двух запусков
Функция ниже получает два уже записанных запуска. Она сначала проверяет inputFingerprint, затем считает разницу времени и размера. Значения учебного набора не являются результатом измерения проекта; они показывают формат карточки и ожидаемый результат. Если fingerprint различается, функция возвращает несопоставимость и не вычисляет победителя.
import { compareBuildRuns } from './upgrade-2027-04.mjs';
const baseline = { inputFingerprint: 'src-42', durationMs: 420, outputBytes: 180000 };
const candidate = { inputFingerprint: 'src-42', durationMs: 380, outputBytes: 176000 };
const changedInput = { inputFingerprint: 'src-43', durationMs: 350, outputBytes: 174000 };
console.log(compareBuildRuns({ baseline, candidate }));
console.log(compareBuildRuns({ baseline, candidate: changedInput }));
// comparable: true, deltaMs: -40, deltaBytes: -4000
// comparable: false, reason: 'входы сборки различаются'Первый результат говорит только о заданном учебном наборе: candidate быстрее на 40 мс и меньше на 4000 байт при одинаковом fingerprint. Второй запуск не превращается в преимущество, даже если его цифры лучше, потому что input другой. В реальном отчёте добавьте число повторов, медиану или p95, cold/warm state и полный список команд.
Секунды требуют распределения, а не одного числа
Один запуск может попасть на сборку cache, фоновое обновление пакетов или шум CPU. Для локального сравнения отделяйте холодный и тёплый запуск, а в CI фиксируйте runner. Не смешивайте время установки зависимостей с временем bundling, если цель — сравнить именно сборку. Если пользовательский эффект — время до первого экрана, добавьте отдельный браузерный замер; build duration является только одной частью пути.
Размер также нужно разложить. Общий bundle вырос — это симптом, но причина может быть в новой библиотеке, переставшем работать tree-shaking, source map или duplicated dependency. Метafile и список chunks дают материал для следующего шага. Без него команда спорит о сборщике, хотя изменение находится в одном input-модуле.
Действия по порядку
- Зафиксировать commit, lockfile, entry points, mode, flags, Node/OS и runner.
- Очистить или явно сохранить cache state; отдельно записать cold и warm серии.
- Запустить baseline и candidate на одинаковом наборе и сохранить raw output, duration и exit code.
- Сравнить fingerprint, распределение времени, chunks и input bytes; несовпоставимый запуск исключить.
- Сформулировать вывод с единицами и границей: «на этом наборе и runner»; затем проверить пользовательский путь отдельным замером.
Ограничения и следующий шаг
Учебная функция не запускает сборщики и не устраняет шум измерений. Fingerprint должен включать все входы, которые влияют на output; короткий hash без методики легко создаёт ложное совпадение. Разные плагины могут менять дерево зависимостей, а одинаковый размер не означает одинаковое выполнение в браузере. Нельзя обещать эффект для пользователей по двум локальным числам.
Следующий шаг — сделать небольшой JSON-отчёт с условиями, серией запусков и ссылкой на metafile. Проверьте его на одном изменении, которое добавляет известный модуль. Готовность сравнения определяется тем, что другой инженер может повторить вход, увидеть различие и понять, к какому слою относится изменение.
Проверяемые источники
- webpack 5 Guide: Caching — версия и дата: webpack 5 guide, checked 31 July 2026. Применение: Contenthash, deterministic module ids и условия сравнения взяты из руководства webpack 5. Граница: Документация не даёт результатов конкретного проекта и не заменяет серию измерений на одном runner.
- esbuild API: Metafile — версия и дата: esbuild API documentation, checked 31 July 2026. Применение: Metafile используется как JSON-источник inputs, outputs и bytesInOutput для разбора артефакта. Граница: Формат метафайла описывает состав сборки, но не пользовательскую скорость загрузки.
- webpack 5 Configuration: cache — версия и дата: webpack 5 configuration reference, checked 31 July 2026. Применение: Режимы memory/filesystem cache учитываются как отдельная часть условий запуска. Граница: Страница не определяет cache state конкретного CI и не сравнивает инструменты между собой.