Оптимизируем веб с Виталием Фридманом: скорость загрузки, память, CPU
Это второй пост о всевозможных трюках во фронтенд-разработке. В нем затронут вопрос оптимизации сайта, скорости его загрузки на устройствах с недостаточным объемом оперативной памяти и медленным CPU.
Отдельный разговор про инструменты, которые помогут ускорить и контролировать работу сайта и сторонних приложений, которые к нему подключены. Плюс рассказ об особенностях верстки email-рассылок, который вас немало удивит.
В основе материала — расшифровка доклада Виталия с конференции HolyJS 2018 Piter.
Первая часть тут: Оптимизируем веб с Виталием Фридманом, — компрессия, картинки, шрифты, фичи HTTP/2 и Resource Hints.
Вторая часть:
Горчица и медленные устройства
В этой части речь пойдет о производительности. Как ее повысить? Какое-то время мы использовали технику «Сutting the mustard». Суть ее в том, чтобы с помощью сниппета узнать, со старым или с новым браузером мы имеем мы дело. В зависимости от этого определялось, какой скрипт и стили загружать, чтобы не грузить их ненужным функционалом.
Пример сниппета для определения браузера
Потом появилась возможность заменить последовательность из querySelector, localStorage и addEventListener на visibility state API — и именно так в основном поступали в маленьких проектах. Но с этой техникой была проблема — та же, что бывает со всеми остальными техниками, в том числе и теми, которые используете вы.
Проблема заключается в том, что когда мы хотим дать красивые стили новому браузеру, мы не знаем, насколько хорошо он их отрисует, потому что это зависит от устройства, на котором он запущен. Дело в том, что есть устройства из low-end сегмента (например, Motorola Moto G4), которые имеют мало оперативной памяти и слабый процессор. Тем не менее, на них установлен новый браузер, который поддерживает все (ну или почти все) технологии, которые у нас есть.
Поэтому использовать эту технику сегодня уже нельзя.
По следам Google с Moto G4 в кармане
Почему бы не сделать так, чтобы responsive-верстка отображалась только на тех устройствах, который «потянут» ее в части аппаратных ресурсов? Для этого мы можем использовать Device Memory API. Если же данный API недоступен, можно откатиться до «cutting the mustard».
А вот и сам Moto G4:
На рисунке ниже можно увидеть, где находится Moto G4 в рейтинге телефонов по критерию времени, за которое парсится javascript. Это середнячок — и таких телефонов много. По сравнению с лидером рейтинга, этому телефону нужно в 16 раз больше времени для парсинга.
Если посмотреть на данные по сайтам на мобильных телефонах, видно, что больше всего времени тратится именно на парсинг JS.
Желтым отмечено время парсинга страницы
На самом деле, это очень большая проблема. Для того, чтобы запарсить 1 мегабайт скриптов, Moto G4 требуется 35 секунд. Учитывая то, что большинство сайтов использует лишь 40% из всего скрипта на сайте, можно попытаться найти выход из этого положения, дабы снизить время на парсинг. В Gmail, например, комментировали весь код и отправляли в качестве текстовой переменной, а затем, когда этот код требовался, делался eval этой переменной.
Рекомендации Google гласят, что есть так называемый Time to Interactive Budget, промежуток времени, в течение которого большинство пользователей ждет возможности взаимодействовать с сайтом — и это всего 5 секунд. Учитывая, что 1,6 секунды уходят на сетевые взаимодействия, у нас остаются лишь 3,4 секунды — то есть, если взять среднюю скорость соединения 400kbps, за это время мы можем отправить максимум 170KB данных. Это немного для того, чтобы отправить что-либо, пригодное для немедленного использования. Учитывая, что сюда входят фреймворки, утилиты, critical path, js, css, html, такой объем данных — это очень и очень мало.
Как уместить ядро приложения в 170 Кбайт? Google рекомендует много разных метрик, которые можно использовать, они делят загрузку на этапы по времени: time-to first byte (navigation begins) — до передачи первого байта, first paint — до загрузки первой картинки, first contentful paint — до появления навигации на сайте, first meaningful paint — когда почти все готово по контенту, visualy ready — до загрузки всей страницы, time to interactive — до возможности взаимодействовать со страницей, fully loaded — когда страница полностью готова.
В целом для каждого сайта такая метрика будет особой, поэтому нам для нашего проекта тоже нужно составить свою. Итак, как же выглядит оптимальная метрика (performance baseline) на сегодняшний день?
На изображении указано, какие аспекты и цели нужно принимать во внимание при разработке своей метрики.
Как повысить скорость загрузки?
Что полезно использовать в борьбе за время загрузки? Например, code-coverage tool — чтобы смотреть покрытие кодом страницы. А еще он показывает, какой процент кода не используется.
Еще можно применить JavaScript Bundle Auditing. Если вы используете какие-то третьи библиотеки — можно убрать их из runtime, воспользовавшись, например, webpack-libs-optimizations. И, конечно, нужно отказаться от gif. По-возможности отказаться от видео, так как парсинг самого контейнера для видео тоже занимает длительное время. Если отказаться от видео не получается — использовать для его размещения <img src
> контейнер.
Еще если у вас отзывчивые картинки на странице, можно использовать Responsive Image Breakpoints Generator. Он поможет сгенерировать из загруженной вами картинки или группы картинок изображения для responsive верстки. Вы можете указать, на сколько килобайт вам нужна картинка, с помощью движков генератора. Кроме того, инструмент генерирует саму разметку на сайт.
Если в вашей картинке есть какая-то важная часть, например, лицо или какой-то объект, он сделает сropping так, как вам нужно, что очень удобно.
Допустим, у вас очень много картинок. Тогда можно прибегнуть к помощи LazySizes. Это библиотека, которая делает все то же, что предыдущий инструмент, но только при помощи JavaScript.
Можно посмотреть, куда уходит время, когда происходит парсинг.
Интересный инструмент — Priority hints — в нем вы можете задать браузеру, что важно загрузить раньше, а что позже.
Но что же делать с critical CSS? Он не обязательно нужен, если у вас хороший сервер и CDN, браузеры пытаются открыть еще одно соединение, если это HTTP/1. А если это HTTP/2, они пытаются «догадаться», какой CSS нужен, а какой нет. Поэтому стоит протестировать, как работает версия с critical CSS и как — если critical CSS сохранен как отдельный файл в root.
Guess.js
Да, у нас есть webpack, bundling, chunks. Но что, если есть инструмент, который с помощью алгоритмов предугадывания и машинного обучения помогает предугадать, какие chunks будут нужны на следующей итерации пользовательского взаимодействия? Guess.js с помощью predictive анализа, на основе данных google-аналитики, может понять, какое следующее действие совершит пользователь, и загрузить именно тот кусок кода, который необходим для этого взаимодействия.
Для webpack есть Guess плагин, который можно попробовать.
Все это происходит на глобальном уровне: появляются все больше сервисов на основе машинного обучения и искусственного интеллекта, которые позволяют улучшать жизнь не только пользователей, но и разработчиков. Такой инструмент есть, например, в Airbnb: air/shots, поисковая система, которую могут использовать дизайнеры и разработчики этой компании. В ней можно находить компоненты по тегам, смотреть их связи и выбирать необходимые для реализации проекта.
Очень впечатляет функция для дизайнеров: дизайнер рисует эскиз на бумаге, подносит его к камере, и для него автоматически проектируется необходимый интерфейс из заранее разработанных компонентов. Работа практически полностью автоматизирована.
Еще один совет: внимательно выбирайте фреймворк для разработки. Допустим, нам нужно учесть следующие вещи: network transfer, parse/compile, runtime cost. Конечно, для этого есть инструменты, например, можно протестировать, как ваше приложение будет работать в разных сетях передачи данных: 2G, 3G, Wi-Fi.
Проблема заключается в том, что HTTP/2, конечно, хороший новый стандарт, но всегда он быстрее, чем HTTP/1, во-вторых, он значительно медленнее на медленных соединениях, особенно если это мобильное устройство. Еще одна проблема, которая в нем есть, это server push: если есть server push — это было бы идеальной заменой для critical CSS. В таком случае пользователь, запрашивая index.html, получал бы его и в довесок critical.css. Но как только мы запрашиваем страницу с сервера, последний не всегда знает, есть ли она уже в кэше.
Поэтому сейчас разрабатывается механизм под названием cache-digest: если мы первый раз заходим на страницу, происходит server push, если no push — no repeat. Если это не первый заход на страницу, сервер все равно производит push. Это проблема, которую в Google решили исправить с помощью QUIC. Это надстройка над HTTP, которая сделаем механизм более продуманным, она работает вместо TCP на UDP протоколе.
В QUIC много интересных вещей: он быстрее на быстрых соединениях, на 4G. В то же время он медленнее на медленных соединениях. Более того, так как он использует UDP, то требует больших ресурсов CPU в случае с JavaScript. Это отражает следующее изображение:
Service workers
Может быть, они смогут нам помочь? Судя по статистике, они дают ощутимый прирост производительности, если использовать их для кэширования. Первое — оптимизируем шрифты, второе — настраиваем service workers.
Как создать service worker? Можно использовать PWA Builder, он даже сгенерирует для вас иконку и манифест, чтобы ваше приложение было более прогрессивным.
Есть замечательный сайт PWA Stats, который собирает различные истории и кейсы по работе с PWA.
Сторонние скрипты — шаг в сторону зла?
Будет ли толк от улучшения производительности, если вы установили на сайт сторонние скрипты, они подгрузили еще скрипты со стороны и в итоге перегрузили и устройство пользователя?
Мы даже не знаем, какие данные собирают эти скрипты. Они могут подгружать динамические ресурсы, которые могут меняться между загрузками страниц. Таким образом ни хосты, ни ресурсы, которыми пользуются сторонние приложения, нам неизвестны. А если мы загружаем их в тег script, они и вовсе получают доступ ко всей информации, которая есть на сайте.
Для того, чтобы узнать эту информацию, есть инструмент request map.
В нем видно, куда уходят запросы, видно ресурсы, которые выделяются под эти запросы. Вы особенно удивитесь, если обнаружится, что сторонний скрипт использует CPU устройства, чтобы майнить для кого-то биткоины. Сюрприз!
Chrome пытается блокировать недобросовестную рекламу своим собственным блокировщиком. На основе положений коалиции о лучшей рекламе.
Есть еще такое явление как GDPR — соглашение о порядке обработки персональных данных. Оно гласит, что если среди аудитории вашего сайта есть жители Европы, то вы должны соблюдать соглашение по обработке их персональных данных, удалять их по требованию, описывать каждый cookie-файл, который вы храните у себя. Если сторонний скрипт нарушает это соглашение и возникнут проблемы — вы будете отвечать и нести материальную ответственность.
Так как понять, чем занимаются сторонние скрипты, пока вы на них не смотрите? Пользоваться инструментами для их контроля, например, с помощью requestmap.webperf.tools мы можем провести аудит сайта и сторонних скриптов. Посмотреть статистику позже можно будет по ID: requestmap.webperf.tools/render/[ID]
На странице внизу есть ссылка по загрузке CSV файла с отчетом:
Потом можно пропарсить данные через терминал:
И получить код, который нужно вставить в блок web page test, после чего замерить, какая разница в производительности между приложением со сторонними скриптами и без них. Разница будет очевидна. Это не значит, что их нужно удалять. Просто нам надо понять, как их оптимизировать.
Как водится, для этого тоже есть инструменты. Можно вставить этот CSV в Excel и получить довольно подробный обзор того, какие у вас есть сторонние приложения, сколько они «весят», сколько грузятся. А еще интереснее будет попробовать blackhole сервер, его IP:
Чтобы узнать, как будет вести себя ваше приложение в случае, если все сторонние приложения уйдут в тайм-аут, добавьте в файл hosts этот ip и посмотрите, что произойдет с вашим приложением.
Еще один совет: никогда не добавляйте third-party скрипты через тег script. Лучше делать это через iframe, потому что тогда у них не будет доступа в DOM на вашей странице. Кстати, в iframe есть свойство sandbox, в котором можно указать, что именно скрипт на странице делать может, а что нет. Была даже создана спецификация Safe Frame, которая говорит об изоляции внешнего скрипта от данных приложения и контроле его деятельности. Если вас интересует эта тема, информацию по ней можно найти на github, проект safeframe.
Используйте service worker для того, чтобы заблокировать или удалить сторонний скрипт, если он завис.
При помощи Intersection Observer можно посмотреть, отобразилась ли реклама, когда находилась рядом с Viewport. Это позволяет загружать рекламные блоки, когда пользователь при просмотре страницы находится неподалеку от рекламного блока. Расстояние до рекламного блока можно указывать даже в пикселах.
Об этом вы можете прочитать в статьях Дениса Мишунова «Now You See Mee: How To Defer, Lazy-Load And Act With Intersection Observer», Harry Roberts «It«s My (Third) Party, and I«ll Cry if I Want To», Yoav Weiss «Taking back control over third-party content».
Отзывчивые email
У верстки email-рассылки свои особенности и правила. Теги img у клиентов без отображения картинок в письмах надо на что-то менять, поэтому используются атрибуты alt и другая черная магия.
Для нормальной верстки писем используются table-header-group, table-footer-group и другие атрибуты таблиц. Но что, если хочется чего-то новенького, например media queries: в каких-то браузерах они-таки поддерживаются, но с мобильными версиями email-клиентов дела обстоят хуже. Что, если использовать:
Вот пример такой магии:
Какое итоговое значение будет у класса box? Зависит от ситуации, по спецификации читаем так: если width-значение больше, чем max-width, max-width в приоритете.
Но если min-width, больше чем width или max-width, то применяется min-width.
Это можно использовать, когда медиа-запросы не поддерживаются. Вот пример: как сделать из четырех колонок две на мобильном? Ответ:
Грубо говоря, значение width определяет, max или min-width победит. Это же шикарный хак!
А как сделать интерактивное письмо? Например, добавить туда live twitter feed. Да, это возможно! Есть картинка со всеми твитами, которые есть, генерируется на сервере каждые 2 секунды, мы просто анимируем ее и все. Вот вам и решение.
Как сделать интерактивный email, в котором вы можете провести весь заказ и выбор товара? Посмотрите на изображение и вы все поймете:
Логика, завязанная на label, checked и input«ах. Получится довольно длинная цепочка получится, но, тем не менее, это возможно сделать. Вот пример со статистикой использованных элементов. Обратите внимание, цена тоже считается, через counter и инкременты:
Казалось бы, фантастика, оказывается, через email можно привести человека к покупке. Но не все так просто, у email есть ограничения по количеству символов (12000), поддержке: checked и размер письма ограничен 102kb.
Тем не менее, это главные тренды в маркетинге на сегодняшний день, и это видно на графиках:
Вот почему в Google создали AMP. Он нужен, чтобы автоматически генерировать нужную разметку в письме под ваши желания.
Вариабельные шрифты
Laurence Penney делал полезный доклад на эту тему, обязательно посмотрите.
Вот комментарий Hakon Wium Lie, одного из тех, кто писал спецификацию к шрифтам:
Звучит так: «Одна из причин, почему мы выбрали использовать трехзначные номера (в спецификации для значение font-weight) в поддержке промежуточных значений в будущем. И будущее уже наступило».
Очень большая проблема со шрифтами в Азии: с учетом общего большого количества иероглифов и их разных начертаний. Подгрузка разных шрифтов для иероглифов могла бы превратиться в большую проблему с производительностью. Благо, сейчас у нас есть итерполируемые шрифты, для которых можно указать как раз таки одно из трехзначных значений, о которых говорил писавший спецификацию человек.
Дизайнеру достаточно создать два начертания символа — очень толстое, для значения 1000, и очень тонкое, для значения 1. Все остальные начертания создаются автоматически, когда нужно. Но это справедливо не только для оси width, также это работает и с height.
Еще сюрприз: оси мы можем создавать сами без особого труда. Использовать для этого в CSS надо font-variation-settings свойство. Оно задает значения для высокоуровневых свойств, таких как font-weight, font-stretch и других.
Вот пример настройки дизайнером шрифтов нужного шрифта:
А что, если это где-то не поддерживается? Нужно использовать какой-то формат шрифта, позаботиться об откате, для старых версий браузеров, и подумать об отзывчивом поведении — правильно выставить значения для разных экранов. Таким образом, используем формат WOFF2 для шрифтов, он более прогрессивный. Для старых версий браузер подберет сам нужный шрифт, правда, это означает некоторое отсутствие контроля за начертанием.
Резюмируем
Подводя итоги, можно сказать, что спецификации принимаются намного быстрее, чем раньше. Проходит два месяца и появляются новые стандарты, подходы, приложение. Но это нормально, так и должно быть, это повод развиваться дальше.
Если доклад понравился, обратите внимание: 24–25 ноября в Москве состоится новая HolyJS, и там тоже будет много интересного. Уже известная информация о программе — на сайте, и билеты можно приобрести там же.