Оптимизация First Input Delay
Tags: webdev, javascript, performance, metrics
Published: 2020-05-20 17:03:44

Вольный перевод статьи [Optimize First Input Delay](https://web.dev/optimize-fid/)

Как быстрее реагировать на взаимодействие с пользователем.

> Я нажал, но ничего не произошло! Почему я не могу взаимодействовать с этой страницей? 😢

[First Contentful Paint](https://web.dev/fcp/) (FCP) и [Largest Contentful Paint](https://web.dev/lcp/) (LCP) - это метрики, которые измеряют время, необходимое для визуального отображения (paint) контента на странице. Также важно, что время "рисования" не учитывает *реакцию на нагрузку*: или то, как быстро страница реагирует на взаимодействие с пользователем.

[First Input Delay](https://web.dev/fid/) (FID) - это метрика [Core Web Vitals](https://web.dev/vitals/), которая фиксирует первую реакцию пользователя об интерактивности и отзывчивости сайта. Она измеряет разницу во времени с момента, когда пользователь впервые взаимодействует со страницей, до времени, когда браузер действительно способен ответить на это взаимодействие. FID является [метрикой поля](https://web.dev/user-centric-performance-metrics/#in-the-field) (input) и не может быть смоделирован в лабораторной среде. **Реальное взаимодействие с пользователем** требуется для измерения задержки ответа.

![First Input Delay. Критерии](https://web.dev/vitals/fid_8x2.svg)

Чтобы помочь предсказать FID в [лаборатории](https://web.dev/how-to-measure-speed/#lab-data-vs-field-data), мы рекомендуем [Total Blocking Time](https://web.dev/tbt/) (TBT). Они измеряют разные вещи, но улучшения в TBT обычно соответствуют улучшениям в FID.

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

# Выполнение массивного Javascript'a

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

* [Сократите время выполнения JavaScript](https://web.dev/optimize-fid/#reduce-javascript-execution)
* [Разбейте длинные задачи](https://web.dev/optimize-fid/#long-tasks)
* [Оптимизируйте свою страницу для готовности к взаимодействию](https://web.dev/optimize-fid/#optimize-interaction-readiness)
* [Используйте web worker](https://web.dev/optimize-fid/#web-worker)

## Сократите время выполнения JavaScript

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

Чтобы уменьшить количество выполняемого JavaScript'а на вашей странице:

* Минифицируйте и сожмите JavaScript файлы
* Отложите неиспользуемый JavaScript 
* Минимизируйте неиспользованные полифилы

Помимо минимизации JavaScript, [разбиение долго выполняющегося кода на более мелкие задачи](https://web.dev/optimize-fid/#long-tasks) может позволить браузеру быстрее запускать обработчики ввода, что может улучшить FID.

### Минифицируйте и сожмите JavaScript файлы

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

[Terser](https://github.com/terser/terser) поддерживает ES6+ синтаксис и может использоваться для минимизации современных JavaScript файлов без необходимости их транспиляции. Если вы используете bundler модулей и хотите включить Terser в свою цепочку инструментов:

* Webpack и Parcel уже минимизируют использование Terser по умолчанию в production режиме
* Если вы используете Rollup, подключите плагин [rollup-plugin-terser](https://www.npmjs.com/package/rollup-plugin-terser)

В дополнение к минификации, сжимайте ваши JavaScript ресурсы, чтобы минимизировать размер их доставки. Если возможно, используйте [Brotli](https://opensource.googleblog.com/2015/09/introducing-brotli-new-compression.html), который обеспечивает лучшие результаты сжатия, чем [Gzip](https://www.youtube.com/watch?v=whGwm0Lky2s&feature=youtu.be&t=14m11s), и может использоваться [практически во всех новых браузерах](https://caniuse.com/#feat=brotli).

### Отложите неиспользуемый JavaScript

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

На вкладке [Coverage](https://developers.google.com/web/tools/chrome-devtools/coverage) в Chrome DevTools можно узнать, сколько JavaScript не используется на вашей веб-странице.

![Coverage tab](https://dev-to-uploads.s3.amazonaws.com/i/93tbt1dlgcd4gfd9wauj.png)

Чтобы сократить неиспользуемый JavaScript:

* Разделите (code-split) ваш bungle на несколько частей (chunks)
* Отложите любой некритический JavaScript, включая сторонние скрипты, используя async или defer

**Code-splitting** - это концепция разбиения одного большого JavaScript файла на более мелкие порции, которые можно загрузить по условию (концепция называется lazy loading - отложенная загрузка). Большинство новых браузеров поддерживают динамический синтаксис импорта, который позволяет извлекать модули по требованию:

```javascript
import('module.js')	
  .then((module) => {	
    // Логика вашего модуля
  });	
```

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

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

* Если вы используете [webpack](https://webpack.js.org/guides/code-splitting/), [Rollup](https://medium.com/rollup/rollup-now-has-code-splitting-and-we-need-your-help-46defd901c82) или [Parcel](https://parceljs.org/code_splitting.html) в качестве bundler'а, воспользуйтесь их динамической поддержкой импорта.
* Клиентские инфраструктуры, такие как [React](https://reactjs.org/docs/code-splitting.html#reactlazy), [Angular](https://angular.io/guide/lazy-loading-ngmodules) и [Vue](https://vuejs.org/v2/guide/components-dynamic-async.html#Async-Components), предоставляют абстракции, облегчающие отложенную загрузку на уровне компонентов.

### Минимизируйте неиспользованные полифилы

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

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

Для оптимизации использования полифилов на вашем сайте:

* Если вы используете [Babel](https://babeljs.io/docs/en/index.html) в качестве транспилятора, используйте [@babel/preset-env](https://babeljs.io/docs/en/babel-preset-env), чтобы включить только те полифилы, которые необходимы для браузеров, для которых вы планируете разработку. В Babel 7.9 включите опцию [bugfixes](https://babeljs.io/docs/en/babel-preset-env#bugfixes), чтобы дополнительно сократить ненужные полифилы.
* Используйте шаблон module/nomodule для доставки двух отдельных bundl'ов (@babel/preset-env также поддерживает это через [target.esmodules](https://babeljs.io/docs/en/babel-preset-env#targetsesmodules))

```html
<script type="module" src="modern.js"></script>	
<script nomodule src="legacy.js" defer></script>	
```

Многие новые функции ECMAScript, скомпилированные Babel'ем, уже поддерживаются в средах, поддерживающих JavaScript модули. Таким образом, вы упростите процесс проверки того, используется только тот код, который нужен браузерам.

## Разбейте длинные задачи

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

[Длинные задачи](https://web.dev/custom-metrics/#long-tasks-api) - это периоды выполнения JavaScript'а, когда пользователи могут обнаружить, что ваш пользовательский интерфейс не отвечает. Любой фрагмент кода, который блокирует основной поток на 50мс или более, можно охарактеризовать как длинную задачу. Длинные задачи являются признаком потенциального раздувания JavaScript'а (загрузка и выполнение большего, чем может потребоваться пользователю прямо сейчас).

Разделение длинных задач может уменьшить задержку ввода на вашем сайте.

![Вкладка Performance в Chrome Dev Tools. Длинная задача](https://webdev.imgix.net/optimize-fid/long-task.png)

FID должен заметно улучшиться по мере того, как вы применяете лучшие практики, такие как разделение кода и разбиение ваших длинных задач. Хотя TBT не является метрикой полей, он полезен для проверки прогресса в конечном итоге к улучшению как Time To Interactive (TTI), так и FID.

## Оптимизируйте свою страницу для готовности к взаимодействию

Существует ряд распространенных причин плохой оценки FID и TBT в веб-приложениях, которые сильно зависят от JavaScript

### Выполнение первых скриптов задерживают взаимодействие с пользователем

* Увеличение размера JS, большое время выполнения и неэффективное разбиение на блоки могут замедлить скорость реакции страницы на ввод данных пользователем и повлиять на FID, TBT и TTI. Прогрессивная загрузка кода и функций могут помочь разделить эту работу и повысить готовность к взаимодействию.
* Отрендеренные приложения на стороне сервера могут выглядеть так, как будто они быстро рисуют пиксели на экране, но остерегайтесь взаимодействия с пользователем, которое блокируется выполнением больших скриптов (например, регидрации для подключения event listeners). Это может занять несколько сотен миллисекунд, иногда даже секунд, если используется code splitting на основе маршрутов. Попробуйте сместить больше логики на сторону сервера или генерировать больше контента статически во время сборки.

### Загрузка данных может влиять на многие аспекты готовности взаимодействия

* Ожидание каскадных загрузок (например, JS и загрузки данных для компонентов) может повлиять на задержку взаимодействия. Цель минимизировать зависимость от каскадных загрузок данных.
* Большие встроенные хранилища данных могут увеличить время парсинга HTML и повлиять на показатели рисования и взаимодействия. Стремитесь свести к минимуму объем данных, подлежащих последующей обработке на стороне клиента.

### Выполнение стороннего скрипта также может задержать задержку взаимодействия

* Многие сайты содержат сторонние теги и аналитику, которые могут поддерживать занятость сети и периодически отключать основной поток, что влияет на задержку взаимодействия. Выполняйте загрузку стороннего кода по необходимости (например, возможно, не загружайте рекламные объявления, пока станица не прокрутится ближе к их области видимости).
* В некоторых случаях сторонние script'ы могут вытеснять основные script'ы с точки зрения приоритета и пропускной способности в главном потоке, а также задерживать скорость готовности страницы к взаимодействию. Попытайтесь расставить приоритеты при загрузке, что, по вашему мнению, в первую очередь предлагает пользователям наибольшую ценность

## Используйте web worker

Блокированный основной поток является одной из основных причин задержки ввода. Web worker'ы позволяют запускать JavaScript в фоновом потоке. Перемещение операций, не связанных с пользовательским интерфейсом, в отдельный рабочий поток может сократить время блокировки основного потока и, следовательно, улучшить FID.

Рассмотрите возможность использования следующих библиотек, чтобы упростить использование web worker'ов на вашем сайте:

* [Comlink](https://github.com/GoogleChromeLabs/comlink): вспомогательная библиотека, которая абстрагирует postMessage и упрощает использование
* [Workway](https://github.com/WebReflection/workway): экспортер web worker'а общего назначения
* [Workerize](https://github.com/developit/workerize): переместить модуль внутр web worker'а