Ар­хи­тек­ту­ра CSS

Перевод «CSS Architecture»

Перевод Вадим Макеев

Редактура Ольга Алексашенко

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

Что интересно, обычно мы не упускаем из вида эту сторону, когда дело касается других языков. Разработчик на Rails не считается хорошим только потому, что его код работает по спецификации. Это считается базовым уровнем. Конечно, он должен работать по спецификации, но его качество измеряется другим: насколько код читаемый, легко ли его изменить или расширить, достаточно ли он отделён от других частей приложения, будет ли он масштабироваться?

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

Принципы хорошей архитектуры CSS Скопировать ссылку

CSS-сообществу редко удаётся прийти к договорённости, что и как нужно делать. Даже если взглянуть только на комментарии с Hacker News или на реакцию разработчиков на выход CSS Lint, станет ясно, что многие не согласны друг с другом даже по поводу самых базовых вещей, которые стоит или не стоит делать при разработке.

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

Мне кажется, что принципы хорошей архитектуры CSS не сильно отличаются от целей разработки хорошего ПО. Я хочу, чтобы мой CSS был предсказуемым, его можно было использовать повторно, легко поддерживать и масштабировать.

Предсказуемость Скопировать ссылку

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

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

Повторное использование Скопировать ссылку

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

Поддержка Скопировать ссылку

Когда нужно добавить или изменить новые компоненты или возможности, это не должно приводить к рефакторингу существующего CSS. Добавление компонента А на страницу не должно своим появлением ломать компонент Б.

Масштабируемость Скопировать ссылку

Растущий в размерах и сложности сайт обычно требует для поддержки большего числа разработчиков. Масштабируемость для CSS означает, что он с лёгкостью может поддерживаться как одним человеком, так и большой командой разработчиков. Также это значит, что архитектура CSS вашего сайта достаточно доступна и не требует чрезмерных усилий для понимания. То, что вы сегодня единственный разработчик, имеющий дело с CSS, не значит, что так будет всегда.

Распространённые ошибки Скопировать ссылку

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

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

Изменение компонентов в зависимости от родителя Скопировать ссылку

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

.widget {
    background: yellow;
    border: 1px solid black;
    color: black;
    width: 50%;
}

#sidebar .widget {
    width: 200px;
}

body.homepage .widget {
    background: white;
}

На первый взгляд этот код выглядит вполне безобидно, но давайте проверим его на соответствие принципам, которые мы определили выше.

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

Также этот блок плохо масштабируется и не слишком подходит для повторного использования. Что случится, когда его вид на главной понадобится на какой-нибудь другой странице? Придётся добавить новый селектор.

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

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

Части ПО (классы, модули, функции и т.п.) должны быть открыты для расширения, но закрыты для изменения.

Далее в статье мы рассмотрим, как изменять компоненты, не опираясь на родительский селектор.

Слишком сложные селекторы Скопировать ссылку

Удивительно, но эта статья обойдёт стороной демонстрацию всей мощи CSS-селекторов и не расскажет о том, как оформить целый сайт, не используя ни единого класса или ID.

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

#main-nav ul li ul li div { }
#content article h1:first-child { }
#sidebar > div > h3 + p { }

Все эти примеры логически верны. Первый, по-видимому, оформляет выпадающее меню, второй говорит, что заголовок в <article> должен выглядеть иначе, чем остальные элементы <h1>, а последний пример, скорее всего, добавляет отступ к первому абзацу в блоках боковой колонки.

Если бы этот HTML никогда не изменился, можно было бы согласиться с таким подходом. Но насколько реалистичным будет предположить, что HTML никогда не изменится? Чересчур сложные селекторы могут выглядеть впечатляюще и даже помогут обойтись без презентационной разметки в HTML, но вряд ли они помогут соответствовать нашим принципам хорошей архитектуры CSS.

Упомянутые примеры совсем не готовы для повторного использования. Поскольку селектор указывает на очень конкретную часть разметки, то как может другой компонент с иной структурой HTML использовать эти стили? Возьмём для примера первый селектор (выпадающее меню) — что, если похожее меню понадобится на другой странице, на которой не будет элемента #main-nav? Тогда вам придётся повторить все стили.

