Какие-то приложения грузятся быстро, какие-то медленно, но за счет чего это происходит? Только ли скорость загрузки страницы является показателем производительности приложения?
Ответить на эти и многие другие вопросы в одной статье было бы очень сложно. Поэтому я собрал каталог ссылок и разбил его на категории. Но для начала — немного теории о том, что такое производительность и когда о ней стоит задуматься.
Когда возникают проблемы с производительностью
Вы можете годами разрабатывать веб-приложения и практически не сталкиваться с проблемами производительности приложений.
Но, вероятнее всего, проблемы возникают в следующих ситуациях:
- Появляются большие данные (нужно рендерить большие списки или сотни тысяч точек на карте).
- Приложение становится большим (сотни пользовательских сценариев, десятки экранов, формы и так далее).
- Большое количество клиентов из разных регионов (например, 300 000+ клиентов в день из самых разных уголков планеты).
- Высокая конкуренция на рынке (наверняка пользователь предпочтет приложение вашего конкурента, если оно работает быстрее).
- Необходима мобильная версия (до сих пор браузеры на мобильных устройствах страдают от проблем с производительностью).
Из чего состоит производительность
Глобально проблемы с производительностью веб-приложений можно разделить на две категории: передача данных и runtime.
- Под передачей данных подразумевается загрузка любых ресурсов, необходимых для работы приложения.
- Под runtime — работа приложения, рендеринг и обработка пользовательского ввода.
Каждая из этих категорий содержит в себе очень много нюансов, о которых мы часто не задумываемся, но которые отличают качественные приложения от некачественных.
Вот самые популярные метрики производительности веб-приложений (все должны быть минимальными):
Загрузка страницы
- TTFB — время до получения первого байта (Time to First Byte).
- FCP — время первой отрисовки контента (First Contentful Paint).
- FMP — время первой значимой отрисовки (First Meaningful Paint).
- TTI — время до момента, когда страница может реагировать на пользовательский ввод (Time to interactive).
Runtime
- Время отклика на пользовательский ввод.
- Время перерисовки интерфейса.
Хоть TTFB и TTI и относятся к метрикам производительности загрузки страницы, на них могут влиять и проблемы, связанные с runtime.
Как искать и анализировать проблемы с производительностью
Основным набором инструментов в арсенале разработчика является Chrome DevTools или аналогичные инструменты, например Firebug/Firefox developer tools.
Про них можно писать отдельные статьи, но мы ограничимся самыми важными моментами.
Network — позволяет подробно проанализировать, какие ресурсы загружаются на странице, с каких ресурсов, с какой скоростью и так далее. Этот инструмент часто используется для ручного анализа скорости загрузки страницы.
Performance — в этой вкладке можно включить запись вызовов исполнения кода, операций ввода/вывода и других. Помимо этого запись можно делать с эмуляцией троттлинга сети и CPU. Например, проверить работу приложения на слабых устройствах.
Lighthouse — инструмент, встроенный в Chrome DevTools, который запускает загрузку страницы, записывает метрики, делает по ним анализ и даже выдает рекомендации по улучшению производительности.
Как измерять/мониторить производительность
Инструменты для мониторинга производительности веб-приложений можно разделить на две категории: те, что производят синтетические измерения и те, что записывают данные производительности с реальных пользователей.
- К инструментам для синтетического мониторинга можно отнести Lighthouse и Webpagetest.
- Для мониторинга реальных пользователей (RUM — real user monitoring) подойдут mPulse и Sematext.
Помимо этого есть инструменты наподобие Webpack-bundle-analyzer, которые нельзя отнести к этим двум категориям. Но с их помощью можно измерять некоторые параметры, которые влияют на производительность, например размер бандла.
Передача данных
TCP connection, DNS lookup — ускорить загрузку страницы можно даже за счет правильной конфигурации подключений к серверу. В частности, если использовать DNS pre-fetching или даже IP-адреса вместо доменных имен.
TTFB (Time to First Byte). Время до получения первого байта — это важная метрика. Для ее ускорения нужно стараться реализовывать как можно меньше логики на сервере перед выдачей index.html.
HTTP1 vs HTTP2 — HTTP2 может сильно ускорить загрузку страницы за счет мультиплексирования или сжатия заголовков. Помимо этого, новый (относительно) протокол открывает кучу возможностей, например server push.
Domain sharding. Если для запросов к API вам нужно передавать много HTTP заголовков, а для запросов к статике — нет, то лучше разделить их по разным доменам.
CDN (content delivery network) поможет ускорить загрузку для территориально распределенных клиентов.
Resource prioritization (preload, prefetch, preconnect) — это ускорение загрузки страницы за счет правильной стратегии загрузки ресурсов. Браузеры позволяют устанавливать приоритеты для разных типов ресурсов и загружать раньше то, что важно для первой отрисовки.
Static compression: GZIP and Brotli. Brotli — это алгоритм сжатия, который уменьшит вес статики и, соответственно, увеличит скорость загрузки. А вот отличное решение от моего коллеги.
Webp vs Png & Jpg. Webp — отличная альтернатива Png. Помимо меньшего веса изображений, Webp не уступает по качеству. Сейчас этот формат поддерживает примерно 78% браузеров. Но даже если вам нужна 100% поддержка, можно реализовать фоллбэк на Png с помощью тэга picture.
Runtime
Tasks, Microtasks. С помощью правильной приоритизации выполнения кода можно избавиться от «фризов» и ускорить реагирование на пользовательский ввод.
requestIdleCallback — полезная функция, которая позволяет выполнить код в свободное время в конце кадра (фрейма/тика) или когда пользователь неактивен. Поможет избавиться от всех тех же лагов и «фризов».
requestAnimationFrame позволяет правильно запланировать анимацию и максимально увеличить шансы на рендеринг 60 кадров в секунду.
ES2015 vs ES5. ES2015 предоставляет много функций, более производительных, чем ES5.
DOM manipulation. Манипуляции с DOM — дорогие, выполнять их нужно аккуратно и осмысленно. Например, не вызывать querySelector() в цикле, если это можно сделать одним вызовом.
Render blocking resource. Загрузка некоторых ресурсов может блокировать рендеринг. Чтобы этого избежать, нужно пользоваться атрибутами defer, async, preload.
60 FPS by pointer-events: none — отличный хак, с помощью которого можно достичь 60 FPS при скролле страницы. Работает очень просто: на время прокрутки отключаются все обработчики события мыши.
Passive event listener — способ сделать плавную прокрутку страницы на сенсорных экранах. Коротко говоря, браузер имеет несовершенный флоу обработки слушателей touch events. Если при создании обработчика события установить параметр passive, то браузер будет однозначно понимать, что обработчик не будет отменять прокрутку и рендерить, не дожидаясь его завершения.
Virtual scroll — умный способ не рендерить большие списки, а генерировать их при прокрутке. Позволяет потреблять меньше памяти и облегчить скроллинг списков.
Avoid large Complex Layouts and Layout Thrashing. Layout/reflow — это дорогие операции, которые выполняют много пересчетов параметров рендеринга. Чтобы избегать их частого вызова, нужно правильно строить верстку и манипулировать DOM.
What forces layout / reflow — шпаргалка, в которой можно найти список функций и параметров, работа с которыми вызывает layout/reflow.
Сборка
Tree shaking — убираем неиспользуемый код из бандла и ускоряем загрузку страницы.
Code splitting — разделив код на чанки, можно оптимизировать первую загрузку и открыть возможность загружать части кода «лениво».
Obfuscation может уменьшить размер бандла и даже немного поможет скрыть ваш код от чужих глаз.
Архитектура
Server side rendering — наверное, самый известный способ сделать так, чтобы SPA рендерилось сразу, при первой загрузке. Это важное требование для некоторых поисковых систем (и не только).
Lazy loading images and video (+native) — нативное решение, призванное улучшить метрики первой отрисовки за счет «ленивой» загрузки изображений и видео.
Lazy loading modules/routes — инструмент, который есть во всех популярных фреймворках и библиотеках. Позволяет «лениво» подгружать куски функциональностей приложения.
Caching files with Service workers позволяет кешировать файлы в браузере и не загружать их каждый раз с сервера. Возможно, единственный способ сделать офлайн-режим в браузерном приложении.
HTTP Caching — с помощью некоторых HTTP-заголовков можно сильно улучшить скорость загрузки страниц и снизить нагрузку на сервер.
Fess_blaga
Замечательно, что хоть кто-то в Tinkoff.ru об этом задумался. Говорю как человек, вынужденный временами пользоваться вашим интернет-банком.
geted
Привет! Подскажи плиз, какими конкретно продуктами пользовался и какой опыт использования получил. Были проблемы с интерактивностью и отзывчивостью интерфейса, или проблемы с продолжительной отрисовкой?
Fess_blaga
Интернет банк.
Всё вышеперечисленное.
Некоторое время назад на планшете (2Gb оперативной памяти, Intel Atom, Win 10, Firefox) загрузка стартовой страницы вызывала фриз браузера на ~10 секунд.
В данный момент наблюдаю периодические тормоза интерфейса при переходе между вкладками в интерфейсе ИБ.
geted
К сожалению да, такое действительно может быть, так как сейчас у нас есть определенные проблемы с количеством кода поставляемого на клиент, отсюда и проблемы с интерактивностью и возможными фризами интерфейса, что в особенности проявляется на мобильных и таблет устройствах. Работаем над устранением проблемы и спасибо за фидбек.