Зоопарк CSS технологий

Зоопарк CSS технологий

Ilya Yurkin

Стилизациия приложений очень холиварная тема, особенно в React. Я подготовил обзор решений с которыми успел поработать, в той или иной степени. Половина из них применимы, в основном, к React, а другая половина не ограничена библиотеками или фреймворками. Цель этой статьи не рассказывать подробно о каждом подходе, а дать краткое описание и подсветить преимущества и недостатки.

Inline стили

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

Пример: "Мои папки" в почте Яндекса

Плюсы

  • Быстро пишутся;
  • Можно использовать JS для вычислений.

Минусы

  • Не удобно преобразовывать обычный css в объект стилей;
  • Мешанина разметки и стилей
  • Нельзя делать анимации, использовать селекторы и прочие, обычные для css, фичи;
  • Сложно масштабировать.

Обычный CSS

"Голый" CSS в наши дни почти нигде не встречается. Когда говорят о CSS, то подразумевают SASS/SCSS/LESS препроцессоры, да еще и с соглашениями типа БЭМ в придачу. У чистого CSS много фундаментальных проблем, чтобы использовать его "как есть". Я вынес SASS/SCSS и БЭМ в отдельные блоки, нет смысла разбирать обычный CSS.

Плюсы

  • Естественно, все прелести современного CSS: селекторы, анимации и т.д.;
  • Избавляет компонент от инлайн стилей.

Минусы

  • Глобальное пространство имен (конфликты, коллизии);
  • Сложно искать мертвый код;
  • Бойлерплейт;
  • Отображение не всегда ожидаемое из-за каскада. Подробнее про каскад тут.

SASS / SCSS

SASS/SCSS - это CSS на стеройдах. Препроцессоры добавляют синтаксический сахар вложений, переменные и функции. Вот небольшой пример:

Пример SCSS кода
Тот же самый код на чистом CSS


Удобно, не правда ли? Вот только SASS/SCSS решает одну проблему из обычного CSS - бойлерплейт. Часто цена этого решения - размер конечного CSS файла. Я специально добавил .disabled для примера на 12 строке в SCSS коде. Как вы заметили этот .disabled класс добавился для обеих кнопок с одним и тем же значением (второй скрин). А теперь представьте, что вместо этого маленького класса добавили несколько больших статичных классов или миксинов. В конечном CSS файле будут сотни или даже тысячи строк стилей, которые можно было бы скомпоновать.

Плюсы

  • Все плюсы обычного CSS;
  • Динамические функции и переменные;
  • Меньше бойлерплейта;

Минусы

  • Все еще актуальна проблема глобального пространства имен;
  • Мертвый код стало сложнее искать. Например, .button-40px класс не найти в CSS файлах обычным поиском по проекту. Нужно понимать, где и как он генерируется;
  • Размер конечного CSS файла;

CSS/SCSS + БЭМ

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

Конвенции упираются в человеческий фактор и поэтому проблемы CSS только сглаживаются. Разработчик поленился, ревьюверы не доглядели, и вот, код не по БЭМ попал в боевую версию. Иногда, он может стать бомбой замедленного действия.

Философия БЭМ решает проблему глобального пространства имен, а так же уменьшает потребность в примесях (mixins) и итераторов SCSS, за счет которых и раздувался код.

Я бы рекомендовал ознакомиться с документацией БЭМ, чтобы иметь представление как поддерживать проект, в котором присутствует конвенция имен. Сейчас существует достаточное количество проектов на SCSS + БЭМ, особенно за пределами React. И это количество продолжает расти. Это тот минимум, на котором действительно можно написать проект средних размеров и не испытывать боль и проблемы при стилизации. 

В больших же проектах БЭМ может давать сбои и доставлять проблемы. Например, две разные команды могут создать компонент с одинаковым классом. Если же команды синхронизировались, то начинается головоломка: "Как же семантически назвать новый компонент, чтобы он не конфликтовал с другими похожими?". Если ваш проект на React, то существуют инструменты для решения и этих проблем.

Плюсы

  • Проблема глобального пространства почти не актуальна;
  • Самодокументируемый код, благодаря классам, привязанным к контексту.

Минусы

  • Коллизия имен все еще существует, хоть и минимально;
  • Имена классов могут стать очень огромными;
  • В крупных проектах придумывание нового класса становится квестом;
  • Излишняя семантичность.

CSS модули

CSS модуль — это CSS файл, в котором все имена классов и анимаций имеют локальную область видимости по умолчанию. Иными словами CSS модули - почти автоматизированный БЭМ. "Почти" - потому что разработчикам все еще нужно продумывать архитектуру стилей и решать конфликты классов, но уже на локальном уровне.

Пример CSS модулей

Как это работает? При импортировании CSS модуля все классы превращаются в поля объекта. Благодаря хэшированию каждый класс остается уникальным в глобальной области видимости. Если бы вы захотели создать компонент header, в котором есть своя кнопка button, то в src/header/styles/modules.css можно объявить такой же класс .button {...} и он не будет пересекаться и конфликтовать с классом из другого компонента.

Возможно, вы подумали о том, что из-за хэшированных классов будет сложнее читать DOM. Эта проблема легко решается настройками Webpack'а. Можно настроить отображение полного пути до компонента в имени класса в DOM и найти нужный класс не составит труда.

Плюсы

  • Локальная область видимости, которая гарантирована технически;
  • Нет коллизий и конфликтов имен;
  • Удобные и короткие семантические имена классов;
  • Явные зависимости. Удалять мертвый код очень просто;
  • Поддержка SCSS/SASS.

