БЭМ + Tailwind: прагматичный гибрид для современного фронтенда

от автора

Введение: конец войны методологий

АААААААААААА!!! 15 МИНУТ МОЕЙ ЖИЗНИ ТОЛЬКО ЧТО УШЛИ НА ПРИДУМЫВАНИЕ ИМЕНИ ГРЁБАНОМУ DIV-КОНТЕЙНЕРУ!

Знакомо, да? Не я один, кто неистово бился головой о клавиатуру, пытаясь придумать, как назвать очередную обертку для обертки внутри обертки?

Я УСТАЛ. Мы все устали. Устали от:

  • Бесконечного .header__nav-container__menu-wrapper__item-list__element 😵‍💫

  • Спора на код-ревью о том, должен ли это быть block__element или просто новый block 🤬

  • «Не используй margin на блоке! Это нарушает принципы БЭМ!» — и последующего создания еще одного контейнера 🤦‍♂️

  • Конфликтов имён классов в разных компонентах ⚔️

  • А потом приходит Tailwind и превращает твой HTML в нечитаемую простыню из 20 классов на элемент 📜

«Я просто хотел сверстать карточку товара, а закончил философским вопросом о природе существования блоков и элементов.»

В какой-то момент я понял, что трачу больше времени на придумывание названий классов, чем на написание самого CSS. И подозреваю, что не я один такой.

Но что, если я скажу, что можно прекратить эти мучения?

В этой статье я расскажу, как я наконец-то перестал биться в конвульсиях при создании очередного div’а и нашел баланс между структурированностью БЭМа и удобством утилитарного подхода Tailwind. Больше никаких религиозных войн и догматизма — только прагматизм и эффективность.

Проблемы чистого БЭМа в реальном мире

БЭМ — отличная методология, которая решает множество проблем большого проекта. Ага, и создает тысячу новых. Но давайте будем честными, у неё есть свои недостатки:

<!-- Ад нейминга --> <div class="content-block">   <div class="content-block__wrapper">     <div class="content-block__wrapper-inner">       <div class="content-block__wrapper-inner-container">         <!-- Наконец-то контент! Осталось только пробраться через эти девять кругов ада нейминга -->       </div>     </div>   </div> </div> 
  1. Мучительный нейминг для вспомогательных элементов и контейнеров (Серьезно, кто придумал, что все должно иметь осмысленное имя? Моя кошка и та не страдает, когда я называю её «кис-кис-кис»)

  2. Проблема с отступами — в чистом БЭМе блок не должен иметь margin, поэтому приходится создавать дополнительные обертки (Потому что, видите ли, margin — это как НЛО: все знают, что они существуют, но никто не признается в их использовании)

  3. Раздутая разметка из-за множества уровней вложенности (Ничто так не поднимает самооценку, как ощущение, что ты архитектор, спроектировавший Бурдж-Халифу из div’ов)

  4. Сложность переиспользования для позиционирования — приходится создавать множество модификаторов (block_position-top_margin-left_slightly-to-the-right-no-not-that-right-a-bit-more… идеально!)

С другой стороны, чистый Tailwind часто приводит к «салянке» классов, потере семантики и усложнению чтения HTML. (Потому что ничто не говорит «я знаю, что делаю» лучше, чем 42 класса на одном элементе)

Гибридный подход: когда что использовать

1. Используй БЭМ с SCSS и @apply, когда:

  • Имеешь дело с семантически очевидными компонентами: .product-card, .user-profile, .catalog (Ключевое слово: «очевидными». Если ты полчаса думаешь, как назвать div, это уже не очевидно, Капитан Очевидность!)

  • Работаешь с компонентами с богатой внутренней структурой (Как в большой семье — каждый элемент должен знать своё место и родословную до пятого колена)

  • Создаешь переиспользуемые компоненты с разными вариациями (Потому что DRY расшифровывается как «Don’t Repeat Yourself», а не «Damn, Repeat Yelp!»)

// SCSS с использованием @apply для утилит Tailwind .product-card {   @apply bg-white rounded-lg shadow-sm overflow-hidden;   // 👆 Смотри-ка! Одна строка вместо 10 строк CSS! Какая экономия, прямо как в рекламе стирального порошка!    &__image {     @apply w-full object-cover aspect-video;   }    &__body {     @apply p-4;   }    &__title {     @apply text-lg font-medium text-gray-900;   }    &__price {     @apply text-primary-600 font-bold mt-2;     // Ой, margin-top! Не показывайте это БЭМ-пуристам 🙈      &_discounted {       @apply line-through text-gray-400 font-normal;     }   }    // Модификатор для самого блока   &_featured {     @apply shadow-md border-2 border-primary-500;     // А здесь можно было бы создать еще один блок "featured-card",     // пять оберток и два миксина. Но мы не ищем легких путей, верно?   } } 

