(Не очень) скрытая стоимость атомарного CSS

Незнание чего-либо не является смертным грехом. В конце концов, мы все должны с чего-то начинать. К сожалению, современная сеть содержит множество ловушек для ничего не подозревающих. На самом деле, он существует уже более десяти лет. Кто-то делает это по незнанию, кто-то для удовлетворения своих нарциссических наклонностей — вне зависимости от причин, они продают вам не только ненужные, но и откровенно вредные для вашего кода вещи.

Недавно я занимался веб-скрейпингом. Поначалу было забавно увидеть чужой код для разнообразия и пересмотреть всю историю плохого кода в сети. Но это быстро устарело. Боже, в сети так много плохого кода! Хотя усложнение работы скребка не обязательно плохо, в противном случае нет буквально никакой пользы от того, чтобы делать вещи так, как они обычно делаются. Никто.

HTML представляет собой раздутый беспорядок, загруженный (в основном ненужными) классами, и трудно выбрать элементы в несемантическом супе из div, типичном для таких веб-сайтов. Меня совсем не удивляет, что людей нельзя убедить использовать ванильный JavaScript для чего бы то ни было, поскольку HTML и CSS, которые они используют, просто не позволяют этого. У них гораздо большие проблемы. И это без учета доступности.

Худший нарушитель — это, безусловно, атомарный CSS и его последние реализации, такие как Tailwind, Atomizer и StilifyCSS.

Если вы в восторге от атомарного CSS и Tailwind, есть большая вероятность, что вы не используете все преимущества CSS и HTML. Другими словами, вы думаете, что знаете CSS и HTML, но это не так. Вы просто знакомы с их синтаксисом (и, возможно, с несколькими альтернативными), но знание синтаксиса и использование языка (языков) работают на вас — это не одно и то же. Не совсем.

Я вас не осуждаю, не поймите меня неправильно. Если бы я думал, что вы просто глупы, зачем мне писать для вас целую статью? Просто «эксперты», которые продали вам эту идею, ну… по-видимому, действительно хороши в продаже идей, и они иногда даже работают в модных компаниях. И вы, вероятно, никогда не работали с кем-то, кто рассказал бы вам, насколько глупы некоторые из этих идей. Вряд ли это твоя вина.

Некоторый фон

Давайте начнем с предыстории. И я не имею в виду background. Я имею в виду историю.

Идея, что все стили выполняются в HTML, сама по себе не нова. Фактически, он предшествует CSS. Когда CSS был представлен в 1996 году, он предназначался для исправления смеси семантических и презентационных тегов (да, тегов), которые преследовали Интернет в те дни. Идея была проста: HTML фокусируется на семантической структуре документа — что это такое, а CSS фокусируется на представлении — как что-то выглядит (на конкретном устройстве). Вы начинаете с чего-то вроде этого (и это все еще работает в современных браузерах, заметьте):

<center><p>This text is centered.</p></center>

к этому:

<p id="intro">This text is centered.</p>

И вы связываете второй файл, где у вас есть это:

#intro {
  text-align: center;
}

Для достижения заявленного разделения CSS был оснащен богатым синтаксисом для описания элементов и коллекций элементов в HTML без необходимости модификации HTML и без наполнения его несемантической информацией — селекторами. Это также означает, что, заменив файл CSS, можно придать одному и тому же документу совершенно другой вид. CSS Zen Garden демонстрирует эту идею с огромным разнообразием дизайнов, применяемых к одному и тому же контенту. Это разделение имело некоторые дополнительные преимущества, такие как возможность загружать файлы CSS отдельно во время загрузки/анализа HTML или повторное использование одного и того же кэшированного стиля на нескольких страницах.

Идеи и намерения — это одно, а их практическое применение — другое.

