javascript

БЭМ + React: гибкая архитектура дизайн-системы

  • вторник, 24 октября 2017 г. в 03:13:43
https://habrahabr.ru/company/alfa/blog/340522/
  • Open source
  • JavaScript
  • HTML
  • CSS
  • Блог компании «Альфа-Банк»





Дизайн — это фашизм. Фашизму нужна питательная среда. Он начинает раскрываться в полной мере только на крупных масштабах. Идеальная среда для фашизма — это большая компания с огромным количеством продуктов. Например, Google или… Альфа-Банк. Фашизм априори не гибок…

Все кнопочки на всех продуктах компании должны носить одинаковые рубашки, только одного номенклатурного цвета #F02823. Любая ссылка также имеет свою униформу: цвет #0A1E32, нижнее подчеркивание на расстоянии 2px. Если мы нажмем на ссылку, она должна незамедлительно выполнить команду — перенести нас на другой раздел приложения. За неподчинение — изгнание из дизайна системы Альфа-Банка в Зеленый Банк или расстрел. И неизвестно, что бы в этом случае выбрала ссылка.

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

Дизайн — это фашизм во имя Любви


Это фашизм ради драйва и скорости. Мы хотим разрабатывать наши приложения быстро, чтобы разработчики не изобретали каждый раз велосипед для новых приложений и могли шарить лучшие UI/UX-практики между командами.

Любой фашизм предполагает идеологию. Любой фашизм предполагает централизованное принятие решений. Для любого фашизма компании необходимо завести Самый Главный Комитет по Цензуре и Унификации или СГКпЦиУ.

Но, подождите, теперь Альфа-Банк — это бирюзовая компания, в движок которой зашит манифест Agile и Scrum. Это означает, что мы осознанно приняли стратегию, что все решения «зашиты» в команды, а не в комитеты по типу СГКпЦиУ…

Как сохранить консистентность дизайна и не потерять гибкость разработки?


Наша библиотека компонентов ARUI Feather базируется на двух хорошо знакомых решениях из мира фронтенда: БЭМ-методологии и React.

Здесь не будет рассказа про выбор инструментов: мне больше хочется рассказать про принципы и практики масштабирования дизайн-систем, которые мы выработали в процессе создания ARUI Feather.

Подробнее о том, почему у нас именно БЭМ-методология + React, можно узнать из этого видео с Яндекс.Деньги FrontendMix 2017.

В основе инженерных решений ARUI Feather лежит философия
KISS / YAGNI / DRY


KISS означает, что мы изначально для себя решили избегать сложных решений. Перед нами стояла задача сделать код дизайн-системы, в котором сможет разобраться самостоятельно любая команда. ARUI Feather — это АК-47 мира дизайн-систем. Даже лежа по уши в песке в окопах под Багдадом, вы можете самостоятельно разобрать и собрать ее, не обращаясь в сервис-центр ВМС США.

Также, следуя YAGNI, мы делаем только необходимое, и последний тренд — мы чаще удаляем компоненты, чем создаем новые, потому что на самом деле многие вещи лежат за зоной ответственности дизайн-системы.

ARUI Feather — это АК-47 мира дизайн-систем. Даже лежа по уши в песке в окопах под Багдадом, вы можете самостоятельно разобрать и собрать ее, не обращаясь в сервис-центр ВМС США


По этой причине мы используем БЭМ-методологию не в полной реализации, исключая из нее миксы и уровни переопределения. Оба эти подхода про “смешивание”, что при масштабировании включает на проектах “безумный миксер”, делая код тяжелым для отладки.

Технически мы поддерживаем дизайн-систему через наше собственное Open Source решение — cn-decorator, которое позволяет использовать БЭМ-методологии и React вместе.

Мы используем БЭМ-методологию не в полной реализации,
исключая из нее миксы и уровни переопределения


С какими проблемами мы столкнулись при масштабировании дизайн-системы?


В Альфа-Банке уже более 30 команд, которые разрабатывают своей фронтенд независимо, используя ARUI Feather и cn-decorator.

У нас нет отдельной выделенной команды, которая сконцентрирована на разработке UI/UX-библиотеки. Разработка ведется по принципам, сложившимся в Open Source: есть мейнтейнеры библиотеки компонентов, есть контрибьюторы и есть, конечно, пользователи. И все эти люди так или иначе участники разных команд. Мы осознанно пошли на этот шаг, чтобы избежать появления в компании узкого звена в виде команды разработки библиотеки, которой другие команды делают заказ и ожидают, когда им помогут.

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

Далее я расскажу про топ вопросов от команд, которые поступают мейнтейнерам, и как мы их решаем…

А как вообще мне компонент написать-то?


Так выглядит самый простой компонент, написанный с использованием cn-decorator.

import cn from 'cn-decorator';

@cn('button')
class Button extends React.Component {
    render(cn) {
        return <button className={ cn() } />;
    }
}

Достаточно просто использовать декоратор @cn и передать название блока, в данном примере ‘button’. Теперь метод render получит свой экземпляр cn, который может быть использован для генерации имен классов. И наш финальный БЭМ-блок в HTML будет выглядеть приблизительно так:

<button class="button"></button>

Но ведь в БЭМ-методологии есть еще элементы, модификаторы, миксы и уровни переопределения… Пример чуть сложнее:

import cn from 'cn-decorator';

@cn('button')
class Button extends React.Component {
    render(cn) {
        return (
            <button className={ cn({ disabled: true }) }>
                <span className={ cn('text') }>Text</span>
            </button>
        );
    }
}