Что дает:

  • Четкую структуру компонента (Как государство, но без налогов)

  • Легкое переиспользование (Когда-нибудь ваш компонент вырастет и станет библиотекой, и вы будете им гордиться!)

  • Хорошую читаемость благодаря SCSS (Потому что нормальные люди не хотят читать CSS, где селекторы длиннее, чем названия лекарств)

  • Повторно используемые стили утилит благодаря @apply (Tailwind как пицца — берешь только те кусочки, которые нравятся)

2. Используй чистый Tailwind, когда:

  • Работаешь с вспомогательными контейнерами и обертками (Те самые, которым мы всегда даем имена типа wrapper, container, box, и — в момент отчаяния — thingy)

  • Создаешь однократные элементы без особой семантики (Потому что не всё заслуживает имени, некоторым достаточно просто быть flex-1 p-4)

  • Имеешь сложности с придумыванием осмысленного имени (Когда все хорошие имена уже заняты, а плохие не пропустит код-ревью)

<!-- Вместо мучений с .layout__content-wrapper__inner-container --> <div class="max-w-screen-xl mx-auto px-4 py-8">   <div class="grid grid-cols-1 md:grid-cols-3 gap-6">     <!-- Содержимое, которое не нужно искать в CSS-файлах за тридевять земель -->   </div> </div> 

Что дает:

  • Меньше головной боли с названиями (Потому что «придумывание имен» должно относиться к детям и домашним питомцам, а не к div’ам)

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

  • Отсутствие дополнительных CSS-файлов для вспомогательных элементов (Меньше файлов = меньше мест, где можно потеряться)

Решение проблемы с margin и позиционированием

Вот где гибридный подход по-настоящему сияет. ✨ Вместо создания модификаторов для каждого варианта отступа и позиционирования (я тебя умоляю, кому нужны .product-card_margin-bottom-16 .product-card_margin-bottom-32 .product-card_margin-left-0-on-mobile?!), используем Tailwind поверх БЭМ-классов:

<!-- Внутренняя структура через БЭМ, позиционирование через Tailwind --> <article class="product-card mb-4 md:mb-0 md:mr-6 flex-1">   <img     class="product-card__image"     src="product.jpg"     alt="Product"   />   <div class="product-card__body">     <h3 class="product-card__title">Отличный продукт</h3>     <div class="flex items-center justify-between mt-3">       <!-- Да, я использовал флексы внутри БЭМ-блока! Арестуйте меня! -->       <p class="product-card__price">1999 ₽</p>       <button class="product-card__button">Купить</button>     </div>   </div> </article> 

Обратите внимание:

  • product-card и его внутренние элементы следуют БЭМ (Потому что мы воспитанные люди)

  • Внешние отступы (mb-4, md:mb-0, md:mr-6) и некоторое позиционирование (flex-1) задаются через Tailwind (Потому что жизнь слишком коротка, чтобы создавать модификаторы для каждого пикселя отступа)

  • Для уникальной внутренней структуры, которую сложно выразить в БЭМ, также используем Tailwind (flex items-center justify-between mt-3) (Потому что иногда flex — это просто flex, а не product-card__price-button-row_layout-horizontal)

Переиспользуемые секции: база через БЭМ, вариации через Tailwind

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

