CSS-модули
Какими CSS обладает особенностями, которые приносят боль на больших проектах?
- глобальное пространство имен
- разрешение зависимостей
- поиск «мертвого» кода
- отсутствие констант
- неоднозначный результат (каскад)
Давайте разберемся с тем, как мы сейчас пишем 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