У людей были разные мнения о проекте AMP, но я не слышал одного: он не был быстрым. Однако мы еще даже не приблизились к завершению и надеемся значительно ускорить AMP в ближайшие месяцы. Через несколько недель после запуска предварительной версии для разработчиков мы уже внесли несколько хороших оптимизаций. В этом посте мы подробнее рассмотрим некоторые из них. Даже читателям, не использующим AMP, некоторые из них могут быть интересны для применения в своих собственных проектах.

Предварительное подключение

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

При предварительном подключении выполняется поиск DNS, квитирование TCP и SSL. Это экономит более 100 мс даже на WIFI и может быть на несколько секунд быстрее при плохом мобильном соединении.

К сожалению, только Chrome изначально поддерживает preconnect, но мы разработали очень простой полифилл для других браузеров:

В чем дело?

  1. Мы создаем URL-адрес на хосте и добавляем случайное число, чтобы очистить кеш, потому что мы действительно хотим установить соединение.
  2. Мы делаем XHR. "HEAD" как метод запроса - это замечательно, потому что нас не волнует возвратный документ. Также мы в любом случае ожидаем получить 404.
  3. Наконец отправляем заявку. Обратите внимание, что это всегда запрос с перекрестным происхождением (мы никогда не подключаемся к себе заранее) и не вызываем withCredentials (true). Это означает, что мы никогда не отправляем файлы cookie с запросом, ни реферер, ни ответ не может устанавливать файлы cookie, что мы и хотим в данном случае.

Итак, если вы видите «amp_preconnect_polyfill» в журналах вашего сервера: Да, это мы. На практике мы подключаемся только к крупным хостам, таким как Twitter, YouTube или рекламные сети, где эти дополнительные запросы, надеюсь, не будут замечены :)

Предварительная выборка

Даже лучше, чем просто предварительное подключение, - это предварительная выборка ресурса. Одна из причин, по которой мы, например, загрузка рекламы очень поздно заключается в том, что загрузка их JS использует ЦП, который может мешать прокрутке и т. д. Предварительная выборка не имеет тех же проблем: JavaScript загружается, но еще не запускается, что означает, что если у нас есть пропускная способность, но нет уверен в CPU, тогда предварительная выборка великолепна.

Предварительная загрузка так же проста, как:

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

Эта простая «попытка полифилла» очень приближает нас. Он выполняет предварительную выборку URL-адреса, и когда он действительно извлекается, он обслуживается из кеша. Однако в следующий раз, когда ресурс будет кэширован, но это не изображение, он снова получит ресурс, что приведет к потере полосы пропускания.

Если у кого-то есть лучший способ сделать это в Safari, мы будем очень благодарны. На данный момент у нас нет предварительной выборки для пользователей iOS.

Настройка полифиллов Babel ES6

Мы любим ES6 и Babel, но, к сожалению, оказалось, что некоторые из его полифиллов тяжелы по сравнению с размером JavaScript. С большой неохотой мы разделили core-js-shim (всего один файл) и оставили только Array.from, Promise и Math.sign из всех достоинств ES6. Кроме того, мы ограничили синтаксические функции ES6, которые мы используем в проекте, теми, которые могут быть эффективно перенесены Babel, и создали для этой цели специальный файл помощников Babel. Трудно больше не использовать такие вещи, как string.endsWith, но влияние на размер JS того стоит для такого проекта, как наш. Размер нашего основного файла JavaScript уменьшился со 142 КБ (39 КБ в сжатом виде) до 134 КБ (36 КБ), что не так уж и впечатляюще в процентном отношении, но двоичный файл, который мы компилируем для нашей песочницы iframe, уменьшился с 50 КБ (17 КБ) до 7 КБ (4 КБ) - после того, как мы решили, что можем жить и без промисов и еще одного полифила, не относящегося к Babel, что имеет большое значение.

