Привет! Меня зовут Вадим Казаченко, я лид фронта дизайн-системы ВТБ. Год назад устроился в банк и получил командную задачу — построить единую библиотеку компонентов, настолько универсальную, чтобы ее можно было использовать в любом продукте дизайн-системы банка, и при этом она не должна становиться «узким горлышком», как это обычно происходит с UI-китами в крупных компаниях. Дело в том, что в ВТБ существует множество дизайн-систем, над которыми работают десятки дизайнеров.
Непростая задача требовала проработки архитектуры дизайн-системы и сильно зависела от выбора решения для стайлинга. В этой статье подробно расскажу, от чего мы отталкивались и на чем остановили свой выбор.
К истории стилей
Первую версию CSS мы получили в далеком 1996 году, и с тех пор концептуально ничего не менялось: схема Selector { Property: Value } и полная статичность. Плюсом было только то, что грузятся стили из файла параллельно с загрузкой JS-кода. Если вы, конечно, не произвели весь стайлинг внутри HTML-страницы, что замедлило бы ее загрузку.
Работать со стилями стало приятнее с появлением препроцессоров. Некоторые из них добавляли улучшенный синтаксис, переменные и функции для генерации CSS, другие же, как postCSS, дали возможность совершать манипуляции со стилями.
Популярные расширения postCSS позволили автоматически добавлять префиксы для одинакового отображения стилей в браузерах, уменьшали размерность файлов для загрузки пользователями или же помогали проверять разработчикам код на ошибки линтерами.
Кроме этого, пришлось еще решать существенную проблему: бесконечное число хаотично названных классов периодически создавали конфликты, что крайне мешало в работе над масштабным проектом.
Решением стало использование CSS-модулей в сочетании с методологией нейминга классов БЭМ (Блок, Элемент, Модификатор).
Пример использования классов в БЭМ:
<div class=”card”>
<h3 class=”card__title”>Заголовок</h3>
<p class=”card__text”>Текст</p>
</div>
<div class=”card card–large”>
<h3 class=”card__title”>Заголовок большой карточки</h3>
<p class=”card__text”>Текст большой карточки</p>
</div>
БЭМ описала правила к наименованию компонентов, его составных частей и модификаций.
CSS-модули позволили безболезненно распиливать стили на разные файлы и ограничивать локальной областью, где к их классам добавился уникальный ключ, исключающий повторы в названиях.
В совокупности с препроцессорами эти решения так хорошо зашли, что до сих пор поставляются из коробки в самых современных фреймворках.
Итак, при использовании этого подхода получаем следующее:
Плюсы
Компоненты стилизуются модульно.
Нет наложения нейминга классов.
Статичный CSS загружается параллельно с JS.
Нет проблем с SSR.
Минусы
Сложно заводить и поддерживать токены для реализации тем.
Отсутствие типизации и подсказок без настройки IDE.
Чтобы соединить стили с компонентами, нужно маппить классы с пропсами.
Актуальный этап развития CSS = > CSS-in-JS
Давайте забьем на CSS и будем генерировать стили прямо из JS. Это легло в основу большинства библиотек, реализующих этот подход. Здесь не нужно прописывать какие-либо классы и разбираться, как компонентам их переключать. Под капот уходят все вопросы добавления префиксов, ключей к названиям и минификации.
Все стало крайне легко: берется функция styled, которая оборачивает HTML-тег, который нужно стилизовать. Стили добавляют через шаблонные строки, и в них же можно получать прямой доступ как к пропсам компонента, так и к общей теме, которую одним движением легко переключить со светлой на темную и так далее.
Итого, при использовании этого подхода получаем следующее:
Плюсы:
Не нужно думать о соединении стилей с пропсами.
Темы переключаются на лету.
SSR поддерживается, но есть нюансы.
Минусы:
Отсутствие типизации и подсказок.
Рендер стилей происходит только после загрузки JS.
Бесконечная генерация классов с хаотичным неймингом.
А что лучше, чем CSS-in-JS?
Логичным продолжением развития работы с CSS-in-JS стал переход к CSS-in-TS и его реализация в библиотеке stitches.dev с отличной документацией и широкими возможностями. Какие еще важные для нас преимущества можно отметить?
Первый плюс — базовый стайлинг. Обертка styled практически такая же, как в styled-components, только стилизация происходит внутри объекта, а не строки. Для каждого CSS-свойства заданы типы, что позволяет без особого труда выставить стили с подсказками редактора кода и заняться более важными делами.
Второй плюс. Динамичность в этой библиотеке реализована с помощью вариантов: пропсы, которые передаются в компонент, переключают статичные объекты стилей. Помните, я упоминал БЭМ ранее в статье? Это по сути и есть его реализация, но без лишних движений!
Плюс третий. Способ реализации stitches.dev исключает возможность использования функций и при этом повышает читаемость кода. В своих же бенчмарках (https://stitches.dev/docs/benchmarks) авторы сравнивают подход вариантов против полностью генерируемых стилей. Это не особо показывает реальную производительность, зато подсвечивает, что в других библиотеках достаточно легко можно допустить ошибку и потерять эффективность.
Стайлинг для UI-кита
Если библиотека позволяет стилизовать компоненты, это не значит, что ее будет удобно использовать в UI-ките. В вопросах типового стайлинга stitches.dev в целом понятная и удобная система, но давайте рассмотрим ее в рамках работы UI-кита — это непосредственно относится к нашей задаче.
Stitches со стартового гайда предлагает создать файл конфигурации, в котором можно определить тему и общие параметры:
theme: Объявление токенов темы, которые самостоятельно соотносятся с CSS-параметрами.
media: Объявление брейкпоинтов для адаптивной верстки.
utils: Создание уникальных функций-утилит для удобства добавления стилей.
prefix: Добавление префикса для избежания конфликтов.
themeMap: Добавление соотношений кастомных токенов к CSS-параметрам.
Странно, что в других библиотеках темизация спрятана где-то в разделах Advanced, тогда как это уже практически стандарт для сайтов и приложений.
На этом приятные моменты не закончились. Токены в этой библиотеке — не что-то эфемерное, как переменные в том же SASS, а использование нативных CSS variables!
Достаточно на верхнем уровне передать значения новых токенов, а сами компоненты уже нативным CSS сменят свои стили.
Пропустим разбор брейкпоинтов — с ними создавать адаптивный дизайн здесь очень легко. Лучше перейдем к важнейшей части любой библиотеки компонентов, когда дизайн выходит за рамки готовой реализации и разработчикам приходится создавать кастомы самостоятельно.
С кастомизацией у stitches также нет проблем — это крайне нативный процесс, происходящий с помощью обертки styled или добавления CSS-пропса. При этом любой вариант кастомизации поддерживает работу токенов.
Отдельно стоит отметить простоту использования тем: базовая создается в стартовой конфигурации, а все последующие поверх уже перезаписываются токенами. Нужно лишь на верхнем уровне приложения добавить класс созданной темы. При этом внутри компонентов идет привязка токенов по объекту темы, поэтому при потере каких-либо токенов тайпскрипт сразу укажет на эту проблему.
Соответственно, для продуктовых команд можно предложить кастомизировать токены темы, не опасаясь их потерять. А предоставив доступ к низкоуровневому API компонентов, мы расширяем возможности кастомизации нашего набора компонентов до предела. Но об этом в другой раз :)
Итого, при использовании этого подхода получаем следующее:
Плюсы:
Типизация и подсказки.
Вариативность стилей по канонам БЭМ.
Нативный стайлинг внутри JS/TS.
Темы переключаются на лету.
Отличная документация.
SSR поддерживается, но есть нюансы.
Из минусов — рендер стилей только после загрузки JS.
Наш опыт
Более чем за шесть месяцев работы со stitches мы столкнулись только с одной проблемой — случайным порядком вариантов при генерации классов. Например, компонент Input имеет булевые варианты Error и Disabled, и стили первого перекрывали стили второго. А нам нужен как раз обратный эффект.
Решением этой проблемы стало банальное добавление !important или использование compoundVariants. Но в будущем рассчитываем, что ситуацию исправит добавление поддержки CSS layers в библиотеку.
В остальном stitches отлично подошла под требования в рамках дизайн-системы. В следующей статье расскажем о применении библиотеки в нашей архитектуре. До встречи!