Сборка фронтенда рядом с шаблоном - исходники, бандл, приложение
Ставим современную сборку рядом с шаблоном: исходники, бандл, приложение в узле разметки и запросы к серверу.
Механика
Платформа отдаёт статику из двух мест: из шаблона сайта и из расширений. Всё остальное - вопрос договорённости, куда сборщик кладёт результат и как этот результат попадает на страницу.
Штатная единица сборки - расширение. У него есть свой каталог, описание файлов и зависимостей, а подключается оно одной строкой из любого места; собранный бандл просто указывают в описании вместо исходного файла.
Официальный сборщик рассчитан ровно на эту структуру. Он читает конфигурацию сборки рядом с каталогом и складывает результат в подкаталог с собранными файлами, поэтому спорить с ним о путях не приходится.
Свой сборщик тоже подходит, и это нормальный выбор. Условие одно: путь к собранному файлу должен быть стабильным, потому что именно его указывают в описании расширения или в подключении из шаблона.
Приложение на фреймворке монтируют в отдельный узел разметки. Это модульное внедрение, а не переписывание сайта: на странице спокойно живут два-три приложения рядом с обычной серверной разметкой.
У такого подхода есть плата, и её стоит знать заранее. Всё, что попало под управление фреймворка, платформа больше не редактирует своим режимом правки, и включаемые области там работать перестают.
Данные приложение берёт запросом к контроллеру. Ключ сессии при этом передают из разметки, а не зашивают в бандл: в долго открытой вкладке он протухает, и запрос возвращает отказ.
Выкладка - отдельная договорённость команды. На боевом сервере сборщика обычно нет, поэтому либо собранные файлы держат в репозитории, либо сборку выполняет отдельный шаг выкладки.
Шаги
- Выбрать единицу сборки: расширение, шаблон сайта или шаблон компонента.
- Разложить исходники и собранный бандл по разным каталогам одного модуля.
- Указать собранный файл в описании расширения вместо исходного скрипта.
- Смонтировать приложение в свой узел, оставив редактируемые области снаружи.
- Забирать данные запросом к контроллеру, передавая ключ сессии из разметки.
- Договориться, кто собирает бандл при выкладке: репозиторий или отдельный шаг.
Код
Раскладываем файлы расширения:
/local/js/vendor/catalog/├── bundle.config.js - что и куда собирать├── src/index.js - исходники приложения├── dist/catalog.bundle.js - результат сборки└── config.php - описание расширения для платформы// имя расширения складывается из пути: vendor и catalog дают vendor.catalogИсходники и результат живут рядом, но в разных каталогах. Платформа знает только о собранном файле, а всё остальное - внутренняя кухня проекта, которую она даже не разбирает.
Описываем конфигурацию сборки:
module.exports = { input: 'src/index.js', // точка входа приложения output: 'dist/catalog.bundle.js', // результат, который увидит платформа};// файл кладут рядом с каталогом расширения: сборщик обходит дерево самПрописываем собранный файл в расширении:
return [ 'js' => 'dist/catalog.bundle.js', // именно собранный, а не исходник 'css' => 'dist/catalog.bundle.css', 'rel' => ['main.core'], // зависимости расширения];// зависимости платформа подключит раньше нашего бандла, порядок держать не нужноОписание расширения указывает на результат сборки. Исходники в него не попадают никогда, иначе на страницу уедет неготовый код, а сборка потеряет смысл.
Собираем и следим за изменениями:
npm i -D @bitrix/clinpx bitrix build # разовая сборка по конфигурацииnpx bitrix build -w # то же самое с наблюдением за файлами# отладочные карты остаются только после сборки в режиме наблюденияРежим наблюдения нужен для работы, разовая сборка - для выкладки. Разница принципиальная: в наблюдении сборщик оставляет отладочные карты, которых на боевом сайте быть не должно.
Монтируем приложение в узел шаблона:
<!-- template.php шаблона компонента --><div id="catalog-app" data-sessid="<?= bitrix_sessid() ?>"></div><?php \Bitrix\Main\UI\Extension::load('vendor.catalog'); ?><!-- редактируемые области держат снаружи этого узла -->Узел монтирования - это граница ответственности. Внутри него разметкой управляет фреймворк, снаружи - платформа, и смешивать их в одном контейнере не выйдет ни одним из известных приёмов.
Читаем ключ сессии и ходим за данными:
const root = document.getElementById('catalog-app');const res = await fetch('/bitrix/services/main/ajax.php?action=vendor:catalog.api.list', { method: 'POST', headers: { 'X-Bitrix-Csrf-Token': root.dataset.sessid },});// ключ берут из разметки: зашитый в бандл он протухнет в открытой вкладкеЕдиная точка входа для запросов - контроллер платформы. Он проверяет ключ сессии, права и параметры, а приложение получает готовый ответ в понятном формате, без разбора разметки страницы.
Подключаем бандл шаблона без расширения:
use Bitrix\Main\Page\Asset;
Asset::getInstance()->addJs(SITE_TEMPLATE_PATH . '/dist/app.bundle.js');Asset::getInstance()->addCss(SITE_TEMPLATE_PATH . '/dist/app.bundle.css');// метку версии по времени изменения файла платформа добавит сама// путь от корня сайта: SITE_TEMPLATE_PATH уже содержит /local/templates/<имя>Договариваемся о том, что лежит в репозитории:
node_modules/local/js/vendor/catalog/dist/*.map# сам бандл в репозитории оставляют: сборщика на боевом сервере обычно нетСобранные файлы в репозитории - обычная практика на проектах без отдельного шага сборки при выкладке. Решение принимают один раз и записывают, иначе на боевом сайте однажды окажется бандл недельной давности.
Ограничения
Режим правки внутри примонтированного приложения не работает. Включаемые области, кнопки редактирования компонентов и панель управления рассчитаны на серверную разметку, и восстановить их внутри клиентского приложения нечем.
Композитная страница отдаётся раньше, чем стартует приложение. Данные, которые зависят от посетителя, тянут отдельным запросом после загрузки, а не подставляют в разметку при сборке страницы.
Содержимое, отрисованное на клиенте, поисковик видит хуже. Каталог, карточки и тексты оставляют на серверной отрисовке, а на клиент выносят интерактив: конфигураторы, фильтры, корзину, подсказки поиска.
Шаблоны компонентов плохо дружат с общим сборщиком. Путей много, каждый шаблон живёт своей жизнью, поэтому либо собирают каждый шаблон отдельно, либо выносят логику в расширения и подключают их из шаблонов.
Ключ сессии протухает, и это ломает долгие вкладки. Приложение должно уметь получить отказ по ключу, обновить его запросом и повторить действие, а не показывать пользователю ошибку с просьбой перезагрузить страницу.
Сборщик на боевом сервере - редкость. Установка узловых пакетов на площадке хостинга часто просто невозможна, поэтому решение о месте сборки принимают до начала работы, а не в день выкладки.
Типичные проблемы
Режим правки не открывается внутри приложения.
Разметкой в этом узле управляет фреймворк, и штатные области платформы там не живут. Редактируемые блоки выносят за пределы точки монтирования.
Запрос возвращает отказ по ключу сессии.
Ключ зашит в собранный файл или получен при загрузке и успел протухнуть. Его читают из разметки и обновляют при отказе.
На боевом сайте старая версия скрипта.
Бандл собран локально и не попал в выкладку, потому что каталог с результатом сборки исключён из репозитория. Правило хранения собранных файлов записывают явно.
Расширение подключается, но кода на странице нет.
В описании расширения указан исходник вместо собранного файла или путь к результату сборки изменился. Описание правят вместе с конфигурацией сборки.
После сборки на странице появились отладочные карты.
Выложен результат сборки в режиме наблюдения. Для выкладки используют разовую сборку, а карты исключают из репозитория.
Приложение не видит данных на композитной странице.
Страница отдана из кэша до выполнения серверного кода, и данных в разметке нет. Их запрашивают отдельно, после загрузки страницы.
Частые вопросы
Как получить ключ сессии в своём скрипте?
Прочитать его из разметки: из атрибута узла, скрытого поля или мета-тега, куда его положил шаблон. Зашивать ключ в собранный файл нельзя - он меняется, и в долго открытой вкладке запросы начнут получать отказ.
Нужен ли этап сборки для небольшого приложения?
Нет: фреймворк подключается обычным скриптом и работает без сборки, и для одного-двух виджетов этого достаточно. Сборка окупается там, где появляются компоненты, импорты и общий код между приложениями.
Как подружить сборщик с шаблонами компонентов?
Проще не собирать каждый шаблон, а вынести код в расширения и подключать их из шаблонов одной строкой. Общий конфиг сборки на десятки путей шаблонов компонентов быстро становится неуправляемым.
Можно ли переписать весь сайт на фреймворке?
Технически можно, но тогда платформа остаётся источником данных, а вёрстка, адреса и поисковая оптимизация ложатся на приложение. Обычно выбирают модульное внедрение: интерактив на клиенте, страницы на сервере.
Где держать исходники: в репозитории проекта или отдельно?
В том же репозитории, рядом с каталогом расширения или шаблона: так правка вёрстки и правка кода едут одной выкладкой. Отдельный репозиторий фронтенда оправдан только при отдельной команде и своём цикле выпуска.
Смежное
- Свои расширения JS - оглавление подтемы
- Клиентский код в шаблоне: инициализация, делегирование, дубли - что писать в самом шаблоне
- Расширение JS не подключается: разбор причин - когда правки не доезжают до браузера
- Vue-приложение на странице: запуск, компоненты, данные - штатная обёртка вместо своей сборки
- Своё расширение JS: каталог, зависимости, подключение - устройство самого расширения
- AJAX-запрос: контроллер, действия, ответ - куда ходит приложение за данными
- Стили и скрипты в шаблоне: подключение, порядок, кэш - подключение статики без сборки
- BitrixVue 3 - штатный фреймворк платформы
- JS и UI - устройство клиентской части целиком