Документация

Стили и скрипты дополнения на витрине

Asset::addCss и Asset::addJs: как подключить файлы из любого места страницы, чтобы они попали в общую склейку, и как не сломать копии, которые владелец правит в теме.

На этой странице

Дополнение, которое что-то показывает на витрине — кнопку, окно, форму, отметку в карточке, — приносит с собой стили и скрипты. С ядра 2.86.0 оно подключает их одним вызовом, как addJs и addCss в Битриксе, а куда поставить файл и с чем его склеить, решает магазин.

Зачем это нужно

Раньше каждое дополнение печатало свои <link> и <script> прямо в разметку. Работало, но дорого обходилось покупателю:

  • Файлы не попадали в склейку. Магазин склеивает стили и скрипты страницы в сжатые файлы, а тег, напечатанный в обход, оставался отдельным запросом. На главной штатной темы с пятнадцатью дополнениями было 23 файла, теперь их 4.
  • Стиль приходил поздно. Чат, окно согласия на cookie и уведомления выводятся в подвале, и их стиль загружался после всей страницы: блок успевал мигнуть неоформленным.
  • Один файл грузился дважды. Тема подключала Swiper своим путём, а слайдер — полным адресом, и браузер получал библиотеку два раза.

Как подключить файл

use App\Core\View\Asset;

return Asset::addCss('/plugins/acme/callback/callback.css')
    .Asset::addJs('/plugins/acme/callback/callback.js')
    .$tpl->fetch();

Вызывать можно откуда угодно, где собирается страница: из места в теме (storefront.head, storefront.body.end, product.buybox.end…), из своего тега шаблона, из блока в карточке товара. Магазин печатает списки файлов последними, когда страница собрана целиком, поэтому стиль, подключённый из подвала, всё равно окажется в <head>.

Вызов Что делает
Asset::addCss($path, $attrs = []) стиль в <head>
Asset::addJs($path, $attrs = ['defer' => true], $head = false) скрипт перед </body>; $head — в <head>
Asset::addInlineJs($code, $key) встроенный скрипт в <head>, раньше всех файлов
Asset::addInlineCss($code, $key) встроенный стиль в <head>
Asset::addString($html, $key, $footer = false) готовая разметка: meta, link rel="preload" и прочее

Путь к файлу

Как написан путь Что это Пример
от корня сайта файл дополнения в public/plugins/<vendor>/<code>/ /plugins/acme/callback/callback.js
без / в начале файл активной темы, в её assets/ plugins/callback/callback.css
https://… чужой адрес, подключается как есть https://cdn.example.com/x.js

Метку версии (?v=…) магазин допишет сам по времени изменения и размеру файла. Обновили дополнение — у покупателя загрузится свежий файл.

Повторы

Один и тот же файл, подключённый несколько раз, на странице будет один раз. Кнопка «купить в один клик» стоит в каждой карточке списка, но её скрипт загрузится однажды. Флаги «уже подключил» в дополнении больше не нужны.

Файл темы магазин узнаёт и по полному адресу: /theme-assets/default/vendor/swiper/swiper-bundle.min.js?v=… и vendor/swiper/swiper-bundle.min.js — это один и тот же файл.

Строка, которую возвращает вызов

Каждый вызов возвращает строку, и её надо выводить рядом с блоком, как в примере выше:

  • На собираемой странице строка пустая. Файл ушёл в общий список и в склейку.
  • В ответе без страницы это готовый тег. У куска, который браузер подгружает отдельно, нет ни шапки, ни подвала: так приходят таблица заказов в кабинете при переходе по страницам и карточки во вкладках ленты товаров. Тег встаёт прямо перед блоком, а второй раз за запрос тот же файл тега не даст: двадцать карточек с вашей кнопкой получат один комплект файлов.

Так один и тот же код работает в обоих случаях, и проверять, где вы находитесь, не нужно.

Чтобы файл попал в склейку

Склеиваются стиль без атрибутов и скрипт ровно с defer — именно так и работают addCss и addJs по умолчанию. Отдельным файлом останется:

  • скрипт без defer, с async или type="module";
  • скрипт со своими атрибутами (data-…);
  • стиль с media, кроме all, и стиль с @import;
  • файл с чужого адреса.

Настройки скрипту передавайте объектом, а не атрибутами тега. Скрипт, который читает data-* у собственного тега, склеить нельзя: в склейке его тега нет. Так было у «Гибкой маски телефона» до версии 1.1.0.

Было:

$event->append('<script src="/plugins/acme/mask/mask.js" defer data-country="UA"></script>');
var country = document.querySelector('[data-mask-config]').dataset.country;

Стало:

Asset::addInlineJs('window.AcmeMask = '.json_encode(['country' => 'UA'], JSON_HEX_TAG).';', 'acme-mask');
Asset::addJs('/plugins/acme/mask/mask.js');
var country = (window.AcmeMask || {}).country || 'UA';