Эти селекторы также очень непредсказуемы в случае, когда меняется оригинальный HTML. Представим, что разработчик решил поменять тег <div> в третьем примере на <section> — в результате весь селектор развалится.

И наконец: поскольку эти селекторы работают, только когда HTML остаётся неизменным, они по определению не поддерживаемы и не масштабируемы.

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

Слишком общие имена классов Скопировать ссылку

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

<div class="widget">
    <h3 class="title">…</h3>
    <div class="contents">
        Лорем ипсум…
        <button class="action">Жми сюда!</button>
    </div>
</div>
.widget {}
.widget .title {}
.widget .contents {}
.widget .action {}

Идея состоит в том, что классы вложенных элементов .title, .contents и .action можно безопасно оформить, не боясь, что стили повлияют на элементы с такими же классами. Это, конечно, так, но совсем не значит, что эти стили не повлияют на элементы с таким же классом, которые могут оказаться внутри.

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

Слишком общие имена классов приводят к непредсказуемому CSS.

Когда правило делает слишком много Скопировать ссылку

Однажды вы создаёте визуальный компонент, который должен отстоять на 20 пикселей от верхнего левого угла блока на вашем сайте:

.widget {
    position: absolute;
    top: 20px;
    left: 20px;
    background-color: red;
    font-size: 1.5em;
    text-transform: uppercase;
}

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

Проблема в том, что вы делаете с помощью одного селектора сразу слишком многое. Вы описываете внешний вид блока вместе с раскладкой и позиционированием в одном правиле. Внешний вид можно использовать повторно, раскладку и позиционирование — нет. И поскольку они описаны вместе, всё правило скомпрометировано.

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

Причина Скопировать ссылку

Упомянутые выше проблемные примеры объединяет одна особенность: все они слишком полагаются на оформление CSS.

Это заявление может прозвучать странно. В конце концов, это же стили. Разве не должны они нести большую (если не всю) нагрузку для оформления? Не этого ли мы хотим?

Простой ответ на этот вопрос «да», но, как обычно, всё не так просто. Разделять содержимое и представление хорошо, но содержимое не отделяется от представления только потому, что ваш CSS отделён от HTML. Скажем иначе: вы не достигнете цели, если просто уберёте всё представление из HTML, но при этом для работы вашего CSS потребуется подробнейшее знание структуры HTML.

Более того, HTML редко состоит только из содержимого, почти всегда в нём есть и структура. И часто эта структура состоит из контейнеров, единственной задачей которых является объединение некоторой группы элементов для работы CSS. Даже без презентационных классов такие структуры добавляют представление в HTML-код. Но действительно ли это смешивает содержимое с представлением?

Я уверен, что на текущем уровне развития HTML и CSS необходимо и зачастую разумно использовать HTML и CSS в качестве единого слоя представления. Слой содержимого в таком случае может быть отделён с помощью шаблонизаторов или подключаемых фрагментов (как partials в Ruby).

Решение Скопировать ссылку

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

Лучший подход, который мне удалось найти, состоит в том, чтобы как можно меньше опираться на структуру HTML в CSS. CSS должен определять как выглядит набор визуальных элементов. Для минимизации влияния HTML эти элементы должны выглядеть ровно так, как они описаны, независимо от того, где они находятся в HTML. Если некоторые компоненты должны выглядеть по-разному в разных ситуациях, они должны быть вызваны по-другому, и отвечать за этот вызов должен HTML.

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

CSS определяет, как выглядит ваш компонент, а HTML применяет этот вид к элементам на странице. Чем меньше CSS «знает» про структуру HTML, тем лучше.

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

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

Несмотря на то, что эти сомнения справедливы, они могут привести к ошибочным выводам. Вывод такой: либо вы используете родительский селектор в CSS, либо вам придётся написать этот HTML-класс тысячу раз руками. Но, очевидно, есть и другие варианты. Уровень абстракции компонентов в Rails и других фреймворках легко позволяет явно описывать внешний вид прямо в HTML без необходимости снова и снова писать один и тот же класс.

