SFC Vue3 Компоненты в Bitrix, с использованием Composition API, без сборщиков, без CDN и NPM пакетов

В данной статье будет показана нестандартная реализация компонентов Vue + Bitrix, которая не была показана этом видео, и в других подобных.

Спасибо большое разработчикам Битрикса за интеграцию, но как многие писали уже про данную интеграцию: «Она находится лишь в зачаточном виде», в целом да, я согласен. Без Bitrix CLI будет трудно написать полноценный SPA сайт на Битриксе, да и тем более кому это надо? Это же просто сущий ад будет для любого Frontend разработчика, проще будет использовать Bitrix как API, но не суть.

Я нашел способ ± нормально писать без костылей SFC Vue компоненты на Bitrix, возможно кто то уже так делал, а возможно и нет, тем не менее эта статья возможно кому то поможет.

CDN мы использовать не будем, а будем использовать интегрированный Vue, как минимум потому что разработчики Bitrix не плохо смогли его интегрировать для SFC компонентов.

Для начала давайте подключим собственно Vue.

Сразу хотелось бы оговорится для тех кто не в курсе, Composition API появился только в 3 версии Vue. По этому мы его и подключаем. Обычный Vue тоже подойдет, но придется использовать Options API вместо Composition API.

Данный экстеншен 3 версии появился в относительно новой версии Bitrix, по этому не забываем обновляться!

После того как мы подключили Vue — далее приступаем к разработке компонента Vue.

SCRIPT.JS

/** This function is called when the DOM is ready. */
BX.ready(() => {
    /**
     * Returns an element by its ID
     * @param {string} id - The ID of the element to be returned
     * @returns {HTMLElement} The element with the specified ID, or null if no element with the specified ID exists
     */
    const node = BX('app')
    const application = BX.Vue3.createApp({
      /**
       * This function is a lifecycle hook that is called when the component is first created.
       * It sets up the component's state by returning an object that contains the component's data.
       * @returns {object} An object containing the component's data
       */
      setup() {
        /**
         * Returns a reactive reference to a value.
         * @param {any} value - The value to be referenced
         * @returns {import('vue').Ref} A reactive reference to the value
         */
        const counter = BX.Vue3.ref(0);

        setInterval(() => {
          counter.value++;
        }, 300);

        return {
          counter,
        };
      },
    });
    /**
     * Mounts the Vue 3 application instance to the DOM element.
     * @param {HTMLElement|String} node - The DOM element to mount Vue 3 application instance
     */
    application.mount(node);
})

и многие сейчас заметили кто уже пробовал писать Vue компоненты что у нас отсутствует свойство template в объекте который мы передаем в createApp.

Я же обещал что будем писать без костылей ?) Значит будем без костылей.

TEMPLATE.PHP

{{ counter }}

Да все верно, мы пишем в template.php разметку как будто мы работаем во Vue файле.

Ну и соответственно в style.css размечаем стили для нашего компонента, и даже выглядит без костылей я бы сказал.

Прям слишком уже красиво для Bitrix.

А я скажу что можно еще красивее.

Еще красивее…

100% все заметили что мы обращаемся через объект Bitrix BX.Vue3 и выглядит это все таки мягко говоря не красиво, не смотря на то что мы сделали SFC компонент без костылей. Без вебпака, без Vite, без Bitrix CLI и прочей мишуры.

Но все же выглядит это так себе, но и тут у нас есть куча вариаций как быть и тут уже каждый будет решать для себя как ему нравится.

Если на проекте используется Vue везде, значит подключим Vue глобально. Условно в header.php

А значит что объект BX.Vue3 есть везде, следовательно мы подключим другой скрипт глобально. И сделаем следующие операции в данном скрипте.

for (const key in BX.Vue3) window[key] = BX.Vue3[key]

Да мы просто вынесем функции из BX.Vue3 в window, и нам станет доступен нативный синтаксис Vue3

и значит мы можем переписать свой script.js

BX.ready(() => {
    const node = BX('app')
    const application = createApp({
      setup() {
        const counter = ref(0);

        setInterval(() => {
          counter.value++;
        }, 300);

        return {
          counter,
        };
      },
    });
    application.mount(node);
})

Не надо оваций я всего лишь засорил вам window. Но как решение проблемы — сойдет. После чего можно еще по желанию удалить из window BX.Vue3, так как все функции мы вынесли уже.

Но многие спросят. А что если у меня Vue используется не везде, и я не хочу засорять window, но хочу использовать функции Vue без BX.Vue3?

Так вот (кто-то уже догадался) и тут выход тоже есть, и причем весьма простой.

Мы используем деструктуризацию для этого.

const { ref, createApp } = BX.Vue3

И у нас все так же будет замечательно работать, только теперь данные функции у нас не в объекте window

И да этот способ тоже подойдет для глобального скрипта, и ваши функции будут в объекте window, но константами.

Вот полный код скрипта как это будет выглядеть внутри компонента

BX.ready(() => {
    const { ref, createApp } = BX.Vue3
    const node = BX('app')
    const application = createApp({
      setup() {
        const counter = ref(0);

        setInterval(() => {
          counter.value++;
        }, 300);

        return {
          counter,
        };
      },
    });
    application.mount(node);
})

Окей, разобрались с компонентом. Давайте перейдем к вещам посложнее. Связка front + back.

Первый вопрос у многих, а как нам получить данные с бэка которые у нас были получены в component.php.

