- глобальное пространство имен
- разрешение зависимостей
- поиск «мертвого» кода
- отсутствие констант
- неоднозначный результат (каскад)
Давайте разберемся с тем, как мы сейчас пишем CSS на больших проекта и как хотелось бы его писать в идеальном мире.
Возьмем простой пример: кнопка и ее состояния.
В реальности
Учитывая, что пространство имен глобальное, приходится вводить определенные соглашения по именованию классов, чтобы избежать случайных пересечений.
В БЭМ-нотации это бы выглядело так:
.button {...}
.button_state_disabled {...}
.button_state_error {...}
.button_state_progress {...}
Я думаю, что многие согласятся с тем, что при первом знакомстве с БЭМ когнитивный диссонанс вызывают огромные названия классов, которые получаются в итоге.
Естественно было бы написать:
.button {...}
.button.disabled {...}
Однако, через месяц, когда все забудут про это место, появится класс .disabled в другом файле(который будет означать совсем другое), а здесь неожиданно сломается — пространство имен единое.
Можно было бы написать так:
.button {...}
.button-disabled {...}
Но тогда получается слишком много дублирования кода, потому что кнопки отличаются всего одним стилем: .button-disabled должен содержать все то же, что и .button, но, например, другой цвет фона.
Сейча эта задача решается с использованием миксинов на уровне препроцессоров, потому что в CSS нет такой возможности.
В идеальном мире
.button {
display: inline-block;
padding: 8px 2px;
border-radius: 3px;
}
.button-disabled {
composes: button;
background-color: gray;
}
Все селекторы локальные в рамках конкретного файла.
Это означает, что в файле button.css, я пишу:
.text {...}
А мой коллега в недрах совсем другого компонента:
.text {…}
Нет пересечений для .text — нет необходимости в специальных классах для элементов блоков.
Локальное пространство имен справедливо так же для так же для анимаций, объявленных через @keyframes.
В шаблоне не хочется думать про композицию классов вида .button.button_state_disabled для получения определенного состояния.
Чтобы этого избежать, каждый класс должен содержать в себе все необходимое для отрисовки каждого состояния компонента:
.button-disabled {
composes: base from "./base.css";
}
Ключевое слово composes дает мне функционал миксинов.
Причем я могу попросить стили из другого файла, что дает мне модульность на уровне CSS.
Реальность или вымысел
Выглядит неплохо. Что нужно для реализации такого интерфейса? Очевидно, необходимо установить связь между шаблонами и CSS.
Все зависит от того, какой шаблонизатор используется. В современном фронтенде практически все шаблонизаторы — javascript-приложения, задача которых превратить шаблоны в html.
Представим, что у нас есть простой шаблонизатор, который умеет только интерполяцию строк:
<% var styles = require("./button.css") %>
<button class="<%=styles.button%>">Отправить заявку</button>
Весь CSS экспортируется как объект, ключами которого являются понятные, семантичные, имена классов для использования в шаблоне, а значениями — те имена классов, которые будут в итоговой разметке (например, уникальные хеши).
Сейчас это можно сделать с помощью плагина для webpack или плагина для browserify.
Более современный, реальный пример — в шаблоне reactjs-компонента:
import { Component } from 'react';
import styles from './button.css';
export default class button extends Component {
render() {
let className = styles.button
let text = "Отправить заявку"
if (this.state.loading) {
className = styles.buttonDisabled
}
return <button className={className}>{text}</button>
}
}
Что почитать
Кажется, видимая движуха началась с доклада «CSS in JS»
Статья CSS-модули: добро пожаловать в будущее. Прежде, чем читать, откройте исходный код, посмотрите на скомпилированные названия классов: красота! :)
Организация на гитхабе, где ребята штурмуют тему модульности в CSS. Здесь документация: примеры, концепции и конкретные инструменты: postcss, browserify и webpack плагины.
Доклад Павла Ловцевича на последнем WSD (слайды)
Комментарии (34)
Iskin
04.11.2015 00:17+1Зря не упомянули postcss-autoreset или postcss-initial. Изоляция селекторов — только малая часть. Ещё есть большая проблема глобальных ресетов и наследования свойств. Эти плагины их как раз решают.
Ну а так как CSS Modules тоже написан на PostCSS, то даже парсить два раза не придётся.
На Front Talks я более подробно рассказывал, как использовать PostCSS для изоляции стилей: www.youtube.com/watch?v=XJaJqLVaR-c
BananaBobby
04.11.2015 01:12Извините, может что-то не понял, но чем это кардинально отличается от бэма, не считая того, что для модификатора не надо писать название изначального блока? (То есть button button_loading button_disabled превратится в button-disabled button-loading)
Iskin
04.11.2015 01:16+1Так это и есть просто автоматический БЭМ — не надо ни о чём думать. Примерно как Автопрефиксер и примеси для префиксов.
Iskin
04.11.2015 01:16Там, правда, есть ещё один плюс — в конец селектора добавляется случайная строка. Но это важно только для разработки с виджетов для стороннего сайта, где может произойти конфликт имени блока.
musuk
Мысль интересная, но вообще писать CSS вредно. Человечество уже придумало SCSS и LESS для этих целей. А там можно делать всякие:
ivan386
Не вредно
musuk
Ну это же страшно выглядит и сложно сопровождается.
Ведь есть няшные LESS/SCSS с миксинами, переменными, и красивым иерархичным, читабельным кодом.
vitkarpov
Имеется ввиду, наверное:
Или disabled — это вложенный элемент, а не модификатор для самого компонента?
gwer
Полагаю, имеется в виду подобное:
Это как раз то самое пространство имен, об отсутствии которого все постоянно плачут и уходят на три буквы.
vitkarpov
Что случится если внутри my-mega-widget появится my-another-mega-widget внутри которого будет button?
На button уже будут мачится два селектора: .my-mega-widget .button и .my-another-mega-widget .button, а это не то, чего ожидаешь от композиции компонентов.
gwer
Описанная проблема мне ясна.
Люди занимаются разной работой, пишут разные сайты, разные компоненты. «Жесткие» компоненты с максимальным приоритетом собственных стилей — это круто, когда пишется нечто, что будет использоваться на разных сайтах (в своих проектах или распространяется) и что должно отображаться везде одинаково. И в этом случае тот же БЭМ может оказаться уместным.
Но таких проектов далеко не большинство. Большинству не нужна эта жесткость, а один и тот же виджет может наоборот требовать разного оформления в зависимости от контекста. И если внутри .my-mega-widget таки понадобится использовать родные стили от .my-another-mega-widget, всегда есть тот же extend, которые пробросит стили и решит все проблемы на уровне приоритетов CSS.
Я не говорю, что все эти модули/компоненты/БЭМы не нужны. Речь лишь о том, что не нужно их пихать там, где им не место.
vitkarpov
На любом сайте, который живет и развивается, компонентый подход оправдан.
Завтра приходит менеджер и просит переставить вот эту штуку сюда, а этот блок вложить вот в этот — не круто говорить в таких случаях «Я так не задумывал! Мне нужно неделю, чтобы все переверстать».
gwer
Если на это требуется неделя, значит что-то тут нечисто.
dzhiriki
В этом-то и проблема. Когда один компонент влияет на другой и «разное оформление в зависимости от контекста», то просьба менеджера переставить компонент в другое место превращается в игру «угадай, сломается ли что-нибудь при переносе».
И нужно время на то, чтобы разобраться что сломалось и починить.
А если бы сразу писались компоненты, которые ни от чего не зависят, проблемы бы даже не возникло.
tenbits
Популярный аргумент. Но вообще-то, принцип области ответственности для компонент никто не отменял. Чем конкретнее компонент — тем лучше. Это значит, что он не принимает внутрь посторонии компоненты, если конечно изначально не было это предусмотренно, тогда конечно и css будет другим и разметка. И более того, хорошо когда композиция компонент является плоской, а не вглубину.
stychos
В чём вообще проблема?
vitkarpov
Проблема в том, что завтра это превратится в
А послезавтра потребуется добавить обертку для кнопки и селектор непосредственного потомка перестанет работать.
stychos
И что, так трудно сфолдить группу и добавить обёртку? Труднее, чем делать то же самое во всяких там БЭМ?
vitkarpov
Кажется, и так и так — сложнее, чем локальный скоуп для каждого компонента по дефолту.
stychos
Согласен =) Но чем это отличается от обычного нормального владения scss'ом?
Iskin
Вот тут как раз хороший пример, почему изолировать нужно не только селекторы, но и свойства с помощью `all: initial` и других PostCSS-плагинов.
AmdY
С чего это будет матчится button?
если одна кнопка
.my-mega-widget .button {}
а вторая совсем в другом неймспейсе
.my-another-mega-widget .button {}
При этом используя less можно сделать миксин, чтобы не было дублирования или добавить возможность наследования и расширения.
vitkarpov
Если вложить один компонент в другой:
Уникальные имена классов для каждого элемента избавляют от таких казусов. В БЭМ это решается неймингом, а в CCS-модулях — автоматически, за счет локального скоупа.
AmdY
Чтобы не дублировать http://habrahabr.ru/post/270075/?reply_to=8641825#comment_8641835
BananaBobby
Одно дело когда какие-то стили из разряда color идут дальше по дереву и другое, когда специально прописываешь каскады и попадаешь в вышеописанный случай, где верстка кнопок ломается
Iskin
CSS Modules — это не обычный CSS ;). Это PostCSS, так что подключите postcss-nested и у вас будет та же вложенность.
dzhiriki
Так-то, можно писать на том же LESS \ SASS, а потом уже получившуюся CSSку грузить.
Iskin
Да так тоже можно — но postcss-nested будет быстрее (1 раз парсить) и проще настраивать (не будет проблем с путями карт кода).
Sullenor
CSS прекрасен своей простотой. Зачем превращать его в язык программирования?
А по поводу примера, селекторы типа .my-mega-widget с ростом кодовой базы становятся довольно длинными и работать с ними не всегда удобно.
Я бы сказал, тут интерес другой момент. В эру компонентного подхода можно довольно лаконично описывать состояние компонента без необходимости добавлять какой-то префикс, который гарантирует уникальный неймспейс. Например, с помощь CSS Модулей, туже самую кнопку можно описать как-то так:
BananaBobby
В итоге после сборки мы получим все те же .button.error в стилях? По сути ничего от button_error не отличается
Sullenor
Да, на выходе результат схожий, но с исходными файлами становится чуть проще работать за счет подобной автоматизации. Также уменьшаются возможные коллизии, так как задача генерации уникальных селекторов перекладывается на роботов.
Реже возникает необходимость увеличивать специфичность селекторов, так как можно свободно писать .title и не думать о последствиях.