Правильный подход Скопировать ссылку

Совершив описанные выше ошибки снова и снова, заплатив позднее за их последствия, я пришёл к некоторым принципам. Не претендуя не всеобъемлемость, мой опыт показывает, что эти принципы помогут достичь хорошей архитектуры CSS.

Будьте точнее Скопировать ссылку

Лучший способ добиться того, чтобы ваши селекторы не влияли на ненужные элементы — это не дать им такой возможности. Со временем вы можете обнаружить, что селектор вроде #main-nav ul li ul li div применяется уже совсем не к тому элементу из-за изменившейся разметки. Класс .subnav, напротив, имеет очень мало шансов быть случайно применённым не к тому элементу. Назначать классы прямо элементам, которые вы хотите оформить — лучший способ сохранить ваш CSS предсказуемым.

/* Граната */
#main-nav ul li ul { }

/* Снайперская винтовка */
.subnav { }

Если взять два примера выше, то первый больше напоминает гранату, а второй снайперскую винтовку. Граната может прекрасно сработать сегодня, но кто может гарантировать, что завтра в радиус поражения не попадёт невинный гражданский?

Разделяйте ответственность Скопировать ссылку

Я уже упоминал, что хорошо организованный компонентный слой может уменьшить зависимость CSS от структуры HTML. Помимо этого, сами компоненты должны быть модульными. Компоненты должны знать только, как оформить себя, и делать это хорошо. Но они совсем не должны отвечать за раскладку, позиционирование или делать слишком много предположений насчёт того, в каком окружении они будут располагаться.

В общем случае компоненты должны определять свой внешний вид, а не раскладку и позиционирование. Будьте осторожны, когда видите свойства вроде background, color и font в одном правиле с position, width, height и margin.

Раскладку и позиционирование стоит задавать либо отдельным классом для раскладки, либо использовать для этого отдельный элемент. (Помните, что для эффективного разделения содержимого и представления часто необходимо отделить содержимое от его контейнера).

Задайте пространство имён Скопировать ссылку

Мы уже выяснили, почему родительские селекторы не всегда на 100% эффективны для ограничения действия и пересечения стилей. Гораздо лучший подход — добавить пространство имён к самим классам. Если элемент является частью визуального компонента, то каждый из классов его вложенных элементов должен использовать имя класса базового компонента в качестве пространства имён.

/* Высокий риск пересечения имён классов */
.widget { }
.widget .title { }

/* Низкий риск пересечения имён классов */
.widget { }
.widget-title { }

Добавление пространства имён к классам делает ваши компоненты самодостаточными и модульными. Это также уменьшает вероятность конфликтов с уже существующими классами и снижает специфичность, необходимую для оформления дочерних элементов.

Расширяйте компоненты модификаторами классов Скопировать ссылку

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

/* Плохо */
.widget { }
#sidebar .widget { }

/* Хорошо */
.widget { }
.widget-sidebar { }

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

Организуйте CSS в логическую структуру Скопировать ссылку

Джонатан Снук в своей замечательной книге SMACSS убеждает разделять CSS-правила на четыре отдельные категории: базовые, раскладку, модули и состояния. Базовые состоят из сбросов и умолчаний для элементов. Раскладка — для расположения глобальных элементов сайта, а также общих вспомогательных вещей вроде модульных сеток. Модули — это визуальные элементы для повторного использования и стили состояний для оформления того, что можно включить или выключить с помощью JavaScript.

В системе SMACSS модули (эквивалентные тому, что я называю компонентами) составляют большинство от всех правил в CSS, поэтому я часто прихожу к необходимости разбить их ещё больше, отделив абстрактные шаблоны.

Компоненты — это отдельные визуальные элементы. Шаблоны, напротив, скорее строительные блоки. Шаблоны не живут сами по себе и редко описывают внешний вид. Они являются одиночными повторяемыми элементами, из которых можно собрать компонент.

