Внимание, мы переехали! Если вы хотите и дальше быть в курсе последних технических материалов Square, посетите наш новый дом https://developer.squareup.com/blog

На любом веб-сайте электронной коммерции время загрузки страницы напрямую связано с коэффициентом конверсии. Столкнувшись со страницей, которая загружается дольше 3 секунд, 53% пользователей уходят, возможно, никогда не вернутся. Задержка загрузки в 1 секунду приводит к потере конверсий на 7%, просмотрам страниц на 11% и снижению удовлетворенности клиентов на 16%. Что касается SEO, низкая скорость страниц также влияет на алгоритмы индексации, что снижает эффективность сканирования и приводит к тому, что индексируется меньше страниц.

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

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

Мы наметили основные веб-страницы на критическом пути нашего пользователя и решили сосредоточиться на отправной точке: нашей домашней странице. Это страница, с которой начинают большинство посетителей trycaviar.com, и она остается одной из наших страниц с наибольшим посещаемостью. Мы использовали Google Lighthouse для аудита нашей домашней страницы в качестве основы и полного раскрытия информации - это было некрасиво.

Настройка показателей

В Caviar наш стек включает React / Rails в нашем веб-интерфейсе в комплекте с Webpack, инструментом сборки, обычно используемым вместе с React. Webpack - это сборщик модулей, который строит граф зависимостей, начиная с указанных точек входа, и выдает пакет для загрузки браузеру. Он в основном используется для JavaScript, но способен управлять любыми типами интерфейсных ресурсов, включая HTML / CSS и даже изображения, и в конечном итоге несет ответственность за большую часть манипуляций с JavaScript, которые мы в конечном итоге сделали. Вместе с этим мы используем изящный инструмент - Webpack Bundle Analyzer, который позволяет нам видеть, какие и где зависимости мы втягивали, и сколько места они занимают по сравнению с другими. Позже это помогло нам выявить повторяющиеся зависимости и зависимости, которые можно было разделить на более мелкие фрагменты, которые можно было динамически импортировать, когда они нам понадобились, а не загружать все сразу.

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

Нам нужен был простой и систематический способ запуска Lighthouse CLI, чтобы он точно отслеживал наш прогресс. Поэтому мы встроили его в наш конвейер CI. Мы обнаружили, что оценки Lighthouse варьировались в зависимости от того, на каком компьютере мы его запускали, так что это уменьшало дисперсию, поскольку он всегда будет запускаться на нашей машине сборки. Мы начали внимательно следить за нашим JavaScript и установили пакет bundlesize, чтобы лучше отслеживать размеры нашего приложения и комплектов поставщиков. Исходя из этого, мы добавили бюджетный тест, чтобы убедиться, что наши пакеты находятся в пределах указанной суммы. Эти изменения позволили нам отслеживать наши показатели на основе каждой фиксации и определять, как новые функции или изменения кода повлияли на нашу оценку производительности с тем же уровнем точности. Это не означало, что это заблокирует любые развертывания, поскольку мы могли просто увеличить размер бюджета, обновив тест, а скорее повысило ясность в отношении влияния на производительность и заставило нас явно подтвердить, что мы (или нет) согласны с этим. Для большей наглядности мы регистрировали время от объекта производительности браузера при каждой загрузке страницы, доступ к которой можно получить через window.performance, чтобы мы могли видеть наш прогресс с помощью инструментов визуализации данных.

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

Узел DOM и оптимизация изображений

С помощью рекомендаций Lighthouse и наших инструментов аналитики мы начали определять, где и как мы можем оказать наибольшее влияние. Caviar - это сайт с большим количеством изображений, поэтому мы пришли к выводу, что оптимизация загрузки изображений может привести к значительному повышению производительности за счет масштабирования. Мы обнаружили, что в некоторых случаях мы рендерили изображения размером более 3000 x 2000 пикселей в небольшое пространство 260 x 100 пикселей! Здесь, в Caviar, мы ценим высококачественные изображения, но достаточно сказать, что в этом не было необходимости и отрицательно сказалось на нашей работе с минимальным увеличением качества изображения.

Мы пропустили наши изображения через службу обработки изображений под названием Thumbor и заменили их все оболочкой, загружающей адаптивные изображения в формате WebP. Это гарантировало, что наши пользователи на мобильных устройствах не будут загружать изображения большего размера, чем необходимо, с дополнительным преимуществом, гарантирующим согласованность между нашими загруженными изображениями, что невероятно важно, поскольку в этом году мы внедряем самостоятельную адаптацию для наших партнеров по ресторанам. Это изменило нашу метрику First Contentful Paint с 9,45 секунды до 3,5 секунды, т.е. почти на 6 секунд!

