Как уменьшить размер бандла раз и навсегда: приемы, метрики, мониторинг

Зачем нужна эта статья?

Всем привет, меня зовут Семён, в своей работе, я, в основном, занимаюсь такими вещами, как улучшение перформанса фронтенда, поддержкой и развитием инфраструктуры фронта, Developer Experience. Последние полгода мой фокус сильно сместился в сторону оптимизации размера бандла нашего веб-приложения.

Когда я начинал работать над уменьшением размера клиента, мне сильно не хватало подробного гайда или руководства как вообще подступиться к размеру бандла существующего большого приложения: с чего начать, какие инструменты использовать, на что обращать внимание, как и какие метрики снимать. Все статьи на тему, которые я находил, были либо слишком узконаправленные, либо в них всё сводилось к тому, что «делайте хорошо, а плохо не делайте», «избавляйтесь от лишних зависимостей», оптимизируйте импорты lodash и используйте ленивые модули, конечно же, со ссылкой на документацию по React.lazy. Но что делать, если ты новый инженер в команде, а продукт зрелый и со сложной кодовой базой?

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

А как мы вообще оказались там, где оказались? Роковые ошибки, которые всё раздули

Первый коммит в главной ветке приложения был создан в 2015 году. То есть, репозиторию примерно 8–9 лет, он пережил несколько смен репозиториев и девопс-платформ. 

Далеко не все авторы кода, который до сих пор используется в продакшене, работают с нами. Кроме этого, кодовая база пережила несколько подъемов версий ключевых зависимостей (react, mobx), typescript. К чему я всё это? За 9 лет постоянно что-то происходило — как в компании (помним закон Конвея), так и в целом в мире Frontend — фреймворки приходили и уходили (а помните, был такой Backbone?), менялись внутри, например, в React перешли от классовых компонентов на функциональные, методы жизненного цикла поменялись на хуки и сейчас практически не используются. Typescript стал новой нормой.

Что могло пойти не так?

Что могло пойти не так?

Разработчики не всегда шли в ногу со временем, хоть и старались. При написании кода Древние Разработчики Компании заложили много простых и сложных внутренних API для упрощения написания прикладного кода, чтобы разработчик легко и просто закрывал свои прикладные задачи и решал задачи бизнеса, не задумываясь о корном коде. Хорошо ли это? Да, конечно — фичи едут быстро и бизнес доволен, но, на мой взгляд, такой подход хорошо работает, только если:

  1. Надежный корный код не воспринимается как данность, актуализируется и развивается в ногу со временем;

  2. Коллеги помнят как этот код устроен, умеют с ним работать;

  3. Код можно безопасно редактировать: он покрыт тестами и обвешан метриками, вы уверены, что поймете, когда что-то идёт не так.

Где-то тут что-то и пошло не так. В ходе поднятия версии React с 15.6 до 17 — что очень непросто (да-да, хуки появились в 16.8, а сейчас актуальной версией считается 18) у разработчиков замылился глаз (что немудрено) и мы не уделили должное внимание изменению, которое ломало систему асинхронных компонентов. Незаметно для нас бандл вырос в пару раз. Мы поняли, что что-то пошло не так, лишь когда пользователи стали жаловаться на медленную загрузку.

Как понять, что что-то идёт не так: инструменты анализа сборки

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

Lighthouse report

Пойдём по возрастанию усилий. Самый простой способ сделать оценку перформанса всего приложения — построить отчет Lighthouse. Это инструмент, разработанный в Google, он оценивает ваш сайт или приложение по 100-бальным шкалам: Performance, Accessibility, Best Practices, SEO, наличие или отсутствие PWA. В контексте размера бандла, наиболее интересная для нас шкала — Performance.

На самом деле, этот балл не отображает исключительно качество сборки бандла и её размер, но я всё-таки решил рассказать об этой метрике по нескольким причинам: во-первых, она снимается из DevTools в два клика и может помочь избежать преждевременной оптимизации; во‑вторых, эта метрика может помочь оценить влияние размера бандла на опыт конечного пользователя, легко понять как изменения в размере бандла или разбиении на чанки влияют на скорость загрузки с помощью сравнения по выборкам пользователей (А/Б-тесты, привет) или между релизами; в-третьих, сбор метрик можно вынести в CI и оценивать внесенные изменения до влития, обеспечивая безопасность перформанса и накапливая историю метрик.

