Отложенная загрузка

Концепция

При обычном рендере страницы парсер запускает все модули последовательно: сначала роутер, затем шапка, меню, контент, блоки рекомендаций, виджеты — и только после того, как все они отработали, страница отдаётся браузеру. Если один из модулей выполняет тяжёлый запрос к базе данных или обращается к внешнему API, он задерживает отдачу всей страницы.

Отложенная загрузка (Lazy Loading) решает эту проблему. Идея проста: «тяжёлые» модули не запускаются вместе со страницей — вместо их результата парсер подставляет AJAX-заглушку. Браузер получает страницу мгновенно, отображает её пользователю, а затем в фоне запрашивает содержимое отложенных модулей и вставляет их результаты в соответствующие места без перезагрузки страницы.

Это особенно полезно для блоков, которые: - выполняют медленные агрегирующие SQL-запросы (статистика, рекомендации, «похожие товары»); - обращаются к внешним сервисам (курсы валют, наличие на складе через API поставщика); - персонализированы и не могут быть закешированы на уровне страницы; - не критичны для первичного восприятия страницы пользователем.

Разумный предел

Отложенная загрузка — инструмент, которым легко злоупотребить. Если включить её для большинства модулей страницы, браузер отправит десятки параллельных AJAX-запросов сразу после загрузки — по одному на каждый модуль. Это создаст пиковую нагрузку на сервер, которая по характеру ничем не отличается от DDoS-атаки, только организованной самим сайтом.

Практическое правило: отложенная загрузка имеет смысл только для модулей, которые заметно медленнее остальных и не влияют на первичный контент страницы. Таких модулей на странице, как правило, один-два, редко больше трёх.

Активация

Для включения отложенной загрузки достаточно установить флаг «Отложенная загрузка» в правой панели IDE в параметрах нужного модуля.

После этого поведение модуля меняется: при рендере страницы вместо его результата парсер автоматически подставляет блок JavaScript-кода, который: 1. Создаёт на странице контейнер с уникальным идентификатором (id = md5(имя_модуля + параметры)). 2. Немедленно после загрузки страницы отправляет AJAX-запрос за содержимым модуля. 3. Вставляет полученный HTML в контейнер и выполняет все <script>-теги из ответа.

Схема работы

Браузер                          Сервер (index.php)
   |                                     |
   |── GET /?topic_id=5 ────────────────>|
   |                                     |── Run(melbis_base_page)
   |                                     |    ├─ melbis_base_header  [обычный]
   |                                     |    ├─ melbis_cataloge     [обычный]
   |                                     |    ├─ melbis_store_topic  [обычный]
   |                                     |    └─ melbis_store_random [lazy] → AJAX-заглушка
   |<── HTML страницы (быстро) ──────────|
   |
   |── POST /lazy/ {mod: melbis_store_random, params: ...} ──>|
   |                                                           |── Run(melbis_store_random)
   |<── HTML блока рекомендаций ───────────────────────────────|
   |
   [вставить в контейнер]

AJAX-запрос уходит на тот же index.php через маршрут lazy/, который должен быть прописан в .htaccess:

RewriteRule ^lazy/$ index.php?lazy [L,QSA]

В корневом скрипте index.php этот маршрут обрабатывается отдельной веткой:

if ( isset($_GET['lazy']) )
{
    $entry_point = $_POST['mod'];
    $entry_param = $_POST['params'];
}

Параметры модуля передаются в URL-encoded виде через POST — парсер формирует их автоматически из тех же параметров, с которыми модуль был вызван в шаблоне. Разработчику ничего дополнительно делать не нужно: включил галочку — модуль стал lazy, выключил — вернулся к обычному режиму.

Модуль с включённой отложенной загрузкой остаётся полноценной входной точкой — при lazy-вызове он обрабатывается точно так же, как при обычном, и может использовать кеш, библиотеки и любые другие возможности платформы.

Своя заглушка

Штатная заглушка — это готовый блок с XMLHttpRequest на чистом JavaScript, без зависимостей от библиотек на странице. Обычно её менять не нужно, но два метода позволяют заменить и адрес, и саму заглушку. Вызываются они в корневом скрипте, до Run().

DefineLazyScript($mUrl) — адрес, на который уходит AJAX-запрос. По умолчанию lazy/ от корня сайта. Меняется, если маршрут в .htaccess назван иначе или запросы отложенной загрузки нужно увести на отдельный домен:

MELBIS()->DefineLazyScript('/ajax/lazy/');

DefineLazyLoader($mHtml) — сам HTML-код заглушки. В переданном тексте парсер подставит четыре ключа:

Ключ Значение
{SCRIPT} адрес из DefineLazyScript
{MODULE} имя отложенного модуля
{PARAMS} параметры вызова, в URL-кодированном виде
{ID} уникальный идентификатор контейнера
$loader = '<div id="{ID}" class="lazy-block"><span class="spinner"></span></div>
           <script>
               fetch("{SCRIPT}", {
                   method: "POST",
                   headers: {"Content-type": "application/x-www-form-urlencoded"},
                   body: "mod={MODULE}&params={PARAMS}"
                   })
                   .then(function(r) { return r.text(); })
                   .then(function(html) { document.getElementById("{ID}").innerHTML = html; });
           </script>';

MELBIS()->DefineLazyLoader($loader);

Заменяют заглушку обычно ради двух вещей: показать скелетон или спиннер вместо пустого места, пока блок грузится, и выполнить <script>-теги из ответа — штатный загрузчик это делает, и своя реализация на innerHTML не сделает. Если отложенный модуль возвращает разметку со скриптами, их запуск придётся предусмотреть в своём загрузчике самостоятельно.