javascript

Цена JavaScript

  • пятница, 1 декабря 2017 г. в 03:13:42
https://habrahabr.ru/post/343562/
  • Тестирование веб-сервисов
  • Разработка веб-сайтов
  • Клиентская оптимизация
  • Высокая производительность
  • JavaScript


По мере того как наши сайты всё сильнее зависят от JavaScript, приходится расплачиваться за то, что мы отправляем пользователям. Иногда цена не видна с первого взгляда. В этой статье я объясню, почему полезно проявить немного дисциплины, если вы хотите ускорить загрузку и производительность на мобильных устройствах.

tl;dr: меньше кода = меньше парсинг/компиляция + меньше передача + меньше распаковка

Сеть


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



Это может стать проблемой даже в странах первого мира, поскольку эффективный тип сетевого соединения у пользователя необязательно 3G, 4G или WiFi. Вы можете сидеть в кафе с WiFi, но быть подключённым к хотспоту через сотовую связь со скоростью 2G.

Расходы на сетевую передачу JavaScript можно сократить следующими способами:

  • Передавать только код, который нужен пользователю. Здесь поможет разделение кода на части.
  • Минификация (Uglify для ES5, babel-minify или uglify-es для ES2015).
  • Максимальное сжатие (используя Brotli ~q11, Zopfli или gzip). Brotli превосходит gzip по степени сжатия. На CertSimple он уменьшил код JS на 17%, а LinkedIn сократил время загрузки на 4%.
  • Удалить неиспользуемый код. Его можно выявить инструментом покрытия кодом в DevTools. Об удалении ненужного кода см. tree-shaking, продвинутые оптимизации Closure Compiler и соответствующие плагины для библиотек, такие как lodash-babel-plugin или Webpack ContextReplacementPlugin для библиотек вроде Moment.js. Используйте babel-preset-env и browserlist, чтобы избежать транспиляции в современных браузерах. Продвинутые разработчики могут заняться аккуратным анализом бандлов Webpack на предмет удаления ненужных зависимостей.
  • Кэширование для сокращения сетевого трафика. Определите оптимальное время жизни скриптов (max-age) и обеспечьте токены валидации (ETag), чтобы не передавать неизменившиеся байты. Кэширование сервис-воркеров может сделать ваше приложение устойчивым перед сбоями сети и предоставит желанный доступ к функциям вроде кэширования кода в V8. Узнайте о долговременном кэшировании путём хэширования имён файлов.



Лучшие советы по поводу того, как сократить количество кода JavaScript, передаваемого пользователям

Парсинг/компиляция


После скачивания кода JavaScript одной из главнейших затрат становится время разбора/компиляции этого кода в JS-движке. В Chrome DevTools парсинг и компиляция — часть жёлтого времени Scripting на панели Performance.



На вкладках Bottom-Up и Call Tree можно посмотреть точное время парсинга и компиляции:


Панель Chrome DevTools Performance > Bottom-Up. При активированной функции V8 Runtime Call Stats показывается время, потраченное на этапах парсинга и компиляции

Но почему это важно?



Большой расход времени на разбор и компиляцию кода может сильно увеличить задержку на взаимодействие пользователя с сайтом. Чем больше кода JavaScript вы отправляете, тем дольше будет продолжаться этап разбора и компиляции, прежде чем сайт начнёт откликаться на действия пользователя.



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

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


Байты JavaScript и изображений расходуют ресурсы по-разному. Изображения обычно не блокируют основной поток и не замораживают интерфейс во время декодирования и растрирования. А вот JS способен повлиять на интерактивность из-за парсинга, компиляции и выполнения кода.

Когда мы говорим о медленном разборе и компиляции, важен контекст —  здесь мы говорим о средних мобильных телефонах. У обычных пользователей могут быть телефоны с медленными CPU и GPU, без кэша L2/L3 и даже с ограниченным объёмом памяти.

Возможности сети и возможности устройства не всегда совпадают. У пользователя на отличном оптоволоконном канале необязательно отличный CPU для разбора и оценки JavaScript, который приходит на его устройство. Верно и обратное… ужасное сетевое соединение, но исключительно быстрый процессор.  —  Кристофер Бакстер, LinkedIn

В статье «Производительность старта JavaScript» я отметил время разбора примерно 1 мегабайта распакованного (простого) кода JavaScript на слабом и на мощном железе. Между самыми быстрыми смартфонами и средними разница в 2–5 раз по времени парсинга/компиляции.


Время парсинга бандла JavaScript размером 1 МБ (~250 КБ в gzip) на ПК и мобильных устройствах разного класса. При учёте расходов на парсинг нужно прибавить ещё расходы на распаковку файла размером примерно 250 КБ в код JS размером примерно 1 МБ

