Отложить загрузку JavaScript и отображать временный контент при первой загрузке страницы

Как насчёт: отложенная загрузка JavaScript-скриптов Discourse

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

Таким образом, некоторый статический промежуточный контент может быть показан довольно рано в процессе загрузки (или даже до) запуска Discourse. Это должно обеспечить более высокую воспринимаемую пользователем скорость загрузки страницы при первом обращении.

Идеи для статического промежуточного контента:

  • экран приветствия с логотипом и индикатором загрузки
  • просмотр темы с постами, полученными из бэкенда

POC и PR

Для последнего доказательства концепции и PR — пожалуйста, посмотрите этот пост.


В настоящее время vendor-скрипты и все предшествующие JavaScript-скрипты не откладываются.
@see: https://github.com/rr-it/discourse/commit/328efd5c055f5f2a4d93b5e52268cfe92913faf7

Идеи по решению этой проблемы очень приветствуются.


JavaScript async против defer против none

Подробнее о вариантах загрузки JavaScript, включая defer: https://flaviocopes.com/javascript-async-defer/
(Это не о ускорении реального запуска Discourse.)


Fastboot/регидратация

Я прочитал эту статью:
Вывод там, по-видимому, заключается в реализации Fastboot/регидратации.
Есть ли сроки для этого?

Это приведёт к тому, что LCP всё ещё будет происходить после загрузки и повторного рендеринга EmberJS, что не решает основную проблему, связанную с новыми алгоритмами ранжирования Google.

Это наш текущий среднесрочный план по улучшению LCP в Discourse.

Начиная с Chrome 88, к счастью, это уже не так! :rocket:
Я тоже об этом не знал до сих пор. :))

«До этого изменения удаление элемента приводило к тому, что он больше не считался допустимым кандидатом на LCP. […] После этого изменения удалённый элемент всё ещё считается допустимым кандидатом на LCP.»

«Изменение, позволяющее учитывать контент, который позже удаляется из DOM, как возможные элементы с самым большим контентом, улучшит показатели Largest Contentful Paint на сайтах, где изображения [для Discourse: текстовые элементы] одинакового размера вставляются несколько раз. Это распространённый паттерн для каруселей, а также некоторых JavaScript-фреймворков, использующих серверный рендеринг


Журнал изменений LCP

В будущем могут появиться и другие полезные изменения:

Вот некоторые симулированные статистические данные для страницы темы с реализованным POC.

Lighthouse: “Значения являются оценочными и могут варьироваться.”

WebPageTest

webpagetest.org

Симуляция Moto4G


Примечание: мы — черная стрелка сверху.


Примечания:

  • 0–2 с — пустой экран:
    WebPageTest игнорирует defer для JavaScript и загружает все скрипты перед первым отрисовкой — на реальном устройстве это работает корректно.
  • 2,5 с — LCP: статический контент из серверного рендеринга
  • 3,5 с — визуальное изменение: загружен логотип
  • 6,5 с — визуальное изменение: контент из рендеринга EmberJs
  • 7 с — визуальное завершение

PageSpeed Insights

Элемент Largest Contentful Paint

PageSpeed корректно определяет статический текстовый узел из серверного рендеринга как элемент LCP вместо FCP:
div.row > div.topic-body > div.post > p

Текстовый узел, отрендеренный через EmberJs:
div.row > div.topic-body > div.regular.contents > div.cooked > p

Однако, похоже, PageSpeed не использует корректно определенный статический текстовый узел для своего симулированного результата: симулированные FCP и LCP слишком велики.

Реальные данные пользователей

Давайте подождём ещё 14–28 дней, чтобы получить «реальные» данные из Chrome UX Report с реализованным POC.

Статистика без реализации POC для протестированной страницы темы:
(Данные относятся к этому отдельному URL темы, а не ко всему домену.)

О, это очень интересное открытие! Отличная работа!

А какие показатели вы получаете с этим расширением https://chrome.google.com/webstore/detail/web-vitals/ahfhijdlegdabablpippeagghigmibma?

Через расширение Chrome Web Vitals

  • на рабочем столе
  • версия Chromium 90.0.4430.212
  • первая загрузка в новом окне инкогнито


Примечание по задержке первого ввода: Я ждал полной загрузки страницы, а затем нажал на фон — после завершения рендеринга EmberJs.


Примечание по задержке первого ввода: Здесь я нажал на фон сразу после появления статического контента. Добавьте к этому значению FID время моей реакции :sloth:.

Дополнительное примечание по процентилям под столбцами на этих графиках:
Процентили не так важны, так как они сравнивают только измеренные значения с исходными. В качестве основы использована веб-страница TYPO3 с установкой Discourse в подпапке.

