Привет, Хабр!
Меня зовут Дмитрий Матлах. Я тимлид в AGIMA. Мы с коллегами обратили внимание, что в сообществе часто возникает вопрос о том, как совместить на одном проекте Bitrix-компоненты и реактивные фронтовые движки. Мы неоднократно сталкивались с подобными задачами, и поэтому я решил подробно рассказать, как мы их решаем. Думаю, если вы используете Bitrix-фреймворк в своих проектах, прочитать будет интересно. Ну и забегая вперед, если вы решаете те же задачи по-другому, то интересно в комментариях узнать поподробнее.

Теория
Подходы к разработке шаблонов представления в проектах на 1С-Битрикс практически не менялись с момента создания платформы. 1С-Битрикс — MVC-фреймворк со своими особенностями. Но в то же время он всё равно работает по понятной схеме: данные модели + шаблонизатор с шаблоном представления = итоговый html клиенту. Классический вариант. Компоненты Битрикс и компоненты фронтовой части хранятся отдельно — всё круто.
Но вокруг все давно говорят о реактивных фронтовых движках и о новом уровне скорости работы и удобстве поддержки.
Интересно попробовать в своем проекте? Да! Но с Битрикс есть некоторые сложности.
Назрел вопрос: как уйти с проторенного пути и поменять фронтовый стек и при этом сохранить все возможности платформы:
Возможность инкапсулировать логику в компоненты Битрикс.
Использовать визуальный редактор для настройки роутинга и ключевых параметров модели.
Сделать интеграцию Backend- и Frontend-разработки простой и понятной.
Поддержать возможности современной фронтовой разработки. Прежде всего, сборку с Webpack. Что даст фронтовому разработчику возможность использовать ES6 и модульную структуру JS.
Обеспечивать индексацию контента страниц поисковым ботом, так как инициализация приложения проходит методами JS и до этого момента на странице не отображаются элементы DOM контентной части. Это создает сложности работы поисковым ботам, которые «не умеют» запускать JS на странице либо делают это частично. В качестве решения выступает механизм SSR (Server Side Rendering). Этот вопрос выходит за рамки данной статьи, и мы уже написали про это отдельную статью.
Теперь пробуем со всем этим взлететь.
Сейчас на рынке для перехода по фронтовому стеку есть два популярных решения — два реактивных JS-фреймворка. Думаю, вы догадываетесь, какие именно: React.js и Vue.js.
Нам ближе оказался Vue.js. Вот почему:
простота использования;
прекрасная документация;
легковесность (около 20 КБ — минимизированная сжатая версия);
может быть принят постепенно, даже используется как замена jQuery;
удобная структура хранения html, CSS, JS в компонентах;
все необходимые библиотеки в составе Router, Vuex (global store);
хорошая расширяемость (миксины, плагины и т. д.).
Итак, какие есть сложности?
Менять нужно не только стек, но и базовый паттерн с MVC на MVVM. Далее небольшой экскурс.

Работа компонента Битрикс при подходе MVC
Для вывода контента страниц в общем случае используется компонент и его шаблон как основа визуальной части. Роутинг начинается от корня раздела, где находится компонент. Компонент может быть простым и комплексным, т. е. отдает контент одной страницы либо различных в зависимости от параметра в URL.
Такая структура отражает паттерн MVC. Всё привычно. View — шаблон template.php — собирает html для отображения клиенту и, возможно, добавляет JS, который дополнит динамические возможности страницы.
Входные параметры при этом мы задаем при вызове компонента. Эти параметры попадают в шаблон вместе с данными из базы, которые им соответствуют, а роутинг осуществляется на серверной стороне.

Работа с Vue.js реализует паттерн MVVM.

Работа компонента Битрикс при подходе MVVM
Посмотрим на схему работы приложения Vue.js.