Приведём конкретный пример: компонент может быть модальным диалоговым окном. Это окно может иметь специальный для этого сайта градиент в заголовке, какую-то тень вокруг, кнопку закрытия в правом верхнем углу, а также фиксированное положение и выравнивание в центре по вертикали и горизонтали. Все четыре свойства этого окна могут быть повторно использованы по всему сайту, поскольку вы вряд ли захотите описывать эти свойства снова и снова. Таким образом, все они являются шаблонами, которые вместе образуют компонент диалогового окна.

Обычно я не использую классы шаблонов прямо в HTML, если для этого нет веских причин. Вместо этого я использую препроцессоры для включения стилей шаблона в описание компонента. Далее мы обсудим подробнее этот подход и мои причины для его использования.

Используйте классы строго для оформления Скопировать ссылку

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

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

Это проблема. Когда к классам обращаются слишком многие составляющие приложения, становится страшно удалять их из HTML.

Тем не менее, устоявшиеся договорённости помогут полностью избежать этой проблемы. Когда вы видите класс в HTML, вы должны быть способны чётко сказать, для чего он предназначен. Мой совет — добавлять префикс ко всем неоформительским классам. Я использую .js- для JavaScript и .supports- для классов Modernizr. Все классы без префикса — для оформления и только для оформления.

Это позволяет искать неиспользуемые классы и удалять их из HTML простым поиском по папке со стилями. Вы можете даже автоматизировать этот процесс с помощью JavaScript, сравнивая классы в HTML с классами в объекте document.styleSheets. Классы, которых нет в document.styleSheets, можно безопасно удалять.

Итак, полезно разделять содержимое и представление, и столь же важно отделять представление от поведения. Использование классов с оформлением в качестве основы для JavaScript так сильно связывает CSS с JavaScript, что становится сложно или даже невозможно обновить оформление некоторых элементов, не сломав их поведение.

Логическая структура в именах классов Скопировать ссылку

Сегодня большинство пишет CSS с дефисами в качестве разделителя для слов. Но только дефисов обычно недостаточно, чтобы различать между собой разные типы классов.

Николас Галлахер недавно написал о своем решении этой проблемы, которое я тоже (с некоторыми изменениями) с большим успехом применил. Чтобы проиллюстрировать необходимость в договорённости об именовании, рассмотрим следующий пример:

/* Компонент */
.button-group { }

/* Модификатор компонента (изменяющий .button) */
.button-primary { }

/* Вложенный объект (находится внутри .button) */
.button-icon { }

/* Это класс компонента или раскладки? */
.header { }

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

/* Правила шаблонов (с использованием Sass) */
%template-name
%template-name--modifier-name
%template-name__sub-object
%template-name__sub-object--modifier-name

/* Правила компонентов */
.component-name
.component-name--modifier-name
.component-name__sub-object
.component-name__sub-object--modifier-name

/* Правила для раскладки */
.l-layout-method
.grid

/* Правила состояний */
.is-state-type

/* Классы для JavaScript без оформления */
.js-action-name

Переработанный первый пример:

/* Компонент */
.button-group { }

/* Модификатор компонента (изменяющий .button) */
.button--primary { }

/* Вложенный объект (находится внутри .button) */
.button__icon { }

/* Класс раскладки */
.l-header { }

Инструменты Скопировать ссылку

Поддержка эффективной и хорошо организованной архитектуры CSS может быть очень сложной, особенно в больших командах. Несколько плохих правил здесь, несколько там — и снежный ком неразрешимых проблем уже покатился. Когда CSS вашего приложения уже дошел до войн за специфичность и сомнительных побед за счёт !important, может оказаться, что пути назад уже нет, разве что переписать всё с нуля.

К счастью, есть инструменты, которые позволят гораздо проще контролировать архитектуру CSS вашего сайта.

Препроцессоры Скопировать ссылку

