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

Это может быть проблемой, поскольку эффективный тип сетевого подключения пользователя может не быть 3G, 4G или Wi-Fi. Например, вы можете подключиться к Wi-Fi в кафе, но при этом использовать мобильную точку доступа со скоростью 2G.
Уменьшить стоимость передачи кода JavaScript по сети можно следующими способами:
- Отправка только того кода, который нужен пользователю.
- Используйте разделение кода, чтобы разбить JavaScript на критически важные и некритичные части. Сборщики модулей, такие как webpack, поддерживают разделение кода.
- Отложенная загрузка некритичного кода.
- Минификация
- Используйте UglifyJS для минификации кода ES5.
- Чтобы уменьшить размер кода ES2015+, используйте babel-minify или uglify-es.
- Сжатие
- Удаление неиспользуемого кода.
- Определите, какой код можно удалить или загрузить в отложенном режиме, с помощью инструментов разработчика.
- Используйте 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 синтаксический анализ и компиляция относятся к желтому разделу "Скрипты" на панели "Производительность".

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

Если на синтаксический анализ и компиляцию кода уходит много времени, это может значительно задерживать начало взаимодействия пользователя с сайтом. Чем больше JavaScript вы отправляете, тем больше времени потребуется на его анализ и компиляцию, прежде чем ваш сайт станет интерактивным.
Браузеру сложнее обрабатывать код JavaScript, чем изображение или веб-шрифт того же размера (Том Дейл).
По сравнению с JavaScript, обработка изображений эквивалентного размера требует больших затрат (их все равно нужно декодировать!), но на среднем мобильном оборудовании JS с большей вероятностью негативно повлияет на интерактивность страницы.
Когда мы говорим о медленном синтаксическом анализе и компиляции, важен контекст – мы имеем в виду среднестатистические мобильные телефоны. У обычных пользователей могут быть телефоны с медленными центральными и графическими процессорами, без кэша L2/L3 и даже с ограниченным объемом памяти.
Возможности сети и устройства не всегда совпадают. У пользователя с отличным оптоволоконным подключением может быть не самый мощный ЦП, который не справляется с обработкой и выполнением кода JavaScript, отправляемого на устройство. Это также верно и в обратном случае: ужасное сетевое подключение, но невероятно быстрый процессор. Кристофер Бакстер, LinkedIn
Ниже показано время, которое требуется для синтаксического анализа 1 МБ распакованного (простого) кода JavaScript на устройствах с низкой и высокой производительностью. Время, необходимое для синтаксического анализа и компиляции кода, на самых быстрых телефонах в 2–5 раз меньше, чем на средних.
А как насчет реального сайта, например CNN.com?
На мощном iPhone 8 синтаксический анализ и компиляция JS-кода сайта CNN занимают около 4 секунд, а на среднем телефоне (Moto G4) – около 13 секунд. Это может значительно повлиять на то, насколько быстро пользователь сможет полностью взаимодействовать с этим сайтом.
Это подчеркивает важность тестирования на среднем оборудовании (например, Moto G4), а не только на телефоне, который может быть у вас в кармане. Однако не забывайте, что оптимизировать сайт нужно для устройств и сетей, которыми пользуются ваши посетители.
Действительно ли мы отправляем слишком много JavaScript? Возможно :)
Согласно данным HTTP Archive (около 500 000 самых популярных сайтов), JavaScript на мобильных устройствах загружается более 14 секунд на 50 % сайтов. Эти сайты тратят до 4 секунд только на синтаксический анализ и компиляцию JS.

Если учесть время, необходимое для получения и обработки JS и других ресурсов, неудивительно, что пользователям приходится ждать, пока страницы будут готовы к использованию. Мы можем сделать лучше.
Удаление некритичного кода JavaScript со страниц может сократить время передачи, синтаксический анализ и компиляцию, требующие больших ресурсов процессора, а также потенциальные накладные расходы на память. Это также помогает быстрее сделать страницы интерактивными.
Время выполнения
Затраты могут быть связаны не только с синтаксическим анализом и компиляцией. Выполнение JavaScript (запуск кода после синтаксического анализа или компиляции) – одна из операций, которые должны выполняться в основном потоке. Кроме того, из-за долгого выполнения кода пользователь может не сразу получить возможность взаимодействовать с сайтом.