Отличная идея! @rrit

Я полностью согласен: Discourse — это очень медленное веб-приложение с большим количеством JavaScript. Если мы сможем отложить загрузку CSS/JS-файлов, это значительно улучшит показатели LCP, FCP, FID и CLS.

Было бы здорово увидеть это в действии: мы и многие другие сталкиваемся с этой проблемой. Все сайты на Discourse не проходят проверку по основным веб-метрикам (Core Web Vitals). Если мы будем отдавать пользователям быстрый статический HTML при первом посещении и откладывать всю логику JS/CSS на начальную загрузку, мы сможем ускорить все страницы и получить высокие баллы по CWV! С нетерпением жду, когда это появится в основном обновлении Discourse.

Позиции сайтов на Discourse в поиске Google снижаются из-за того, что они не соответствуют требованиям Core Web Vitals.

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

Можете ли вы попробовать добавить настройку сайта в ваш PR @rrit? Также было бы неплохо добавить некоторые тесты RSpec для проверки поведения при включённой/выключенной настройке.

Разве это не означает, что мы можем просто разместить на странице, рендерящейся на сервере, полноэкранный спиннер (шириной и высотой 100%), а затем заменить его на приложение Ember, когда оно наконец загрузится, чтобы получить экстремально низкий LCP?

Мы могли бы сделать этот спиннер SVG-изображением, имитирующим интерфейс Discourse, чтобы переход был плавнее и меньше напоминал FOUC.

Думаю, ключевой момент здесь — LCP «кандидат».

Он будет считаться самым крупным отрисованным элементом (LCP) только в том случае, если он действительно является самым крупным (или, по крайней мере, таким же по размеру), как и то, что в итоге будет отрисовано.

Поэтому использование вида для поисковых роботов работает довольно хорошо, поскольку контент (то есть текст) в основном одинаков?

(Я в основном догадываюсь, основываясь на приведённых выше скриншотах — я не пробовал полноэкранный спиннер).

Флаг функции реализован.

Я совсем не разработчик на Ruby — здесь мне определённо нужна помощь.

Возможно, стоит выгрузить мой POC в новую ветку репозитория discourse/discourse, прежде чем делать PR на main?

Вот мой PR по этой функции:

@david, не могли бы вы помочь мне с разработкой RSpec-тестов для этих изменений:

app/helpers/application_helper.rb: spec/helpers/application_helper_spec.rb

Здесь я не вижу возможности написать модульные тесты. Похоже, это можно проверить только с помощью интеграционных тестов.
app/models/theme.rb
app/models/theme_field.rb

Мне пришлось отключить тег defer для QUnit Test Runner: app/views/qunit/index.html.erb
Ранее QUnit-тесты всё ещё работали с флагом функции "javascript defer" = false. А теперь тесты работают и при "javascript defer" = true.

Вероятно, это уже заблокировано по ссылке: https://chromium.googlesource.com/chromium/src/+/master/docs/speed/metrics_changelog/2020_11_lcp.md:

Изображения на весь viewport, которые визуально эквивалентны фоновым изображениям, больше не считаются самым крупным элементом для отрисовки (LCP)


Отличное замечание: см. Largest Contentful Paint (LCP)  |  Articles  |  web.dev

Для текстовых элементов учитывается только размер их текстовых узлов (наименьший прямоугольник, охватывающий все текстовые узлы).

Для всех элементов любые отступы, внутренние отступы или границы, применённые через CSS, не учитываются.

  • Вот почему статический текстовый узел должен быть отрисован точно такого же размера, как и текстовый узел EmberJs.
  • Или даже немного больше, увеличив line-height.
    Например, если ширина текстовых узлов не совпадает, возникает множество геометрических случаев из-за различных переносов строк, когда статический текстовый узел становится меньше, чем узел EmberJs.

См.: Примеры LCP


На самом деле я использовал рендеринг через noscript для постов внутри страницы темы. CSS-классы немного совпадают с реальными — поэтому внешний вид идентичен.

См.: Изменения в app/views/layouts/application.html.erb

Редактирование: Моя ошибка, на самом деле это представление для поисковых роботов: app/views/topics/show.html.erb

В POC объединены две функции — стоит ли разделить их на два экспериментальных флага?

  • JavaScript с тегом defer (флаг функции в панели настроек)
    (скрытый флаг функции, так как для этого требуется перестроение контейнера или очистка кэша темы)Исправление: горячее переключение с кэшем
  • Отображение статического контента в просмотре темы (флаг функции в панели настроек)