Большинство веб-разработчиков даже в те дни уделяли основное внимание визуальному аспекту страницы. Это стало особенно распространенным после того, как веб-дизайнер (сегодня он называется UX-дизайнером) стал отдельной профессией — ролью непрограммиста, специализирующейся на визуальном внешнем виде. Веб-разработчики, которые сосредоточились исключительно на визуальных аспектах страницы, обычно игнорировали семантику HTML и использовали HTML для «дизайна» страниц. Для них идея заключалась в том, что HTML представляет собой набор стилизованных элементов — их роль в документе в основном состоит в том, чтобы облегчить применение объявлений CSS, по сути связующих между контентом и CSS. Если для вас это звучит как «стилизованные компоненты», это потому, что стилизованные компоненты являются лишь расширением этой идеи.

Это привело к появлению «CSS на основе классов» — термин, который я придумал для написания этой статьи с меньшим количеством нажатий клавиш. CSS на основе классов использует классы в качестве меток для блоков объявлений. Повторное использование стилей облегчается за счет наследования, аналогичного наследованию в ООП. Например, базовый класс .button применяется ко всем кнопкам, а затем к определенным кнопкам применяются более специализированные версии кнопок, такие как .button-cta.

.button {
  background: blue;
  color: white;
  font-weight: bold;
  cursor: pointer;
}

.button-cta {
  font-size: 120%;
  background: red;
  color: yellow;
}

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

.button-disabled {
  background: grey;
  color: silver;
}

Если и .button-disabled, и .button-cta необходимо применять одновременно, порядок, в котором эти правила отображаются в CSS, имеет значение. Если .button-disabled появляется перед .button-cta, это не дает желаемого эффекта (предположим, что -disabled должно иметь приоритет над -cta). В этом простом случае переупорядочивание правил решает проблему, но в нетривиальных приложениях редко бывает так просто.

Это становится еще сложнее, когда эти правила не находятся в одном файле, и тонкая настройка их порядка становится более сложной, если не невозможной. Иногда это приводит к попыткам раздуть специфичность селектора (например, .button.button-disabled) или страшный !important. А что, если нам нужно переопределить стили в -disabled версии? Это особенно верно в тех случаях, когда мы имеем дело с популярными фреймворками пользовательского интерфейса CSS, такими как Bootstrap, где мы не контролируем большие части (часто сложного) кода.

С тех пор были изобретены различные методы для решения этих проблем. В первую очередь это схема именования BEM, атомарный CSS (предшественник Tailwind с 2013 года) и, с появлением организации кода на основе компонентов, CSS с ограниченной областью действия, включая модули CSS и CSS-in-JS.

Проблемы, которые пытаются решить эти многочисленные инструментальные и неинструментальные подходы, в конечном итоге связаны не с самим CSS, а с доминирующим способом использования CSS, и все они — без исключения — по-прежнему классифицируются как классовые. на основе CSS. Они также повсеместно страдают от одной или нескольких проблем, связанных со всеми подходами, основанными на классах. В частности, атомарный CSS (а также большинство реализаций CSS с ограниченной областью действия) примечателен сверхплотной связью между презентацией и структурой документа, потому что атомарный CSS намеренно пожертвовал разделением ради некоторых предполагаемых преимуществ.

Почему атомарный CSS

Атомарный CSS, вероятно, является самой радикальной идеей (до CSS-in-JS), которую индустрия видела со времен самого CSS. Любопытная вещь, которую он сделал, это то, что он перевернул таблицу в CSS и сделал что-то, что выглядит почти точно так же, как HTML до CSS, но при этом не слишком сильно щурясь. Изобретатель сознательно пожертвовал разделением, введенным CSS, для достижения некоторых других преимуществ (и нет, это не «я не хочу редактировать файлы CSS», потому что не для этого был изобретен атомарный CSS).

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

Изобретатель также цитирует некоторые другие преимущества, такие как стабильные имена классов, отсутствие группирующих селекторов, но они, в лучшем случае, предназначены для разработчиков, которые плохо справляются с кодированием CSS для начала, например, используя презентационные классы, или, в худшем случае, совершенно произвольно (например, «группировка селекторов приводит к большему количеству CSS», что, по моему собственному опыту, не имеет никакого смысла). Некоторые из «преимуществ» атомарного CSS были вытеснены прогрессом в технологии браузера (например, RTL против LTR).

