Мы живем в эпоху, когда у нас есть телефоны, которые работают быстрее, чем компьютеры прошлых эпох, у нас есть соединения 4G, которые быстрее, чем широкополосная связь в прошлом. Но мы до сих пор не получили спасения от уродливого пользовательского опыта медленных веб-сайтов, от большого количества времени загрузки страниц, которые кричат ​​разработчикам о том, что ваш сайт недостаточно быстр. У менеджеров по продукту есть нескончаемое желание отправить как можно больше функций конечному пользователю, а для нас, как разработчиков, - свести к минимуму объем полезной нагрузки, передаваемой в каждой транзакции, избегая ада, создаваемого большим временем загрузки. .

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

Но теперь при наличии таких инструментов, как webpack и parcel, JS-файлы колоссального размера по сути тратят время:

  1. Конечный пользователь, отправив ему функции, которые он никогда не сможет использовать
  2. Браузер, заставив его анализировать неиспользуемый javascript

есть выход?

Короткий ответ, да.

Разделение и фрагменты кода

Существуют различные парадигмы, которые возникли как небольшие набеги на повышение производительности. Некоторые решили разделение кода на основе функциональности, некоторые занялись разделением кода на основе компонентов. Общая цель, которую разделяют оба подхода, - это отправка критического количества кода, который потенциально может использовать пользователь. У нас, с другой стороны, был единый монолит кода пакета Javascript, размер разработки которого очень велик, то есть 7,7 МБ (ни минимизированный, ни сжатый).

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

Зачем нужно разбивать по маршрутам?

Изначально файл, в котором были определены маршруты, выглядел так:

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

Предположим, у вашего приложения есть два маршрута:

1. '/' ← Домашняя страница, на которой будет контейнер домашней страницы, выполняющий код

2. '/hotels-in-:location/:hotel.html' ← HotelPage (страница отображения нашего продукта), где будет контейнер отеля, выполняющий код.

Теперь должен быть (и всегда есть) какой-то код, который является общим между '/' и '/hotels-in-:location/:hotel.html'. Кроме того, существует некоторый базовый «код фреймворка», который необходим для выполнения обоих для обоих этих маршрутов.

Так что мы можем сделать?

Один из подходов:

1. bundle.js // JS-файл, содержащий общий, а также код начальной загрузки и модули узлов для приложения.

2. homePageChunk.js // Чанк, содержащий исключительно код домашней страницы, необходимый пользователю только тогда, когда он переходит на домашнюю страницу.

3. HotelPageChunk.js // Чанк, содержащий исключительно код страницы отеля, необходимый пользователю только тогда, когда он переходит на страницу отеля.

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

При определении маршрутов в реагирующем маршрутизаторе версии 3 или версии 4 мы устанавливаем ключ getComponents для маршрутов для обратного вызова. Этот обратный вызов выполняется один раз, когда маршрут фактически посещается впервые. Второй аргумент обратного вызова (cb) - это компонент, который отныне будет загружаться при каждом посещении этого маршрута.

Обратный вызов для require.ensure предоставляет асинхронную функцию require, которая динамически требует модуль и вызывает обратный вызов (cb) для этого модуля, чтобы сделать его компонентом по умолчанию для этого маршрута. Второй аргумент require.ensure - это имя блока, которое будет присвоено блоку, в данном случае «HomePage».

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

bundle.js + HomePage.chunk.js = ›2,73 МБ + 281 КБ = 3 МБ

Мы добились сокращения на 61% объема данных, которые мы отправляем пользователю на главной странице.

Для нашей HotelPage это было

bundle.js + HotelPage.chunk.js = ›2,73 МБ + 1,61 МБ = 4,34 МБ

Это дает сокращение на 43%.

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

Так что 43% выглядели хорошо. Но можем ли мы сделать больше?

Компонентное разбиение

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

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

Логика очень похожа на то, как мы делали что-то на наших маршрутах. Именно в этом случае мы можем скрыть этот компонент за взаимодействием с пользователем. Отображение только тогда, когда пользователь нажимает кнопку «НАЖМИТЕ ДЛЯ ОТОБРАЖЕНИЯ».

В конечном итоге размер HotelPage.chunk.js уменьшился с 1,61 до 938 КБ. Итого вычет 43% из размера фрагмента страницы гостиницы.

Общий вычет для страницы отеля составил около 50%.

Что еще мы можем сделать?

Плагин Webpack Splitchunks:

Webpack предлагает встроенный плагин для разделения фрагментов, с которым мы сейчас экспериментируем. Часто называют загадочным плагином splitchunks

Https://medium.com/dailyjs/webpack-4-splitchunks-plugin-d9fbbe091fd0

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

Создание этого блога стало возможным благодаря совместным усилиям Прашанта Сингха (технический специалист), Чаранджита Каура и других членов команды Fabhotels. Это часть текущей серии, в которой мы начали записывать то, что мы сделали за последний год, чтобы сделать веб-сайт работоспособным. Большое спасибо Вивеку Рай, Анкиту, Насиру, Сурбхи, Анкуру и Джаведу за их вклад. Наша цель - 1 секунда на индийском 4G, и мы будем постепенно запускать блоги о передовых вещах, которые мы делаем.