Представление формируется на базе компонента по текущему роуту и данных, которые содержит состояние (store).
В этом случае рассматриваем вариант хранения данных в едином внешнем для компонентов Vue-хранилище. Возможен упрощенный вариант передачи компонентам данных через параметры. |
Предлагаю обратить внимание на два момента.
Источник данных абстрагирован, т. е. это некий Backend, API, который отдает View-модели данные для отображения в компоненте по Endpoint’ам, определенным в методах actions.
Vue-приложение с несколькими логическими блоками, такими, как список/детальная, должно иметь внутренние правила роутинга для выбора подходящего компонента и комплектации представления нужными данными.
По нашему условию, приложение должно подключаться через один общий компонент, настраиваться через параметры в визуальном редакторе и содержать всё необходимое для работы в одном компоненте (возможно, комплексном).
С практической стороны это значит, что компонент Битрикс должен реализовать работу Vue-приложения с пробросом настроек компонента в настройки внутреннего роутинга SPA и обеспечить работу Endpoint’ов в части источника данных (Backend на схеме). Компонент должен «научиться» двум режимам работы:
При запросе к странице с Vue-приложением должен вернуть разметку SPA (single page application), код приложения, объект роутинга, объект хранилища и начальное состояние. Т. е. ведет себя как простой компонент с одним шаблоном.
При запросах данных в режиме Backend должен возвращать структуру для определения текущего состояния. Используем формат JSON. В таком варианте удобно использовать ЧПУ-режим комплексного компонента.
Соберем всё вместе
Упрощая, можно сделать абстрактный пример работы нашего компонента для каталога с выводом страницы списка и детальных страниц товаров.
При синхронном обращении компонент вернет разметку с блоком привязки Vue-приложения:
Скрипты фреймворка и плангины Vue, Vue-router, Vuex, Axios.
Базовые JS-объекты данных GlobalVuexStoreCatalog(list), GlobalVuexStoreProductCard(detail).
В составе SPA могут находиться сразу несколько шаблонов различных по назначению страниц, таких, как страница списка, детальная, страница результатов поиска и т. д. В таком случае требуется внутренний роутинг. JS-объект c правилами роутинга. Ремарка: в роутинге может не быть необходимости, если в составе SPA страница только по одному шаблону.
Глобальный JS-объект данных для SPA.
<script type="text/javascript" data-skip-moving="true"> if (typeof window.vueData !== "object") { window.vueData = {}; } window.vueData.url404 = '/404.php'; window.vueData.is404 = <?=$is404 ? 'true' : 'false'?>; … </script>
При асинхронном обращении компонент вернет данные из компонента Section либо Detail в виде JSON, который будет добавлен в ветке STATE Vuex-хранилища приложения.
Макеты верстки не содержатся в шаблонах компонентов Битрикс.
Шаблоны компонентов Vue, инициализация приложения целиком на стороне Frontend. Подключается в виде файлов сборки Webpack: catalog.js, catalog.css.
Разберем на примере для шаблона компонента каталога Axios. Для вывода SPA каталог должен иметь такую структуру:

Состав файлов /js/vuex.catalog.list.js и /js/vuex.catalog.product.js Vuex-структуры хранения и управления состоянием.

element.php и section.php — точки входа запроса. Они содержат вызовы компонентов реализации с буферизацией. В составе их обработки входит:
ob_start(); $ElementID = $APPLICATION->IncludeComponent( "bitrix:catalog.element", "shop.vue.element", array( "IBLOCK_TYPE" => $arParams["IBLOCK_TYPE"], "IBLOCK_ID" => $arParams["IBLOCK_ID"], ... ), $component, array("HIDE_ICONS" => "Y") ); $jsonElementData = ob_get_clean(); $is404 = (defined(ERROR_404) && ERROR_404 === 'Y') || \CHTTP::GetLastStatus() === '404 Not Found';
На выходе получаем объект JS, который можем добавить в component_epilog.php — общая часть. В нем содержится контейнер для вывода Vue-приложения и определение общей части значений Store.
<div id="catalog-vue" v-cloak></div> <div class="vue-preloader"> <div class="vue-preloader__container"> ... </div> </div>
Также здесь подключаются файлы сборки Frontend.
Asset::getInstance()->addCss(MARKUP_DIR . '/css/catalog.css', true);
Asset::getInstance()->addJs(MARKUP_DIR . '/js/catalog.js');
В итоге выходит такая схема:

На стороне Frontend-сборки JS-объекты Store подключаются в качестве модулей Vuex:
<script> import {useRouter} from "vue-router"; import {useStore} from "vuex"; export default { setup() { const router = useRouter(); const store = useStore(); /* eslint-disable */ if (typeof GlobalVuexStoreCatalog !== 'undefined') { store.registerModule('catalog', GlobalVuexStoreCatalog); } if (typeof GlobalVuexStoreProductCard !== 'undefined') { store.registerModule('product', GlobalVuexStoreProductCard); } return { router, } }, } </script>
Настройки роутинга также подключаются из внешнего JS-объекта, который мы определили в компоненте Битрикс.
const routes = window.routerSettings.routes.map((it) => { switch (it.type) { case 'catalog': return { path: it.path, component: catalog }; case 'product-card': return { path: it.path, component: product }; } }); const router = createRouter({ history: createWebHistory(window.routerSettings.BASE_URL), routes, scrollBehavior: () => { return { left: 0, top: 0 }; } }) ... app.use(VueWindowSizePlugin, { delay: 300, }); app.use(router); app.mount('#catalog-vue')
Итоги
Таким образом, мы даем возможность оставить всю реализацию компонентов отображения в отдельном репозитории и собирать их с помощью удобных для Frontend-разработчика инструментов.
Мы не переносим верстку в шаблоны компонентов Битрикс, что позволяет избежать этапа интеграции верстки и использовать протестированные макеты, точно совпадающие с требованиями. При проработке задачи требуется создать структуры хранения данных состояния «Store»-компонентов, общие для Backend и Frontend, для создания JSON-файла «заглушки» с приемочными данными для этапа сдачи макета верстки со стороны Frontend.
Реализация может показаться непростой, но в итоге приводит к понятной схеме разделения ответственности разработчиков и удобному внедрению компонентов с Vue-приложениями, в том числе и для использования в блоках конструктора лендингов «Сайты24».
Если вы дочитали до конца, то в этом точно есть что-то героическое, и думаю, вы заслуживаете плюс в карму.