Суть атомарного CSS — и его преемника Tailwind — заключается в том, что вы должны кодировать CSS в HTML, используя альтернативный синтаксис, чтобы при перемещении HTML вам не приходилось менять CSS, и для этого вы жертвуете возможностью предоставить CSS исключительный контроль над представлением.

Произвольно что?

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

Простые изменения в стиле нашего модуля привели к появлению новых правил в таблице стилей.

Это утверждение бессмысленно. CSS описывает представление. Изменение внешнего вида некоторых элементов пользовательского интерфейса потребует изменения CSS. Всегда ли это приводит к новому правилу? Конечно, нет. Это только в том случае, если вы добавляете новые варианты элемента к существующему или изначально делаете что-то не так с CSS. Я думаю, что слово «модуль» дает нам подсказку о том, где может быть проблема, но я там не был, поэтому не могу сказать наверняка.

Группировка селекторов вместо использования класса, связанного с этими стилями, приведет к большему количеству CSS.

Я понятия не имею, как это у них работает, но это опять же не правило. Это просто наблюдение изобретателя над CSS, основанным на классах, с которым они работали в то время. Добавление нескольких селекторов в блок объявлений не приводит к увеличению CSS, если только вы не считаете селекторы «больше CSS», и в этом случае… что я могу сказать. Вам больше силы.

Чтобы изменить направление [с rtl на ltr или наоборот], нам нужно перезаписать некоторые из наших стилей (например, написать дополнительные правила).

Это до некоторой степени верно, если вы смешиваете разные направления на одной странице и делаете это динамически. В противном случае это совсем не нужно. По крайней мере, не больше.

Что касается несемантических имен классов […] [в] «Советах для веб-мастеров» W3C, где говорится «Хорошие имена не меняются», вы увидите, что аргумент касается обслуживания, а не семантики се. [Поэтому атомарный CSS может использовать несемантические имена классов.]

Я уже писал об этом в Что за класс? Семантическое наименование не должно произвольно меняться (если только не меняется роль элемента), и разметка тоже не должна меняться при изменении внешнего вида. С точки зрения обслуживания, единственная разница заключается в том, где вы вносите изменения, а не в том, вносили вы их или нет. Если вы используете весь спектр селекторов в полной мере и меняете внешний вид некоторого пользовательского интерфейса, все изменения (обычно и в идеале) ограничиваются CSS. Если вы используете атомарный CSS, все изменения ограничиваются HTML. Хотя вы не меняете сами имена классов, вам нужно отредактировать содержимое атрибута class, так как это ваш CSS! Этот аргумент о стабильных именах классов в значительной степени неуместен. Неизменные имена классов не принесут вам абсолютно никакой пользы в случае атомарного CSS. Это все равно, что заявить, что имена свойств CSS, такие как display или position, никогда не меняются — совершенно бесполезное утверждение.

А вот современный от Tailwind:

создавайте веб-сайты […], не выходя из HTML-кода.

Это утверждение часто повторяется как первое преимущество, которое приходит на ум сторонникам Tailwind. По-другому они выразились: «Постоянное переключение между HTML и CSS — это головная боль». Причина, по которой это переключение происходит в первую очередь, заключается в том, что HTML и CSS не разделены четко для этих людей. Если вы правильно разделите их, то получится, что вы работаете либо с HTML , либо с CSS, но редко с обоими одновременно. Иногда — то есть редко — вы можете зайти в HTML, чтобы добавить какой-нибудь класс здесь или дополнительный элемент-оболочку там, и на этом все. С другой стороны, если вы постоянно настраиваете свой HTML, пока работаете над внешним видом страницы, вы либо неопытны (и это нормально, нам всем нужно с чего-то начинать, как я сказано) или вы изначально делаете HTMLнеправильно, считая его расширением CSS, а не разметкой для структуры документа (это нехорошо, если бы вы могли не скажешь).

Не столь произвольные претензии?

Есть одно утверждение, которое отчасти имеет смысл. И вот это:

Поскольку стили «инкапсулированы», вы можете перемещать модули без потери их стилей.

Делается это якобы по следующей причине:

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