Есть вариант вот такой вот. Не самый красивый, но работать будет.

Нам нужно в template.php добавить тег скрипт и поместить туда следующий код.

const arResult = 

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

const arResult = 

Либо для совсем дедов вот этот метод, но как по мне он уже в прошлом.

А теперь самый крутой вариант. Буквально NEXT GEN

Раз мы все равно написали свой компонент. тк как нативные компоненты bitrix работают без Vue. Предлагаю переходить от component.php, к class.php

Чтобы прям совсем круто было.

Создаем с нуля наш компонент и пишем его на class.php, и делаем ± что то посложнее чем реактивные таймеры.

TEMPLATE.PHP

  • {{ user }}

Создаем наш темплейт, с логикой Vue, в данном примере будет пример добавления пользователя и удаление пользователя (не из базы данных, а из массива в классе, но вы же умные — все сможете подстроить под себя)

Немного добавим стилей в style.css, чтобы все убедились что стили работают

.users-list {
    padding: 12px;
}
.users-list__item {
    padding: 12px;
    background-color: #e2e2e2;
    border-radius: 4px;
    margin-bottom: 8px;
}

Далее переходим к самому интересному

CLASS.PHP

 1,
            'name' => 'Alexander',
        ],
        [
            'id' => 2,
            'name' => 'Ivan',
        ],
        [
            'id' => 3,
            'name' => 'Sergey',
        ],
        [
            'id' => 4,
            'name' => 'Smith',
        ],
        [
            'id' => 5,
            'name' => 'John'
        ],
    ];
    public function configureActions(): array
    {
        return [];
    }

    public function deleteUserAction($id): array
    {
        foreach (self::$users as $key => $user) {
            if ($user['id'] == $id) {
                unset(self::$users[$key]);
                break;
            }
        }
        return self::$users;
    }

    public function getUsersAction(): array
    {
        return self::$users;
    }

    public function addUserAction(): array
    {
        $lastUser = end(self::$users);
        $newUser = [
            'id' => $lastUser['id'] + 1,
            'name' => $_POST['name'],
        ];
        self::$users[] = $newUser;
        return self::$users;
    }

    public function executeComponent(): void
    {
        $this->includeComponentTemplate();
    }
}

Тут все просто подключаем наш Vue, далее создаем класс и наследуемся от стандартного CBitrixComponent и реализуем интерфейс чтобы наш класс был контролируемым и мы могли работать с ним через BX.ajax.runComponentAction, чтобы не выходить за пределы компонента и создаем массив пользователей с которым будем работать. Далее переходим к скрипту.

SCRIPT.JS

const { runComponentAction } = BX.ajax;
const { ref, createApp, reactive } = BX.Vue3;
BX.ready(() => {
  createApp({
    setup() {
      function deleteUser(id) {
        const request = runComponentAction("test:test", "deleteUser", {
          mode: "class",
          data: { id: id },
        });
        request.then((reponse) => arResult.users = reponse.data);
      }

      function addUser(e) {
        e.preventDefault();
        const request = runComponentAction("test:test", "addUser", {
          mode: "class",
          data: new FormData(e.target),
        });
        request.then((reponse) => arResult.users = reponse.data);
      };

      const arResult = reactive({});
      const request = runComponentAction("test:test", "getUsers", {
        mode: "class",
      });
      request.then((reponse) => (arResult.users = reponse.data));

      return {
        arResult,
        addUser,
        deleteUser
      };
    },
  }).mount("#users-list");
});

Далее тут все просто, берем создаем наше Vue приложение, создаем нужные нам объекты, переменные, функции и работаем с ними.

В самом начале создаем реактивный объект arResult в который будем помещать наши другие массивы, или объекты, данный объект будет отслеживать изменения в нем и менять страницу в соответствии с нашей логикой в template.php.

Вот что должно было у нас получится на странице.

Вот что должно было у нас получится на странице.

Соответственно нажимая на кнопку удалить пользователя, он удалится у нас на странице, если добавить, то пользователь добавиться. Только из за того что мы удаляем из статичного массива, при добавлении после удалении пользователя, мы все равно получим пользователя которого удалили (ну как бы из кода понятно).

Примеров можно придумать уйму как с этим работать, в любом случае стоит отметить что это довольно удобно и быстро.

Так же для удобства можно подключить VueDev Tools они так же довольно удобно подключаются из коробки.

Выводы

SFC компоненты очень удобно разрабатывать, до SPA вряд ли такими темпами дойдем, нужно ждать более лучшую интеграцию, либо использовать Bitrix как API и не парится, но если такого варианта нет. То данный способ очень не плох.

Преимущества:

  1. Не нужно настраивать package.json, устанавливать node и node_modules в Bitrix, все реализовано «нативными средствами»

  2. Не нужно знать Bitrix CLI для того чтобы создавать свой экстенешн в котором будет Vue приложение.

  3. Верстка template размещается не в JS, её рендерит сервер, а значит если у вас есть какой то текст то страница в теории не будет помечена ботом как пустая

  4. Есть возможность использовать COMPOSITION API, которая является более производительной чем предыдущая API + кода стало меньше, и он стал более читаемый

Недостатки:

  1. Вряд ли получится создать отдельно другой маленький компонент и использовать его.

  2. Не получится сделать адекватное SPA приложение. (Ну вернее можно, но не захочешь)

Как по мне вышло не плохо

© Habrahabr.ru