Перейти к содержимому

Кастомизация Vue-компонента - мутация, клон, порядок загрузки

Правим чужой Vue-компонент, не трогая его исходники: мутацией на месте или клоном рядом. Правка файлов ядра при первом же обновлении продукта откатывается, а мутация переживает обновление.

Решение

Тип компонента

Смотрим, как объявлен исходный компонент:

import { BitrixVue } from 'ui.vue3';
// мутабельный: объявлен методом обёртки и дальше доступен по имени
const Widget = BitrixVue.mutableComponent('local-widget', {
template: 'Список задач',
});
// классический: обычный объект Vue, имени в обёртке у него нет
export const Plain = { template: 'Список задач' };

Мутабельным компонент делает только объявление через обёртку платформы. Имя пишут в kebab-case и начинают с имени модуля, иначе решения разных вендоров столкнутся именами.

Проверяем тип возвращаемым значением:

const applied = BitrixVue.mutateComponent('local-widget', {
template: `<div class="my-frame">#PARENT_TEMPLATE#</div>`,
});
console.log(applied); // false - компонент классический, правка не применилась

Для классического компонента вызов возвращает признак неудачи и делает это молча: ошибки в консоли не будет. Такому компоненту доступно только клонирование.

Мутация

Заменяем шаблон и метод оригинала:

BitrixVue.mutateComponent('local-widget', {
template: `<div class="my-frame">#PARENT_TEMPLATE#</div>`, // оригинал внутри
methods: {
save(item) {
this.trackChange(item); // своя строка перед штатной логикой
return this.parentSave(item); // вызов исходного метода
},
},
});

Мутация применяет только перечисленные ключи и накапливается: второй вызов не отменяет первый. К исходным частям обращаются через префиксы, а не копируют их код к себе.

Дотягиваемся до данных и наблюдателей:

BitrixVue.mutateComponent('local-widget', {
data() {
return { ...this.parentData(), compactMode: true }; // данные плюс своё
},
watch: {
items(value) { this.parentWatchItems(value); }, // наблюдатель оригинала
},
replaceEmits: ['local:widget:saved'], // список событий целиком
});

Родительский префикс даёт доступ к исходной части, префикс замены затирает её целиком. Списки примесей, внедрений и событий меняются только вторым способом.

Мутация в третьей версии обёртки действует лишь до создания экземпляров. Уже отрисованные экземпляры и клоны оригинала правку не получат, поэтому регистрируют её при загрузке расширения.

Клон

Делаем независимую копию оригинала:

const Compact = BitrixVue.cloneComponent('local-widget', {
template: `Компактный список`,
});
// клон снимается с ОРИГИНАЛА, а не с уже мутированной версии компонента
// клонировать можно и классический компонент, которому мутация недоступна

Клон нужен там, где правка требуется на одном экране, а штатный вид компонента остаётся на всех остальных. Мутация же меняет компонент везде, где он выводится.

Порядок загрузки

Объявляем оригинал зависимостью своего расширения:

/local/js/local/widget-custom/config.php
return [
'js' => 'dist/widget-custom.bundle.js',
'rel' => ['ui.vue3', 'local.widget'], // оригинал подключится раньше нас
];

Обращение к компоненту по имени до загрузки оригинала даёт некорректный результат. Ошибки при этом не будет: клон соберётся пустым, а мутация уйдёт в никуда.

Проверка результата

Включаем режим разработки обёртки:

/bitrix/php_interface/init.php
define('VUEJS_DEBUG', true); // обёртка перестаёт молчать об ошибках
define('VUEJS_LOCALIZATION_DEBUG', true); // коды фраз вместо переводов

Дерево компонентов и применённые правки смотрят браузерным расширением Vue.js devtools. Само расширение обёртки требует модуль интерфейсов версии 22.100.0 и выше.

Пересобираем исходники после каждой правки:

Окно терминала
cd /local/js
bitrix build -w # флаг включает слежение за изменениями исходников

Без пересборки страница получит прежний бандл, и правка не появится. Мутацию перепроверяют после каждого обновления продукта: состав ключей у штатного компонента между версиями меняется.

Типичные проблемы

Мутация не применилась, ошибки в консоли нет.

Компонент объявлен обычным объектом Vue, а не через мутабельное объявление обёртки. Вызов mutateComponent вернул признак неудачи молча, и правка нигде не применилась.

Правка видна не везде: часть блоков осталась прежней.

Мутацию зарегистрировали после того, как экземпляры компонента уже отрисовались на странице. На отрисованные экземпляры и на клоны оригинала мутация не действует.

Клон получился пустым, а оригинал при этом работает.

Клон снимали по имени раньше, чем на странице загрузилось расширение с оригиналом. Оригинал объявляют зависимостью своего расширения, и тогда порядок держит платформа.

Правки чужого компонента исчезли после обновления продукта.

Компонент правили прямо в файлах ядра, а обновление вернуло их к исходному виду. Свой код держат в отдельном расширении и меняют компонент мутацией.

В собранном бандле изменений нет, хотя исходники правились.

Сборщик не запускали после правки, и страница получила прежний собранный файл. Слежение за изменениями включает флаг у команды сборки расширений.

Частые вопросы

Как поменять штатный компонент Битрикса, не трогая его файлы?

Мутацией: обёртка применяет к оригиналу только перечисленные ключи, а к исходным частям даёт доступ через префиксы. Правка живёт в своём расширении и переживает обновление продукта, в отличие от правки файлов ядра.

Мутация или клон - что выбрать?

Мутация меняет компонент везде, где он выводится, клон - только там, где подключён сам клон. Если штатный вид компонента должен где-то остаться прежним, берут клон.

Почему mutateComponent вернул false?

Компонент классический, а не мутабельный. Мутация работает только с тем, что объявлено через mutableComponent; классический компонент копируют вызовом cloneComponent, который работает с обоими видами.

Компонент не рендерится после обновления Битрикса, ошибок в консоли нет.

Сначала проверяют версию модуля интерфейсов и пересобирают свои расширения командой сборки. Потом сверяют мутацию с оригиналом: состав ключей у штатного компонента между версиями меняется, и правка уходит в никуда.

Где искать имя штатного компонента, который надо поправить?

В папке компонентов первого уровня внутри расширения: имя мутабельного компонента пишется в kebab-case и начинается с имени модуля. У классического компонента имени в обёртке нет, его находят по экспорту расширения.

Смежное

Первоисточник