Очки перформанса складываются из следующих метрик:

  • First Contentful Paint (FCP), секунды — время отрисовки первой части содержимого DOM, первого текста или картинки, не-белого канваса. Если минимально необходимый чанк большой, долго грузятся необходимые шрифты — время растёт.

    Картинка из документации Google: First Contentful Paint

    Картинка из документации Google: First Contentful Paint

  • Largest Contentful Paint (LCP), секунды — время отрисовки наибольшей значимой части контента внутри вьюпорта, текста или изображения. Эта метрика тоже зависит от размера чанков.

    Картинка из документации Google: Largest Contentful Paint

    Картинка из документации Google: Largest Contentful Paint

  • Time to Interactive (TTI), сек — общее время, после которого страница становится интерактивной: достигнут FCP, на видимые элементы повешены хендлеры, а время ответа на операции пользователя — менее 50 мс.

  • Total Blocking Time (TBT), мс — сумма времени блокирующих операций (занимающими более 50 мс) между FCP и TTI. Для подсчета берется время всех операций, занявших более 50 мс, вычитаем из каждой «нормальные» 50 мс и складываем. Эта метрика зависит от размера чанка в меньшей степени, но на нее может влиять неиспользуемый код, удаление которого уменьшит и размер бандла.

    Картинка из документации Google: Total Blocking Time

    Картинка из документации Google: Total Blocking Time

  • Cumulative Layout Shift (CLS) — метрика, которая показывает насколько «неожиданно смещаются» элементы верстки за все время жизни страницы. Эта метрика будет расти, если, например, асинхронный компонент загрузился поздно и своим появлением значительно сместил верстку. Метрика косвенно показывает качество разбиения на чанки, стабильность асинхронных компонентов.

    Картинка из документации Google: Cumulative Layout Shift.Мерзкое дёргание, правда?

    Картинка из документации Google: Cumulative Layout Shift.
    Мерзкое дёргание, правда?

  • Speed Index (SI), сек — метрика, которая показывает как быстро отрисовываются визуальные элементы страницы.

Более подробно можно прочитать в официальной документации от Google. Мы используем метрики web vitals, чтобы лучше понимать как работа по оптимизации размера бандла влияет на опыт наших пользователей. 

Webpack Bundle Analyzer

Webpack Bundle Analyzer — широко известный инструмент для анализа бандла приложения. С его помощью можно сгенерировать интерактивную карту бандла веб‑приложения, собираемого с помощью webpack.

Webpack Bundle Analyzer не работает «в два клика» как отчет Lighthouse, но всё ещё довольно прост в использовании — нужно всего лишь установить npm‑пакет и подключить плагин в webpack config. Результат работы анализатора — карта чанков приложения. Она достаточно гибко конфигурируется. С её помощью можно понять структуру чанков: их размеры, модули, которые в них попадают, размеры.

Пример карты, построенной с помощью Webpack Bundle Analyzer

Пример карты, построенной с помощью Webpack Bundle Analyzer

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

Statoscope

Statoscope — это буквально швейцарский нож для анализа бандла. Тулкит опенсорсный, разработан инженером Яндекса и контрибьютором webpack Сергеем Мелюковым. Вот доклад, в котором он сам рассказывает об инструменте.

Statoscope можно установить как плагин в webpack (больше информации), но можно «скормить» ему и стандартные статистики webpack. В результате работы инструмент генерирует интерактивную веб‑страницу с очень детальной информацией, полученной в процессе сборки проекта.

Пример стандартного дашборда Statoscope

Пример стандартного дашборда Statoscope

Вообще, инструмент умеет много чего, мне кажется, я пользуюсь, в лучшем случае, половиной. Сейчас расскажу, чем:

  1. Древовидное представление модулей. Я смотрю какие чанки являются асинхронными, а какие — initial, их размеры. Как модули расположены по чанкам

  2. Детальная информация о формации чанков: почему тот или иной модуль попал в то или иное место сборки (да, прямо видно файлики, где были импорты)

  3. Дашборд. Главная страница — это дашборд, на ней видно ключевые метрики бандла. Кстати, он настраиваемый — можно собрать свой.

  4. Зависисмости: видно дубликаты, peerDependencies, причины появления

  5. Карта модулей. Динамическая. На каждый чанк или модулей в древовидном представлении можно кликнуть, чтобы посмотреть, что у него внутри, используя FoamTree‑карту из webpack‑bundle‑analyzer. Да, на самом деле, если вы собрались использовать Statoscope, webpack‑bundle‑analyzer можно не ставить отдельно — он уже включён.

  6. Сравнение бандлов. Инструмент может сравнить статистики двух сборок, так можно детально увидеть как изменения сказались на бандле.

  7. CLI и jora‑query. С помощью jora‑query мы собрали отчет с интересующими нас метриками, а с помощью CLI мы можем прогнать анализ в CI и показать разработчику как его работа повлияла на размер бандла.

