Привет, Хабр!

Меня зовут Дмитрий Матлах. Я тимлид в 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-хранилище. Возможен упрощенный вариант передачи компонентам данных через параметры.

Предлагаю обратить внимание на два момента.

  1. Источник данных абстрагирован, т. е. это некий Backend, API, который отдает View-модели данные для отображения в компоненте по Endpoint’ам, определенным в методах actions.

  2. Vue-приложение с несколькими логическими блоками, такими, как список/детальная, должно иметь внутренние правила роутинга для выбора подходящего компонента и комплектации представления нужными данными.

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

С практической стороны это значит, что компонент Битрикс должен реализовать работу Vue-приложения с пробросом настроек компонента в настройки внутреннего роутинга SPA и обеспечить работу Endpoint’ов в части источника данных (Backend на схеме). Компонент должен «научиться» двум режимам работы:

  1. При запросе к странице с Vue-приложением должен вернуть разметку SPA (single page application), код приложения, объект роутинга, объект хранилища и начальное состояние. Т. е. ведет себя как простой компонент с одним шаблоном.

  2. При запросах данных в режиме 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».

Если вы дочитали до конца, то в этом точно есть что-то героическое, и думаю, вы заслуживаете плюс в карму.

Комментарии (10)


  1. potaningen
    19.10.2021 14:20
    +2

    Интересно. Очень


  1. gian_tiaga
    19.10.2021 21:14

    Придумать проблему и героически её решить.

    Если vue.js берёте то какой смысл костылить к нему что-то, напишите апи нормальный с которым ваше фронт приложение будет общаться.

    А если хотите стандартные компоненты использовать то не используйте vue или используйте в рамках, не как spa.


    1. mclaod Автор
      19.10.2021 21:15
      +4

      Никакой проблемы нет, если мы реализуем приложение с нуля, с выбором фреймворка специально для построения api. Очевидность преимуществ использвания api и других вполне себе рациональных подходов в статье не оспаривается.

      Проблема для меня видна в том, что использование Битрикс в проектах в большинстве своем не про api, а про компоненты со встроенным шаблоном представления.

      Задачей являлось продолжать использовать практически весь написанный ранее код (модель), не переписывая проект с нуля. При этом, научиться наполнять Vue компонент данными , по-прежнему использовать возможность настройки через визульный редактор: настройку путей ЧПУ, количество элементов в списке, постраничку и т.д. Также сохранить все возможности, присущие компонентам Битрикс - возможность встраивания и повторного использования с настройкой "по-месту", своего рода SPA в компоненте. Это хорошо себя показало на практике. Некоторые базовые компоненты Битрикс, как sale.order.ajax "из коробки" работают с выдачей шаблону компонента данных в json, что позволило использовать vue компонент с малым количеством правок.

      Кроме того, решение позволило в составе компонента использовать сборку webpack, что очень хорошо принято фронтовыми разрабочиками по понятным причинам. Также, больше не нужна интеграция верстки, как это бывает на практике с Битрикс компонентами. Можем протестировать верстку однократно. Преимещества перед стандартными компонентами оказались весомыми. Мы хотели использовать Vue.js SPA в виде компонента Битрикс и нашли возможность. Если у Вас работающий Битрикс-проект, то, на мой взгляд, - это возможность его развить.


    1. zorn-v
      26.11.2021 16:43

      Придумать проблему и героически её решить.

      С битриксом всегда так. Ну хоть в "массы" пытаются вынести свои проблемы.


  1. serbinyo
    22.10.2021 15:14

    А можете код примера на githab выложить? Хочется потрогать)


    1. mclaod Автор
      22.10.2021 15:15

      К сожалению,код проекта под NDA. Основная идея представлена в примерах кода в контексте статьи


  1. ArrayPop
    22.10.2021 16:04

    Слишком усложненная схема. SPA для сайтов которые должны индексироваться в поисковых системах так себе идея для битрикс. Но если заказчик так сильно хочет, то наверно проще было бы сделать SPA на основе html а не json. Просто в проекте отлавливаем все клики на js по ссылкам, и выполняем ajax запрос на эти страницы, например http://example.xom/about/. Мы получим полностью контент с хедером и футером по ajax запросу в виде html и сможем вырезать workarea на js, можно и на php, это дело техники. Да, скорости это не даст SPA приложению, так как мы делаем запрос на всю страницу с хедером и футером, также нужно позаботиться о том что бы все события js работали после рендеринга контента. Но это будет скорее проще и надежнее чем пытаться на vue или react перенести.


    1. mclaod Автор
      22.10.2021 16:44
      +1

      Я согласен, что выглядит не очень просто, но плюсы все же есть.

      В этом случае, принципиальное отличие подхода с перезагрузкой блока внутри страницы через ajax в использовании реактивного фронтового движка. Ajaх перезагрузка контента остается перезагрузкой и это, на мой взгляд, не совсем SPA.

      К примеру, для SPA каталога (в нашем случае на Vue.js) в теле страницы загружены сразу шаблоны списка товаров и детальной страницы (также могут быть другие шаблоны "страниц": сравнения и т.д.). . Внутри SPA свой собственный роутинг и все работает как "отражение" текущего состоянию JS объекта с данными. Что это дает:

      1. Смена данных в JS объекте приводит к соответвующим изменениям на странице. Это та самая "реактивность". И больше нет необходимости объявлять обработчики незвисимо на как каждом интерактивном элементе. (jQuery для простоты выбора элементов можно больше не подключать, кто ранее использовал) - дает очень существенное упрощение работы с любыми обработчиками и упрощение логики работы компонента в целом.

      2. Переключение между шаблонами происходит мгновенно. При необходимости, запрашиваются недостающие данные от компонентов и только данные. Результат может кэшироваться. При повторном переходе внутри spa задержки нет вообще. С точки зрения user experience отклик страницы всегда очень быстрый( можно привести аналогию с мобильным приложением)

      3. Повторное использование фронтовых компонентов практически повсеместное. Компонент товара с небольшим числом доработок используется в списке каталога, в детальной странице, в корзине, в разделе checkout. Т.е. практически везде.

        На самом деле, есть очень существенные плюсы. Но есть и минус - страница не работает без JS, что ограничивает индексацию. Но проблема также имеет решение. Мой коллега описывал ее в отдельной статье. Прошу обратить внимание, если эта тема Вам интересна: https://habr.com/ru/company/agima/blog/578056/

      .


  1. greenkey
    02.11.2021 01:13

    Да! Но с Битрикс есть некоторые сложности...

    ... и эта сложность - сам битрикс.


    1. mclaod Автор
      08.11.2021 16:47

      Есть плюсы и минусы. Выбор всегда за Вами.