CSS-препроцессоры против CSS

Не баттл, а унижение

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

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

Необходимое введение: компонентный подход

Мы живём в эпоху компонентного подхода: интерфейс проекта делится на компоненты (кнопки, вкладки, записи…), которые можно использовать много раз, вкладывать друг в друга, модифицировать. Проще всего добиться этого — использовать методологию БЭМ, в которой компоненты называются Блоками. Более сложные варианты (CSS-модули, CSS-in-JS), обычно завязаны на технологии (фреймворки, вроде React).

Удобно делить исходный код на несколько папок (по имени компонента), в каждой из которых лежит всё, что нужно компоненту для работы:

# некий сферический проект в вакууме
📁blocks/
  📁 blog-post/
    📁 bg-img/
    📄 blog-post.scss
    📄 blog-post.js
    📄 blog-post.pug
    📄 tests.js
    📄 readme.md
  📁 btn
  📁 tabs

Это позволяет:

  • Безопасно включать и отключать компоненты. Когда компонент не используется, его стили, скрипты и пр. не загружаются посетителю (зависит от организации сборки).
  • Иметь несколько «сборок» (стилевых и других файлов), отдаваемых в браузер для разных частей проекта. К примеру, если на сайте есть публичная и приватная части, то стили, скрипты и спрайты приватной части можно подключать отдельно, чтобы не тратить время на их загрузку и обработку для неавторизованных посетителей.
  • Безопасно модифицировать компоненты. Меняя стили одного компонента, крайне сложно сломать другой компонент (но, если постараться, можно).
  • Быстро находить точку редактирования. Когда известно название редактируемого класса, находим файл с тем же именем (Ctrl + P во многих редакторах) и редактируем. Ориентироваться по коду быстрее и проще, т.к. сам файл имеет меньший размер.
  • Использовать компоненты в других проектах. Базовые стили многих элементов (тех же кнопок) одинаковы от проекта к проекту. Копируем блок из стартовой библиотеки и дописываем нужные проекту стили.
  • Собирать свою библиотеку компонентов. Компоненты, из которых состоят страницы сайтов, часто повторяются: кнопки, соц. ссылки, список пользователей, карточка товара, поле ввода текста с описанием. Можно один раз написать для компонента относительно универсальную разметку, стили, javascript для применения на любом проекте и не изобретать велосипед.

CSS-препроцессоры

Это языки написания стилей, похожие на CSS, но с возможностями, которых не хватает в CSS. Напрямую в браузере не работают, нуждаются в компиляции, результатом которой является CSS-файл. Как это работает: вы запускаете какое-либо ПО (в моём случае — gulp или webpack), оно следит за сохранением препроцессорных файлов и собирает (компилирует) из них CSS-файл. Заодно и страницу в браузере автоматически обновляет.

Актуальные CSS-препроцессоры: Sass (2006 г., лидер рынка), LESS (2009 г., весьма хорош), stylus (2010 г., прекрасен, но не разрабатывается) и PostCSS (2013 г., изначально для постпроцессинга, но с ним можно повторить почти всю функциональность настоящих препроцессоров). PostCSS используется почти во всех проектах и в виде постпроцессора.

Далее сравнение CSS-препроцессоров с обычным CSS.

Разделение на небольшие файлы

Выше есть упоминание компонентного подхода. Действительно удобно, когда в одном стилевом файле описан лишь один компонент (блок), это ускоряет восприятие кода и поиск точки редактирования.

В CSS есть импорты

/* Два равновозможных варианта использования */
@import url("additional-style.css") screen and (orientation: landscape);
@import "print.css" print;

Браузер, встречая импорт, идёт по указанному пути, загружает и обрабатывает CSS-файл.

Дьявол в мелочах:

  1. Если в одном CSS-файле написать импорт другого, они будут загружаться последовательно, а не параллельно, как могли бы (увеличение времени до начала рендера на медленных каналах). Это хуже, чем написать в разметке два <link>.
  2. Любое подключение дополнительного файла к странице — дополнительный запрос к серверу (увеличение нагрузки на аппаратную часть сервера), как следствие — при средней и высокой посещаемости сайта все файлы будут отдаваться в браузер медленнее. Спасти может использование протокола HTTP2 (позволяет за один запрос получать несколько файлов), но всего 29,6% из 10 млн. самых популярных сайтов используют его (данные на середину 2018 г., за последний год распространённость выросла почти в 2 раза).

В CSS-препроцессорах импорты работают иначе

В CSS-препроцессорах импорты обрабатываются на этапе компиляции в CSS: если мы импортируем препроцессорный файл, его содержимое «вставится» вместо импорта. В итоге — один CSS файл, содержащий стили всех импортированных файлов.

// SCSS
@import 'btn.scss';    // «вставка» содержимого файла btn.scss или btn.sass
// Можно не указывать расширение файла
@import 'page-header'; // «вставка» содержимого файла page-header.scss, или page-header.sass, или page-header.css

// Если нужен CSS-импорт
// Что писать:                // Во что скомпилируется:
@import 'foo.css';            // @import url(foo.css);
@import 'foo' screen;         // @import "foo" screen;
@import 'http://foo.com/bar'; // @import "http://foo.com/bar";
@import url(foo);             // @import url(foo);

Итог: импортирование в препроцессорах лучше

Без HTTP2 (менее 1/3 всех сайтов) CSS-импорты — почти всегда зло (увеличение количества запросов к серверу).

Переменные

Это удобно и расширяемо. Записываем в переменную с именем color-danger предупреждающий об опасности цвет, используем переменную везде, где нужен такой цвет (цвет текста в сообщении об ошибке под текстовым полем, бордюр текстового поля, цвет фона сообщения о критической ошибке в нижнем правом углу, фоновый цвет кнопки опасного действия и т.п.). Сменим значение переменной — получим изменения везде, где она применялась.

В CSS есть custom properties

Это как переменные, но круче, ибо работает прямо в браузере. Если значение «переменной» изменить (по медиа-условию или javascript-ом), изменятся стили на всех селекторах, где применена «переменная». К custom properties применяется наследование и каскад.

Первый минус: custom properties не работают в Internet Explorer всех версий (разработка IE прекращена в пользу Edge, где custom properties работают) и в старых Safari (поддерживаются с 9.1 и 9.3 — для мобильного). Костыли в виде cssnext позволяют в небольшом количестве случаев решить эту проблему (глючно, поскольку плохо работают с медиавыражениями или не работают с ними вове). В любом случае, это потеря тех возможностей, благодаря которым custom properties круче, чем препроцессорные переменные.

Второй минус: custom properties не получится применить в CSS-анимациях, т.к. их значения — строки, а браузер не может (пока?) вычислить плавный переход между двумя строками. Решение этой проблемы возможны с javascript, я уверен.

:root {
  --font-size: 12px;
}

body {
  font-size: var(--font-size);
}

/* rem-имитатор */
@media (min-width: 1024px) {
  :root {
    --font-size: 16px;
  }
}

В CSS-препроцессорах переменные — обычные переменные

Препроцессорные переменные работают только на этапе компиляции. Это никак не мешает присвоить их значения в CSS custom properties, чтобы использовать их же как CSS-переменные.

// Внимание: хорошей практикой является вынос переменных в отдельный файл.
$font-size: 12px;

body {
  font-size: $font-size;
}

Итог: CSS-переменные круче, если в ТЗ нет IE и старых Safari

Если в техническом задании упомянута поддержка IE или по метрике вы не можете игнорировать старые Safari (особенно актуально для мобильных версий), то CSS custom properties «превращаются в тыкву» — становятся ничуть не лучше препроцессорных переменных.

Математические операции

Мат. операции и переменные созданы друг для друга. Но мат. операции ещё «гуляют налево» (в CSS).

В CSS есть calc()

И он восхитителен по тем же причинам, по которым прекрасны CSS custom properties. Но calc() круче, поскольку поддерживается в IE (хотя и не без багов).

/* если вы писали CSS в 2000-х, тут можно вновь немного поплакать от счастья */
.tabs {
  width: calc(100% - 3em);
}

Поскольку это срабатывает в браузере, в момент выполнения предыдущего примера браузер знает чему равно 3em и ширина действительно будет 100% - 3em.

:root {
  --margin: 3em;
}

.tabs {
  width: calc(100% - var(--margin));
}

В CSS-препроцессорах есть мат. операции

И calc() никто не запрещает использовать (в LESS придется экранировать, иначе внутри скобок произойдёт мат. операция).

// Внимание: хорошей практикой является вынос переменных в отдельный файл.
$font-size: 12px;

h1 { font-size: $font-size * 2; }
h2 { font-size: $font-size * 1.618; }

Что есть в CSS-препроцессорах, чего нет в CSS

Единственное, в чём функциональность CSS превосходит препроцессоры — custom properties. Это действительно офигенно. Но что такого есть в препроцессорах, чего нет в CSS?

Вложения

.promo {
  padding: 1em;

  h1 { // после компиляции превратится в .promo h1 {...}
    font-size: 48px;
  }

  @media (min-width: 1024px) { // после компиляции «вывернется»
    padding: 2em;
  }
}

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

Амперсанд

.btn {
  color: #000;

  &:hover { // после компиляции превратится в .btn:hover {...}
    color: #808080;
  }

  &__icon { // после компиляции превратится в .btn__icon {...}
    position: absolute;
  }

  .promo & { // после компиляции превратится в .promo .btn {...}
    margin-left: 30px;
  }
}

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

ВНИМАНИЕ: амперсанд не стоит использовать в местах разделения слов, а только в местах отделения БЭМ-элементов и модификаторов (для псевдоселекторов и псевдоэлементов — без ограничений). Подробнее — см. Как работать с CSS-препроцессорами и БЭМ.

Примеси

Набор правил, который можно использовать многократно.

// Создание примеси
@mixin clearfix {

  &:after {
    content: "";
    display: table;
    clear: both;
  }
}

// Применение примеси
.promo__wrapper {
  @include clearfix;
  padding: 1em;
}

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

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

Условия

// Параметризированная примесь. При вызове ожидает передачи параметра — цвета.
@mixin mixin($color) {
  color: $color;

  @if (lightness($color) >= 50%) {
    background-color: black;
  }
  @else {
    background-color: white;
  }
}

Добавляют логику, используются, чаще всего, в примесях. Вне примесей (да и в целом) используются редко.

Циклы

$columns: 12;

@for $i from 1 through $columns {
  .col-#{$i} { width: 100% / $columns * $i; }
}

@for, @while, @each. Удобны при использовании типов данных, напоминающих массивы (см. пример ниже в разделе о типах данных). Используются относительно редко.

Функции

Есть встроенные функции для математических операций (округления, получение наибольшего, генератор случайных чисел и пр.), операций с цветами (осветление, насыщение и пр.), строками, массивами. Удобны при написании фреймворков. Используются умеренно.

$base-color: #AD141E;

.btn {
  background-color: $base-color;

  &:hover {
    lighten( $base-color, 10% ); // тот же цвет, но на 10% светлее
  }
}

В черновике CSS Color Module level 4 есть функции работы с цветом. Когда будут готовы для использования в реальных проектах — не ясно (вероятно, когда IE умрёт). Сейчас (осень 2018) поддержи нет никакой.

Можно добавлять свои функции, с rem и пикселями.

@function rem($px) {
  @return ($px / 16) + rem;
}

h1 { font-size: rem(32); }

Позволяют дописать любую дополнительную логику. Используются относительно редко.

Расширения (экстенды)

.error {
  border: 1px #ff0;
  background-color: #fdd;
}

.fatal-error {
  @extend .error; // в CSS будет взят .fatal-error и дописан через запятую к .error
  border-width: 5px;
}

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

Типы данных

Помимо чисел и строк, во многих препроцессорах есть и составные типы данных (массивы).

$status-colors: (
  primary: #3971ED,
  success: #27BA6C,
  info:    #03a9f4,
  warning: #FF8833,
  danger:  #ff1a1a
);

.message {

  @each $status, $color in $status-colors {

    &--#{$status} {
      background: $color;
    }
  }
}

Удобны при работе с наборами свойств, которые можно перебирать в цикле (цвета, брейкпоинты). Используются относительно редко.

Предупреждения

Не пытайтесь программировать на CSS-препроцессорах. Особенно, если не умеете программировать! В больших возможностях — большая сила и большая опасность выстрелить себе в ногу. См. Как работать с CSS-препроцессорами и БЭМ.

Пишите код так, как будто сопровождать его будет склонный к насилию психопат, который знает, где вы живёте (Стивен Макконнелл).

Итого: преимущества CSS-препроцессоров

  • Ускоряют работу.
  • Облегчают модификацию проекта.
  • Упрощают компонентный подход.

Итого: недостатки CSS-препроцессоров

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

Выводы

  1. В реальной жизни сверстать что-то без процессинга стилей (используя только CSS) можно, но это будут проекты уровня «Сайт моего хомячка». Изучайте CSS-препроцессоры.
  2. Изучайте новые возможности CSS. Они классные.
  3. CSS-препроцессоры — не конкурирующая с CSS технология, но дополняющая её.

P.S.

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

Понравилась статья? Ставьте лайк, делитесь в соц. сетях или купите мне кофе.