Сколько стоит JavaScript?

Перевод «The Cost Of JavaScript» Эдди Османи.

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

TL;DR Меньше кода = меньше парсить и компилировать + меньше передавать + меньше распаковывать

Сеть

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

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

Вы можете снизить затраты на JavaScript следующими способами:

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

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

После загрузки, JS-движок тратит уйму времени на парсинг и компиляцию кода. В Chrome DevTools время парсинга и компиляции показаны желтым в панели Perfomance.

Вкладки Bottom-Up и Call Tree позволят оценить время, затраченное на эти задачи:

Панель Performanc в Chrome DevTools: Bottom-Up.

Но, почему это имеет значение?

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

Байт за байтом, JavaScript является более ресурсозатратным для браузера, чем изображения или шрифты того же размера.
Том Дэйл

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

170 КБ сжатого JS против байтов JPEG на Moto G4 в медленной сети.

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

Когда мы говорим о медленном выполнении парсинга и компиляции, не стоит забывать, что мы рассматриваем среднестатистическое мобильное устройство. Однако многие пользователи могут иметь телефоны с медленными ЦП и графическими процессорами, без кэша L2 или L3 и могут быть ограничены памятью.

Возможности сети и устройства не всегда совпадают. Пользователь с удивительно быстрым подключением не обязательно имеет лучший процессор для анализа и исполнения JavaScript, отправленного на его устройство. Это также верно и наоборот: ужасное сетевое подключение, но невероятно быстрый процессор.
Кристофер Бакстер, LinkedIn

При проведении тестов скорости JavaScript, я замерял стоимость парсинга ≈1 МБ обычного (несжатого) JavaScript на медленных и высокопроизводительных устройствах. Среднестатистическое устройство тратит на парсинг и компиляцию кода в 2–5 раз больше времени, чем флагманские устройства.

Время парсинга для 1Мб JS бандла (≈250KB gzipped) на настольных и мобильных устройствах различных классов.

А что если мы проверим реально существующий сайт, например CNN?

iPhone 8 потребуется для этого всего 4 секунды, в то время как для среднего по характеристикам смартфона (в нашем случае, Moto G4), необходимо около 14 секунд. Это существенно повлияет на то, как быстро пользователь сможет полностью взаимодействовать с этим сайтом.

Сравнение производительности чипа A11 Bionic и Shapdragon 617.

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

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

А мы точно отправляем слишком много JavaScript? Может я ошибся :)

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

Много времени требуется для анализа и обработки JS и других ресурсов, и не удивительно, что пользователям приходится ждать, прежде чем начать взаимодействовать со страницей. Мы определённо можем ускорить этот процесс.

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

Время исполнения

Это не просто парсинг и компиляция. Исполнение JavaScript (выполнение кода после анализа и компиляции) — одна из операций, которая должна выполняться в основном потоке. Этот процесс так же влияет на то, как скоро пользователь сможет взаимодействовать с сайтом.

Если скрипт исполняется более 50 мс, то время до интерактивности задерживается на всё время, необходимое для загрузки, компиляции, и исполнения.
Алекс Рассел

Для решения этой проблемы JavaScript разбивается на небольшие фрагменты, чтобы избежать блокировки основного потока. Посмотрим, можно ли сократить объем выполняемой работы во время выполнения.

Паттерны для уменьшения стоимости JS

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

PRPL — это паттерн, основанный на агрессивном разделении и кэшировании кода.

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

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

Другие факторы

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

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

Прогрессивная загрузка

Многие сайты оптимизируют видимую часть контента. Для быстрой первоначальной отрисовки, при наличии JS большого размера, разработчики разделяют этот процесс на два этапа: выполняют первоначальный рендер на сервере, а затем подгружают JavaScript.

Будьте осторожны — это тоже требует ресурсов. Как правило, передается более крупный HTML-ответ, который может ограничить вашу интерактивность. Во-вторых, вы частично лишаете пользователя возможности взаимодействия с интерфейсом, пока JavaScript не подгрузится.

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

Progressive Bootstrapping, иллюстрация Пола Льюиса.

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

Подведём итоги

Размер передаваемой информации критически важен для сетей с медленной скоростью. Время парсинга важно для устройств, со слабым ГП. Запомните это.

Команды разработчиков добиваются успеха устанавливая для себя жёсткие ограничения производительности (бюджет быстродействия), соблюдение которых заключается в минимизации затрат на передачу, парсинг и компиляцию JS-кода. Читайте «Can You Afford It? Real-world Web Performance Budgets» Алекса Рассела для более подробного изучения темы.

Полезно посмотреть, какой объём выделяется для логики приложения.

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

Подробнее

Доклад Эдди Османи на Chrome Dev Summit 2017 о цене использования JS.

Отдельное спасибо Нолану Лоусону, Кристоферу Бакстеру и Джереми Вагнеру.