Процесс работы пользователей с интерактивными веб-сайтами может включать в себя шаг отправки им JavaScript-кода. Часто — слишком большого объёма такого кода. Если на сайте используется очень много JavaScript, это особенно сильно сказывается на мобильных пользователях. Например, вам случалось бывать на мобильных веб-страницах, которые, вроде бы, выглядят как уже готовые к работе, но не реагируют на щелчки по ссылкам или на попытки прокрутить страницу?
JavaScript-код, который попадает в мобильные браузеры, всё ещё остаётся самым дорогостоящим ресурсом, так как он, многими способами, может задержать переход страниц в состояние, в котором с ними можно взаимодействовать. Какую нагрузку на системы создаёт JavaScript в наши дни? Как анализировать сайты? Как ускорить загрузку и обработку браузерами интерактивных веб-страниц? Эдди Османи, перевод материала которого мы сегодня публикуем, решил найти ответы на эти и на многие другие вопросы, встающие перед теми, кто пользуется JavaScript для разработки веб-сайтов в 2018 году.
Общие положения
Взгляните на этот отчёт, демонстрирующий время, необходимое для обработки JavaScript-кода сайта CNN различными устройствами. Он построен на основе измерений, проведённых средствами проекта WebPageTest.org (вот таблица с исходными данными для этого отчёта, тут есть ещё данные по сайту Walmart).
Время, необходимое для обработки JS-кода сайта cnn.com различными устройствами
Как видно, телефон высшего класса (iPhone 8) справляется с задачей обработки JS-кода примерно за 4 секунды. Телефону среднего уровня (Moto G4) на решение той же задачи надо уже примерно 13 секунд. А бюджетному, хотя и современному устройству, такому, как Alcatel 1X, требуется около 36 секунд.
Сегодня мы поговорим о тех стратегиях, которые могут применять веб-разработчики для того, чтобы, с одной стороны, создавать удобные и современные сайты, а с другой — чтобы обеспечивать эффективную загрузку, обработку и функционирование JavaScript-составляющей этих сайтов. Вот, вкратце, основные моменты нашей беседы, которая, кстати, основана на моём недавнем выступлении:
- Для того чтобы веб-проекты работали быстро, нужно загружать лишь JavaScript-код, необходимый для текущей страницы. Использование технологий разделения кода (code splitting) позволяет организовать как можно более оперативную загрузку того, что пользователю точно понадобится, и применить методику ленивой загрузки (lazy load) для других фрагментов кода. Благодаря такому подходу страница получает шанс быстрее, чем в других случаях, загрузиться, и быстрее дойти до состояния, когда с ней можно будет взаимодействовать. Если вы используете системы, в которых, по умолчанию, применяется разделение кода, основанное на маршрутах, это меняет ситуацию в лучшую сторону.
- Придерживайтесь ограничений, установленных в отношении параметров проекта, так называемого «бюджета производительности» (performance budget), и научитесь их соблюдать. Так, например, рассчитывайте на то, что размер минифицированного и сжатого JS-кода, необходимого для страниц, предназначенных для мобильных устройств, не должен превышать 170 Кб. При преобразовании таких материалов к обычному виду получается примерно 700 Кб кода. Подобные ограничения имеют огромную важность для успеха проекта, однако, только они одни не могут чудесным образом сделать медленный сайт быстрым. Здесь играют роль командная культура, структура и средства контроля за исполнением правил. Разработка проекта без бюджета способствует постепенному падению производительности и может привести к неприятным последствиям.
- Научитесь проводить аудит JavaScript-бандлов (сборок) и сокращать их размер. Весьма вероятно, что клиентам приходится загружать полные версии библиотек, в то время как для работы проекта нужны лишь их части. Возможно, в проект включены полифиллы для браузеров, которые им не нужны, не исключено и дублирование кода.
- Каждое взаимодействие пользователя с сайтом — это начало нового периода ожидания, по истечении которого с сайтом можно будет работать (это то, что называют «время до интерактивности», TTI, Time to Interactive). Оптимизации стоит рассматривать в этом контексте. Размер передаваемого кода весьма важен для медленных мобильных сетей, а время, необходимое на парсинг JavaScript-кода — для устройств со скромными вычислительными возможностями.
- Если использование JavaScript на стороне клиента не приводит к улучшению пользовательского опыта — подумайте о том, нужно ли применять JS в данной ситуации. Возможно, в подобном случае быстрее было бы отрендерить HTML-код на сервере. Подумайте об ограничении использования клиентских веб-фреймворков, о применении их только для формирования страниц, которые совершенно не могут без этого обойтись. И рендеринг на сервере, и рендеринг на клиенте, при неправильном использовании этих технологий, превращаются в источник серьёзнейших проблем.
Современные веб-проекты и проблема чрезмерного использования JavaScript
Когда пользователь обращается к вашему сайту, то вы, вероятно, отправляете ему целую кучу файлов, многие из которых являются скриптами. С точки зрения веб-браузера это выглядит примерно так.
Примерно так чувствует себя браузер, который заваливают файлами
Как бы я ни любил JavaScript, я не могу не признавать тот факт, что он всегда представляет собой самую дорогостоящую, самую тяжёлую для обработки браузерами часть сайта. Собственно, мне хотелось бы рассказать о том, почему JavaScript может стать главной проблемой сайта.
JavaScript — самая тяжёлая часть сайта
По данным июльского отчёта HTTP Archive, посвящённого использованию JavaScript, средняя веб-страница в наши дни предусматривает использование примерно 350 Кб минифицированного и сжатого JavaScript-кода. Этот код распаковывается в нечто вроде скрипта размером 1 Мб, который нужно обработать браузеру. Для того чтобы мобильный пользователь смог взаимодействовать с такой страницей, ему придётся ждать более 14 секунд.
Средняя страница, содержащая 350 Кб сжатого JS-кода, становится интерактивной на мобильном устройстве примерно через 15 секунд
Если вы не знаете точно, как ваши JavaScript-бандлы влияют на то, сколько времени пользователям приходится ждать до того момента, когда они смогут взаимодействовать с вашим сайтом — взгляните на инструмент Lighthouse.
Время загрузки данных по мобильным сетям и их обработка на не самом быстром процессоре делают серьёзный вклад во время ожидания подготовки страницы к работе на мобильных устройствах.
Проанализируем состояние дел в области мобильных сетей, взглянув на данные OpenSignal. На следующем рисунке, слева, показана доступность 4G-сетей в мире (чем темнее цвет, которым закрашена территория страны — тем выше доступность), справа показаны скорости передачи данных (опять же — чем темнее — тем выше скорость). Страны, территория которых закрашена серым, в исследование не включены. Тут стоит отметить, что в сельской местности, даже в США, скорость мобильной передачи данных может быть на 20% медленнее, чем в городах.
Доступность 4G-сетей и скорость передачи данных
Выше мы говорили о том, что для загрузки и разбора 350 Кб минифицированного и сжатого JS-кода требуется определённое время. Однако если взглянуть на популярные сайты, окажется, что они шлют пользователям намного больше кода. Данные, представленные на рисунке ниже, взяты отсюда. Они представляют собой размеры распакованных JavaScript-бандлов различных известных веб-ресурсов, вроде Google Sheets, распакованный JS-код которого на настольных платформах занимает 5.8 Мб.
Размеры распакованных JavaScript-сборок для различных ресурсов
Как видите, и на настольных и на мобильных платформах браузерам, для работы с различными сайтами, иногда приходится обрабатывать по несколько мегабайт JS-кода. Главный вопрос здесь — можете ли вы позволить себе использовать такие большие объёмы JavaScript?
JavaScript — это не бесплатно
По словам Алекса Рассела, сайты, для работы которых требуются очень большие объёмы JavaScript, попросту недоступны для широких слоёв пользователей. По статистике пользователи не ждут (и не будут ждать) загрузки подобных ресурсов.
Можете ли вы себе это позволить?
Если вам приходится отправлять пользователям слишком большие объёмы JS-кода — подумайте о применении технологии разделения кода для разбиения бандлов на небольшие части, или об уменьшении объёма кода с использованием алгоритма tree-shaking.
В состав JS-бандлов современных сайтов часто входит следующее:
- Клиентский веб-фреймворк или библиотека пользовательского интерфейса.
- Средство для управления состоянием приложения (например, Redux).
- Полифиллы (часто для современных браузеров, которым они не нужны).
- Полные версии библиотек, а не только те их части, которые реально используются (например — вся библиотека Lodash, библиотека Moment.js в варианте с поддержкой всех региональных стандартов).
- Набор компонентов пользовательского интерфейса (кнопки, заголовки, боковые панели, и так далее).
Всё, что перечислено выше, вносит вклад в общий размер кода, который необходимо принять и обработать браузеру пользователя. Естественно, чем больше кода — тем больше времени нужно на то, чтобы браузер смог привести страницу в рабочее состояние.
Процесс загрузки веб-страницы похож на движущуюся в проекторе киноплёнку, на которой запечатлены три ключевых шага, отвечающих на следующие вопросы:
- Это происходит? (Is it happening?).
- Какая от этого польза? (Is it useful?).
- Можно ли этим пользоваться? (Is it usable?).
Вот как можно представить эти шаги, которые, в свою очередь, можно разбить на более мелкие «кадры», если продолжать аналогию с киноплёнкой.
Загрузка страницы — это процесс. В последнее время фокус внимания веб-индустрии смещается к показателям, которые отражают ощущения пользователей от работы с веб-страницами. Вместо того, чтобы отдавать всё внимание событиям onload
или domContentLoaded
, теперь мы задаёмся вопросом о том, может ли пользователь по-настоящему работать со страницей. Если он щёлкнет что-нибудь на странице, последует ли за этим немедленная реакция?
Процесс загрузки веб-страницы
- На шаге «Это происходит?» сайт может приступить к передаче каких-то данных браузеру. Здесь можно задаться вопросами о том, зафиксировано ли обращение браузера пользователя к серверу (например, по нажатию некоей ссылки), и о том, приступил ли сервер к формированию ответа.
- На шаге «Какая от этого польза?» сайт выводит на экран некий текст или другое содержимое, которое позволяет пользователю, во-первых, узнать что-то полезное, а во-вторых, способно его заинтересовать.
- На шаге «Можно ли этим пользоваться?» пользователь может начать полноценное взаимодействие с сайтом, которое может привести к неким событиям.
Интерактивные страницы и их проблемы
Выше мы упоминали такой показатель, как время до интерактивности (Time to interactive, TTI). Поговорим о нём подробнее. Ниже, на анимированном рисунке, который подготовил Кевин Шааф, можно видеть, как страница, не все материалы которой загружены и обработаны, заставляет пользователя думать, что он может выполнить некое действие, но, на самом деле, из-за того, что соответствующий JS код пока не до конца обработан, выполнить это действие нельзя.
Пользователей расстраивают страницы, которые слишком долго готовятся к работе
Для того чтобы страница была интерактивной, она должна иметь возможность быстро реагировать на воздействия пользователя. Маленькие объёмы JS-кода, приводящие страницы в действие, способствуют сокращению времени, необходимого для подготовки страниц к работе.
Если пользователь щёлкает по ссылке или прокручивает страницу, ему нужно видеть, что, в ответ на его действия, что-то происходит. Если страница не реагирует на воздействия пользователей, им это не нравится.
Вот отчёт, сформированный в тестовой среде средствами Lighthouse, содержащий набор показателей (здесь есть и Time to interactive), ориентированных на то, как страницу воспринимают пользователи.
Отчёт Lighthouse, включающий в себя показатели, отражающие восприятие страницы пользователем
Нечто подобное тому, что показано на предыдущем рисунке, происходит тогда, когда в некоем проекте используется серверный рендеринг, а клиенту передаются и результаты того, что сформировано на сервере, и JS-код, который используется для «оживления» пользовательского интерфейса (он, например, может подключать обработчики событий и некие дополнительные механизмы, отвечающие за поведение страницы).
Когда браузер занимается обработкой подобного кода, подключающего события, которые, вероятно, понадобятся пользователю, скорее всего, всё это будет выполняться в том же самом потоке, который обрабатывает пользовательский ввод. Это — так называемый главный поток.
Загрузка слишком большого объёма JavaScript-кода средствами главного потока (например, такое происходит при использовании тега <script>
) плохо влияет на время до интерактивности. Загрузка JS-кода, который планируется выполнять в веб-воркерах или кэширование с помощью сервис-воркеров, не оказывают столь сильного негативного влияния на TTI.
Вот видео, в котором показан пример работы пользователя с неудачным сайтом. Обычно пользователь может спокойно устанавливать флажки или щёлкать по ссылкам. Однако если сымитировать блокировку главного потока, пользователю уже ничего сделать не удастся — ни установить флажок, ни щёлкнуть по ссылке.
Старайтесь свести к минимуму ситуации, в которых может быть заблокирован главный поток. Вот материал, в котором можно найти подробности об этом.
Мы видим, как команды, с которыми мы сотрудничаем, страдают от того, что JavaScript воздействует на TTI на сайтах многих типов.
JavaScript способен задержать выход видимых элементов в интерактивный режим
Подобная ситуация может быть серьёзной проблемой для многих компаний. Выше показано несколько примеров из поисковика Google. На экране появляются элементы, выглядят они так, как будто с ними уже можно работать, но, если за их функционирование отвечают слишком большие объёмы JS-кода, в рабочий режим они выходят с некоторой задержкой. Это может не понравиться пользователям. В идеале нужно, чтобы всё, с чём может взаимодействовать пользователь, приходило бы в рабочее состояние как можно быстрее.
TTI и мобильные устройства
Вот показатели TTI для сайта news.google.com при использовании медленного 3G-подключения к интернету. Здесь можно посмотреть данные, на основе которых построена эта диаграмма. Измерения произведены средствами WebPageTest и Lighthouse.
TTI для news.google.com
Проанализировав эти данные, можно увидеть, что между устройствами самой низкой и самой высокой категорий наблюдается серьёзный разрыв. Так, у самого мощного устройства в тесте TTI составляет около 7 секунд. У самого простого — это уже 55 секунд.
Тут у нас возникает вопрос о том, каким должен быть показатель TTI. Мы полагаем, что стоит стремиться к тому, чтобы страницы становились бы интерактивными на устройствах среднего класса, подключённых к медленным 3G-сетям, менее чем за 5 секунд.
Стоит стремиться к TTI в 5 секунд
Возможно, тут вы скажете, что все ваши пользователи обладают топовыми телефонами и подключены к быстрым сетям. Однако, так ли это на самом деле? Возможно, некто подключён к «быстрой» Wi-Fi-сети в каком-нибудь кафе, но, на самом деле, ему доступна лишь скорость, характерная для 2G или 3G-соединений. Оценивая возможности пользователей нельзя сбрасывать со счетов разнообразие устройств и способов выхода в сеть, применяемых ими.
Влияние уменьшения размеров JS-кода на TTI
Вот несколько примеров того, как уменьшение размеров JS-кода страниц повлияло на TTI.
- Проект Pinterest уменьшил JS-бандлы с 2.5 Мб до менее чем 200 Кб. Время до интерактивности снизилось с 23 секунд до 5.6 секунд. Доход компании вырос на 44%, число подписок выросло на 753%, количество активных еженедельных пользователей мобильной версии сайта выросло на 103%.
- Сайт AutoTrader сократил JS-бандл на 56%, что привело к уменьшению TTI примерно на 50%.
- Ресурс Nikkei.com урезал размер JS-бандла на 43%, что позволило улучшить TTI на 14 секунд.
Эти результаты позволяют говорить о том, что сайты стоит проектировать в расчёте на гибкую мобильную среду, и при этом стараться, чтобы они не были привязаны к большим объёмам JavaScript-кода.
Проектировать сайты стоит в расчёте на гибкую мобильную среду
На TTI влияет много всего. Например, на этот показатель может подействовать использование для просмотра сайта мобильного устройства, сетевые возможности которого ограничены неким тарифным планом, на TTI может повлиять то, что пользователь работает через общедоступную, не особенно быструю, точку доступа Wi-Fi, или то, что мобильный пользователь находится в постоянном движении, периодически теряя сеть.
Когда с вашим сайтом работает кто-то, находящейся в ситуации, похожей на те, которые мы только что описали, и при этом, для обеспечения работоспособности ресурса требуется загрузить и обработать большой объём JS-кода, для пользователя сеанс взаимодействия с сайтом может окончиться пустым экраном. Или, если сайт всё же хоть что-то выведет на экран, может понадобиться очень долго ждать, прежде чем с ним можно будет работать. Подобные проблемы, в идеале, можно смягчить, просто сокращая размер JS-кода, необходимого для работы сайтов.
Почему JavaScript — это так дорого?
Для того чтобы понять, почему подготовка JavaScript-кода страниц к работе способна создать огромную нагрузку на систему, давайте поговорим о том, что происходит, когда сервер отправляет данные браузеру.
Итак, всё начинается с того, что пользователь вводит в адресную строку браузера URL сайта, на который он хочет попасть.
Всё начинается с ввода URL в адресную строку браузера
Затем запрос отправляется серверу, который возвращает браузеру HTML-разметку. Браузер разбирает эту разметку и находит ресурсы, необходимые для формирования страницы: CSS, JavaScript, изображения. Затем браузеру нужно всё это запросить у сервера и обработать.
Именно по такому сценарию всё происходит, например, при работе с Google Chrome.
Сложность во всём этом процессе вызывает то, что JavaScript, в итоге, оказывается узким местом всей системы. В идеале нам хотелось бы, чтобы браузер быстро вывел графическое представление страницы, после чего с ней уже можно было бы взаимодействовать. Но, если JavaScript — это узкое место, то, после вывода чего-либо на экран, пользователь вынужден беспомощно разглядывать то, с чем он не может работать.
Наша задача заключается в том, чтобы JavaScript перестал бы быть узким местом в современных сценариях взаимодействия пользователей с веб-ресурсами.
Как ускорить работу с JavaScript?
Задача ускорения JavaScript может быть разбита на несколько подзадач. Время, которое тратится на всё то, что связано с JS, складывается из времени загрузки, парсинга, компиляции и выполнения кода.
Скорость JavaScript складывается из скорости загрузки, парсинга, компиляции и выполнения кода
Всё это значит, что скорость нам нужна и при передаче кода, и при его обработке.
Если слишком много времени тратится на парсинг и компиляцию скрипта в JS-движке, это влияет на то, как скоро пользователь сможет взаимодействовать со страницей.
Рассмотрим некоторые данные о вышеупомянутых процессах. Вот подробные сведения о том, на что V8, JavaScript-движок, используемый в Chrome, тратит время при обработке страниц, содержащих скрипты.
Шаги парсинга и компиляции кода занимают 10-30% времени в V8 при загрузке страницы
Оранжевым выделены фрагменты, представляющие собой время, необходимое на парсинг кода популярных сайтов. Жёлтым представлено время компиляции. Вместе два эти этапа занимают около 30% общего времени, требуемого на обработку JS-кода страницы. 30% — это серьёзная цифра.
В Chrome 66 V8 занимается компиляцией кода в фоновом потоке, что способно сэкономить до 20% времени компиляции. Однако парсинг и компиляция всё ещё остаются чрезвычайно дорогими операциями, поэтому редко можно увидеть большой скрипт, который начинает выполняться менее чем через 50 мс. после загрузки, даже если его компиляция выполняется в фоновом потоке.
Чем JavaScript-код отличается от других ресурсов?
Стоит учитывать, что время, необходимое, например, на обработку скрипта размером 200 Кб, и на обработку изображения, имеющего такой же размер, серьёзно различается. В этом примере объём данных, передаваемых по сети, может быть одинаковым, время на их передачу тоже. Этого нельзя сказать о затратах ресурсов, необходимых на то, чтобы превратить полученные данные в нечто такое, с чем можно работать.
200 Кб JavaScript-кода и JPEG-файл такого же размера — это разные вещи
JPEG-изображение нужно декодировать, растеризовать и вывести на экран. А JS-бандл надо, если рассматривать это упрощённо, загрузить, распарсить, скомпилировать, выполнить. На самом же деле движку приходится решать и другие задачи в процессе обработки JS-кода. В целом, стоит учитывать, что на обработку JavaScript-кода, размеры которого, в байтах, сопоставимы с размерами других материалов, тратится гораздо больше системных ресурсов.
Одна из причин, по которой к вопросу скорости обработки JavaScript-кода браузерами в последнее время привлечено столько внимания, заключается в невероятном распространении мобильных технологий.
Мобильный веб и разнообразие устройств
В мире мобильного веба существуют самые разные устройства. Если попытаться, довольно упрощённо, классифицировать их по стоимости, то это телефоны начального уровня, стоящие в районе 30$, устройства среднего уровня, стоимость которых не превышает 200$, и топовые аппараты, цена которых находится в районе 1000$.
Различные мобильные устройства
Если обстоятельства сложатся удачно, то сайту придётся работать на аппаратах среднего и высшего уровня. Однако в реальности не у всех пользователей есть подобные устройства.
Так, большинство пользователей может работать в интернете с аппаратов начального и среднего уровня. При этом, даже если ограничиться этими двумя уровнями, разброс возможностей устройств, входящих в них, может быть очень большим. Скажем, процессоры в некоторых из них будут работать на полной скорости, а в некоторых их частота может быть понижена ради энергосбережения или для того, чтобы устройства не перегревались.
Сравнимые процессоры могут иметь разные размеры кэшей. В устройствах, в конце концов, могут использоваться совершенно разные процессоры, не говоря уже о других компонентах систем. Как результат, время, необходимое на обработку ресурсов, таких, как JavaScript, будет очень сильно зависеть от характеристик устройств, используемых для просмотра сайтов. Причём, телефоны начального уровня используются по всему миру, даже в США.
Вот результаты исследования используемых в настоящее время Android-смартфонов, проведённого компанией newzoo. Как видно, ОС Android установлена на 75,9% используемых телефонов, при этом ожидается, что в 2018 году к ним добавится ещё 300 миллионов устройств. Многие из этих новых устройств будут бюджетными.
Android-смартфоны в 2018 году
Посмотрим, сколько времени занимает парсинг и компиляция JavaScript на различных современных устройствах. Вот исходные данные.
Время, необходимое для обработки (парсинга и компиляции) 1 Мб несжатого JS-кода (это, например, может быть около 200 Кб минифицированного кода, сжатого с помощью gzip). Замеры произведены вручную, на реальных устройствах.
В верхней части диаграммы находятся устройства высшего класса, вроде iPhone 8, которые обрабатывают скрипты сравнительно быстро. Если спуститься пониже, то там уже находятся устройства среднего уровня, вроде Moto G4, и совсем простой Alcatel 1X. Разница между этими устройствами заметна невооружённым глазом.
Со временем телефоны, работающие под управлением Android, становятся дешевле, но не быстрее. Обычно в дешёвых телефонах используются слабые процессоры с небольшим L2/L3 кэшем. В результате, если ориентироваться на устройства высшего уровня, можно совершенно не учесть нужды среднего пользователя, таким устройством не обладающего.
Вернёмся теперь к примеру с реальным сайтом, которого мы уже касались. Измерения проведены средствами WebPageTest, вот исходные данные.
Время, необходимое для обработки JS-кода сайта cnn.com различными устройствами
На iPhone 8 (с использованием чипа A11) обработка выполняется на 9 секунд быстрее, чем на телефоне среднего уровня. Речь идёт о том, что на iPhone 8 сайт станет интерактивным на 9 секунд раньше.
Теперь, когда, когда WebPageTest поддерживает Alcatel 1X (этот телефон продавался в США примерно по 100$, его распродали на старте продаж), мы можем просмотреть «раскадровку» загрузки сайта cnn.com на телефонах начального и среднего уровня. У Alcatel 1X полная загрузка сайта с использованием 3G-сети заняла 65 секунд. Это даже не «медленно». Это просто недопустимо.
Загрузка cnn.com телефонами Alcatel 1X, Moto G gen 1, Moto G4
Это исследование подсказывает, что нам, вероятно, надо перестать считать, что все наши пользователи работают в быстрых сетях на мощных устройствах. Поэтому приложения стоит тестировать в реальных сетях и на реальных аппаратах. Разнообразие мобильных устройств, на которых приходится работать сайтам — это реальная проблема.
Не стоит считать, что все пользователи пользуются быстрыми сетями и мощными устройствами
Илья Григорик говорит, что изменчивость — это то, что убивает пользовательский опыт. Даже быстрые устройства могут иногда работать медленно. И быстрые сети могут бы медленными. Изменчивость способна сделать медленным абсолютно всё, что угодно.
В то время как изменчивость способна убить пользовательский опыт, разработка веб-проектов в расчёте на не самые производительные устройства позволяет сделать так, чтобы и «быстрые», и «медленные» пользователи чувствовали бы себя комфортно. Если ваша команда может проанализировать статистические сведения и понять, какие устройства используются для работы с вашим сайтом, это даст вам подсказку касательно того, какое устройство, вероятно, стоит держать в офисе и использовать его для тестирования сайта.
Тестировать сайты стоит на реальных устройствах, подключённых к реальным сетям
Если вариант с собственным телефоном среднего уровня вам не подходит — проект WebPageTest предлагает доступ к настроенным телефонам Moto G4 при выборе мобильных конфигураций тестирования. Там есть и другие конфигурации.
Кроме того, тесты надо проводить в сетях, в которых работают типичные пользователи. Выше я говорил о том, как важно тестировать сайты на телефонах начального и среднего уровня, а Брайан Холт сделал следующее отличное замечание на эту тему: очень важно знать свою аудиторию. Он говорит, что знание аудитории и оптимизация производительности приложения с учётом нужд аудитории — это крайне важно.
Знайте свою аудиторию
Тут надо отметить, что не каждый сайт должен хорошо работать на устройстве начального уровня, подключённом к 2G-сети. Таким образом, ориентация на производительные устройства — это тоже вполне нормальная линия поведения.
Вот отчёт, который можно получить, пройдя по пути Google Analytics → Audience → Mobile → Devices. Он демонстрирует сведения об устройствах и операционных системах посетителей сайта.
Отчёт из Google Analytics
Многие ваши пользователи могут быть в верхней части диапазона, или они могут быть распределены по разным диапазонам. Главное — знать, какие устройства используются для работы с вашими сайтами, что позволит принимать обоснованные решения о том, что важно, а что нет.
Оптимизация для медленных сетей и маломощных устройств
Если вам нужна скорость в применении к JavaScript — обращайте внимание на время загрузки материалов в медленных сетях. Вот какие улучшения можно внести в проект в данном направлении: уменьшение объёма кода, минификация кода, сжатие (с использованием gzip, Brotli, Zopfli).
Время загрузки чрезвычайно важно для тех, кто пользуется медленными сетями
Не забудьте и о кэшировании данных при обслуживании повторных визитов.
Время парсинга JavaScript — это крайне важный показатель для маломощных телефонов.
Время парсинга кода — крайне важный показатель для телефонов с медленными процессорами
Если вы являетесь бэкенд-разработчиком, или разработчиком полного цикла, вы представляете себе, какой процессор, дисковое пространство и сетевые возможности можно получить за определённую цену.
Учитывая то, что мы создаём сайты, которые всё сильнее и сильнее полагаются на JavaScript, мы иногда платим за те неоправданно большие материалы, которые отправляем пользователям, причём так, что эти расходы не всегда можно чётко распознать.
Уменьшение объёмов JavaScript-кода, отправляемого пользователям
Здесь нам подойдёт всё, что позволяет уменьшить объёмы JS-кода, отправляемого пользователям, сохранив при этом удобства, которые даёт им сайт. Один из вариантов, который делает такой сценарий возможным — это разделение кода (code splitting).
Разделение больших, монолитных JS-бандлов можно выполнять, основываясь на том, какой код необходим для вывода страницы, маршрута или компонента. Особенно хорошо, если разделение кода изначально поддерживается в используемом вами наборе инструментов.
Разделение кода — это хорошо
Идею разделения кода можно сравнить с тем, что вместо того, чтобы отправлять пользователям целую огромную пиццу, роль которой тут играет большой JS-бандл, вы отправляете им эту пиццу кусочками. Каждый такой кусочек кода содержит исключительно то, что нужно для работы текущей страницы.
Разделение кода хорошо поддерживают многие современные библиотеки и фреймворки благодаря бандлерам вроде webpack и Parcel. Руководства по выполнению подобных операций имеются для React, Vue.js и Angular.
Разделение кода в React
Рассмотрим пример добавления возможностей по разделению кода в React-приложении с использованием React loadable — компонента высшего порядка, который оборачивает динамические команды импорта в API, с которым удобно работать в React, тем самым добавляя в приложение возможности по разделению кода в конкретном компоненте.
Вот текст программы до добавления возможностей по разделению кода.
import OtherComponent from './OtherComponent';
const MyComponent = () => (
<OtherComponent/>
);
Вот изменённый код.
import Loadable from 'react-loadable';
const LoadableOtherComponent = Loadable({
loader: () => import('./OtherComponent'),
loading: () => <div>Loading...</div>,
});
const MyComponent = () => (
<LoadableOtherComponent/>
);
Результаты внедрения систем разделения кода
Многие крупные проекты уже ощутили на себе преимущества внедрения систем разделения кода.
После внедрения технологии разделения кода Twitter стал на 45% быстрее, а Tinder — на 50%
Twitter и Tinder предприняли усилия к тому, чтобы их пользователям пришлось бы, перед началом работы с их сайтами, ждать как можно меньше. В результате им, после того, как они внедрили агрессивные технологии разделения кода, удалось снизить TTI примерно на 50%.
Инструменты вроде Gatsby.js (React), Preact CLI и PWA Starter Kit стремятся задействовать разумно подобранные стандартные настройки для того, чтобы обеспечить быструю загрузку сайтов и их быстрый выход в интерактивный режим на средних мобильных устройствах.
Аудит кода
В ходе разработки многих современных сайтов, кроме того, применяют аудит кода. Рекомендуется регулярно проводить аудит JavaScript-бандлов. Например, для анализа готовых сборок можно применять отличный инструмент webpack-bundle-analyzer
. Для оценки «стоимости» зависимостей прямо в процессе разработки (то есть, сразу после того, как использована команда npm install
для установки некоего пакета и этот пакет импортирован в проект), можно, в Visual Code, применять расширение import-cost
.
Регулярно выполняйте аудит JS-кода
Итак, аудит JS-бандлов можно проводить с помощью таких инструментов, как Webpack Bundle Analyzer, Source Map Explorer и Bundle Buddy. Они позволяют оценить состав бандла и проанализировать возможности его уменьшения.
В частности, эти средства позволяют визуализировать содержимое JS-бандлов: они обращают внимание разработчика на большие библиотеки, случаи дублирования кода, и на зависимости, от которых, скорее всего, можно избавиться.
Вот примеры результатов подобного анализа (изображение взято из этого материала).
Результаты анализа
В ходе аудита бандлов часто проявляются возможности по замене тяжёлых зависимостей (вроде Moment.js с полным набором региональных стандартов) на более лёгкие (такие, как date-fns).
Если вы используете Webpack, то, возможно, вам пригодится наш репозиторий, в котором собраны сведения по типичным проблемам с библиотеками, которые возникают в бандлах.
Измерение, оптимизация и мониторинг
Если вы не знаете точно, есть ли в вашем проекте некие общие проблемы с производительностью JavaScript, проверьте его с помощью Lighthouse.
Аудит производительности с помощью Lighthouse
Недавно в Lighthouse появилось довольно много новых возможностей, направленных на аудит производительности кода, о которых вы, возможно, ещё не знаете.
Lighthouse — это средство, входящее в состав инструментов разработчика Google. Оно существует и в форме расширения для Chrome. Этот инструмент позволяет проводить глубокий анализ кода, с помощью которого можно выявить возможности по оптимизации производительности приложений. Вот фрагмент отчёта, формируемого Lighthouse.
Отчёт Lighthouse
Недавно в Lighthouse добавлена возможность выделения высоких значений показателя JavaScript boot-up time. В ходе анализа данных, влияющих на этот показатель, можно узнать о скриптах, которые слишком долго парсятся и компилируются, что откладывает переход страницы в интерактивный режим. По результатам подобного аудита можно принять меры, выражающиеся либо в разделении кода подобных скриптов, либо в снижении вычислительной нагрузки, создаваемой ими.
Кроме того, анализируя проект, надо обратить внимание на то, чтобы не отправлять пользователям неиспользуемый код.
Найти неиспользуемый CSS и JS-код можно с помощью закладки Coverage инструментов разработчика Chrome
Закладка Coverage в инструментах разработчика позволяет находить на страницах неиспользуемый CSS и JavaScript-код. В ходе анализа страницы с помощью средств этой закладки можно узнать о том, каков объём выполненного кода в сравнении с общим объёмом загруженного кода. Улучшить производительность можно, избавившись от неиспользуемого кода.
Вот описание последовательности действий, которую можно применить в ходе анализа страницы с помощью закладки Coverage. В ходе записи показателей можно взаимодействовать со страницей, это приведёт к уточнению показателей использования кода.
Инструмент Coverage может быть ценным средством для поиска возможностей по разделению кода скриптов, что позволяет отложить загрузку тех из них, которые не нужны сразу после перехода страницы в интерактивный режим, до того момента, когда они действительно понадобятся.
Паттерн PRPL
Если вы ищете какой-нибудь паттерн, позволяющий организовать эффективную доставку JS-кода пользователям, взгляните на PRPL (Push, Render, Precache, Lazy-load).
Этот паттерн реализует агрессивное разделение кода для каждого маршрута. Он использует возможности сервис-воркеров для заблаговременного кэширования JS-кода и других материалов, необходимых для обслуживания других маршрутов, и, когда в них возникает необходимость, пользуется методикой ленивой загрузки.
Паттерн PRPL направлен на улучшение производительности проектов благодаря повышению эффективности загрузки их материалов. Ресурсы критически важных маршрутов передаются сразу (Push), тут же осуществляется их обработка (Render), затем производится кэширование материалов, необходимых для оставшихся маршрутов (Pre-cache), после чего, при возникновении необходимости в них, применяется методика ленивой загрузки (Lazy-load) для данных, необходимых для обслуживания дополнительных маршрутов.
Паттерн PRPL
При использовании паттерна PRPL, когда пользователь переходит к очередному фрагменту приложения, высок шанс того, что код, необходимый для работы этого фрагмента, уже находится в кэше браузера. В результате значительно снижается время, необходимое на то, чтобы этот фрагмент перешёл в интерактивный режим.
Командная культура и бюджеты производительности
Если вас заботят вопросы производительности, или вы когда-то работали над чем-то вроде патча, улучшающего производительность сайта, вы знаете, что иногда работа может закончиться тем, что, через несколько недель после внедрения исправления, вы обнаруживаете, что кто-то в вашей команде работал над какой-то новой возможностью сайта и непреднамеренно испортил всё то, что делали вы.
После того, как в код внесены исправления, кто-то может случайно всё испортить
К счастью, есть методики, позволяющие бороться с подобными вещами. Одна из них заключается в применении бюджета производительности.
Бюджет производительности — это важно
Бюджеты производительности чрезвычайно важны, так как благодаря им вся команда проекта видит общие, чётко выраженные ориентиры. Бюджеты создают среду, когда все в команде стремятся к единой цели, что позволяет постоянно улучшать восприятие проекта пользователями и повышает уровень командной ответственности.
Бюджеты задают ограничения, поддающиеся измерению, что позволяет команде достигать целей, касающихся производительности проектов. Так как эти ограничения постоянно учитываются, производительности уделяется внимание на каждом шаге работы. Это отличается от подхода, когда в проект сначала вносят изменения, а потом осмысливают их последствия.
Если основываться на этом материале, то вот какие показатели можно включать в бюджеты производительности:
- Сроки наступления ключевых событий. Это показатели, учитывающие время наступления неких событий, основанные на TTI, на восприятии пользователем того, как загружается страница. Часто нужно объединять несколько подобных показателей для точного представления того, что происходит в ходе загрузки страницы.
- Показатели, основанные на качестве чего-либо. Эти показатели базируются на неких исходных данных (например — размер JavaScript-кода, число HTTP-запросов). Они нацелены на поддержание определённого уровня качества при взаимодействии ресурса с браузерами.
- Показатели, основанные на правилах. Это — заданные в бюджете производительности показатели результатов анализа проектов с помощью неких инструментов, вроде Lighthouse или WebPageTest. Часто они представлены неким числовым показателем или набором таких показателей, позволяющих оценить состояние сайта.
В твиттере Алекса Рассела имеется довольно большая ветка обсуждения вопросов, связанных с бюджетами производительности. Стоит обратить внимание на некоторые идеи, высказанные там:
- Готовность приостановить работу над некими новыми возможностями ради сохранения хорошего общего восприятия проекта пользователями говорит о серьёзном отношении руководства к управлению проектом.
- Стремление к производительности выражается в командной культуре, поддержанной соответствующими инструментами. Браузеры оптимизируют, настолько, насколько это возможно, HTML и CSS. Сосредоточение усилий на JavaScript перекладывает соответствующую нагрузку на команды и на используемые ей инструменты.
- Бюджеты создают не для того, чтобы терроризировать разработчиков. Их применение ведёт к саморегуляции команд. Бюджеты помогают командам ограничивать пространство решений и позволяют достигать поставленных целей.
Каждый, кто влияет на то, как пользователи воспринимают сайт, воздействует и на производительность.
Производительность — это то, о чём надо говорить
Стремление к производительности обычно выражается в усилиях по построению новой командной культуры, а не в решении неких чисто технических вопросов.
Вопросы производительности стоит обсуждать в ходе командных встреч и других подобных мероприятий. О том, что такое, в их понимании, «производительность», стоит спрашивать и у представителей компании, для которой разрабатывается веб-проект. Например, стоит узнать, есть ли у них понимание того, как производительность веб-ресурса может повлиять на показатели эффективности бизнеса, которые их интересуют. У команды инженеров стоит поинтересоваться тем, как они планируют решать проблемы узких мест, влияющих на общую производительность проекта. Хотя ответы на эти вопросы могут оказаться не особенно полезными, они помогают начать обсуждение вопросов производительности.
Эллисон Макнайт говорит, что производительность должна соответствовать бизнес-целям компаний, для которых создают веб-проекты. Им надо продемонстрировать, как производительность может повлиять на интересующие их показатели.
Эллисон Макнайт предлагает информировать владельцев компаний о том, как производительность веб-проектов влияет на бизнес
Вот какой план действий можно предложить для формирования бюджетов производительности.
- Нужно прийти к пониманию того, что «производительность» значит для конкретного проекта. Видение понятия «хорошая производительность» можно оформить в виде небольшого документа, отражающего позицию представителей компании и разработчиков.
- Надо составить бюджеты производительности. После того, как удалось прийти к пониманию того, что такое «хорошая производительность» и сформировать соответствующий документ, из этого документа надо извлечь ключевые индикаторы эффективности (KPI, Key Performance Indicators) и сформировать на их основе реалистичные, поддающиеся измерению, целевые показатели. Например — нечто вроде «Страница должна загрузиться и выйти в интерактивный режим за 5 секунд». Ограничения на размеры можно оформлять в такой форме: «Размер минифицированного и сжатого JS-кода не должен превышать 170 Кб».
- Нужно регулярно составлять отчёты по сформированным KPI. Такие отчёты, например, можно регулярно предоставлять руководству компании, отмечая в них достигнутые в ходе совершенствования проекта результаты.
Книги Web Performance Warrior и Designing for Web Performance — это отличные работы, которые посвящены формированию командной культуры, в которой уделяет особое внимание производительности проектов.
Инструменты для работы с бюджетами производительности
Поговорим об инструментах для работы с бюджетами производительности. При применении методики непрерывной интеграции для оценки показателей бюджета можно использовать средства проекта Lighthouse CI.
Например, ниже показано, как эта система не даёт включать в код проекта пулл-запросы в том случае, если применение того, что в них содержится, приводит к падению оценки проекта ниже некоего значения. Для контроля параметров бюджетов, кроме того, можно использовать пакет Lighthouse Thresholds.
Контроль показателей бюджета производительности проекта
Множество сервисов мониторинга производительности поддерживают настройку бюджетов и соответствующих оповещений. В их число входят Calibre, Treo, Webdash и SpeedCurve.
Вот пример контроля бюджетов для моего сайта teejungle.net с использованием сервиса SpeedCurve, который поддерживает множество показателей.
Контроль показателей бюджета производительности проекта с использованием SpeedCurve
Использование бюджетов заставляет команды серьёзно размышлять над последствиями любого принимаемого решения, идёт ли речь о самом начале работы, о фазах проектирования, или о неких этапах, завершающих работу над проектом.
Если вы хотите больше узнать о контроле бюджетов, взгляните на этот материал U.S Digital Service, в котором раскрыты подходы к контролю производительности проектов с использованием LightHouse путём задания целей и бюджетов для различных показателей вроде TTI.
Анализ производительности сайта
Обратите внимание на то, что, при анализе производительности сайта нужно иметь в своём распоряжении и реальные данные, и данные, полученные в ходе специально проводимых экспериментов.
Для того чтобы проверить, как JavaScript влияет на пользовательский опыт в RUM-окружении (Real User Monitoring, мониторинг реальных пользователей), существуют два подхода, проникающих в веб-среду, о которых мы сейчас поговорим. Полевые данные, или RUM-данные, это сведения о производительности, собранные в ходе реальных загрузок страниц, выполняемых настоящими пользователями в их обычных рабочих средах. Сайты, имеющие большие объёмы JavaScript-кода, имеет смысл исследовать с точки зрения нагрузки, создаваемой кодом на главный поток с использованием API Long Tasks и показателя First Input Delay.
Анализ RUM-данных
- API Long Tasks позволяет собирать реальные данные о задачах (и о связанных с ними скриптах), выполнение которых занимает более 50 мс., что может привести к блокировке главного потока. Благодаря этому API сведения о таких задачах можно записывать и анализировать.
- Показатель First Input Delay (FID, задержка до первого события ввода) позволяет измерять время, проходящее с момента первого взаимодействия пользователя с сайтом (например, с момента, когда он щёлкнул по некоей кнопке), до момента, когда браузер может отреагировать на это воздействие. FID — это пока не особенно распространённый показатель, но у нас имеется соответствующий полифилл.
Благодаря этим двум средствам вы сможете собрать достаточно данных от реальных пользователей, позволяющих оценить проблемы с производительностью JavaScript, с которыми они сталкиваются.
Сторонние и собственные скрипты
У Марселя Фрейнбихлера есть один вирусный твит о том, что USA Today предлагает своим европейским пользователям облегчённую версию своего сайта. Она загружается на 42 секунды быстрее полной версии.
Твит о различиях полной и облегчённой версий сайта USA Today
Хорошо известно, что сторонние JS-библиотеки могут оказывать огромное влияние на скорость загрузки страниц. С этим никто не спорит, но тут надо отметить, что и скрипты, созданные самим разработчиком сайта, оказывают такой же эффект. Если мы стремимся к быстрой загрузке страниц, нам нужно уменьшить воздействие на этот процесс как сторонних, так и собственных скриптов.
В этой сфере можно наблюдать множество оплошностей. Например, в теге <head>
страницы может быть использован блокирующий скрипт, выполняющий A/B-тестирование, который выясняет, что именно нужно показать пользователям для того, чтобы наилучшим образом их обслужить. Или в похожем сценарии в браузер могут отправляться скрипты для абсолютно всех вариантов использования сайта, при том, что реально нужен лишь один из них.
Если вас интересуют дополнительные сведения, касающиеся использования сторонних скриптов на ваших сайтах, взгляните на этот материал.
Итоги
Разработка высокопроизводительного сайта — это процесс, похожий на путешествие. Множество мелких изменений способно привести к серьёзным результатам.
Улучшение производительности веб-проекта — это путь
На пути к повышению производительности веб-проекта постарайтесь, чтобы пользователь сталкивался с как можно меньшим количеством помех при взаимодействии с сайтом.
Попытайтесь реализовать по-настоящему полезный функционал, обойдясь минимальным объёмом JS-кода. Постепенно улучшая свой проект, вы, в результате, сможете добиться того, что он будет работать быстро и надёжно, и того, что пользователи будут вам за это благодарны.
Уважаемые читатели! Какие показатели вы используете, оценивая производительность собственных веб-проектов?
Автор: ru_vds