Вот оно: флаги функций


Конечно, полное влияние на LCP достигается только при использовании обоих: FCP: статический контент

Возможно, в некоторых экземплярах Discourse плагины или компоненты тем будут работать некорректно из-за отложенной загрузки JS. Разделив эти функции, они смогут получить небольшую выгоду от статического контента без отложенной загрузки JS: FCP: статический контент без отложенной загрузки JS

Первые впечатления от Google Search Console после применения POC с 30 января 2022 года:

Десктоп

Для десктопа потребовалось время, чтобы появились результаты:


Примечание: старая зелёная базовая линия представляет веб-страницы, не относящиеся к Discourse, на том же домене.

Мобильные устройства


Примечание: старая зелёная базовая линия представляет веб-страницы, не относящиеся к Discourse, на том же домене.

Давайте подождём ещё 7–14 дней, чтобы, надеюсь, увидеть дальнейшие улучшения для мобильных страниц, так как значения усредняются за последние 28 дней — на данный момент POC применяется только 12 дней.

Сводка по LCP на этапе Proof of Concept

POC внедрён с 30 января 2022 года, и потребовалось более 4 недель, чтобы изменения отразились на всех страницах в отчёте «Основные показатели веб-производительности» (Core Web Vitals) в Google Search Console — на основе данных CrUX.

Все тематические страницы находятся в зелёной зоне по показателю LCP (измерено по данным CrUX):

  • Десктоп: LCP 1,7 сек
  • Мобильные устройства: LCP 2,0 сек

Данные по LCP: Google Search Console / CrUX

Впечатления из Google Search Console после внедрения POC с 30 января 2022 года:

Десктоп


Примечание: старая зелёная базовая линия соответствует веб-страницам, не использующим Discourse, на том же домене.

Хорошие URL

Мобильные устройства


Примечание: старая зелёная базовая линия соответствует веб-страницам, не использующим Discourse, на том же домене.

Хорошие URL

Проблема с LCP: значение больше 2,5 с (мобильные устройства)


Примечание: только тематические страницы показывают статический контент до загрузки контента EmberJS.


Требуется одобрение PR с использованием флагов функций

@sam, не могли бы вы передать этот PR кому-либо для рассмотрения и одобрения?

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

@rrit спасибо за обмен данными с вашего сайта! Мы обсуждали это внутренне, и, боюсь, на данный момент мы не будем добавлять эту функциональность в ядро Discourse.

Хотя показатели Web Vital, которые вы представили, очень впечатляют, мигание контента в «режиме краулера» не обеспечивает хорошего пользовательского опыта. Внесенные вами изменения в стилизацию, безусловно, помогают, но их придется корректировать для каждого сайта Discourse с индивидуальным оформлением.

Наша долгосрочная цель — реализовать истинную серверную рендеринг (SSR) с помощью таких решений, как Ember FastBoot. Теоретически это обеспечит те же статистические улучшения, которые вы измерили, и при этом обеспечит безупречный пользовательский опыт. Мы предпочитаем сосредоточить свои усилия на достижении этой цели.


Тем не менее, Discourse обладает отличной расширяемостью, поэтому, я думаю, вполне возможно реализовать вашу идею в виде плагина для Discourse и затем поделиться ею здесь в канале #plugin.

Самое значительное изменение, которое вы внесли в PR для ядра, — это добавление атрибута defer к тегам скриптов. Переопределить все эти места из плагина было бы очень сложно. Однако, я думаю, того же результата можно достичь с помощью подхода на основе промежуточного программного обеспечения (middleware). Я нашел эту статью в блоге, описывающую похожую проблему:

Используя эту технику, вы можете написать middleware, который проверяет ответы с типом text/html, парсит их, а затем добавляет атрибуты defer там, где это необходимо.

Добавление middleware из плагина можно выполнить примерно так:

# name: my-plugin
# about: Описание моего плагина
# version: 1.0
# url: https://example.org

require_relative "lib/script_defer_middleware"

on(:after_initializers) do
  Rails.configuration.middleware.use(ScriptDeferMiddleware)
end

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

Если у меня найдётся время, я, вероятно, реализую это в виде плагина.

Но пока я пытаюсь обойтись подходом с патчами в файле web_only.yml:

# не протестированный псевдокод!
hooks:
  after_code:
    - exec:
        cd: $home
        cmd:
          - curl https://patch-diff.githubusercontent.com/raw/discourse/discourse/pull/15858.diff | git apply

Ember FastBoot выглядит как идеальный долгосрочный подход. Тем временем тема LCP остаётся актуальной:

Спасибо за работу над этим, @rrit :+1:

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