Короткого ответа нет
Вопрос «важен ли размер бандла» сформулирован как запрос на «да» или «нет», но честного короткого ответа у него давно нет. Не потому, что мы уходим от ответственности, а потому что доставка кода в браузер перестала быть одной операцией.
Когда-то модель была честной и простой: страница грузила один скрипт, тот блокировал рендер, целиком парсился и целиком исполнялся до того, как человек что-нибудь увидит. В этой модели байты почти линейно превращались в ожидание: больше кода — дольше ехать по сети, дольше парсить, дольше исполнять. Вес был жёстко привязан к тому, что чувствует человек, и мог с чистой совестью называться главной метрикой.
Потом модель разобрали. Код режется на куски и отдаётся по требованию, запросы едут параллельно, сжатие на лету меняет и вес, и его смысл, кэш делает один и тот же файл бесплатным для вернувшегося и полновесным для новичка, разметка отдаётся потоком, а часть работы вообще уехала на сервер. «Размер бандла» из одного числа превратился в форму кривой: что приезжает, когда приезжает и что из этого стоит между человеком и первым действием.
Обычно за абстрактным вопросом стоит бытовой спор в ревью: тянуть ли зависимость, спорить ли из-за лишнего веса или махнуть рукой. Ответ «зависит» в такой ситуации бесполезен — решение нужно сегодня.
Наш короткий ответ: важен — но как ограничение, а не как цель. Дальше — тупик, в который почти каждый успевает упереться до этого ответа, и условие, при котором мы неправы.
Тупик: война с общим весом
Первое, что пробуют, когда приложение ругают за скорость, — уменьшить общий вес сборки. Рефлекс понятен: тормозит — значит, кода много — значит, код надо ужать. К тому же вес — единственное число про производительность, которое видно без приборов. Дальше сценарий развивается примерно одинаково.
Сначала гигиена: минификация пожёстче, выкидывание «тяжёлой» зависимости, добивание tree-shaking. Цифра в отчёте сборки подрастает вниз. Следом порядок: бюджет в CI — сборка выше порога, билд красный, регрессии заперты. Дальше визуализатор сборки: открываем, находим самый большой прямоугольник, выпиливаем его. Цифра падает уже заметно.
Ожидание простое: цифра вниз — жалобы вниз.
Стена выглядит так: цифра вниз, а жалобы на месте. Визуально — прогресс, в отчёте — прогресс, по ощущениям людей — ничего. И самое неприятное — непонятно почему: каждый шаг ведь «правильный», из учебника.
Разберём по механизмам, потому что дело не в плохом исполнении, а в самой постановке.
Ожидание пользователя — не число, а цепочка: соединение, первый байт, разметка, критические куски, парс, гидрация, первый осмысленный отклик. Общий вес — один член этой суммы. Полировать его имеет смысл, только если именно он доминирует, а общее число принципиально не сообщает, какой член доминирует у вас. Если узкое место не в нём, вы прилежно шлифуете не ту деталь — с чувством прогресса и без результата.
Визуализатор усугубляет: он сортирует куски по весу, а не по посещаемости. Самый большой прямоугольник может жить на роуте, куда почти никто не заходит, а тонкая полоска на входном экране держит всех. Это две разные сортировки, и второй в визуализаторе нет.
Бюджет на общий вес ломается с обеих сторон. Он молчит, когда тяжелеет входной роут: сумма не сдвинулась, а путь, которым ходят все, стал длиннее. И он же орёт, когда вы добавили предзагрузку или перенесли что-то в кэшируемый слой: сумма выросла, ожидание упало — гейт наказывает за улучшение. Метрика, которая наказывает за улучшение и пропускает регрессию, оторвана от цели, что бы она ни измеряла.
Отдельно про замену библиотеки самописным куском покороче: это обмен измеренных байтов на неизмеренные баги. Если байты не были тем, что чувствовал человек, приложения вы не ускорили — вы взяли на себя сопровождение чужой работы.
Справедливости ради: гигиена из первого шага — дело правильное, минифицировать и выкидывать мёртвый код надо всегда. Просто гигиена — не лечение. Тупик не в том, что делали плохо, а в том, что мерили не то.
Почему байт не равен байту
У байта кода по меньшей мере три цены. Передача — сеть. Парс и компиляция — процессор; эта цена растёт с числом байтов. Исполнение — растёт не с байтами, а с работой, и происходит там, где живёт отзывчивость: на главном потоке. Два куска кода одного веса могут стоить по-разному: один — это только доставка и парс, другой — доставка, парс и исполнение между человеком и первым откликом. Складывать их в одно число — всё равно что складывать счёт за электричество со штрафом за просрочку: сумма что-то сообщает, но не то, что вам нужно.
Сжатие и кэш усложняют картину. То, что измерено в сборке, и то, что реально едет по проводам, — разные величины: сжатие съедает часть веса, и сколько именно — зависит от характера кода. Дальше кэш: для вернувшегося пользователя передача почти ничего не стоит, но парс и исполнение он оплачивает целиком, при каждом визите. Метрика веса не знает, кто перед ней — новичок или постоянный посетитель, а от этой пропорции зависит, какой рычаг вообще имеет смысл двигать.
Есть и скрытые платежи, которых в весе не видно. Код, который ни разу не исполнился, всё равно проходит парс и компиляцию: «догрузили лениво, но распарсили сразу» — не бесплатно. Гидрация заставляет платить за одно дерево дважды: сначала нарисовать, потом оживить, и вторая оплата идёт главным потоком — ровно в момент, когда человек уже тянется что-то нажать.
Порядок и момент оплаты в итоге важнее суммы. Предзагрузка, инлайны, ленивые куски — каждое такое решение меняет не вес, а расписание платежей: кто, что и когда оплачивает. Общая цифра не может арбитрировать эти сделки: она видит сумму, человек живёт в моменте оплаты.
Потоковый рендер сдвинул финиш ещё дальше: человек начинает читать раньше, чем приехал весь код, и порог проходит не «скачано», а «оживлено и отвечает». После этого бой за вес файла — бой за этап, который человек мог вообще не заметить.
Это тот же спор, что и вокруг серверных компонентов, о котором мы писали отдельно: перенос работы с клиента на сервер меняет распределение стоимости, но не отменяет её. Там, где рендер уехал на сервер, вопрос «что и когда исполняется на клиенте» никуда не девается.
Что замерять вместо общего числа
Метод, в который вы подставляете свои числа.
Возьмите роут, который пользователи открывают первым, — или тот, что приносит деньги; обычно это одно и то же. Откройте его на устройстве и в сети, похожих на целевые: не на рабочем ноутбуке, а на том, на чём сидит аудитория. Не знаете, на чём сидит аудитория, — берите худшее из разумных предположений: оптимистичный замер обманывает первым делом того, кто его сделал.
Прогоните дважды: с холодным кэшем и с тёплым. Разложите время от первого байта до первого осмысленного взаимодействия на две части — ожидание сети и работу главного потока: парс, компиляция, гидрация, обработчики.
Дальше арифметика:
- доминирует сеть при холодном кэше — байты на критическом пути и правда важны: режьте их и выстраивайте порядок загрузки;
- доминирует главный поток — вес сборки почти ни при чём: рычаг — делать меньше работы при старте, разносить исполнение во времени, уводить его с главного потока;
- тёплый прогон почти не отличается от холодного — сеть и не была вашей проблемой, и вся борьба за байты шла не за то.
Тёплый прогон вообще недооценён: он показывает, сколько стоимости вы заставляете повторно оплачивать тех, кто уже всё скачал. Если разница между визитами мала, вопрос не в байтах, а в том, что вы делаете на главном потоке при каждом заходе.
В CI имеет смысл гейтить два числа на каждый входной роут: холодный путь до первого взаимодействия и работу главного потока до первого отклика. Общий вес оставьте как санитарную норму: он ловит грубые ошибки вроде случайно втащенной зависимости, но не должен быть местом, где заканчивается разговор о производительности.
Позиция редакции
Наша позиция: размер бандла — санитарная норма, а не цель. Держите его как бюджет, чтобы регрессия в байтах на критическом пути не проезжала молча: рост веса почти всегда тянет за собой рост передачи и парса, и гейт здесь отрабатывает честно. Но главной метрикой мы считаем время до первого осмысленного взаимодействия на целевом устройстве, по конкретным роутам. Число, которое показывают на ревью и празднуют в отчётах, должно быть тем, что чувствует человек, — иначе вы оптимизируете слайд, а не продукт.
Оговорка для тех, кому пока нечем мерить: если доступ к целевым устройствам и телеметрии есть не везде, дешёвый прокси — общий вес с бюджетом — лучше, чем ничего. Но это костыль, и держать его стоит ровно до появления настоящих замеров.
Условие, при котором мы неправы: у вас преимущественно холодный трафик — первые визиты, кэш почти не срабатывает, сеть слабая. Тогда передача байтов по критическому пути становится доминирующим членом суммы, и общий вес снова оказывается хорошим приближением главной метрики. В этом мире спорить о каждом байте — не педантизм, а прямая работа над скоростью. Проверка — замер из предыдущего раздела: если сеть съедает ожидание, вы наш контрпример, и правы вы, а не мы.