Отложить загрузку 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 с индивидуальной стилизацией.

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


При всем при этом, Discourse очень легко расширяется, поэтому я думаю, что вашу идею вполне можно реализовать в плагине Discourse, а затем поделиться ею здесь в разделе Customization > Plugin.

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

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

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

# name: my-plugin
# about: My plugin description
# 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, которая должна значительно помочь в решении этой проблемы.