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

Адди Османи
Addy Osmani

Поскольку мы создаем сайты, в большей степени зависящие от 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% времени загрузки.
  • Удаление неиспользуемого кода .
    • Определите возможности для кода, который можно удалить или отложенно загрузить с помощью DevTools Code Coverage .
    • Используйте Babel-preset-env и Browserlist, чтобы избежать переноса функций, уже имеющихся в современных браузерах. Опытные разработчики могут обнаружить, что тщательный анализ пакетов веб-пакетов помогает выявить возможности для удаления ненужных зависимостей.
    • Для удаления кода см. Tree-shaking , расширенные оптимизации Closure Compiler и плагины обрезки библиотек, такие как lodash-babel-plugin или ContextReplacementPlugin веб-пакета для таких библиотек, как Moment.js.
  • Кэширование кода для минимизации сетевых отключений.
    • Используйте HTTP-кэширование , чтобы браузеры эффективно кэшировали ответы. Определите оптимальные сроки жизни сценариев (max-age) и предоставьте токены проверки (ETag), чтобы избежать передачи неизмененных байтов.
    • Кэширование Service Worker может повысить устойчивость сети вашего приложения и предоставить вам быстрый доступ к таким функциям, как кэш кода V8 .
    • Используйте долгосрочное кэширование, чтобы избежать необходимости повторно получать ресурсы, которые не изменились. Если вы используете Webpack, см. хеширование имен файлов .

Разбор/компиляция

После загрузки одна из самых тяжелых затрат JavaScript — это время, необходимое движку JS для анализа/компиляции этого кода. В Chrome DevTools анализ и компиляция являются частью желтого времени «Сценарии» на панели «Производительность».

ALT_TEXT_ЗДЕСЬ

На вкладках «Снизу вверх» и «Дерево вызовов» показано точное время синтаксического анализа/компиляции:

ALT_TEXT_ЗДЕСЬ
Панель Chrome DevTools «Производительность» > «Снизу вверх». Если включена статистика вызовов во время выполнения V8, мы можем видеть время, потраченное на такие этапы, как анализ и компиляция.

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

ALT_TEXT_ЗДЕСЬ

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

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

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

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

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

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

Ниже мы можем увидеть стоимость анализа ~1 МБ распакованного (простого) JavaScript на низком и высокопроизводительном оборудовании. Разница во времени анализа/компиляции кода между самыми быстрыми телефонами на рынке и средними телефонами составляет 2–5 раз .

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

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

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

ALT_TEXT_ЗДЕСЬ
Выше мы видим время анализа, сравнивающее производительность чипа Apple A11 Bionic и Snapdragon 617 на более среднем оборудовании Android.

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

ALT_TEXT_ЗДЕСЬ
Google Analytics может предоставить информацию о классах мобильных устройств, с которых ваши реальные пользователи заходят на ваш сайт. Это может дать возможность понять реальные ограничения ЦП/ГП, с которыми они работают.

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

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

ALT_TEXT_ЗДЕСЬ

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

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

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

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

ALT_TEXT_ЗДЕСЬ

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

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

Другие расходы

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

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

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

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

ПРПЛ

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

ALT_TEXT_ЗДЕСЬ

Давайте представим, какой эффект это может оказать.

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

ALT_TEXT_ЗДЕСЬ

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

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

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

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

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

ALT_TEXT_ЗДЕСЬ
Прогрессивная загрузка , Пол Льюис

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

Выводы

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

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

ALT_TEXT_ЗДЕСЬ
Полезно подумать, какой «запас» JS могут оставить нам принимаемые нами архитектурные решения для логики приложения.

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

Узнать больше