Минусы

  • Немного сложнее читать классы в DOM;
  • Я не придумал ничего весомого, что можно было бы приписать к минусам CSS модулей. Возможно, для кого-то будет необычно использовать :global {} для объявление глобальных стилей, т.к. такого синтаксиса нет в обычном CSS.

CSS-in-JS

Подобно тому, как React позволил нам писать HTML как JavaScript с помощью JSX сахара, CSS-in-JS сделал нечто подобное с CSS. Это позволяет нам писать правила CSS стилей без создания .css файлов, и эти правила имеют локальную область видимости. Styled components - один из самых популярных представителей CSS-in-JS. Еще отдельно хотел бы выделить linaria, но о ней чуть позже.

Подробное сравнение популярных библиотек можно найти здесь.
Пример стилизации на styled-components

CSS-in-JS - избавляют разработчиков от порой страшного нагромождения условий для классов. Примерно вот так выглядит стилизация в реальных проектах на БЭМ:

Большинство CSS-in-JS библиотек собирают стили в рантайме на клиенте, но не linaria. Linaria собирает стили во время билда проекта и на клиент отдается обычный css. Динамика в стилях достигается за счет css переменных. Цена этому - гибкость и функциональность. В linaria компонентах не получится использовать JS на всю мощь.

Плюсы

  • Во время стилизации доступна вся мощь JS;
  • Предсказуемость, локальная область видимости;
  • Хороший Developer Experience. Не нужно придумывать ни имена классов, ни их селекторы.

Минусы

  • Использование сторонних библиотек;
  • Могут быть проблемы с производительностью на больших страницах (если стили собираются в рантайме на клиенте);
  • Стили не кэшируются (если они собираются в рантайме);
  • Проблематично делать семантическую верстку. Из названия компонента далеко не всегда понятно на каком HTML тэге он создан;
  • Читать классы в DOM и сопоставлять их с кодом еще сложнее, чем c CSS модулями.

Tailwind и CSS фреймворки

Еще пару слов о tailwind. Посыл этого раздела будет справедлив и для других css фреймворков, в том числе, и для bootstrap сеток.

Tailwind тоже решает проблемы инлайн стилей и обычного CSS, но иначе. В отличие от БЭМ, который предлагает делать абстрактные классы, tailwind добавляет утилитарные классы. Стиль style={{ display: 'flex' }}

заменяется классом 'flex' и т.д.

Для каждого css свойства (или даже групп свойств) существует свой отдельный класс. В отличие от инлайн стилей css фреймворки более гибкие. Они позволяют делать и анимации, и адаптивную верстку.

Пример разметки на tailwind

Обратите внимание, имена классов уже длинные, а ведь это простой пример. При реализации анимации или адаптивной верстки - колбаса из классов гарантирована. Да, в теории можно создать абстрактный css класс и применить утилитарные классы через директиву @apply, но чем такой подход принципиально отличается от тех же примесей в SCSS?

Проблема глобальной области видимости здесь не стоит, так как нет завязки на абстракциях. Утилитарный класс будет применять свой css стиль вне зависимости от места использования. Но отказ от абстракций приносит и свои минусы. Поиск по проекту затрудняется, например, класс "flex p-6 font-mono" может быть применен ко множеству компонентов или вообще составляться на основе данных/состояний компонента.

const className = clsx('flex', { 'p-6': isLast }, 'font-mono')

Я бы рекомендовал перед внедрением tailwind согласовать ваше решение с дизайнерами, иначе придется спорить об отступах и менять конфиг достаточно часто, что снижает смысл использования фреймворка. Когда дизайнеры следуют философии tailwind'а, то верстать, действительно, становится в радость, не нужно высчитывать цвета, пиксели для отступов и т.д..

Плюсы

  • Нет привязки к библиотеке или фреймворку;
  • Упрощает и ускоряет процесс верстки, если вы с дизайнерами на одной волне;
  • Компактный конечный CSS файл.

Минусы

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

Вывод

В качестве вывода хотел бы показать область применения каждого из подходов:

  • Inline стили. Учитывая все минусы, я рекомендую использовать их в комбинации с любым другим подходом для вычисляемых значений, которые проблематично описать в css;
  • Обычный CSS. Без БЭМ или CSS модулей адекватно можно сверстать, разве что, лабу в универе или пример в codeopen;
  • SASS/SCSS. Аналогично пункту выше. Не рекомендую использовать без БЭМ или CSS модулей;
  • CSS/SCSS + БЭМ. Классика. То, с чем можно построить приложение маленьких и средних размеров. На любом стэке. В больших приложениях растет риск коллизий имен и их решение превращается в квест. Для проектов на React есть варианты получше;
  • CSS модули. Золотая середина для приложений на React. Настроить под них вебпак не составляет труда, а в CRA вообще работают из коробки. Можно строить приложения любых размеров, используя все прелести css, не ломать голову над именами классов и не бояться за глобальную область видимости;
  • CSS-in-JS. Не стоит использовать для сайтов, в которых важен SEO, по крайней мере, run-time решения. Для маленьких проектов слишком много лишнего придется тянуть в проект. Хорошо подходят для средних и больших проектов и команд. В моей практике и styled-components и linaria хорошо показали себя на больших проектах-микрофронтах с UI китом.
  • Tailwind. Может хорошо себя показать в проектах любого размера и назначения. Должен показывать хороший SEO результат. Tailwind подойдет, если вы: синхронизированы с дизайнерами, готовы отказаться от абстрактных классов и длинные имена классов вам не режут глаз. Если нет - выберете что-то другое.
  • Bootstrap. Подойдет для мелких проектов, прототипов и всяких админок и дашбордов, на которые не выделили дизайнера или не хватило бюджета. В остальном может принести больше вреда, чем пользы.

Report Page