Внедрение в CI

Инструменты, о которых я рассказал, можно встраивать в CI‑пайплайны и на это стоит потратить время. Для сбора метрик в CI есть несколько причин:

  1. Накопление информации Вы сможете понять какие изменения помогали вам сокращать размер бандла и улучшать перформанс, а какие, наоборот, откатывали вас назад. Эти знания позволят вам обучиться и понять какие изменения вносить безопасно, а какие — нет, поймать возникающие «узкие места» для их дальнейшей оптимизации.

  2. Обоснование бизнесу Красивые графики, понятные и оцифрованные результаты помогут объяснить коллегам что и зачем вы делаете, нужно ли продолжать инвестировать в оптимизации или можно заканчивать. Кстати, чем раньше вы начнёте собирать статистику, тем эффектнее будут цифры.

  3. Futureproofing Знание метрик на этапе влития изменений помогает «поймать» ошибки до их попадания на прод. Кроме этого, это позволит значительно сэкономить время: вы увидите какой набор изменений влечет потерю производительности.

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

Стандартные подходы к уменьшению размеров бандла

После (а иногда и до) анализа текущего состояния, хочется «сорвать низковисящие фрукты» — сделать какие‑то очевидные и легкие действия, которые дадут максимум эффекта при минимуме усилий. Попробую перечислить вещи, которые легко проверить и починить.

Отключение sourcemaps

Мы знаем, что фактический JS код, который исполняется, и тот, который мы видим в консоли разработчика и IDE могут отличаться. Продовые сборки проходят через различные инструменты, которые бьют этот код на чанки, минифицируют его, иногда обфусцируют. Чтобы разработчику не приходилось страдать над нечитаемым кодом в одну строку, на помощь приходят sourcemaps: грубо говоря, это совокупность непосредственно файлов, которые содержат исходный код и файлов, которые объясняют как этот исходный код «наложить» на пропущенный через сборщик. Нетрудно догадаться, что исходный код, да еще и с дополнительными файлами для сопоставления может весить в разы больше, чем минифицированный бандл.

Современные сборщики не отдают soucemaps вместе с бандлом, а используют специальный комментарий, который добавляется в минифицированный исходный код, чтобы «подтянуть» sourcemaps только при открытых devTools. Комментарий выглядит примерно так:

//# sourceMappingURL=http://example.com/path/to/your/sourcemap.map

Стоит потратить время и убедиться, что ваш сборщик делает именно так и не отгружает не‑минифицированный код. Для этого нужно посмотреть на получаемые JS файлы во вкладке network devtools. Если среди полученных файлов есть не‑минифицированные, во вкладке sources работает стандартная навигация и поиск файлов и вы видите свой код как в IDE — время идти в конфиг своего сборщика и проверять настройки минификации и sourcemaps.

Дедупликация пакетов

e7a7102cf9e3915cf36a3c90f3a7707c.jpeg

Если в вашем приложении много зависимостей, вполне возможно, что в процессе резолва со временем нарос слой дубликатов разных версий. Например, в проекте у вас указана зависимость A версии 3.3, а пакет Б требует версию А ^4. Соответственно, чтобы все были довольны, в бандл попадает две зависимости: для пакета и для вас. Команды вроде pnpm dedupe умеют в автоматическом режиме «причесывать» lockfile и разрешать простые конфликты версий, указывая на те, которые можно разрешить в ручном режиме.

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

Кроме этого, всегда хорошая идея регулярно проводить аудит библиотек, не забывать вычищать неиспользуемые или малоиспользуемые, своевременно поднимать версии и избавляться от депрецированных.
Например, когда я решил изучить, что мы импортируем, я обнаружил, что у нас одновременно используются lodash, lodash‑es и underscore. Первые двое — буквально одно и то же, а третий пакет — прародитель lodash. Кроме этого, в проекте до сих пор оставались и использовались Bluebird promises — это библиотека‑прародитель современных нативных промисов, которая была в ходу во времена, когда промисы еще не были включены в стандарт языка.