Если скрипт выполняется дольше 50 мс, время до интерактивности задерживается на весь период, необходимый для скачивания, компиляции и выполнения JS. Алекс Рассел (Alex Russell)
Чтобы избежать этого, JavaScript разбивается на небольшие фрагменты, которые не блокируют основной поток. Подумайте, можно ли уменьшить объем работы, выполняемой во время выполнения.
Прочие затраты
JavaScript может влиять на эффективность страницы и другими способами:
- Память. Страницы могут часто зависать или приостанавливаться из-за сборки мусора. Когда браузер освобождает память, выполнение JS приостанавливается, поэтому браузер, часто собирающий мусор, может приостанавливать выполнение чаще, чем нам хотелось бы. Избегайте утечек памяти и частых пауз в сборке мусора, чтобы страницы не зависали.
- При выполнении кода JavaScript, требующего много времени, основной поток может быть заблокирован, из-за чего страницы перестанут отвечать на запросы. Разбиение работы на более мелкие части (с использованием
requestAnimationFrame()илиrequestIdleCallback()для планирования) может минимизировать проблемы с отзывчивостью, что может помочь улучшить Interaction to Next Paint (INP).
Шаблоны для снижения стоимости доставки JavaScript
Чтобы сократить время синтаксического анализа, компиляции и передачи по сети для JavaScript, можно использовать такие шаблоны, как разбиение на основе маршрутов или PRPL.
PRPL
PRPL (Push, Render, предварительное кеширование, Lazy-load) – это шаблон, который оптимизирует интерактивность за счет агрессивного разделения кода и кеширования:

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

Wego, сайт, использующий PRPL, умудряется поддерживать низкое время синтаксического анализа для своих маршрутов, очень быстро становясь интерактивным. Многие из упомянутых выше сайтов используют разделение кода и бюджеты производительности, чтобы снизить затраты на JS.
Постепенная загрузка
Многие сайты оптимизируют видимость контента за счет интерактивности. Чтобы ускорить первую отрисовку при наличии больших пакетов JavaScript, разработчики иногда используют отрисовку на стороне сервера, а затем "обновляют" ее, чтобы прикрепить обработчики событий, когда JavaScript наконец будет получен.
Будьте осторожны — это имеет свои издержки. 1) вы отправляете более крупный HTML-ответ, который может повлиять на интерактивность; 2) пользователь может оказаться в ситуации, когда половина интерфейса не будет интерактивной, пока не завершится обработка JavaScript.
В этом случае лучше использовать прогрессивную начальную загрузку. Отправьте страницу с минимальной функциональностью (состоящую только из HTML/JS/CSS, необходимых для текущего маршрута). По мере поступления ресурсов приложение может выполнять отложенную загрузку и разблокировать дополнительные функции.
Загрузка кода, пропорционального тому, что находится в поле зрения, – это святой Грааль. PRPL и прогрессивная начальная загрузка – это шаблоны, которые могут помочь в этом.
Выводы
Размер передаваемых данных критически важен для сетей с низкой пропускной способностью. Время синтаксического анализа важно для устройств, производительность которых ограничена мощностью процессора. Важно, чтобы эти показатели были низкими.
Команды разработчиков добились успеха, приняв строгие бюджеты производительности, чтобы сократить время передачи и синтаксического анализа/компиляции JavaScript. Рекомендуем ознакомиться со статьей Алекса Рассела (Alex Russell) Can You Afford It?: Real-world Web Performance Budgets" (Бюджеты производительности сайтов в реальных условиях).
Если вы создаете сайт, ориентированный на мобильные устройства, постарайтесь разрабатывать его на репрезентативном оборудовании, сократите время синтаксического анализа и время компиляции JavaScript и примите бюджет производительности, чтобы ваша команда могла следить за затратами на JavaScript.
Подробнее…
- Chrome Dev Summit 2017 – рекомендации по современной загрузке
- Производительность при запуске JavaScript
- Solving the web performance crisis – Нолан Лоусон (Nolan Lawson)
- Можете ли вы себе это позволить? Реальные бюджеты производительности — Алекс Рассел (Alex Russell)
- Оценка веб-фреймворков и библиотек – Кристофер Бакстер (Kristofer Baxter)
- Результаты экспериментов Cloudflare со сжатием Brotli (обратите внимание, что динамическое сжатие Brotli с более высоким качеством может задерживать начальную отрисовку страницы, поэтому тщательно оцените его). Вместо этого лучше использовать статическое сжатие.
- Будущее производительности — Сэм Сакконе (Sam Saccone)