Сегодня сложно говорить про инструменты для CSS и не упомянуть препроцессоры, и эта статья не будет исключением. Но прежде чем я воспою их ценность, мне следует коротко предостеречь вас.

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

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

  • Никогда не вкладывайте правила только для организации кода. Вкладывайте только тогда, когда это нужно в CSS на выходе.
  • Никогда не используйте примеси (mixin), если не передаёте аргумент. Примеси без аргументов гораздо лучше использовать в качестве шаблонов, которые можно расширить.
  • Никогда не используйте @extend для селектора, который не является одиночным классом. Это не имеет смысла с точки зрения дизайна и раздувает скомпилированный CSS.
  • Никогда не используйте @extend для компонентов интерфейса в модификаторе компонента, иначе вы нарушаете цепь наследования (подробнее об этом дальше).

Лучшая часть препроцессоров — это функции, вроде @extend и %placeholder. Обе они позволяют просто управлять абстракциями в CSS, при этом не раздувая код и обходясь без добавления в HTML огромного количества базовых классов, с которыми потом очень сложно управиться.

@extend нужно использовать с осторожностью, потому что иногда эти классы будут нужны в HTML. Например, когда вы впервые узнаёте про @extend, то его сразу хочется применить ко всем классам-модификаторам как-то так:

.button {
    /* Стили кнопки */
}

/* Плохо */
.button--primary {
    @extend .button;
    /* Стили модификатора */
}

Проблема с этим подходом в том, что вы теряете цепь наследования в HTML. Теперь довольно сложно выбрать все экземпляры кнопки с помощью JavaScript.

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

Как это может выглядеть на примере модального окна, о котором шла речь выше:

.modal {
    @extend %dialog;
    @extend %drop-shadow;
    @extend %statically-centered;
    /* Другие стили окна */
}

.modal__close {
    @extend %dialog__close;
    /* Другие стили кнопки закрытия */
}

.modal__header {
    @extend %background-gradient;
    /* Другие стили заголовка окна */
}

CSS Lint Скопировать ссылку

Николь Салливан и Николас Закас создали CSS Lint, инструмент для контроля за качеством кода, который помогает разработчикам находить плохие подходы в их CSS. На сайте он описан так:

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

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

Инструмент вроде CSS Lint необходим любой большой команде, чтобы поддерживать в коде, как минимум, базовую последовательность и соответствие договорённостям. Как я уже упоминал раньше, одна из главных ценностей таких договорённостей в том, что с помощью инструментов вроде CSS Lint можно просто выявить то, что их нарушает.

Основываясь на договорённостях, которые я предложил выше, очень легко написать правила для определения неверных подходов. Вот несколько правил, которые я использую:

  • Не допускайте ID в своих селекторах.
  • Не используйте несемантические типы селекторов (div и span, напр.) для любого неодиночного правила.
  • Не используйте больше двух комбинаторов в селекторе.
  • Не допускайте использование классов, начинающихся с .js-.
  • Внимание, если раскладка и позиционирование применяются в правилах для элементов без префикса .l- в названии класса.
  • Внимание, если класс, определённый сам по себе, позднее переопределяется как дочерний или иначе.

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

HTML Inspector Скопировать ссылку

Ранее я предположил, что будет довольно просто пройтись по вашим HTML-классам и всем подключенным стилям и выдать предупреждение, если класс был использован в HTML, но не определён ни в одном файле стилей. Сейчас я разрабатываю инструмент, который называется HTML Inspector, чтобы сделать этот процесс проще.

HTML Inspector проходит по вашему HTML и (почти как CSS Lint) позволяет вам написать собственные правила, которые вызывают ошибки или предупреждения, когда какие-то договорённости нарушаются. Сейчас я использую следующие правила:

  • Внимание, если один и тот же ID используется на странице два раза.
  • Не используйте классы, не упомянутые ни в одном из файлов стилей или передайте список разрешённых (с префиксом .js-, напр.)
  • Классы модификаторов не должны использоваться без их базовых классов.
  • Классы вложенных объектов не должны использоваться в отсутствие родительского базового класса.
  • Простые элементы <div> и <span> не должны использоваться в HTML без назначенных классов.

Заключение Скопировать ссылку

CSS — это не просто средство визуального дизайна. Не стоит забывать о правильных подходах к программированию только потому, что вы пишете CSS. Подходы вроде ООП, DRY, принцип открытости и закрытости, разделение ответственности и т.п. также применимы к CSS.

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