Оптимизация импортов библиотек

Библиотеки можно импортировать по‑разному. Так, например, неудачный импорт функции из lodash может увеличить размер бандла более, чем на 100 кб.

Сравнение способов импорта из lodash

Сравнение способов импорта из lodash

Многие популярные библиотеки, в частности, вышеупомянутый lodash или, например, momentjs в документации указывают нюансы импортов и как их избежать, предлагают плагины для сборщиков, предназначенные для оптимизации импортов из библиотек. Кроме этого, для поддержания внимательности разработаны плагины для IDE, показывающие стоимость того или иного импорта в килобайтах. Ну и нельзя забывать про современные альтернативы. Так, например, старый добрый moment.js можно заменить на более «худые» библиотеки, которые делают то же самое и даже имеют совместимый API. Кстати, изучить размеры библиотек можно с помощью сервиса bundlephobia.

Как работает Bundlephobia

Как работает Bundlephobia

Сервис покажет размер библиотеки и подкинет идеи легковесных аналогов. Кроме этого, инструмент умеет сканировать package.json проекта. На данный момент эта функция существует только на сайте, её нельзя встроить в процесс разработки.

Рекомендации легковесных альтернатив momentjs

Рекомендации легковесных альтернатив momentjs

Не webpack«ом единым

Несмотря на безусловную популярность webpack, у него есть достойные конкуренты. В 2023 году только ленивый не рассуждал над преимуществами Rollup и vite, но перейти с webpack на альтернативный современный сборщик готовы далеко не все. На это есть множество причин — у webpack гигантское комьюнити, множество порой крайне необходимых плагинов, решающих повседневные задачи. На эту тему есть интересный доклад Зара Захарова, записанный на UDW'23.

Тем не менее, переход на более современный сборщик стоит рассмотреть. Например, те же vite или rollup могут дать значительный прирост как в скорости сборки (привет, Developer Experience), так и помочь «условно бесплатно» уменьшить размер бандла.

Стратегии и инструменты разбиения на чанки

Все вышеперечисленные изменения требуют минимального взаимодействия с кодом приложения, во многом, именно это и делает их более‑менее простыми. Но после того, как все простые оптимизации сделаны — время переходить к более сложным. Поговорим о разделении на чанки.

Что вообще выделять?

Чтобы начать выделять чанки, нужно взглянуть на бандл с точки зрения кода и пользовательского опыта и задать себе простые вопросы. Что нужно загрузить в первую очередь? (Что нельзя выделять в отдельный чанк?) Что пользователь будет использовать регулярно? Какой код используется редко? Какие части приложения менее популярны? Какие части приложения мы можем загружать не моментально?

Вы когда-нибудь пользовались Портфелем в Windows?А могли бы

Вы когда-нибудь пользовались Портфелем в Windows?
А могли бы

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

Выбираем Route

Выбираем Route

Кроме этого, частой стратегией разделения является деление по роутам или по логическим частям приложения. Наша цель на данном этапе — отделить от основного чанка те части приложения, которые не нужны большинству пользователей в повседневной эксплуатации.

Звучит легко, но на практике всё зависит от архитектуры и организации кода. Помните мантру из GRASP про low coupling и high cohesion? Разделение на чанки позволит вам её прочувствовать в полной мере.

Чистая архитектура приложения вызывает радость

Чистая архитектура приложения вызывает радость

Асинхронные чанки будут успешно выноситься только если код достаточно изолирован и не импортируется хаотически по всему приложению, ведь любой синхронный импорт может сломать разделение. Если ваш код разделен плохо и имеет высокое зацепление (high coupling) — прежде чем что‑то выносить, лучше потратить время и постараться качественно выделить модули и подкорректировать их использование. Например, в своей работе я применяю для этого подход мультипакетного монорепозитория с помощью workspaces, но это, конечно, не единственная стратегия.

Способы асинхронной загрузки кода

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

Динамический импорт es2020 или webpack

В стандарте языка JavaScript с недавних пор существуют динамические импорты import(). Динамический импорт позволяет указать путь до модуля и получить Promise, результатом которого является объект модуля. В отличии от статических импортов, динамические являются явным указателем для сборщиков, что модуль можно вынести в отдельный чанк. Кроме этого, динамические импорты можно использовать в методах, условиях, путь до модуля можно задавать программно.