Что насчёт реальных сайтов, вроде CNN.com?

Современный iPhone 8 тратит примерно 4 с на парсинг/компиляцию скриптов CNN, по сравнению с ~13 с на среднем телефоне (Moto G4). Это может значительно повлиять на то, сколько времени пройдёт, прежде чем пользователь получит возможность взаимодействовать с сайтом.


Время парсинга на чипе Apple A11 Bionic по сравнению со Snapdragon 617 в обычном смартфоне Android

Это показывает важность тестирования на среднем оборудовании (вроде Moto G4), а не только на телефоне, который лежит у вас в кармане. Но важен контекст: оптимизируйте для устройств и сетевых условий, которые есть именно у ваших пользователей.



Инструменты аналитики могут показать, мобильные устройства какого класса у реальных пользователей вашего сайта. Это даёт возможность понять реальные ограничения CPU/GPU.

Неужели мы действительно отгружаем слишком много JavaScript? Ну, возможно :)

HTTP Archive (топ около 500 тыс. сайтов) показывает использование JavaScript на мобильных сайтах. И мы видим, что 50% сайтам требуется больше 14 секунд, прежде чем они становятся интерактивными. Эти сайты тратят до четырёх секунд только на разбор и компиляцию JS.



Если учесть ещё время, нужное для загрузки и обработки JS и других ресурсов, то ничего удивительного, что пользователи покидают страницу, не дождавшись её «разморозки». Здесь определённо есть пространство для улучшения.

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

Время выполнения


Свою цену имеют не только разбор и компиляция. Выполнение JavaScript (исполнение кода после парсинга/компиляции) — одна из операций, которая выполняется в основном потоке. Большое время выполнения тоже влияет на то, как скоро пользователь сможет взаимодействовать с вашим сайтом.



Если скрипт выполняется дольше 50 мс, время до начала взаимодействия с сайтом увеличивается на всё время, которое требуется для скачивания, компиляции и выполнения JS  — Алекс Расселл

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

Способы ускорения доставки JavaScript


Если цель сократить время парсинга/компиляции JavaScript и передачи по сети, есть некоторые полезные техники, такие как разделение на части по проходам (route-based chunking) и PRPL.

PRPL — это техника оптимизации интерактивности путём агрессивного разделения кода на фрагменты и кэширования:



Посмотрим, какой эффект можно получить.

Мы проанализировали время загрузки популярных мобильных сайтов и прогрессивных веб-приложений при помощи V8 Runtime Call Stats. Как видим, время парсинга (показано оранжевым) составляет значительную часть временны́х издержек:



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

Другие издержки


JavaScript может повлиять на производительность разными способами:

  • Память. Страницы могут двигаться рывками или часто затормаживаться из-за сборки мусора. Когда браузер требует память, исполнение JS приостанавливается, так что частая сборка мусора браузером может останавливать выполнения чаще, чем хотелось бы. Избегайте утечек памяти и частых пауз на сборку мусора, чтобы страницы двигались плавно.
  • Длительное выполнение JavaScript блокирует основной поток, из-за чего страница замораживается. С этой проблемой можно бороться, разбивая код на части с помощью requestAnimationFrame() requestIdleCallback().

Прогрессивное бутстрапирование


Многие сайты оптимизируют видимость контента за счёт интерактивности. Чтобы быстро начать отрисовку при больших бандлах JavaScript, разработчики иногда применяют рендеринг на стороне сервера; затем «обновляют» картинку, когда JavaScript наконец-то обработан.

Будьте осторожны — у всего есть своя цена. Вы 1) по сути отправляете HTML-ответ большего размера, что может повлиять на интерактивность; 2) можете оставить пользователя в правдоподобной «зловещей долине», где на самом деле половина интерактивности недоступна, пока не завершится обработка JavaScript.

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


Прогрессивное бутстрапирование, иллюстрация Пола Льюиса

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

Выводы


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

В разных компаниях добились определённого успеха, введя жёсткие бюджеты производительности на время передачи, парсинга и компиляции JavaScript. См. руководство Алекса Рассела по бюджету для мобильных сайтов и приложений «Мы можем это позволить?: Бюджеты производительности в реальном мире».


Полезно рассмотреть, какое пространство в JS оставляют принимаемые нами архитектурные решения для логики приложения

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

Дополнительно


Моё выступление на Chrome Dev Summit 2017 о цене JavaScript. Позже производительность анализируется на примере реальных площадок вроде Pinterest и Tinder