Встроенный скрипт стоит в <head> и выполняется раньше любого файла, поэтому объект уже есть, когда скрипт до него доберётся. Для JSON внутри <script> используйте JSON_HEX_TAG, иначе строка </script> в настройках закроет тег.

Скрипт, который должен успеть раньше остальных, подключайте в шапку без defer:

Asset::addJs('/plugins/acme/analytics/analytics.js', [], head: true);

В склейку он не пойдёт, и это осознанная плата: так устроен счётчик аналитики, которому нужно поставить согласие на cookie до того, как заработают остальные скрипты.

Файлы, которые владелец правит в теме

Многие дополнения кладут свои шаблоны и стили в тему, в templates/plugins/<code>/ и assets/plugins/<code>/. Там их видит редактор оформления, и владелец меняет вид блока как любую другую страницу. Отсюда три правила.

1. Подключайте копию в теме, а не файл из пакета. Иначе правка владельца не дойдёт до витрины.

// копия лежит в themes/<тема>/assets/plugins/gifts/gifts.css
return Asset::addCss('plugins/gifts/gifts.css')
    .Asset::addJs('plugins/gifts/gifts.js')
    .$tpl->fetch();

2. Не печатайте <link> и <script> внутри шаблона, который копируется в тему. Подключайте файлы из кода дополнения: тогда владелец, удалив тег по незнанию, не сломает блок, а файл попадёт в склейку.

3. Учитывайте копии, которые уже лежат у клиентов. Нетронутую копию дополнение обновляет само: сравнивает её отпечаток с отпечатками своих прежних заводских версий. Правленую не трогает никогда. Если в правленой копии остался старый тег, свой файл второй раз не подключайте, иначе скрипт запустится дважды:

$source = (string) @file_get_contents($themeTemplatePath);

$tags = str_contains($source, '$chat.css') ? '' : Asset::addCss($css);
$tags .= str_contains($source, '$chat.js') ? '' : Asset::addJs($js);

return $tags.$html;

Так переведены «Чат на сайте» 1.8.0, «Push-уведомления и PWA-приложение» 1.6.0 и окно согласия в «Аналитике и согласии на cookie» 1.2.0.

Версия ядра в манифесте

Asset появился в ядре 2.86.0. Дополнение, которое им пользуется, обязано объявить это в plugin.json, иначе на старом магазине оно уронит страницу:

"requires": {
    "core": "2.86.0"
}

Если дополнение обращалось к списку файлов напрямую

Внутренний список страницы — класс Modules\Storefront\Support\ThemeAssets. До 2.86.0 некоторые дополнения звали его сами. Что поменялось:

  • методы css(), js(), inlineCss(), inlineJs() теперь возвращают ключ нового файла или null для повтора, а не void. Вызовы это не ломает, но класс-наследник с переопределением : void не загрузится;
  • renderHead() и renderFooter(), пока страница собирается, возвращают метку, а не теги: список подставляется в конце. Не вставляйте их результат в свой блок, иначе на странице окажется вторая копия всего списка.

Самый простой путь — перейти на Asset: он работает одинаково на странице и вне её.

Скрипты дополнений склеиваются отдельно

Скрипты дополнений идут своей склейкой, отдельно от скриптов темы и магазина. Если в вашем скрипте случится ошибка, она не остановит корзину и оформление заказа. Но соседние дополнения в той же склейке она остановит, поэтому оборачивайте код в функцию и не объявляйте на верхнем уровне let, const и class: два файла с одинаковым именем на верхнем уровне в одной склейке дают ошибку разбора.

(function () {
    'use strict';
    // весь код дополнения здесь
})();

Как проверить

  1. Откройте страницу с вашим блоком и посмотрите код страницы (Ctrl+U). Ваших <link> и <script> в теле страницы быть не должно, а в <head> будут ссылки на /storage/bundles/….css и ….js.
  2. Откройте склейку страницы и найдите в ней класс из вашего стиля.
  3. Проверьте блок в ответе без страницы, если он там бывает (корзина, подгрузка списка), — стиль должен прийти тегом перед блоком.

Какие дополнения уже переведены

Дополнение Версия
Гибкая маска телефона 1.1.0
Акции 1.2.0
Чат на сайте 1.8.0
Push-уведомления и PWA-приложение 1.6.0
Аналитика и согласие на cookie 1.2.0
Уведомление о поступлении 2.5.0
Купить в 1 клик 1.6.0
Обратный звонок 2.4.0
Автокомплит города 1.4.0
Реквизиты компании по коду 1.1.0
Подарки к заказу 1.5.0
Оплата по счёту 1.4.0
Повторить заказ 1.2.0
Слайдеры и баннеры 1.2.0
Оптовые цены 1.3.0

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

Обновлено 1 октября 2026