Поскольку импульс загрузки изображений в полной мере проявился, мы продолжили внимательно изучать наши запросы. Мы внесли небольшие изменения, такие как преобразование наших png-спрайтов в svgs, чтобы они загружались встроенными, а не выполняли другой сетевой запрос. Мы внесли большие изменения в пользовательский интерфейс, например, переосмыслили наш контент на странице. Наша домашняя страница использовалась для загрузки всех ресторанов при посадке. После ограничения количества узлов DOM, которые мы отображали при начальной загрузке страницы, и требования, чтобы пользователь нажимал на CTA для загрузки остальных, мы улучшили время до интерактивного режима на 4 секунды. Из-за количества динамической информации на этой странице почти всегда будет большое количество узлов DOM, но мы сокращаем почти 50% узлов при начальной загрузке одной из наших наиболее посещаемых страниц.

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

Уменьшение размера и разбивка пакетов

Мы начали с огромных размеров пакетов по любым меркам. Перейдя вниз по списку самых больших пакетов, которые у нас были, используя визуальный элемент, предоставляемый Webpack Bundle Analyzer в качестве руководящего механизма, мы вычеркнули те, которые мы абсолютно не могли удалить, такие как React и, к сожалению, jQuery. Оставшимися самыми крупными нарушителями были момент и лодаш, оба огромные и, соответственно, два из npm больше всего зависели от пакетов. Мы обнаружили, что на самом деле загружаем момент и часовой пояс. Момент-часовой пояс охватил все наши варианты использования, поэтому мы смогли убрать момент. Webpack по умолчанию включает в себя все файлы локалей для моментов / моментов и часовых поясов. Поскольку мы не использовали большинство из них, мы удалили все файлы локалей с IgnorePlugin и загрузили только те, которые нам были нужны, через специальный файл конфигурации. В аналогичной строке lodash (CommonJS) был заменен на lodash-es, который экспортируется как модули ES и может быть изменен по дереву.

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

Для остальных пакетов, которые мы обслуживали, мы провели аудит с помощью панели покрытия кода Chrome DevTool, чтобы выяснить, сколько JavaScript было использовано на странице. Неудивительно, что большая его часть была загружена и не использовалась.

В то время мы все еще использовали Webpack 3, и разделение кода ограничивалось CommonsChunkPlugin. Плагин разрешал только фрагменты приложения и иногда приводил к загрузке большего количества кода, чем необходимо, из-за его методологии фрагментов родитель-потомок. Webpack 4 с тех пор устарел этот плагин и теперь использует более эффективный плагинoptimization.SplitChunks, который также может обрабатывать разделение поставщиков. Разделение кода загружает код с помощью динамического импорта, поэтому пользователи загружают только тот код, который им нужен для той части сайта, которую они просматривают.

// Application
import('./Modal').then(({default: Modal }) => {
  this.openComponent(Modal);
})
// Vendor
import('package').then(({ default: Package }) => {
  Package.doStuff();
})

Этот синтаксис автоматически сообщает Webpack, что с этого момента нужно начать разделение кода и создать отдельный фрагмент. Разделение кода позволило нам урезать базовые пакеты, которые мы обслуживаем на каждой странице, и при необходимости динамически загружать остальные.

Вместо того, чтобы напрямую монтировать отдельные компоненты, мы теперь динамически загружали компоненты, используя react-loadable. Однако улучшение производительности было минимальным, когда мы использовали HTTP / 1.1 из-за накладных расходов на увеличенное количество запросов. Мы начали замечать кардинальные изменения после перехода на HTTP / 2 благодаря его способности мультиплексировать параллельные запросы / ответы. Это позволило нам быстро сократить начальное время выполнения JavaScript в основном потоке без значительных усилий по рефакторингу и привело к существенному улучшению наших оценок Lighthouse.

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

vendor.js: 436kb → 233kb
application.js: 186kb → 46kb

Окончательные результаты

Резюмируем:

  • Изменение размера / сжатие в формат WebP и использование адаптивных изображений уменьшило нашу Первую Contentful Paint на 6 секунд.
  • Ограничение наших узлов DOM сократило время до интерактивности на 4 секунды.
  • Оптимизация использования нашего пакета привела к уменьшению примерно на 145 КБ в комплекте нашего поставщика.
  • Разделение кода и динамический импорт пакетов и компонентов по HTTP / 2 в совокупности уменьшили размер наших базовых пакетов более чем на 50%, что еще больше уменьшило наш TTI на 10 секунд и снизило его примерно до 13 секунд.

Что дальше

При наличии соответствующей инфраструктуры и улучшений кода, которые мы внесли в последний квартал, мы уверены, что сможем и дальше улучшать производительность мобильного Интернета. Мы принимаем это близко к сердцу, когда приступаем к следующему рубежу производительности: унаследованный код в форме jQuery, fluxxor и CSS. До скорого!