Польза
Умное объединение стилей и скриптов

Умное объединение стилей и скриптов

15 марта 2020 года
Битрикс, css, js, объединение.

Каждый, кто задавался вопросом оптимизации работа сайта (по-большому счёту, — все) и применял битриксовский механизм объединения css- или js-файлов (далее — ассетсов), при анализе результата не мог быть удовлетворён полностью. Процесс объединения не поддаётся явному контролю, результат объединения может измениться по непонятным для нас причинам. При чём этот подход — сваливание в общую кучу всех ассетсов — явно не то, чего мы хотели бы получить: в любом проекте есть «зафиксированные» ассетсы (по которым работа не ведётся) и не хочется, чтобы на их кеш влияли новые, которые только находятся в работе. Давайте разберёмся в особенностях механизма (речь про чекбоксы «Объединять CSS файлы» и «Объединять JS файлы», в настройках Главного модуля) и посмотрим, на что мы можем повлиять.

Претензии, с которыми мы будем разбираемся:

  • Мне не нравится, что добавление нового компонента в каком-то месте сайта, приводит к перегенерации уже скомпилированного кеш-файла.
  • Мне не нравится, что они смешаны в кучу. Я хочу понять, по какому принципу механизм решает, какие ассетсы в какой кеш-файл определить.
  • Зная, какие ассетсы не будут меняться в обозримом будущем (к примеру, стили каркаса сайта), я хотел бы чтобы они объединялись в отдельный кеш-файл и никто из компонентов их не перегенеривал.

1. Стандартные потоки ассетсов.

В контексте обсуждаемого вопроса, в Битриксе есть понятие целевых потоков (\Bitrix\Main\Page\Asset::$targetList). Главные, с которыми мы имеем дело: ассетсы ядра, шаблона, страницы.

1.1. Ассетсы ядра

Код целевого потока: KERNEL — все, которые лежат в путях /bitrix/js/, /bitrix/css/.

Особенность: на них механизм объединения не действует.

Здесь же: аналогично не действует объединение для тех ассетсов, которые расположены в путях:

  • /bitrix/panel/
  • /bitrix/themes/
  • /bitrix/modules/

1.2. Ассетсы шаблона

Код целевого потока: TEMPLATE — те ассетсы, которые были добавлены во время выполнения кода, инициированного из файлов шаблона сайта: header.php и footer.php. Ещё раз, это важно: если у нас начал подключаться и выполняться header.php нашего шаблона сайта и внутри него был выполнен компонент и компонент добавил в общий список какой-то ассетс, этот ассетс детектится как относящийся к пространству TEMPLATE.

Признак: кеш-файл, подключенный в итоговый html-кода страницы, имеет в пути вкрапление template_, пример:
/bitrix/cache/css/{{SITE_ID}}/{{TEMPLATE_NAME}}/template_e4f06/template_e4f06_v1.css

1.3. Ассетсы страницы

Код целевого потока: PAGE — те ассетсы, которые были добавлены за границами файлов шаблона сайта. То есть подключили мы шаблон, начали выполнять /index.php — всё, начало действовать пространство страницы. И все компоненты, которые выполняются во время выполнения данного файла при добавлении нового ассетса в общий список маркируются пространством PAGE.

Вот почему например, если у нас какой-то компонент находился в шаблоне сайта (или до него дело доходило во время фактического выполнения header.php — например, через подключаемые области или шаблоны других компонентов), его ассетсы объединялись в кеш-файл template_*, а как только мы перенесли компонент на страницу, ассетсы переехали в page_*-файл.

Признак: кеш-файл, подключенный в итоговый html-кода страницы, имеет в пути вкрапление page_, пример:
/bitrix/cache/css/{{SITE_ID}}/{{TEMPLATE_NAME}}/page_e4f06/page_e4f06_v1.css

2. Пользовательские потоки ассетсов

Мы можем в любой момент времени, находясь в любом из пространств, прервать его (или вклиниться) и начать формировать своё пространство.

Порядок действий:

  • запоминаем текущий открытый поток (мы же не вредители, мы хотим грамотно вклиниться в процесс);
  • начинаем формировать свой поток.
    Важно! Прервать кастомный поток и дополнить ниже по коду мы не можем (в отличие от стандартных потоков);
  • набрасываем в поток ассетсы;
  • возвращаем поток, который действовал до того, как мы вклинились.

Код-пример:

use Bitrix\Main\Page\Asset;

$asset = Asset::getInstance();
$assetToRestore = $asset->getTargetName();

// Использовать только ЗАГЛАВНЫЕ_НАЗВАНИЯ
$asset->startTarget('ALEXEY_GFI');

// Устанавливаем идентификатор:
$asset->setUnique('ALEXEY_GFI', 'gfi');
// ...целевой файл будет выглядеть так:
// /bitrix/cache/css/{{SITE_ID}}/{{TEMPLATE_NAME}}/_gfi/_gfi_v1.css

$asset->addCss('/css/style.css');
$asset->addJs('/local/js/script.js');

// Стоп-аем наш поток перед возвратом в стандартный
// ...иначе не сможем переключить
$asset->stopTarget();

// Восстанавливаем поток, который действовал до нас
$asset->startTarget($assetToRestore);

3. Накапливание ассетсов в кастомный поток

Накапливать один и тот же кастомный поток из разных мест мы не можем. То есть как только мы стоп-аем кастомный поток, его повторно уже запустить не получится. Если же нам нужно иметь возможность накапливать кастомный поток, рекомендую набросать свой хелпер (рассмотрено на видео).

Действия при работе с хелпером:

  • накапливаем ассетсы в массив хелпера. Из любого места проекта, под любыми кодами потоков, с любым набором ассетсов
  • когда приходит момент — как можно ближе к формированию html-кода, но перед компиляцией ассетсов ядром Битрикса — хелпер формирует на основе собранных данных кастомные потоки и наполняет их ассетсами.

Код класса-хелпера из видео:

namespace AlexeyGfi;


use Bitrix\Main\Page\Asset;

class AssetsMergeHelper
{

    // Структура потоков
    protected static $assets = [];

    // Накапливаем css по разным потокам
    public static function add($streamName = null, $cssAsset = null)
    {

        if (empty($streamName) || empty($cssAsset)) {
            return;
        }

        if (!is_array($cssAsset)) {
            $cssAsset = [$cssAsset];
        }

        $streamName = ToUpper($streamName);

        self::$assets[$streamName] = self::$assets[$streamName] ?? [];
        foreach ($cssAsset as $css) {
            self::$assets[$streamName][] = $css;
        }

    }

    // Выгружаем накопленные потоки
    //
    // Метод подписан на событие
    // main / onBeforeEndBufferContent
    public static function finalize()
    {
        if (empty(self::$assets)) {
            return;
        }

        $asset = Asset::getInstance();
        $assetToRestore = $asset->getTargetName();

        foreach(self::$assets as $targetName => $assetsList) {

            $asset->startTarget($targetName);
            $asset->setUnique($targetName, ToLower($targetName));

            foreach($assetsList as $path) {
                $asset->addCss($path);
            }
        }

        $asset->stopTarget();
        $asset->startTarget($assetToRestore);
    }

}
 

Обсуждение статьи

Написать