Оптимизация запуска JavaScript

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

Сеть

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

Когда браузер запрашивает ресурс, его нужно получить, а затем распаковать. В случае с такими ресурсами, как JavaScript, они должны быть проанализированы и скомпилированы перед выполнением.

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

Уменьшить стоимость передачи кода JavaScript по сети можно следующими способами:

  • Отправка только того кода, который нужен пользователю.
    • Используйте разделение кода, чтобы разбить JavaScript на критически важные и некритичные части. Сборщики модулей, такие как webpack, поддерживают разделение кода.
    • Отложенная загрузка некритичного кода.
  • Минификация
  • Сжатие
    • Для сжатия текстовых ресурсов используйте как минимум gzip.
    • Рекомендуем использовать Brotli ~q11. Brotli превосходит gzip по степени сжатия. Это помогло CertSimple сэкономить 17 % на размере сжатых байтов JS, а LinkedIn – 4 % на времени загрузки.
  • Удаление неиспользуемого кода.
    • Определите, какой код можно удалить или загрузить в отложенном режиме, с помощью инструментов разработчика.
    • Используйте babel-preset-env и browserlist, чтобы не транспилировать функции, которые уже есть в современных браузерах. Опытные разработчики могут провести анализ пакетов webpack, чтобы выявить ненужные зависимости.
    • Для удаления кода используйте tree-shaking, расширенные оптимизации Closure Compiler и плагины для обрезки библиотек, такие как lodash-babel-plugin или ContextReplacementPlugin для библиотек вроде Moment.js.
  • Кеширование кода для минимизации сетевых запросов.
    • Используйте кеширование HTTP, чтобы браузеры эффективно кешировали ответы. Определите оптимальное время жизни скриптов (max-age) и предоставьте токены проверки (ETag), чтобы избежать передачи неизмененных байтов.
    • Кеширование Service Worker может сделать ваше приложение устойчивым к сетевым сбоям и предоставить вам доступ к таким функциям, как кеш кода V8.
    • Используйте долгосрочное кеширование, чтобы не загружать повторно ресурсы, которые не изменились. Если вы используете Webpack, ознакомьтесь с информацией о хешировании названий файлов.

Синтаксический анализ и компиляция

После скачивания JavaScript одна из самых больших затрат – это время, необходимое движку JS для анализа/компиляции этого кода. В инструментах разработчика в Chrome синтаксический анализ и компиляция относятся к желтому разделу "Скрипты" на панели "Производительность".

ALT_TEXT_HERE

На вкладках Bottom-Up (Снизу вверх) и Call Tree (Дерево вызовов) показано точное время синтаксического анализа и компиляции.

ALT_TEXT_HERE
Инструменты разработчика в Chrome > панель "Производительность" > "Снизу вверх". Если включить статистику вызовов во время выполнения V8, мы сможем увидеть время, затраченное на такие этапы, как синтаксический анализ и компиляция.

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

ALT_TEXT_HERE

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

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

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

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

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

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

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

ALT_TEXT_HERE
На этом графике показано время синтаксического анализа пакета JavaScript размером 1 МБ (около 250 КБ в сжатом виде) на компьютерах и мобильных устройствах разных классов. При оценке стоимости синтаксического анализа следует учитывать распакованные цифры, например, ~250 КБ сжатого JS распаковывается примерно в 1 МБ кода.

А как насчет реального сайта, например CNN.com?

На мощном iPhone 8 синтаксический анализ и компиляция JS-кода сайта CNN занимают около 4 секунд, а на среднем телефоне (Moto G4) – около 13 секунд. Это может значительно повлиять на то, насколько быстро пользователь сможет полностью взаимодействовать с этим сайтом.

ALT_TEXT_HERE
На диаграмме выше показано время синтаксического анализа для чипа Apple A11 Bionic и Snapdragon 617, который используется в большинстве устройств Android.

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

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

Действительно ли мы отправляем слишком много JavaScript? Возможно :)

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

ALT_TEXT_HERE

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

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

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

Затраты могут быть связаны не только с синтаксическим анализом и компиляцией. Выполнение JavaScript (запуск кода после синтаксического анализа или компиляции) – одна из операций, которые должны выполняться в основном потоке. Кроме того, из-за долгого выполнения кода пользователь может не сразу получить возможность взаимодействовать с сайтом.

ALT_TEXT_HERE

Если скрипт выполняется дольше 50 мс, время до интерактивности задерживается на весь период, необходимый для скачивания, компиляции и выполнения JS. Алекс Рассел (Alex Russell)

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

Прочие затраты

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

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

Шаблоны для снижения стоимости доставки JavaScript

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

PRPL

PRPL (Push, Render, предварительное кеширование, Lazy-load) – это шаблон, который оптимизирует интерактивность за счет агрессивного разделения кода и кеширования:

ALT_TEXT_HERE

Давайте представим, как это может повлиять на ситуацию.

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

ALT_TEXT_HERE

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

Постепенная загрузка

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

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

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

ALT_TEXT_HERE
Прогрессивная начальная загрузка, Пол Льюис (Paul Lewis)

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

Выводы

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

Команды разработчиков добились успеха, приняв строгие бюджеты производительности, чтобы сократить время передачи и синтаксического анализа/компиляции JavaScript. Рекомендуем ознакомиться со статьей Алекса Рассела (Alex Russell) Can You Afford It?: Real-world Web Performance Budgets" (Бюджеты производительности сайтов в реальных условиях).

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

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

Подробнее…