Первоначально опубликовано на https://leandrotk.github.io

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

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

Контекст

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

Первое, что я хочу упомянуть, это тот факт, что приложение (PWA), над которым мы начали работать, имело (на самом деле, оно все еще есть) большая часть кодовой базы, написанной 2 года назад. Это React PWA с использованием Webpack 3, Babel 6, react-redux 5 и т. Д. Небольшое количество крючков. Большинство классов компонентов.

В проекте не было реальной команды или ответственного инженера, который бы позаботился о кодовой базе. Разным командам нужно добавлять функции здесь и там в PWA, но на самом деле они не владеют базой кода. Эффект: в кодовой базе появляются новые функции, ошибки и технический долг, но она не улучшается и не подвергается рефакторингу.

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

Мой коллега и я стали «Владельцами услуг» для этого проекта. Идея «Владельцев услуг» - это кто-то (или в данном случае два человека), которые должны быть координационным центром для устранения сомнений, управления техническими долгами, проблемами, ошибками и т. Д. По сути, это кто-то, кто заботится о проекте.

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

Вместе с менеджером по продукту и дизайнером мы создали дорожную карту функций, которые хотели бы выпустить в этом квартале. В то же время производительность является важной частью головоломки, обеспечивающей удобство работы пользователей. Мы можем начать с двух основных «показателей»: загрузки страницы и времени до интерактивности. Между этими показателями и пользовательским опытом существует корреляция (а иногда и причинно-следственная связь).

Мы также хотели отправить тесты A / B и убедиться, что производительность не является переменной, которая может повлиять на результаты этих тестов. По сути, мы хотели предотвратить снижение производительности, чтобы не повлиять на тесты (но нам нужны были метрики - мы скоро об этом поговорим!).

Наша команда не была командой экспертов по производительности. Но у компании есть команда под названием Core UX, которая в основном занимается веб-производительностью. Команда, которая имела опыт работы с фронтендом в первые 3 квартала 2020 года.

Процесс

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

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

Благодаря этим открытым отношениям мы могли учиться быстрее и расставлять приоритеты для невысоких плодовых задач, чтобы получать более быстрые результаты с минимальными усилиями или вообще без них. Мы обсудим это более подробно позже, в разделе «Улучшения производительности».

Был задокументирован весь процесс: встречи, наше обучение, наши открытия и исправления производительности.

Метрики и меры

У нас было первое обсуждение метрик, которые мы хотели отслеживать, и моя команда начала узнавать о них больше. Для нас, которые поначалу были не очень знакомы, это была совокупность сокращений, которые мы не понимали по-настоящему. FCP, LCP, FID? Что это?

Чтобы понять эти термины, я хотел бы сначала понять показатели взаимодействия с пользователем, потому что все они связаны друг с другом.

Что касается показателей взаимодействия с пользователем, мне нравится эта статья от Google о показателях производительности, ориентированная на пользователя, в которой определены следующие показатели:

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

Мне это нравится, потому что он очень интересный. Как пользователи веб-сайтов, мы можем понимать эти показатели (и возможное разочарование при использовании некоторых веб-сайтов с «плохой производительностью»).

Это также интересно, потому что мы можем сопоставить ориентированные на пользователя метрики с метриками производительности, которые мы обычно видим.

  • Первая отрисовка содержимого (FCP): измеряет время от начала загрузки страницы до отображения на экране любой части содержимого страницы.
  • Самая крупная отрисовка содержимого (LCP): измеряет время с момента начала загрузки страницы до момента, когда на экране отображается самый большой текстовый блок или элемент изображения.
  • Задержка первого ввода (FID): измеряет время от момента, когда пользователь впервые взаимодействует с вашим сайтом (т. е. когда он щелкает ссылку, нажимает кнопку или использует настраиваемый элемент управления на основе JavaScript) до время, когда браузер действительно может ответить на это взаимодействие.
  • Время до интерактивности (TTI): измеряет время с момента начала загрузки страницы до момента ее визуального отображения, загрузки ее начальных сценариев (если таковые имеются) и способности быстро надежно реагировать на вводимые пользователем данные.
  • Общее время блокировки (TBT): измеряет общее время между FCP и TTI, когда основной поток был заблокирован на достаточно долгое время, чтобы предотвратить реакцию на ввод.
  • Накопительный сдвиг макета (CLS): измеряет совокупную оценку всех неожиданных сдвигов макета, которые происходят между моментом начала загрузки страницы и изменением ее состояния жизненного цикла на скрытое.

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

Как я сказал ранее, эта связь очень интересна и заставляет нас сосредоточиться не только на битах и ​​байтах, но и на пользовательском опыте в целом.

Инструменты, аудит и обмен знаниями

Получив лучшее представление о пользовательском опыте и показателях производительности, мы захотели начать их отслеживание. Есть разница между лабораторными и полевыми показателями. Согласно Google:

  • Лабораторные показатели: использование инструментов для имитации загрузки страницы в согласованной контролируемой среде.
  • Полевые показатели: реальные пользователи, фактически загружающие страницу и взаимодействующие с ней.

Лабораторные показатели

Для лабораторных показателей мы настроили Lighthouse в нашем CI с помощью Lighthouse CI. Итак, для каждого открытого запроса на вытягивание (PR) мы запускаем Lighthouse для сбора данных, связанных с производительностью, и блокирования PR, пока мы не исправим проблему с производительностью.

С помощью этого инструмента мы можем проверить различные аспекты PWA (доступность, SEO, лучшие практики и производительность), а также добавить утверждения, чтобы нарушить PR, когда он превышает установленный нами порог бюджета.

Например, мы можем добавить утверждения, относящиеся к размерам JavaScript и изображений (в байтах):

Этот объект JavaScript является частью конфигурации, которую мы можем использовать для сбора различной информации о производительности. Чтобы лучше понять конфигурацию Lighthouse CI, взгляните на эту документацию: Lighthouse CI Configuration.

Еще один очень крутой инструмент, который мы используем для лабораторных показателей, - это Speed ​​Curve. Настроить и начать сбор данных очень просто. Этот инструмент лучше работает для «незарегистрированных страниц», потому что мы добавляем URL-адрес веб-сайта и в зависимости от загрузки и взаимодействия веб-сайта он будет собирать показатели производительности.

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

Это очень здорово работает для наших целевых и домашних страниц.

Последний инструмент, который мы создали, - это внутренний инструмент, созданный командой по производительности. Это инструмент для анализа пакетов приложений, который теперь имеет 3 основные функции:

  • Отчет анализа пакетов: собирает и сохраняет результаты HTML-кода анализатора пакетов.
  • Бюджеты пакетов: настраивает конфигурацию бюджета, чтобы добавить пороговое значение для размеров пакетов. Это нарушает PR, если размер пачки превышает пороговое значение.
  • Bundle Changes: показывает изменения размера связки между PR и главной (или основной) ветвью. Это помогает нам легко ответить: «Увеличил / уменьшил размер пакета для X?»

Этот инструмент запускается в нашем конвейере CI для каждого PR, и результат отображается в PR Github (за ним стоит Danger).

Эти инструменты очень интересны, потому что

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

Метрики поля

На данный момент мы используем Instana для сбора данных, связанных с производительностью реальных пользователей.

Следующим шагом Real User Monitoring (RUM) является отслеживание большего количества действий пользователей в нашем приложении для сбора веб-показателей жизненно важных функций в потоке PWA.

Улучшения производительности

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

Целевая страница

Мы начали с нашей целевой страницы. Первым действием было проанализировать размер пакета JavaScript с помощью Webpack Bundle Analyzer.

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

И вот что у нас получилось:

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

У нас была константа внутри файла компонента React. И мы импортировали эту константу на целевую страницу.

Итак, импортируя эту константу, мы также импортировали React.

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

И импортируем константу из нового файла:

Посмотрим, как изменится размер пакета:

Мы уменьшили 95КБ! Интересно думать, что мы можем оказать огромное влияние на небольшое изменение после тщательного анализа наших пакетов. Это будет процесс, лежащий в основе каждого улучшения, которое мы делали до конца этой статьи:

  1. Анализируйте связки
  2. Исправить проблему с производительностью
  3. Собирайте результаты и отслеживайте показатели

Снова запускаем анализатор пакетов и получаем следующее:

Первое, что привлекло наше внимание, были библиотеки appboy.min.js и transit.js. appboy - это Braze, библиотека, которую мы используем для связи, а transit - это библиотека для преобразования данных формата JSON в состояние нашего приложения.

Библиотека Braze была очень похожа на библиотеку React. Это был оператор import в файле, который использовала целевая страница, но на самом деле не использовала Braze.

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

Снова запустив анализатор пакетов, мы получили потрясающий результат.

Целевая страница AnnounceYourHouse уменьшена до 90 КБ. Мы смогли удалить почти 50% основного посадочного пакета.

Мы также значительно улучшили размер пакета целевой страницы PriceSuggestion. От 115 КБ до 4 КБ - потрясающий результат.

Для библиотеки transit мы сделали временное обходное решение. Он импортировал библиотеку для преобразования строки JSON, сохраненной в локальном хранилище, для получения информации из одного атрибута этого объекта.

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

Мы могли бы значительно улучшить размер связки главной площадки. Удаление почти 50% связки.

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

Резко уменьшился общий размер целевой страницы: -2,16 МБ.

Оценка Lighthouse Performance была от 73 до 97:

Самая большая Contentful Paint была улучшена за 1 секунду:

Чанк поставщика

При запуске npm run bundle:analyzer мы также замечаем большую зависимость в нашем блоке vendor.

В блоке vendor мы заметили все значки из пользовательского интерфейса материала. Каждый раз, когда пользователь входит на веб-сайт, и если фрагмент не кэширован в браузере, ему необходимо будет загрузить весь фрагмент. Если это большой фрагмент для загрузки, это влияет на производительность и, как следствие, на удобство использования.

Это распространенная проблема при импорте значка пользовательского интерфейса материала в компонент React.

Один из наших компонентов использовал внутреннюю библиотеку компонентов, которая использовала стиль «именованный импорт» для импорта значка UI материала. Это, без надлежащего плагина babel, также добавляет остальные неиспользуемые значки в блок поставщика.

Мы пришли к двум решениям:

  1. Исправьте импорт из этой внутренней библиотеки компонентов, в которой мы остановили использование именованного импорта.
  2. Добавьте плагин babel и настройте приложение, чтобы не добавлять неиспользуемые модули.

Поскольку эта внутренняя библиотека компонентов была первой и устаревшей версией нашей дизайн-системы, мы не хотели продолжать ее поддерживать. Лучшим подходом было не использовать эту библиотеку больше и переместить всю кодовую базу для использования новой библиотеки системы дизайна (и мы над этим работаем!).

Этот проект производительности не был нашим основным проектом в этом квартале, поэтому у нас было меньше времени, чтобы сосредоточиться на нем в спринте. Плагин babel на тот момент был для нас более простым и понятным решением.

В основном нам нужно было добавить этот новый плагин babel babel-plugin-transform-imports и настроить babelrc:

И с его помощью мы предотвращаем полный импорт библиотеки в блоке vendor.

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

С помощью этого простого анализа и настройки мы смогли уменьшить размер блока поставщика более чем на 50% (он по-прежнему составляет 2,83 МБ и его можно улучшить. Мы увидим позже!), А основной блок - на 28%.

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

Главный блок

Главный блок имеет несколько общих модулей для всех частей приложения. Но после запуска анализатора пакетов мы получили следующее:

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

Проблема была простой: наше разделение кода не работало должным образом.

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

В этом приложении в то время мы использовали react-loadable, поэтому идея заключалась в том, чтобы просто создать эти загружаемые файлы:

И используйте их для каждой точки входа в маршрут.

Запустив анализатор пакетов, мы получили следующее:

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

Результат был потрясающим. Основной блок стал меньше более чем на 50%, а блок поставщика также уменьшился на 29%.

Кеширование самых больших зависимостей

Читая эту статью, вы, вероятно, видели некоторые большие зависимости в нашем пакете, такие как firebase, braze, immutable и т. Д.

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

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

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

Поскольку в то время мы использовали Webpack 3, нам нужно было использовать CommonsChunkPlugin, чтобы разделить эти зависимости в отдельный фрагмент.

Мы составили список всех самых больших зависимостей:

Он также был отображен как структура данных списка в нашей конфигурации Webpack:

Наряду с CommonsChunkPlugin нам просто нужно было перебрать этот список для создания каждого фрагмента.

Мы видим, что фрагмент поставщика стал намного меньше, и были созданы некоторые новые фрагменты.

Запустив приложение, мы также можем протестировать загрузку каждого отдельного фрагмента.

И получили действительно крутой результат:

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

Мы увидели несколько приятных улучшений в панели управления кривой скорости:

Как и ожидалось, мы увидели огромное улучшение размера JavaScript: -1,43 МБ.

Уменьшение размера JavaScript также повлияло на общее время, в течение которого пользователь заблокирован для взаимодействия со страницей: -1,2 с.

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

А максимальная насыщенная краска выросла с 6 до 3,75 с.

Резюме

Чтобы резюмировать то, что мы видели в этой статье, давайте посмотрим на список вещей, которые мы сделали в этом путешествии:

  • Измерение: показатели как основа повышения производительности.
  • Блокировка: предотвращение регрессов и масштабирование знаний о производительности.
  • Анализ: с помощью данных и показателей проанализируйте возможные проблемы.
  • Улучшения: код.
  • Воздействие: измерьте предыдущее и позднее изображение.

Я также рекомендовал бы поговорить с более опытными людьми в этой области производительности, если это возможно.

Следующие шаги

У нас есть еще дела, но у нас не было времени сосредоточиться на них в последнем квартале. Вот список вещей, которые сейчас приходят мне в голову:

  • Дополнительные показатели: ром для зарегистрированных страниц, показатели ux (взаимодействие, показатель отказов), бизнес-показатели (конверсия).
  • Управление запросами: кеширование запросов сервера.
  • Дополнительный анализ: серверная часть, блоки, предварительная выборка и т. д.
  • Съемные зависимости: анализируйте большие зависимости, которые можно удалить или заменить.
  • Обновление Webpack: переход на версию 5 - кеширование, оптимизация, разделение кода, встряхивание дерева.
  • Оптимизация Webpack: нужно создавать быстрее.
  • Продолжайте учиться: узнайте больше, чтобы открыть для себя больше возможностей.

Ресурсы

У меня есть ресурсы, которые я использовал в процессе работы над этим проектом. Я надеюсь, что это может быть полезно и для вас: Исследования производительности сети.

Первоначально опубликовано на https://leandrotk.github.io 21 января 2021 г.