В результате у нас получается следующая верстка:


   <button class="button button_disabled">
       <span class="button__text">Text</span>
   </button>

На примере наглядно показано, как cn-decorator умеет обращаться с модификаторами и элементами. Остается добавить немного CSS, и компонент готов!

Мы тут подумали: если поменять цвет рамочек вот у этой кнопки, то конверсия повысится на 200%! Нам что, кнопку с нуля делать?


Альфа-Банк — это на 100% продуктовая компания. Наши команды на регулярной основе проводят десятки экспериментов. Иногда даже небольшое изменение цвета рамочки может привести к изменению конверсии.

Если бы у нас был комитет СГКпЦиУ, то нам бы пришлось вынести решение об таком незначительном эксперименте на его ближайшее собрание, дождаться вердикта и, спустя долгие полгода, все-таки повысить конверсию. Технически мы бы использовали WebComponents и запретили бы любое вмешательство в верстку и API компонента.

Но жизнь богаче, и каждая из команд имеет полное право на проведение экспериментов с дизайном. Для этого в cn-decorator встроен механизм className proxy

import Button from 'arui-feather/button';

class App extends React.Component {
    render() {
        return <Button className="my-class" />;
    }
}

В результате мы получаем следующую верстку:


<button class="button my-class"></button>

Теперь мы можем просто на проекте в селекторе .my-class перекрыть пару свойств нашей кнопки…

Мы еще подумали и, кажется, знаем, как повысить конверсию на 500%! Но, нам нужна кнопка… Нет, она должна нажиматься как старая, но выглядит-то она совсем по-другому… Нам опять с нуля делать?


И такое случается. Согласитесь, обидно писать компонент, логика работы которого тебя полностью устраивает, но выглядит он совершенно по-другому. Чуть выше я говорил о том, что мы не любим все паттерны смешивания, а предпочитаем паттерны на основе композиции. Поэтому у нас есть механизм перегрузки имени блока, который позволяет разобрать компонент на составные части: стили и логику.



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



Достаточно просто передекорировать компонент и написать для него новые стили:

import cn from 'arui-feather/cn';
import Button from 'arui-feather/button';
import './tag-button.css';

@cn('tag-button')
class TagButton extends Button {};

Результирующая верстка TagButton:


<button class="tag-button tag-button_disabled">
    <span class="tag-button__text">Text</span>
</button>

Такой несложный паттерн позволяет нам шарить логику компонента между разными представлениями.

А у нас тут дизайнер нарисовал компонент, который выглядит как Link, но работает как Select… А у вас такого нет! А это повысит конверсию на 1000%!


Это были простые примеры, но часто наши компоненты составные (помните, мы любим композицию). Например, таким составным компонентов является Select: он состоит из двух компонентов Button и Popup.



Приблизительно так выглядит код Select:

import cn from 'arui-feather/cn';
import Button from 'arui-feather/button';
import Popup from 'arui-feather/popup';

@cn('select')
class Select extends React.Component {
    render(cn) {
        return (
            <div className={ cn() }>
                <Button />
                <Popup />
            </div>
        );
    }
}

Но иногда командам нужно поменять составной компонент. Например, команде нужно, чтобы Popup выпадал из ссылки, а не из кнопки. Приблизительно так:



Но у нас модульная система на ES6 modules. Единственная возможность заменить составной компонент — это сделать патч на уровне сборки. Здесь на помощь снова приходит cn-decorator и его фича Dependency Injection Components. Давайте передадим наши составные компоненты через cn:

import cn from 'arui-feather/cn';
import Button from 'arui-feather/button';
import Popup from 'arui-feather/popup';

@cn('select', Button, Popup)
class Select extends React.Component {
    render(cn, Button, Popup) {
        return (
            <div className={ cn() }>
                <Button />
                <Popup />
            </div>
        );
    }
}

Теперь мы можем сделать собственный Select, заменив в нем Button на наш собственный.

import cn from 'arui-feather/cn';
import Select from 'arui-feather/select';
import Popup from 'arui-feather/popup';
import MyLinkButton from './my-link-button';

@cn('my-link-select', MyLinkButton, Popup)
class MyLinkSelect extends Select {};



Ура! Теперь мы можем менять любой составной компонент композиции!

Теперь вы видели все


Дизайн-система в большой компании — это не про технологии: это не про холивар Angular vs БЭМ vs React. Дизайн-система — это поиск компромиссов между консистентностью и возможностью проводить быстрые эксперименты. Дизайн-система — это работа с комьюнити и работа с бизнес-требованиями одновременно. Это b2b- и b2c-решение: на одной чаше весов бизнес, который хочет быстро, дешево и качественно, и с другой стороны разработчики, которые хотят гибко, расширяемо, но предсказуемо и надежно.

Хочется завершить эту статью одним очень точным законом, который лучше всего объясняет архитектуру дизайн-систем (да и в принципе любую архитектуру):

«Организации, проектирующие системы (здесь имеется в виду более широкое толкование, включающее не только информационные системы), неизбежно производят конструкцию, чья структура является копией структуры взаимодействия внутри самой организации»
—Закон Конвея

Наши Open Source-решения:

ARUI Feather — Библиотека UI-компонентов Альфа Банка
cn-decorator — Лучший способ использовать БЭМ-методологию с React

Наши вакансии во фронтенд-разработке и дизайне:

Дизайнер интерфейсов / Дизайнер цифровых продуктов
Фронтенд-разработка
Дизайн