Фреймворк React с момента его запуска в 2013 году превратился в невероятную интерфейсную среду разработки. React - это не просто еще один фреймворк Javascript, он изменил разговор о том, что значит быть разработчиком веб-приложений в 21 веке. Прошли те времена, когда мы ждали, когда большие потоки HTML будут отправлены и обработаны нашими браузерами, и на их место пришли быстрые, удобные для мобильных устройств веб-приложения, неотличимые от нативных приложений.

Единственная вещь, в которой React был особенно невероятен, почти несравненный, - это отправка огромных фрагментов Javascript в ваш браузер. Даже приложение по умолчанию, созданное с помощью create-react-app, отправляет более 40 КБ сжатого сжатого Javascript для приложения, которое чуть более продвинуто, чем hello world.

Хорошо, может быть, это не такая уж и хорошая вещь. Вот как мы можем сделать это лучше.

Предыстория Fender Play

Здесь, в Fender, наше приложение Fender Play написано на React - простом старом ванильном React для веб-браузеров и мобильных браузеров и React Native для наших приложений для iOS и Android. Хотя у нас есть довольно сложное приложение, мы чувствуем, что Fender Play - довольно нормальное веб-приложение на React. Он загружает данные, представляет различные представления, не имеет каких-либо необычных компонентов, все, что вы можете ожидать от приложения React, которое имеет средне-пикантную степень сложности. Мы используем рендеринг на стороне сервера, но с точки зрения разработки этот аспект нашего развертывания прозрачен для инженеров. Fender Play обращается к Lambda на стороне сервера, поддерживает его состояние в Redux и ведет себя примерно так, как вы представляете современное одностраничное веб-приложение.

За последние несколько лет разработки Fender Play мы заметили, что общий размер пакета Javascript быстро выходит из-под контроля. Мы запустили отчеты Lighthouse на нашем маркетинговом сайте и заметили, что наши оценки, мягко говоря, не самые лучшие. Наша Первая значимая краска (оценка, присвоенная Lighthouse за то, что хорошая часть вашей страницы была нарисована) была до - подожди - продолжай ждать - подожди еще немного - еще нет - подожди - 16 секунд в эмулируемом мобильном браузере через соединение 3G.

Ой.

Придумывая план

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

Итерационные улучшения скорости

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

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

Торговля мелкозернистой аналитикой для удобства пользователей

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

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

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

IntersectionObserver внутри компонентов в нижней части страницы

Как и многие веб-сайты в наши дни, Fender использует изображения с высоким разрешением, поэтому наш сайт отлично смотрится на современных дисплеях. Это имеет побочный эффект: наши изображения, как правило, почти в 4 раза превышают пропускную способность сопоставимого изображения с обычным разрешением. Вместо того, чтобы идти по простому пути и раздражать наш дизайнерский отдел вырезанием изображений с высоким разрешением, мы выбрали лучшее решение. Для некоторых компонентов React мы создали перехватчик React под названием useIntersectionObserver. Он принимает два параметра: options и target. Параметры содержит свойства, ожидаемые IntersectionObserver, а target - это элемент, за которым следует наблюдать.

const MyComponent = () => {
  const target = useRef();
  const options = {
    root: null,
    rootMargin: '0px',
    threshold: 0.1,
  };
  const [hasIntersected] = useIntersectionObserver(options, target);
  useEffect(
    () => {
      if (hasIntersected) {
        // load something
      }
    },
    [hasIntersected],
  );
  return <div ref={target} />;
};

Теперь, когда элементы находятся за пределами области просмотра, их изображения не загружаются. Когда они достигают 10% -ной видимости, мы запускаем событие для загрузки любых изображений, чтобы к тому времени, когда они полностью прокручиваются в пределах видимости, они уже были загружены.

Это простое изменение - извините, пользователи IE11, но вам действительно стоит перейти на Windows 10 - имело огромное значение. Наша начальная загрузка страницы упала на большой процент, и мы увидели, что наша первая значимая краска стала намного, намного быстрее. Мы убеждены, что это изменение может быть полезно для любого компонента, а не только для компонентов с большим количеством изображений, и мы проводим мозговой штурм, чтобы открыть исходный код библиотеки, которая будет использоваться с любым компонентом React, чтобы жизненный цикл компонента был сокращен. и запускается только тогда, когда компонент прокручивается близко к окну просмотра. Мы думаем, что это окажет огромное влияние на сообщество React в целом.

Разделение кода и ленивая загрузка нашего пакета Javascript

Затем последней итеративной частью головоломки было начало разделения кода, встряхивания дерева и ленивой загрузки нашего Javascript. Здесь требовалась изрядная предварительная работа; убедившись, что мы использовали последние версии React, Webpack и Babel. Перенос нашей маршрутизации на React Router. Некоторое время было потрачено на то, чтобы убедиться, что наша отложенная загрузка работает с рендерингом на стороне сервера. Это одно из тех мест, где работать с React очень сложно; без знания конкретных магических заклинаний, ваш пакет Javascript не будет разделен и загружен при необходимости. В Fender мы считаем, что это должно быть стандартным, готовым к использованию интерфейсом React.

В нынешнем виде этап сборки / развертывания требует жесткого контроля в рамках ванильного опыта React. Такие фреймворки, как Next, упрощают эту задачу, но для младших и средних инженеров - непростая задача - заставить ленивую загрузку работать хорошо. У Fender невероятная команда инженеров, и нам все же потребовалось несколько спринтов для предварительной работы, повторения и проверки того, что все наши маршруты работают успешно. К счастью, вся эта работа не помешала разработке других функций, и мы внесли изменения в ленивую загрузку, что привело к еще большему увеличению производительности.

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

В сочетании с остальной частью нашей работы по спринту, улучшения скорости происходили довольно непрерывно в течение примерно 8 спринтов. Мы очень рады результатам для наших пользователей, и мы очень рады, что мы сможем добиться большего прогресса в скорости. Пока что мы снизили общую нагрузку на полосу пропускания домашней страницы Fender Play более чем на 2 МБ, а значение First Meaningful Paint упало с более чем 16 секунд до - барабанная дробь, пожалуйста, - 4 секунд для соединения 3G. Это 12 секунд, сэкономленных без особых усилий.

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

Спасибо Дэвиду Ариасу, старшему инженеру, за предоставленные технические подробности в этой публикации.