Причина инкапсуляции, конечно, совершенно произвольна. Вряд ли это правило всегда применимо, независимо от того, что вы делаете. Я много раз использовал контекстно-зависимые селекторы, и это никогда не мешало обслуживанию и не мешало повторному использованию. Но свойство, которое допускает инкапсуляция, является законным. Та же идея отражена в CSS с ограниченной областью (CSS-модули, CSS-in-JS).

Однако что-то законное и что-то действительно полезное — это две разные вещи.

Контекстно-зависимые правила — это такие правила:

.hero > .button {
  font-size: 160%;
}

Это выбирает элемент .button только внутри элемента .hero. Если вы переместите эту кнопку за пределы элемента .hero, стиль больше не будет применяться. Аргумент заключается в том, что если вы поместите атомарный класс непосредственно на элемент, например:

<button class="fs-160">Click me now</button>

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

На первый взгляд это звучит логично и звучит как преимущество. Но есть две проблемы с ним.

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

Во-вторых, маловероятно, что вы всегда будете пользоваться этим свойством. Например, можно возразить, что кнопка должна иметь больший размер шрифта только в определенных областях страницы. Я не сомневаюсь, что изобретатель сталкивался с проблемами, когда это свойство атомарного CSS выгодно по сравнению с тем, что они делали раньше, но я там не был, поэтому не могу точно сказать, как бы я решил эти конкретные проблемы и будет ли мое решение улучшением. Лучшее, на что я могу надеяться, это то, что однажды я столкнусь с этим, а затем придумаю лучшее решение. 😎

Если единственное фактическое преимущество использования атомарного CSS (включая Tailwind) — инкапсуляция стилей — имеет сомнительную полезность (каламбур), зачем мы приносим все эти жертвы ради него и изучаем новый язык? Для меня это звучит как город нарушенных обещаний.

Итак, какова альтернатива?

Настоящий вопрос заключается в том, нужна ли нам вообще нужна альтернатива. Если бы люди хоть раз попытались использовать CSS по назначению, возможно, они, как и я, пришли бы к выводу, что «мы делали это неправильно все эти годы/месяцы/дни», и с самого начала в CSS не было ничего сломанного. Ну, не так много, как эти авторы фреймворков/методов/библиотек в любом случае хотели бы вас уверить. Я думаю, может быть, мне было бы труднее сделать это заявление в 2013 году — я действительно не помню, — но сегодня у меня нет абсолютно никаких проблем с тем, чтобы поддержать это заявление.

Позвольте мне показать вам, что я имею в виду.

Мы можем преобразовать пример кнопки на основе класса, чтобы он выглядел следующим образом:

button {
  background: blue;
  color: white;
  font-weight: bold;
  cursor: pointer;
}

button:disabled {
  background: grey;
  color: silver;
}

#hero > button {
  font-size: 120%;
}

#hero > button:not(:disabled) {
  background: red;
  color: yellow;
}

Попробуйте сами. Перепутайте порядок этих блоков объявлений, и вы увидите, что, например, по большей части это не имеет значения. Это лучше для обслуживания. Селектор #hero вряд ли изменится со временем, как и нет других селекторов, которые, вероятно, изменятся. Итак, у нас есть стабильные селекторы.

Некоторые вещи, такие как псевдокласс :not(), не существовали еще в 2013 году, но теперь, когда он поддерживается всеми основными браузерами, никогда не было так просто написать CSS, который не конфликтует с вашим собственным кодом.

Это предполагаемое использование CSS.

В нем больше строк CSS, чем в атомарном CSS (или Tailwind)? Конечно, это так. Но у него также меньше кода в HTML.

Рассмотрим эту разметку с использованием атомарного CSS.

<section id="hero">
  <button class="bg-grey c-silver fs-120 cr-pointer" disabled>Click here</button>
</section>

Я позволю вам представить часть CSS. Сравните это с HTML для обычной версии:

<section id="hero">
  <button disabled>Click here</button>
</section>

И раздувание HTML с помощью атомарного CSS — еще более серьезная проблема с тем CSS, который люди пишут сегодня. Все эти переходы, анимация, эффекты наведения, эффекты фокуса, все, что втиснуто в атрибут класса с очень кратким синтаксисом, который чем-то похож на CSS, но не совсем…

