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

© Habrahabr.ru