От автора: сделать сайт быстрее — на удивление легко; вам нужно только решить, что вы собираетесь это сделать. О, а также вам нужно сделать это.
Изучив и улучшив производительность многих веб-сайтов, я остановился на повторяемом наборе действий, которые применяются повсеместно. Возможно, вы не удивитесь, узнав, что эти шаги являются темой данной статьи.
Эталонное устройство
Если я собираюсь сделать сайт быстрее, я должен быть в состоянии достоверно определить его быстродействие. Поэтому для согласованности я запускаю сайт локально, с помощью сети, настроенной на «Быстрый 3G» в Chrome DevTools (в противном случае запросы слишком быстрые для измерения). Я не ограничиваю процессор по той практической причине, что буду обновлять сайт сотни раз в день. Если мне придется каждый раз ждать 15 секунд, я загорюсь и выпрыгну из окна, прежде чем закончу работу над сайтом. И это плохо для производительности.
Меня не интересуют «реальные метрики пользователей»; измерение производительности сайта в реальном мире — это замечательно, но это не помогает в ускорении процесса.
Инструменты
«Time to Interactive» — это показатель, который меня больше всего интересует, поскольку TTI представляет собой точку, с которой пользователь может начать пользоваться сайтом. Я нахожу точку в коде, которая отмечает, что сайт становится интерактивным, и добавляю console.log(performance.now()). Это мой «инструмент».
Кто-то однажды сказал: «Определение безумия — это делать одно и то же снова и снова и ожидать других результатов». Этот человек явно никогда не обновлял веб-страницу 5 раз, чтобы измерить время загрузки.
Разница всегда будет — часто до 30% — поэтому важно измерить ее. Я написал небольшой инструмент (scatter-bar.web.dev), чтобы зафиксировать это разброс, не выбрасывая данные, но глядя только на медиану или среднее значение.
Разделение пользователей
Большинство рекомендаций по производительности, которые я нахожу в Интернете, ориентированы на новых посетителей. Предположительно, потому что это проще объяснять. Но для многих сайтов улучшение опыта повторных посетителей помогает большему количеству людей, больше раз.
Итак, порядок, в котором я подхожу к следующим разделам, зависит от количества повторных посетителей сайта, над которым я работаю. Если 80% просмотров страниц происходят из браузеров, которые посещали сайт раньше, я потрачу 80% своего времени на оптимизацию работы с повторными посетителями. В отличие от этого, текущий сайт, над которым я работаю, посещают в основном новички, поэтому я сосредоточен на них.
Я отмечу в каждом разделе ниже, кто является основными пользователями. Это будет первое предложение каждого раздела, и через некоторое время это будет немного раздражать. Теперь, разобравшись с этим, переходим к действиям…
Включите сжатие
Это преимущественно приносит пользу новым посетителям. Моя любимая вещь — это сайт, который не сжимает файлы, которые они отправляют по сети. Я могу сообщить им, что существует вещь под названием «сжатие», а затем забронировать билеты на парад, который они устроят в мою честь. Это момент гордости.
Повысьте уровень сжатия до 11
Это преимущественно приносит пользу новым посетителям. Моя вторая любимая вещь, которую нужно проверить — это сжатые ресурсы, попадающие в сеть (поворот сюжета!). Они почти наверняка больше, чем нужно по двум причинам: большинство людей принимают уровень сжатия gzip по умолчанию, и gzip в любом случае не самый лучший метод сжатия.
Я рекомендую использовать сжатие Brotli до 11. Некоторые люди ответят, что более высокий уровень сжатия приведет к более медленному процессу сжатия, что правда, но это не имеет значения. Видите ли, это почти не имеет значения для распаковки. Статические активы распаковываются раньше, чем они нужны, поэтому более высокая степень сжатия не имеет значения.
Люди часто скептически относятся к влиянию этого на реальные сайты, поэтому вот некоторые цифры, из которых вы можете увидеть, что я не просто придумал это… если я возьму с Medium.com файл vendors.js размером 800 КБ:
Gzip на 6 уровне сжимает его до 214 КБ
Brotli на уровне 11 дает 173 КБ
Сокращение количества байтов на ~ 20% при выполнении задачи с минимальными усилиями. Это безумие! (Эта задача не требует больших усилий, потому что я не знаю, как это сделать, и прошу кого-то другого сделать это для меня.)
Это довольно забавно, если подумать: буквально можно повернуть один тумблер, чтобы «сделать мои файлы меньше», но большинство людей не пользуются этим.
Совет: если вы хотите быстро проверить результаты сжатия для данного файла, вы можете запустить Node в терминале (набрав node и аккуратно нажав Enter), затем наберите…
zlib.gzipSync(fs.readFileSync('./my-file.js')).length
… а для Node v11.7 и выше …
zlib.brotliCompressSync(fs.readFileSync('./my-file.js')).length
Идем дальше!
Никогда не ждите повторного входа в сеть
Это выгодно только постоянным посетителям. Бессмертные слова Альберта Эйнштейна: Работники службы чертовски хороши.
Я согласен с Альбертом. Service Workers — это Святой Грааль, когда речь заходит о производительности, и объяснение преимуществ может быть немного забавным…
Например, с детской радостью я сообщаю кому-то, что постоянным посетителям не нужно ждать загрузки кода при посещении их сайта. Они скажут, да, да, мы знаем о кешировании. Далее я объясню, что даже если веб-сайт был обновлен, а у возвращающегося посетителя еще нет нового кода, им все равно не придется ждать загрузки сайта. Ни одной миллисекунды.
В этот момент многие люди поинтересуются, не ударялся ли я в детстве головой. Когда я отвечаю отрицательно, некоторые продолжат расспросы, предполагая, что, возможно, что-то тяжелое падало на меня. (Этого я не могу отрицать; когда я был совсем младенец, в Харви Нормане упала с полки эспрессо-кофемашина. Она упала прямо в мне коляску и треснула меня по ноге. По иронии судьбы такую же кофе машину привезли к нам домой и ее вид преследовал меня, пока я не стал достаточно взрослым, чтобы выбросить ее из окна).
Вернемся к Service Workers.
Если вы еще не знаете, как они работают, магия заключается в том, что когда пользователь посещает URL, на котором он уже был, он мгновенно получает предыдущую версию сайта из кэша. Если доступна более новая версия сайта, она загружается в фоновом режиме и будет использоваться для следующего посещения этого пользователя.
При этом предложении некоторые спрашивают меня «что если мы хотим, чтобы у пользователя была новая версия немедленно?» Я скажу, что они а) на самом деле не хотят и б) в настоящее время нет никакого механизма, который бы препятствовал тому, чтобы пользователь оставлял вкладку открытой в течение двух дней, так что это вряд ли вызовет серьезную озабоченность.
Существуют инструменты, которые облегчают реализацию Service Workers. Google (интернет-компания) создала Workbox, набор инструментов, которые делают то, что, по словам веб-сайта, они делают, и Create React App поддерживает Service Workers из коробки (если можно так сказать, из Workbox).
Много-много файлов
Этот раздел выгоден только постоянным посетителям. И дает очень мало пользы, если на сайте уже работает Service Worker.
Если я вижу сайт, который объединяет JavaScript в один пакет, я знаю, что можно добиться значительного увеличения производительности. И довольно мило видеть, что активы разделены на две части, такие как main.js и vendors.js. Это как, когда ребенок рисует картинку, и вы знаете, что он очень старался, поэтому вы говорите «хорошая работа» и кладете ее на холодильник, даже если это весьма неуклюже.
Эта ситуация «больше файлов — лучше», к сожалению, требует объяснения снова и снова, что наличие меньшего количества файлов — это старый совет, который в настоящее время мешает производительности. (В редких случаях, когда сайт еще не использует HTTP / 2, нужно сначала перейти на него, прежде чем идти дальше.)
Я рекомендую разделять активы сайта на множество мелких файлов, в идеале один файл на пакет npm и настолько малый, насколько это разумно для кода приложения. Разделив JavaScript на более мелкие файлы, сайт может вдвое сократить количество отправляемых байтов в реальных сценариях. Это немного запутанно, поэтому если вы не видите, как это возможно, вы можете прочитать о том, «как» и «почему», в статье 100% правильный способ разделения сниппетов с помощью Webpack.
Неиспользуемый код
Следующие методы оптимизации в основном приносят пользу новым пользователям и делятся на четыре типа в зависимости от неиспользуемого кода.
Никогда не используемый код
Когда сайт использует сторонние пакеты, есть вероятность, что неиспользуемый код будет включен и полностью доставлен.
Чтобы найти случаи таких потерь, я начинаю с чего-то вроде webpack-bundle-analyzer (или менее удачного source-map-explorer, если я проверяю приложение без CRA), а затем приступаю к кропотливому процессу указания пакетов с неиспользуемыми частями.
Иногда это так же просто, как вывести import x from ‘package/x’; вместо import {x} from ‘package’;. Иногда это намного сложнее.
Я хотел бы поделиться с вами одной историей, чтобы продемонстрировать, что даже несмотря на то, что поиск никогда не использованного кода может быть трудной задачей и часто бесплодной, время от времени она раскрывает то, что делает все это стоящим.
Итак: импорт чего-либо из пакета @turf/turf (вам не нужно знать, что делает Turf) даст вам весь Turf, независимо от того, какой метод импорта вы используете. Чтобы избежать этого, вам нужно установить отдельные модули npm. В результате в package.json содержится этот упорядоченный беспорядок:
"@turf/along": "^6.0.1", "@turf/area": "^6.0.1", "@turf/bbox": "^6.0.1", "@turf/bbox-polygon": "^6.0.1", "@turf/bearing": "^6.0.1", "@turf/boolean-contains": "^6.0.1", "@turf/boolean-crosses": "^6.0.1", "@turf/buffer": "^5.1.5", "@turf/center": "^6.0.1", "@turf/centroid": "^6.0.2", "@turf/clean-coords": "^6.0.1", "@turf/clone": "^6.0.2", "@turf/distance": "^6.0.1", "@turf/explode": "^5.1.5", "@turf/helpers": "^6.1.4", "@turf/invariant": "^6.1.2", "@turf/length": "^6.0.2", "@turf/line-intersect": "^6.0.2", "@turf/line-offset": "^5.1.5", "@turf/line-slice": "^5.1.5", "@turf/line-slice-along": "^5.1.5", "@turf/meta": "^6.0.2", "@turf/midpoint": "^5.1.5", "@turf/nearest-point-on-line": "^6.0.2", "@turf/point-to-line-distance": "^6.0.0", "@turf/points-within-polygon": "^5.1.5", "@turf/polygon-to-line": "^6.0.3", "@turf/shortest-path": "^5.1.5", "@turf/simplify": "^5.1.5", "@turf/transform-scale": "^5.1.5", "@turf/truncate": "^6.0.1",
Просматривать базу кода, чтобы выяснить, какие модули использовались, установить соответствующий пакет npm и изменить ссылки, было непросто, но после оптимизации экономия составила 131 КБ. Да, вы правильно прочитали. Установка этих 29 пакетов по отдельности вместо импорта из основного пакета Turf позволила уменьшить сжатый размер сайта на 131 КБ.
Единственный способ для меня обнаружить что-то подобное — пройтись по всем сторонним пакетам на сайте — от большого до маленького, пока я не почувствую себя мертвецки уставшим — и копаться в том, что они загружают и почему, часто доходя до их источника, чтобы увидеть, как они включают модули внутри.
131 кб!
Слишком большой код
Иногда разработчики используют пакеты, которые больше, чем они должны быть.
Самый простой пример: везде, где я вижу используемый пакет moment (сжатый до 20 КБ), я заменяю его date-fns (и использую только необходимые функции). Обычно экономия довольно близка к 20 КБ.
Я также рассматриваю любые особенно большие библиотеки, которые не широко используются в базе кода. Разработчик мог включить Immutable JS (задействует около 20 КБ памяти) для использования только в одном месте. Таким образом, вопрос: если это можно было написать с нуля в течение нескольких дней, стоит ли это экономии, скажем, 15 КБ?
Да? Или все же нет?
Код не для всех пользователей
Этот третий тип неиспользуемого кода относится к полифиллам. Если вы поддерживаете IE11, вам понадобится около 25 КБ полифиллов. Чтобы гарантировать, что эти байты не будут отправлены в браузеры, которые в них никогда не нуждались, я обычно предлагаю сервис под названием polyfill.io. Он загружает только те полифилы, которые требуются браузеру, выполняющему запрос.
Но это создает сторонний риск для сайта, поэтому мой подход зависит от ситуации. Для сайта с миллионом посещений в день я внедрил специальное решение для полифиллов, чтобы избежать стороннего риска. Но на сайте, с которым я работал семь раз в день, я использовал polyfill.io, потому что это было чертовски просто, и у меня была более крупная рыба.
Неиспользуемый прямо сейчас код
Для тех, кто впервые посещает сайт, загрузка только кода, необходимого для перехода к TTI, может значительно сократить время загрузки.
Есть очевидные вещи для реализации, такие как разделение кода на основе маршрутов (загрузка только JavaScript / CSS, необходимая для текущей страницы), поэтому сначала я это выясняю.
Затем я смотрю на каждую страницу и выясню, какие функции не требуются немедленно. Это может быть здоровенный пользовательский интерфейс или функционал, который требует большого количества JavaScript.
После того, как эти функции определены (иногда их нет), я работаю над созданием отдельных пакетов JS / CSS, которые загружаются только при необходимости. Раньше это было гигантской головной болью, но в 2019 году это стало довольно просто, и, вероятно, будет становиться еще проще в последующие годы, вплоть до 2024 года (подмигивание).
Динамический import() — в сочетании с упаковщиком, который автоматически разбивает код, динамический импорт (Webpack, Rollup, Parcel) — делает загрузку кода по требованию простой задачей. В React с помощью React.lazy и компонента <Suspense/> (отличное имя группы) также легко получить необходимый код только тогда, когда компонент монтируется.
Возможно, пример чего-то, что звучит сложно, но на самом деле было довольно легко, соблазнит вас идти по этому пути…
Сайт, который я недавно получил для настройки производительности, является картографическим приложением. Он берет внутреннюю карту клиента и накладывает ее на стороннюю карту мира (предоставленную отличным Mapbox). Приложение может обеспечить направление от одной карты к другой (например, от вашего дома до места на стадионе с лучшим пространством для парковки).
Перед тем, как я начал, все на странице загружалось в один большой кусок кода, который имел большое количество кода, и это задерживало запуск карты Mapbox до тех пор, пока весь JavaScript-код приложения не будет загружен, проанализирован, выполнен и пока не будет визуализирован DOM.
После некоторой настройки производительности последовательность загрузки теперь выглядит следующим образом:
Загружается пустой HTML-файл (нет такой вещи, как сервер-рендеринг карты WebGL)
Загружается CSS, необходимый для отображения оболочки приложения и Mapbox
Загружается JS, необходимый для визуализации оболочки приложения, и начинается загрузка Mapbox.
В то же время, мы получаем файл карты клиента с сервера
Параллельно с этим выбираем список мест для заполнения карты
После того, как местоположения получены, выбираем код, необходимый для отображения интерфейса поиска.
Как только карта клиента будет возвращена, и страница будет свободна, анализируем карту, чтобы подготовить механизм маршрутизации к получению внутренних направлений (интенсивная загрузка ЦП)
Как только механизм маршрутизации будет готов, предоставляем доступ к интерфейсу
Я бы назвал этот сценарий умеренно сложным, но для его реализации потребовалось около половины дня; это всего лишь несколько динамических импортов, некоторая отложенная загрузка React и задержка тяжелой работы процессора requestIdleCallback. Webpack заботится о создании отдельных пакетов JavaScript и CSS для маршрутов, модулей и компонентов и гарантирует, что они загружаются только при необходимости.
ОК, это был последний из типов неиспользуемого кода. А сейчас…
Используйте сеть доставки контента
Это преимущественно приносит пользу новым посетителям. Звучит банально?
Если запросы к статическим файлам направляются на веб-сервер, обычно их можно быстро перевести, переместив файлы в CDN. Большинство людей уже делают это в наши дни, но это не всегда конец истории.
Разработка плана обслуживания активов из CDN простадля статического контента (кэшировать его) и проста для высокодинамичного контента (не кэшировать), но в промежутке между ними все становится сложнее.
Например, я мог бы попытаться улучшить время загрузки домашней страницы сайта покупок, на которой периодически отображается список рекомендованных товаров. Это может кэшироваться в CDN, но только до изменений рекомендованных товаров. Это потребует триггера для очистки кеша CDN в ответ на изменение списка рекомендованных продукта. Это достаточно сложно сделать, но для страниц с интенсивным движением, которые меняются лишь изредка, с высоким показателем посещаемости в первый раз, это стоит усилий.
То же самое касается кэширования ответов API. Обычная мудрость гласит: не делай этого. Моя мудрость гласит: если это сделает сайт быстрее, делайте это, но будьте осторожны.
Небольшие вещи
Все, что я описал до сих пор — это сокращение времени до первой интерактивности (TTI) сайта, и это дает самые большие результаты.
Но есть несколько простых вещей, которые можно сделать, чтобы внести небольшие улучшения в воспринимаемую скорость сайта, изменив порядок, в котором все происходит в браузере.
Для начала: скрипты никогда не размещаются в head. Мне все равно, что говорят документации Google Analytics, Mixpanel или New Relic, скрипты не размещаются в head. Они размещаются внизу body после всего HTML, предназначенного для глаз вашего пользователя.
Конечно, я добавляю тег <link rel=»preload» /> в заголовок для каждого ресурса, который я планирую загрузить на эту страницу, чтобы браузер мог начать загрузку / анализ файла, или, если он уже находится в кеше, просто начать анализ файла.
Эти изменения, вероятно, не повлияют на TTI, но — во многих случаях — сократят время до первого отображения контента и время до первого значимого отображения (когда пользователь может уже что-то видеть, но не трогать).
Для некоторых сайтов, на некоторых страницах, когда можно разумно угадать, куда пользователь пойдет дальше (например, с домашней страницы на страницу статьи), я рекомендую использовать теги <link rel=»prefetch»>, чтобы начать загрузку ресурсов, необходимых для этой следующей страницы. Если вероятность того, что посетитель перейдет на другую страницу, очень мала, а размеры активов очень велики, я не буду делать это из уважения к тарифному плану пользователя. Но, как правило, мы говорим о нескольких десятках дополнительных КБ, которые затем будут кэшироваться, поэтому разумно предварительно извлечь их.
Если я работаю на сайте со сторонней рекламой, у меня до этого момента был включен блокировщик рекламы, но теперь я отключу его, потому что эти усилия по перетасовке могут иметь большое значение. Большая часть рекламы — это огромные проблемы с процессором — как только вы подключите ее, она может занять процессор на несколько секунд — потому что люди, которые делают рекламу, хуже младенцев.
Взаимодействие с пользователем
Большая часть того, что я рассказал до сих пор, очень похожа для всех сайтов — я бы просмотрел именно этот список для любого сайта, на котором провожу аудит производительности.
Этот раздел посвящен вопросам, связанным с производительностью, характерными только для определенных сайтов. Не существует единого подхода, работающего для всех, поэтому я просто оставлю вам свой общий процесс, который позволяет создать таблицу потенциальных задач.
Определите мой путь через сайт и выполните все действия
Создайте список всего, что по ощущению работает медленнее, чем должно (на умеренно мощном устройстве)
Для каждой проблемной области покопайтесь в коде, чтобы понять, насколько это время может быть сокращено и сколько усилий потребуется для этого.
Узнайте, как часто выполняется каждое из действий
Например, при кадрировании фотографии профиля пользователя может потребоваться 2 секунды на загрузку интерфейса кадрирования. Но я вижу, что код делает что-то глупое, и считаю, что за два дня работы это время может быть уменьшено вдвое. Но кадрирование фотографии профиля является относительно редким занятием, поэтому, возможно, это станет задачей, размещенной внизу списка задач (в конце концов, производительность — это функция, и ей следует назначить приоритет по сравнению со всеми остальными функциями).
Был ли этот пример необходим? Возможно нет.
Производительность бэкэнда
Я не собираюсь ничего говорить о производительности бэкенда по двум причинам:
Это не моя область знаний, и я не хочу давать плохие советы (у меня нет проблем с тем, чтобы давать плохие советы по поводу фронтэнд)
Это удвоило бы длину данного 14-минутного поста, и на самом деле у вас, вероятно, есть чем заняться
Это все! Надеюсь, вы не ожидали краткого контрольного списка.
Автор: David Gilbertson
Источник: https://medium.com
Редакция: Команда webformyself.