Ожидается, что наши файлы JavaScript будут часто кэшироваться (особенно когда мы используем Service Worker, чтобы гарантировать, что они есть), но JS все равно нужно анализировать и выполнять. Его меньшее количество тратит меньше драгоценного времени на поток пользовательского интерфейса во время начальной загрузки.

Оптимизация пересчета стилей

При измерении размеров в DOM с помощью JavaScript (например, «Какова высота этого элемента?») Браузеры вынуждены немедленно «материализовать» документ, потенциально пересчитывать его макет и повторно применять стили, если документы изменились с момента последнего изменения. Это может быть очень медленным (1–100 мс в зависимости от того, что изменилось, размера документа и скорости устройства) и блокирует поток пользовательского интерфейса.

С помощью некоторой простой перестановки мы сократили количество перерасчетов стилей при загрузке типичного AMP-документа с 4 до 2. Это может показаться не таким уж большим, но когда вы предварительно визуализируете 3 документа, это сокращает объем работы с 12 до 6, что может быть мир различий.

Два раза, когда нам нужно пересчитать стили:

  1. Когда мы измеряем для каждого элемента AMP (а их, конечно, может быть много), насколько он может быть большим, в зависимости от размера контейнера.
  2. После первоначального макета с изменениями, учтенными в №1, мы измеряем высоту документа.

Не исключено, что эти две фазы могут быть объединены в одну. Был бы хороший проект дождливым днем ​​:)

Наиболее распространенный способ уменьшить количество перерасчетов стилей - это группировать операции DOM в операции чтения и записи через библиотеку, такую ​​как fastdom. Вдобавок к этому можно делать больше пакетных операций на уровне приложения и перераспределения операций. Однако в конечном итоге это очень хрупкое состояние. Очень легко вернуться назад и добавить пересчет стиля с помощью очень тонких изменений. Мы работаем с командой Chrome над добавлением API-интерфейсов для использования в модульных тестах, которые можно использовать для подтверждения того, что определенное количество пересчетов стилей не превышено.

Совместное использование JS в iframe песочницы

AMP помещает в песочницу все сторонние JS-файлы в окнах iframe из разных источников. Это дает несколько преимуществ в производительности, но также требует значительных накладных расходов на память. В будущем проекте предполагается сохранить это дополнительное использование памяти постоянным, но пока мы сосредоточились на сохранении эффективности отдельных кадров.

Какой бы ни был первый сторонний iframe песочницы на странице, он объявляет себя «основным iframe», а затем второй и последующие фреймы пытаются найти этот мастер в родительском элементе:

Таким образом, у нас всегда есть один специальный iframe, который является «основным iframe». Это позволяет легко обмениваться ресурсами между фреймами. Работа может быть выполнена только один раз и повторно использована всеми фреймами, которые в ней нуждаются. Одна из таких вещей - сценарий встраивания Twitter. Вот как мы его загружаем (и да, этот код выглядел лучше до того, как мы избавились от обещаний в нашем встроенном фрейме):

Эта функция загружает код Twitter и по завершении вызывает обратный вызов. Настоящая работа выполняется только в мастере. Все остальные фреймы просто ждут, пока будет выполнена основная работа, а затем их перезвонят.

Друзья в Instagram, DoubleClick и других местах: пожалуйста, помогите нам; есть много ЦП и ОЗУ, которые нельзя использовать :)

К сожалению, эта оптимизация работает только тогда, когда для нее предназначен код встраивания. Твиттер - одно из редких исключений, которые отлично работают в этом контексте. Друзья в Instagram, DoubleClick и других местах: пожалуйста, помогите нам; требуется много ЦП и ОЗУ, которые нельзя использовать :)

Будущая работа

У нас есть несколько проектов по дальнейшему уменьшению размера JS. Вдобавок к этому мы упростим управление загрузкой шрифтов в AMP, а затем начнем использовать Service Workers для более предсказуемой производительности в поддерживаемом браузере. И да, возможно, мы даже будем использовать App Cache в браузерах, которые еще не поддерживают Service Workers, так что вам не придется.