Чем хорош и чем плох Tailwind CSS, или «Допустим, у вас стартап!»
- пятница, 26 мая 2023 г. в 00:00:16
Привет, Хабр! Меня зовут Александр Водолазских. Я живу в Новосибирске и я работаю Frontend Domain Lead в СберМаркете. Сегодня хочу немного поговорить об опыте работы с Tailwind CSS — utility-first CSS framework. Поделюсь болью и радостью, которые возникли при его эксплуатации.
Библиотека Tailwind довольно молодая, первые бета-версии появились в 2017 году. Но по количеству звёзд и форков можно понять, что комьюнити вокруг сложилось солидное. Инструмент предоставляет нам большой набор готовых классов и утилит, которые облегчают и ускоряют стилизацию приложения/сайта. Вы просто пишете короткие имена заранее спроектированных классов: цвета, отступы, позиционирование. Хватает классов и для сайзинга, типографики, анимации бэкграундов и многого другого.
Ниже можно увидеть, как всё это выглядит при реализации на React JS.
Радует и совместимость: всё спокойно собирается и под Next.js, и под Vite, и под Angular, и под Create React App. Более того, есть возможность сборки с помощью PostCSS-плагина, что делает инструмент поистине хорошо имплементируемым в любой системе.
Но есть у Tailwind и недостатки. На самом деле это довольно холиварный инструмент.
Представим, вы с друзьями решили сделать стартап: арендовали гараж для работы, собрали лучшую команду и подобрали крутой технологический стек. Что касается CSS — вы решили найти модное решение для стилизации, и глаз упал на Tailwind. Всем нравится, коллеги довольны, и ничто не предвещает беды.
Вдруг команда получает деньги от инвестора и уже собирается пилить новые бизнес-фичи, но сталкивается с тем, что для комфортного сожительства с Tailwind его нужно «привести в порядок», иначе всё дальнейшее масштабирование под угрозой. И вообще, поддерживать разросшиеся стили становится трудновато, разработчики каждое утро вытирают скупые слёзы левым рукавом.
Почему масштабировать и поддерживать модное решение для стилизации оказывается грустно:
Фреймворк неинформативен. Понятных названий классов в нём просто нет, а как навигироваться в DOM-дереве при дебаге — вообще не понятно.
Классов очень много, их попросту сложно запомнить, особенно вновь прибывающим молодым разрабам. Мы же растём, помните?
Появляются многострочные портянки из классов.
Неясно, каким образом привести груду написанных стилей в общую систему и избавиться от тотального бардака.
Болей достаточно много, как говорится: «Есть от чего впасть в отчаяние». Хорошая новость состоит в том, что все проблемы фреймворка можно сжать в одну: нужно чётко понимать, как его готовить. Если изначально выстроить аккуратный подход, то возможные боли при масштабировании практически нивелируются. Иными словами, у нашей команды есть тактика, и мы должны её придерживаться.
Пройдёмся по каждому из минусов отдельно.
Представим, что мне нужно что-то продебажить в UI. Например, проблема в карточке ритейлера. Я часто начинаю дебаг с того, что открываю DevTools и ищу нужный мне компонент по названию класса.
Вот пример нехитрой навигации в проекте, стилизованном с SCSS. Всё весьма просто и прозрачно.
Tailwind отберёт у вас понятные, читаемые названия классов. Вместо чего-то семантичного вы получите sticky top-0 z-40 w-full и так далее.
Как с этим справиться? Можно просто добавить дата-атрибуты. Да, вы никуда не денетесь от портянки классов. Как «упаковать» их красиво — так и не придумали. Да, останутся непонятные длинные имена, но вы сможете навигироваться по DOM-дереву, сможете искать. Тестировщики скажут вам спасибо, потому что дата-атрибуты очень полезны для автоматизации тестирования.
Ещё одно простое и очевидное решение — красиво разделить код на компоненты. Если у вас React, используем его DevTools, и вот искать что-то по дереву компонентов снова просто и приятно. Если не React — расстраиваемся и переходим на React. Это, конечно, шутка, все инструменты для своих задач.
Иногда я слышу что-то в таком стиле: «Инструмент классно подходит для прототипов. Правда, мы с ним раньше не работали, но хотим что-то накидать на коленке». Это не очень хорошая история, на коленке не получится. При старте с нуля много времени уйдёт на то, чтобы понять, какие вообще есть классы, как правильно готовить фреймворк, чтобы работа выстроилась более-менее системно.
Логика здесь простая: чем больше вы пишете, тем лучше запоминается. Набор классов всё же ограничен. Как всегда, тренировка и потраченные часы рабочего времени сделают своё дело.
Помимо этого, для решения проблемы есть волшебное расширение IntelliSense. Вы ставите его в VS Code или любую популярную IDE и получаете удобный Tailwind-саджестор: расширение подсказывает вам возможные варианты. Поначалу приходится часто смотреть документацию и пользоваться дополнительными средствами, чтобы ориентироваться в коде. Но, когда вы напишете 10-20 компонентов, всё дойдёт до состояния полного автоматизма, и проблема отпадёт.
У вас есть элемент, вы добавляете к нему дата-атрибут — и здесь же куча классов. Если вам нужно сверстать адаптивный интерфейс, где стилевое решение будет сильно отличаться для больших, средних и мелких экранов, задача выглядит уже просто монструозно.
Код становится достаточно читаемым, если использовать библиотеку classNames или что-то подобное.
Мы работаем с ней так: бьём длинный класс на небольшие подклассы, которые семантически объединяем по экранам. Можно дробить классы по другим признакам, например — в зависимости от значений пропсов. Декомпозиция здесь — наше всё. И всегда стоит помнить про компонентный подход: чем аккуратнее и точнее мы дробим, тем меньше громоздких классов у нас будет в будущем.
Однако важно сказать, что по волшебству всё это не происходит. Держите в голове командные договорённости либо ужесточайте правила ESLint или другого линтера. Иначе будет сложно контролировать, чтобы люди писали код правильно. Плагин eslint-plugin-tailwindcss даёт набор правил, который можно использовать для поддержки кода. Он отсортирует и упорядочит классы, запретит использовать неконсистентные значения.
Про переиспользование. В Tailwind есть очень полезная директива @apply, которая добавляет ваши кастомные классы на слой компонентов и позволяет использовать их везде. Это значит, что вы можете написать отдельный класс и использовать в нём набор других классов.
@layer components {
.btn-primary {
@apply py-2 px-4 bg-blue-500 text-white
font-semibold rounded-lg shadow-md
hover:bg-blue-700 focus:outline-none
focus:ring-2 focus:ring-blue-400
focus:ring-opacity-75;
}
}
А теперь вы можете расширить директивой класс любого элемента. Достаточно удобно!
Использование таких apply-классов даёт нам возможность экспортировать целые наборы стилей. Грубо говоря, я могу написать свои компоненты, вынести все стили в общую core-библиотеку и переиспользовать в других своих проектах!
Про масштабируемость. Фреймворк предоставляет пользователю очень гибкий конфиг. Есть tailwind.config.js, который создаётся в корне вашего проекта, когда вы инитите Tailwind. Он даёт возможность настроить всё так, как вам необходимо. С помощью этого конфига вы можете строго ограничить команду разработчиков, дав им доступ лишь к типографике, цветам, отступам, которые предусмотрены именно вашей дизайн-системой. На скрине описан вид поведения boxShadow и цветов.
С трейдоффами выше вполне можно смириться, часть из них разрешается позитивно. Мы либо улучшаем опыт взаимодействия, либо привыкаем.
Однако в Tailwind есть проблемы, с которыми действительно тяжело.
Например, у нас есть такой класс:
Разраб использовал всё, что только можно, скомбинировав:
Кривой и несемантичный дата-атрибут.
Отступы в пикселях, в отрицательных значениях и в rem.
Цвета в текстовых значениях и через hex.
Получилось очень мусорно и не очень читаемо. Вы скажете, что кейс неорганический, а я скажу вам, что в коде стартапов встречается и не такое 🙂 Поэтому важно договариваться либо ужесточать ESLint. Выше я уже писал об этом, но это так важно, что позволю себе повториться.
Другого решения нет, только так.
Прежде чем считать это проблемой, задайте себе вопрос, часто ли вам нужно изобретать велосипед. Библиотека классов огромная, и найти что-то можно практически под любые нужды.
Но есть ситуации, когда персональный велосипед вам точно нужен. Например, Tailwind не умеет в clip-path. Инструмент развивается, у него выходят новые версии, но да, в данный момент действительно есть ряд юзкейсов, которые остались без имплементации.
Вы всегда можете написать нативный CSS и закрепить его на утилитах через директиву @layer. Вроде ничего сложного, но давайте представим, что вы каждый раз пишете под любое более-менее кастомное решение такой кусочек. Страшно подумать, во сколько раз вырастут затраты времени и сил. Если стилизация проекта слишком кастомная, лучше подумайте десять раз, стоит ли использовать Tailwind.
Встроенного RTL в Tailwind не предусмотрено в принципе. В вашем проекте он может быть и не нужен, но кто знает, из какой страны в стартап придут инвесторы.
Отличное комьюнити фреймворка решило эту проблему библиотекой tailwindcss-rtl. Она даёт дополнительный набор классов, с которыми ваш RTL заведётся без какого-либо труда. Недаром говорят: «Добавьте к любому слову .js — и вы получите какую-то существующую NPM-библиотеку». Здесь комьюнити действительно решает!
Допустим, у нас есть div, который должен быть прочитан только скринридером и не должен отображаться в UI. Для этого нам дают класс sr-only, который абсолютно позиционирует элемент, скрывает его и применяет ряд дополнительных стилизаций. На этом всё — больше из коробки ничего в Tailwind просто нет.
Вселяет надежду находящийся на этапе тестирования пакет Tailwind CSS Beta 2 с плюшками аксессибилити вроде атрибутов aria-* разных классов. Они позволяют определять совместимость браузера с тем или иным CSS-решением. Есть и много других полезных фич.
Раз уж мы заговорили про NPM-пакеты, мне будет грустно, если я не скажу, что в связке с React вы можете писать кроссплатформенные стили, используя что-то из этой пары классных пакетов: один, два. UI будет переиспользуемым: мы можем собрать React Native под Web, используя что-то вроде react-native-web, и вкупе со стилизацией на Tailwind получить мощнейшее комбо.
Комьюнити Tailwind даёт нам ещё и готовые UI-киты (например, tailwind-kit). Вы берёте готовый код, копипастите его в свой проект, стилизованный с Tailwind, и всё. Всё работает, никаких дополнительных библиотек не нужно.
Как любой инструмент, Tailwind имеет свои трейдоффы, но он быстрый и кроссплатформенный. Когда вы выйдете на пик кривой обучения, потратив N часов и написав M компонентов, всё станет интуитивно понятно, работа по стилизации пойдёт в удовольствие.
Масштабируем ли код, написанный с Tailwind? Да, но его нужно уметь готовить. Если вы не будете договариваться либо не будете использовать свои жёсткие правила для ESLint, у команды ничего не получится.
Tailwind даёт классное комьюнити: если использовать его силу, можно реализовать практически любую идею. Для меня это решение оказалось удобным и хорошо масштабируемым в рамках проекта. Остальные выводы каждый волен делать самостоятельно. Использовать или нет — решать вам.
Tech-команда СберМаркета ведет соцсети с новостями и анонсами. Если хочешь узнать, что под капотом высоконагруженного e-commerce, следи за нами в Telegram и на YouTube. А также слушай подкаст «Для tech и этих» от наших it-менеджеров.