Динамический import

Динамический import

Если вы используете webpack, то с помощью webpack magic comments, можно добавить метаинформации об импортируемом модуле: что именно нужно импортировать, задать имя чанка, способ загрузки и приоритет. Кроме этого, с помощью плагинов webpack на этом этапе можно пробовать бюджетировать размер модулей.

Как выглядят Webpack Magic Comments

Как выглядят Webpack Magic Comments

Если ваш код недостаточно молодежный, можно изучить возможности вашего загрузчика. Так, например, в webpack существуют методы типа webpack.ensure, которые позволяют сделать примерно то же самое.

React-специфичные методы

Если вы используете React, библиотека предлагает метод React.lazy, который позволяет «завернуть» динамический импорт в компонент и компонент, который позволяет отобразить ленивый компонент. Тут всё очень просто.

Использование React.lazy и Suspense

Использование React.lazy и Suspense

Кроме этого, существует небольшая библиотека Loadable Components, которая позволяет лениво загружать библиотеки, используя компонентный подход React и умеет работать с SSR.

Внедрение мониторинга размера бандла. Как стабилизировать бандл и понять, что пользователю стало лучше

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

«Байтики» — это сугубо технические цифры, которые никак не учитывают конечный пользовательский опыт. «Байтики» могут уменьшаться, что хорошо, но при этом, пользователь будет видеть тонны лоадеров или скелетонов вместо понятной браузерной загрузки или белого экрана. Лучше ли это? Скорее нет. Поэтому, есть смысл ориентироваться и на «секунды» — те самые Web Vitals метрики, которые оценивают опыт пользователя. Важное отличие «секунд» от «байтиков» — учет не только поведения приложения, но и пользовательского окружения: мощности устройства, скорости интернета. Они позволяют смотреть поведение приложения на множестве пользователей, кроме того, «секунды» понятнее бизнесу и могут быть релевантны целям продукта, маркетинга, UX.

На мой взгляд, ориентироваться только на «байтики» или только на «секунды» — ошибочно. Только «байтики» и «секунды» вместе могут дать объективную картину происходящего и помочь в дальнейшей оптимизации.

Сбор «секунд» — метрик web-vitals

Для сбора метрик web‑vitals существует множество решений. Пройдемся по наиболее очевидным и посмотрим что нам удалось воплотить в жизнь.

Околокоробочное решение

Самое простое и наиболее «коробочное» решение — от авторов метрик, Google. Для сбора они предлагают plug«n"play‑библиотеку web‑vitals, которая умеет собирать метрики и отправлять их в Google Analytics, Google Tag Manager или с помощью запросов к вашему самописному API. Насколько помню, эта библиотека даже встроена в create‑react‑app.

Коробка x Grafana

Когда я начинал внедрять снятие метрик web‑vitals, я начинал именно с этого подхода. К сожалению, в текущих реалиях мы не могли использовать сервисы Google, кроме этого, ограничения от партнеров компании не всегда позволяют отправлять данные на сторонние сервера. Все это, естественно, усложняет сбор метрик. Тем не менее, поделюсь возможным решением.

Если у вас такие же условия, один из возможных способов сбора метрик — написание собственного сервиса с API, который умел бы принимать данные с фронтендов и отправлять их в связку Prometheus‑Grafana с помощью Prometheus pushgateway. Это оказалось не так тривиально, готовых решений на тот момент мне найти не удалось, к тому же, коллеги подсказали более простой способ.

Кроме этого, актуальная документация Grafana включает в себя раздел Frontend Observability и предлагает сбор метрик с помощью OpenTelemetry и Grafana Faro Web SDK, их использование должно упростить сбор метрик с фронтенда

Performance metrics в Sentry

После некоторого ресерча, и подсказки коллеги, я остановился на использовании Sentry. Все знают Sentry как приложение для мониторинга ошибок, но, на самом деле, Sentry умеет измерять и перформанс приложений. Более того, Sentry имеет готовый тулкит для снятия метрик web‑vitals и профилирования компонентов на фронтенде и в React в частности — подключить измерение можно изменив пару строк (и возможно подняв версии Sentry и клиентской библиотеки).

Подключить снятие метрик перформанса в Sentry очень легко

Подключить снятие метрик перформанса в Sentry очень легко

Мы используем Sentry для мониторинга улучшения и деградации метрик web‑vitals по всему приложению и по роутам. Так как у нас есть релизы — конкретные версии приложений с понятным набором изменений, мы сравниваем релизы между собой, чтобы понимать как те или иные изменения повлияли на пользовательский опыт.

Сбор «байтиков» — размеров чанков

Аналогично с «секундами», я расскажу общепринятые подходы и поделюсь нашим опытом.

Statoscope, jora-query, валидация

Прежде всего, стоит отметить, что мы начали с того, что подключили тулзы для самостоятельного использования разработчиками. Любой фронтендер компании может одной командой получить отчет Statoscope или карту webpack bundle analyzer и посмотреть как будет собираться приложение с его изменениями. Но, чтобы разработчики и релиз‑менеджеры не тратили время на ручные проверки, мы вынесли это в CI. С помощью Statoscope CLI и jora‑query, мы отсылаем в MR комментарий, который показывает как изменения сказались на бандле: мы выводим общий вес бандла, размеры его синхронной и асинхронной части, сравнение с целевой веткой. Для нас важно, чтобы в процессе работы не возникало резких скачков в в размерах (это сигнал о кривых импортах, поломке ленивой загрузки), размер асинхронной части рос быстрее, чем размер синхронной части (это сигнал о том, что новый код не замедляет первую загрузку приложения).

Наш отчёт в

Наш отчёт в Merge Request

В данный момент у нас нет жесткого бюджетирования размеров чанков, так как пока что вся система не достаточно устоялась и нам предстоит еще много работы. Тем не менее, statoscope‑cli позволяет внедрить бюджетирование — в CI можно добавить джобу, по аналогии с eslint — она будет выполнять jora‑запрос и падать, если мы не попали в допустимый размер.

Webpack Magic Comments

Более «традиционный» способ отслеживания разрастания размеров чанков — использовать раздел performance конфига webpack. С его помощью можно установить ограничение на размеры ассетов, фильтры для файлов, используемых при подсчете размеров и настройки уведомлений — ошибку или ворнинг.

Упавшая джоба намекает разрабочику о необходимых доработках

Упавшая джоба намекает разрабочику о необходимых доработках

Бюджетирование можно организовать и с помощью webpack Magic Comments. В своей работе я сталкивался с самописным плагином, который позволял задать максимальные размеры чанков с помощью webpack magic comments. В процессе сборки плагин сверялся с ограничением размеров чанков и «ронял» билд, если лимит был превышен. Это позволяет контролировать импорты и автоматически отклонять сборки, которые раздувают бандл.

Наши результаты и дальнейшие шаги

На протяжении статьи я акцентировал внимание на том, что мы уже сделали в рамках нашего цикла работ по оптимизации перформанса и проговорил о том, как это нам помогает.

Если коротко, то путь был следующим:

  1. Мы проанализировали наш код, нашли «узкие места», на которых можно получить эффект дешево

  2. Избавились от малоиспользуемых зависимостей, пакетов с одинаковым или устаревшим функционалом

  3. Избавились от пакетов‑дубликатов

  4. Обновили инфраструктуру: перешли с webpack 4 на 5, в качестве менеджера пакетов стали использовать pnpm

  5. Начали разбивать монолит и разрабатывать с помощью пакетов — перешли к мультипакетному монорепозиторию.

  6. Восстановили механизм асинхронных компонентов, переписали старинные require на современные динамические импорты.

  7. Внедрили ручной и автоматический сбор статистик — «байтиков» и «секунд» с помощью statoscope и sentry. Теперь мы можем отслеживать деградацию и улучшения в размере бандла и готовы перейти к жёсткому бюджетированию, когда потребуется.

Мы добились значительных успехов — размер бандла ощутимо уменьшился, при этом, значительная часть кода стала грузиться по требованию — приложение стало быстрее запускаться. Тем не менее, наш подход не был оптимален. Из‑за спешки, внедрение сбора статистик произошло слишком поздно. Если бы сделали это в начале пути,  было бы значительно проще планировать работы, видеть достижения, объяснять бизнесу чем мы занимаемся.

Тем не менее, много работ ещё впереди. Нам предстоит заниматься оптимизацией локализаций, статических ассетов (шрифтов, стилей), выделять новые и новые чанки, приводя в порядок архитектуру приложения, закладывать бюджет на перформанс.

© Habrahabr.ru