Разработка SEO оптимизированных высокопроизводительных фронтенд приложений на базе 1С-Битрикс.

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

Артем Житник

Артем Житник


Фото Pietro Mattia на Unsplash

При проверке страницы через PageSpeed есть аудит "Старайтесь не допускать создания цепочек критических запросов", который в основном удалось решить асинхронной загрузкой js/css (смотрите статью). Однако core.js содержащий BX не получалось победить, его асинхронная загрузка вызывала много ошибок.

Ленивое ядро BX

Js код в типичном проекте на Битриксе размещается в script.js компонентов, иногда прямо php (template.php, component_epilog.php, в коде страниц и т.д.). После того как страница загружается в браузере эти кусочки js начинают выполняться. Если весь этот код написан на vanillajs проблем нет, но если он зависит от других библиотек, например от BX, нужно чтобы они были инициализированы выше. Но core.js достаточно жирная библиотека и на время ее синхронной загрузки блокируется дальнейшее выполнение js. При первом открытии страницы, это может занять несколько десятых секунды.

Решил делать ленивую загрузку BX. Сначала загружаем легкую заглушку BX содержащую всего несколько методов вызываемых в проекте в основном потоке, например BX.ready, BX.setJSList, BX.setCSSList, BX.addCustomEvent. Реализация ready:

let fullBxLoaded = false;

const onFullBxLoad = function () {
    return new Promise(function (resolve) {
        let tries = 30;
        const intervalId = setInterval(() => {
            tries--;
            if (tries <= 0) {
                clearInterval(intervalId);
                return;
            }
            if (Object.keys(BX).length < 100) {
                return;
            }

            fullBxLoaded = true;
            resolve();
            clearInterval(intervalId);
        }, 200);
    });
}

let readyCallbackStack = [];

export function ready(callback) {
    if (document.readyState === 'loading' || !fullBxLoaded) {
        readyCallbackStack.push(callback);
        return;
    }

    callback();
}

document.addEventListener(
    'DOMContentLoaded',
    function () {
        onFullBxLoad().then(function () {
            readyCallbackStack.forEach(BX.ready);
            readyCallbackStack = [];
        });
    }
);

То есть, пока не инициализировался основной BX, мы обрабатываем BX.ready через микро ядро. Как только полное ядро загрузилось, оно принимает на себя вызовы BX.ready.

Сборка кастомного ядра через @bitrix/cli

Через npm устанавливаем консольный инструмент Битрикс-разработчика:

npm install -g @bitrix/cli

Если запустить в консоли команду bitrix build в папке /bitrix/js/main/core ядро соберется в файл core.js в этой папке. Основные файлы влияющие на сборку:

  • bundle.config.js
  • config.php
  • src/core.js

Редактируя их можно облегчать конечную сборку. Но делать в /bitrix/ мы это не будем, создадим копию в /local/js/main/core. После этого ядро будет автоматически подключаться из local, а не bitrix.

Разделение ядра на core_lazy и core_light

В /local/js/main/core_lazy я все повыкидывал, оставил только свои реализации критических методов о которых писал выше (BX.ready и т.д.). Минимизированный core.min.js получился порядка 1КБ. Его контент я включил через <script></script> прямо в текст страницы для того чтобы он сразу начала работу, без дополнительного запроса статики.

Также сделал облегченную сборку /local/js/main/core_light, которая представляла собой полное ядро с некоторыми сокращениями - выкинул BX.Text, BX.Loc, BX.Uri, BX.Validation. Но будьте внимательны, в вашем проекте эти модули возможно используются, у меня нет.

Пришлось еще костыльным методом заменить стандартное ядро на мои версии:

Bitrix\Main\EventManager::getInstance()->addEventHandler("main", "OnEndBufferContent", [OutputHtml::class, "replaceCoreJs"]);
Bitrix\Main\EventManager::getInstance()->addEventHandler("main", "OnEndBufferContent", [OutputHtml::class, "makeAssetsAsync"]);
class OutputHtml {
    public static function replaceCoreJs(&$content) {
        global $USER;
        if ($USER->IsAdmin()) {
            return;
        }
        if (CSite::InDir("/bitrix/")) {
return; } if (Option::get("my.main", "core_js_lazy") !== "Y") { return; } $lazyCoreContent = file_get_contents($_SERVER["DOCUMENT_ROOT"] . "/local/js/main/core_lazy/core.min.js"); if ($lazyCoreContent) { $content = str_replace("", "<script data-skip-moving=\"true\">$lazyCoreContent", $content); $content = preg_replace('#<script type="text/javascript" +src="/bitrix/js/main/core/core\.min\.js(\?\d+)*">#', '<script type="text/javascript" src="/local/js/main/core_light/core.min.js" async>', $content); } } public static function makeAssetsAsync(&$content) { global $USER; if ($USER->IsAdmin()) { return; } if (CSite::InDir("/bitrix/")) {
return; } if (Option::get("my.main", "assets_async") !== "Y") { return; } $content = preg_replace('#(#', "$1 media=\"print\" onload=\"this.media='all'\">", $content); if (Option::get("my.main", "core_js_lazy") !== "Y") { $content = preg_replace('#(<script type="text/javascript" +src="/bitrix/js/main/core/core\.min\.js[a-z0-9/._?]+")(>)#', "$1 $2", $content); } $content = preg_replace('#(<script type="text/javascript" +src="[a-z0-9/._/-?]+")(>)#', "$1 async$2", $content); } }

Тут также реализуется асинхронная загрузка, в частности core_light ядра. Для админки сделал исключение - загружается полноценное ядро, так как иначе ничего не работает. Также добавил в своем кастомном модуле галочки для возможности отключения этого функционала из админки:

Выводы

Аудит PageSpeed наконец-то прошел:

Показатель "Скорость сайта" от Битрикса немного улучшился, в пределах десятой секунды. Кардинально облегчить ядро не получается, библиотеки зависят друг от друга, плюс достаточно много legacy кода в ядре. Вижу в Битриксе пытаются переписать BX, но на данный момент мы находимся в переходном периоде, когда нужно тянуть кучу старья для поддержки совместимости. Жаль, конечно, я бы в коде проекта переписал все под новое ядро, а старое выкинул...

    BX
    SEO
    Битрикс

©2024 ReactiveBx работает на «1С-Битрикс: Управление сайтом» и Next.js