<!-- Базовая структура через БЭМ, специфичные стили через Tailwind --> <section   class="hero-section bg-gradient-to-r from-blue-500 to-indigo-600 py-16 md:py-24" >   <div class="container mx-auto px-4">     <h1 class="hero-section__title text-center md:text-left"       >Заголовок страницы</h1     >     <p class="hero-section__subtitle mb-8 max-w-2xl"       >Подробное описание сервиса</p     >     <div class="hero-section__buttons flex flex-col sm:flex-row gap-4">       <!-- Да, мы используем gap вместо margin между кнопками!            И нет, нам не стыдно! Это 2023-й, а не каменный век! -->       <button class="hero-section__button_primary">Начать</button>       <button class="hero-section__button_secondary">Подробнее</button>     </div>   </div> </section> 
.hero-section {   &__title {     @apply text-3xl md:text-5xl font-bold text-white;     // Мама, смотри! Я адаптивную типографику без медиа-запросов сделал!   }    &__subtitle {     @apply text-lg text-white/80;   }    &__button {     &_primary {       @apply bg-white text-blue-600 font-medium py-3 px-6 rounded-lg;     }      &_secondary {       @apply bg-transparent border-2 border-white text-white py-3 px-6 rounded-lg;       // 15 строк CSS в одну! Дизайнер даже не заметит подмены!     }   } } 

Такой подход позволяет переиспользовать секции, настраивая их внешний вид и позиционирование через Tailwind прямо в месте использования. (А не через 50 модификаторов, имена которых заставляют задуматься о смене профессии)

Работа с компонентами Vue/React/Angular

В мире компонентов гибридный подход тоже прекрасно работает (если ваш тимлид не фанатик какой-то одной методологии):

<!-- ProductCard.vue --> <template>   <article     class="product-card"   >     <img       class="product-card__image"       :src="product.image"       :alt="product.name"     />     <div class="product-card__body">       <h3 class="product-card__title">{{ product.name }}</h3>       <p         class="product-card__price"         :class="{ 'product-card__price_discounted': product.hasDiscount }"       >         {{ product.price }}       </p>     </div>   </article> </template> 

И используем его так:

<template>   <div class="grid grid-cols-1 md:grid-cols-3 gap-6">     <ProductCard       v-for="product in products"       :key="product.id"       :product="product"       class="mb-4 md:mb-0"       <!-- Смотрите, никаких custom-component-margin-for-this-specific-case модификаторов! -->     />   </div> </template> 

Когда НЕ стоит использовать гибридный подход

  1. Микро-проекты — для совсем небольших проектов часто достаточно просто Tailwind (Чтобы потом через 6 месяцев проклинать себя, когда «маленький проект» вырастет в монстра, но кто ж об этом думает в начале?)

  2. Строгие требования к методологии — если в команде есть строгие гайдлайны по БЭМ или Tailwind (Или если ваш тимлид носит футболку «БЭМ или смерть»)

  3. Проекты с устаревшим CSS — может быть сложно интегрировать в унаследованную кодовую базу (Особенно если там есть !important в каждой второй строке и inline-стили «потому что так быстрее»)

Заключение

Гибридный подход БЭМ + Tailwind это не ересь (хотя пуристы с обеих сторон могут сжечь вас на костре), а прагматичное решение реальных проблем:

  • Перестаем мучиться с неймингом вспомогательных элементов (Нет больше «как назвать этот div, который просто центрирует контент?»)

  • Решаем проблему с отступами и позиционированием (Потому что отступы — это отношения между элементами, а не их внутреннее дело!)

  • Сохраняем семантическую структуру для сложных компонентов (Чтобы разобраться даже спустя год)

  • Ускоряем разработку благодаря утилитам (И успеваем закончить до дедлайна, что уже чудо)

Личный опыт: спасение из CSS-ада

Знаете, я ведь не просто так все это пишу. Я лично использовал этот подход на реальном коммерческом проекте, когда уже был готов наплевать на все методологии и начать писать стили прямо в атрибутах (да, было настолько х**во).

Когда мой проект вырос до 30+ компонентов, я начал замечать, что:

  1. На придумывание имён для вложенных контейнеров уходило больше времени, чем на саму верстку

  2. Каждый новый элемент вызывал приступ экзистенциального кризиса: «это новый бл**ь блок или элемент существующего блока?»

  3. Меня начинало потряхивать от необходимости создавать очередной модификатор типа block__element_margin-bottom-md

И тут меня осенило. Если я использую БЭМ для чётких семантических блоков, а Tailwind — для всего остального, жизнь становится прекрасной! Разработка ускорилась примерно в два раза. Я перестал тратить время на бессмысленные споры с самим собой о том, как назвать очередной ёб**ый контейнер, который просто центрирует содержимое.

После перехода на гибридный подход:

  • Верстать стало удовольствием, а не пыткой

  • Компоненты действительно стали переиспользуемыми без тонны модификаторов

  • Код-ревью перестали превращаться в лекции по правильному именованию в БЭМ

  • Дизайнер перестал закатывать глаза, когда я говорил, что внести «небольшие изменения в отступы» займёт полдня

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

Не нужно быть религиозным фанатиком одной методологии — вместо этого используйте лучший инструмент для конкретной задачи. Иногда это БЭМ, иногда Tailwind, а часто — их разумное сочетание. Я за**ался тратить время на споры о методологиях, когда можно просто делать крутой продукт!


ссылка на оригинал статьи https://habr.com/ru/articles/924826/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *