Стили и скрипты дополнения на витрине
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';
// весь код дополнения здесь
})();
Как проверить
- Откройте страницу с вашим блоком и посмотрите код страницы (Ctrl+U). Ваших
<link>и<script>в теле страницы быть не должно, а в<head>будут ссылки на/storage/bundles/….cssи….js. - Откройте склейку страницы и найдите в ней класс из вашего стиля.
- Проверьте блок в ответе без страницы, если он там бывает (корзина, подгрузка списка), — стиль должен прийти тегом перед блоком.
Какие дополнения уже переведены
| Дополнение | Версия |
|---|---|
| Гибкая маска телефона | 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 |
Их код — хорошие образцы: в каждом видно, как файл подключается и откуда.