И позвольте мне повторить утверждение о «независимости от контекста».

Если я перемещу любой элемент button куда угодно, используя наш обычный CSS, это, очевидно, не повлияет. Если я перемещу элемент button:disabled, это тоже не повлияет. Эти селекторы уже не зависят от контекста, потому что они не должны быть в контексте. Единственная кнопка, внешний вид которой меняется в зависимости от контекста, — это та, которая перемещается в элемент #hero или из него. Я хочу, чтобы кнопки вели себя таким образом? Да, на самом деле я делаю! Как бывший веб-дизайнер — извините, я имею в виду бывшего UX-инженера — это потрясающая и долгожданная функция! Внешний вид кнопки зависит от ее контекста. Было бы утомительно редактировать CSS/классы каждый раз, когда мне нужно переместить кнопку.

И это становится еще более странным, когда я хочу повторно использовать код.

Допустим, у меня есть три кнопки на странице.

<button>First</button>
<button>Second</button>
<button>Third</button>

Неатомарная версия CSS уже работает для этого. Как это для повторного использования!

А атомная версия?

<button class="bg-blue c-yellow cr-pointer fw-bold">First</button>
<button class="bg-blue c-yellow cr-pointer fw-bold">Second</button>
<button class="bg-blue c-yellow cr-pointer fw-bold">Third</button>

Ну, это повторное использование, не так ли? Итак, что в этой ситуации говорят фанаты атомарного CSS? «Это потому, что вы не используете компоненты или частичные шаблоны для СУШКИ кода. Бьюсь об заклад, ваш код раздут как fsck и в других областях». Ну, я не знаю. Кому ты рассказываешь. Мне даже не понадобились нужны компоненты для повторного использования моего кода, но, возможно, я что-то упускаю. И я не знаю о вас, но я предпочитаю приберегать свое мастерство работы с несколькими курсорами для чего-то более значимого, чем WET-обработка моего кода, даже если я могу притвориться, что он каким-то образом СУХОЙ с невозмутимым видом.

Довольно иронично, что изобретатель атомарного CSS говорит, что изобрел его, потому что хотел избавиться от раздувания…

слон в комнате

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

Атомарный CSS можно сделать вручную, написав «служебные классы» самостоятельно, или вы можете использовать инструмент, который сделает это за вас на основе имеющихся у вас классов. Я подозреваю, что основной причиной того, что атомарный CSS изначально не был очень популярен, было отсутствие инструментов. Когда я попробовал это тогда, это было так утомительно!

Сегодня у вас есть такие инструменты, как Атомайзер или Попутный ветер. Однако, как и в случае с любым другим инструментом, их использование стоит. Это может быть бесплатно бесплатно, но не так бесплатно, как при нулевой стоимости. Каждый раз, когда что-то другое делает для вас какую-то работу, это не означает, что сама работа испарилась в /dev/null. Это просто означает, что он был делегирован чему-то другому, и чаще всего это создает дополнительную работу, которую вам все еще нужно выполнить. Если бы я хотел звучать как физик, я бы сказал: Работу нельзя ни упаковать, ни разрушить, ее можно только трансформировать и абстрагировать или что-то в этом роде. 🤣

Вы должны знать лучше, чем даже на одну секунду обманывать себя, что npm install что-то бесплатно. Это стоит вам времени, чтобы настроить проект, следить за проблемами безопасности, обновлять вещи и, как правило, вводить дополнительные шаги, которые необходимо предпринять, прежде чем вы сможете увидеть изменения в браузере. Иногда это также означает, что вам нужно (не очень) тонко изменить свой код, чтобы он подходил инструменту, или поддерживать файлы конфигурации. Это может усложнить развертывание, потому что теперь вам нужно что-то собрать, а не просто отправлять файлы как есть на правильно настроенный сервер. Я бы не назвал ничего из этого «бесплатным».

Бонусная разглагольствования

Да, и я упоминал, как подавлен я читаю заявления о том, что атомарный CSS, изобретенный в 2013 году, который воплощает идеи 1990-х годов, является современным?