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

В данной статье я сделаю обзор на препроцессоры и постпроцессор(ы?).

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

Начнем с препроцессоров.

Препроцессоры


Что такое препроцессор вне контекста CSS? Вики знает ответ.

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

Какие же есть препроцессоры? Существует несколько представителей, например: Sass(.sass, .scss), Less(.less) и Stylys(.stylus).
Также среди препроцессоров можно отдельно выделить PostCSS(а точнее его парсер SugarSS и плагин PreCSS). Забегая далеко вперед, скажу что да, PostCSS — не только постпроцессор.

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

Возможности


Переменные


//style.scss

$color: #fff;
span {
    color: $color;
    background: $color;
}

//style.css

span {
    color: #fff;
    background: #fff;
}

Полезность переменных трудно переоценить. Теперь можно давать цветам осмысленные названия($tomato: rgb(255,99,71)), высчитывать значения не через константы, а через переменные(height: $body_height — $footer_height) и много чего еще. Многие могут возразить, что переменные в CSS есть. Но Can I Use говорит, что для IE поддержки нет(и по понятным причинам она не предвидится).

Вложенность


//style.scss
.chat-area {
    width: 40%;
    &__button { // & - указатель на текущий селектор(в данном случае & = .chat-area)
       display: inline-block;
       height:36px;
       width: 10px; 
    }

    a {
        color: red;
    }
}

//style.css
.chat-area {
    width: 40%;
}
.chat-area__button {
    display: inline-block;
    height:36px;
    width: 10px; 
}
.chat-area a {
    color: red;
}

В начале статьи я ссылался на BEM. В данном примере элемент с классом chat-area — блок. В случае, если появилась внезапная потребность его переименовать, то теперь это будет возможно сделать в одном месте, а это становится рутиной если в одном файле набирается несколько десятков селекторов, которые содержат в себе имя блока. Также хочу подметить, что это своеобразная защита от опечаток, ведь имя блока написано единожды.

Миксины


//style.scss
@mixin border-radius($radius) {
     -webkit-border-radius: $radius;
        -moz-border-radius: $radius;
         -ms-border-radius: $radius;
             border-radius: $radius;
}
.box {
    @include border-radius(10px);
}

//style.css
.box {
     -webkit-border-radius: 10px;
        -moz-border-radius: 10px;
         -ms-border-radius: 10px;
             border-radius: 10px;
}

Миксины одна из самых сложных тем для понимания. Грубо говоря миксин — это функция, которая принимает аргументы и применяет правила, зависящие от этих аргументов, к данному селектору. В данном примере миксин border-radius был применен к селектору .box

Дополнительные функции


//style.scss
$color: #202020;
h1, h2 {
    color: lighten($color, 40%);
}

//style.css
h1, h2 {
    color: #868686;
}

В основном новые функции облегчают работу с цветом. Например функция lighten — осветляет цвет на заданное кол-во процентов(противоположная функция darken).

Решаемые проблемы


Модульность


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

Так или иначе раньше ведь не было никаких препроцессоров, а проблему надо было как-то решать. Например можно писать весь код в одном файле.

Как это выглядит
    /* сброс умолчаний */
    /* общие стили */
    /* шапка */
    /* основная часть */
    /* футер */
    /* страница печати */
    /* мобильная версия */

Как это выглядит на самом деле
    /* сброс умолчаний */
    /* общие стили */
    /* шапка */
    /* основная часть */
    /* футер */
    /* страница печати */
    /* мобильная версия */
    /* какие-то правки */
    /* новая страница */
    /* ещё какие-то правки */
    /* стили новой шапки */
    /* скопированные стили  */
    /* хотфиксы */
    /* пасхалка от разработчика */
    /* наработки от стажера */
    /* стили нового футера */



Однако у нас есть препроцессоры и они решат эту проблему за нас. Препроцессор переопределяет стандартный import и теперь он вместо запроса на сервер вставляет в исходный файл импортируемый, прям как мне и хотелось.

//style.scss
@import "selector1";
@import "selector2";

//selector1.scss
span {
  color: white;
}

//selector2.scss
div {
  color: gold;
}

//style.css
div {
 color: gold;
}

span {
 color: white;
}

Прошу заметить, что исходные файлы преобразовались в один. Один запрос на сервер за статикой — один ответ.

Наследование


<sarcasm>У нас есть классы, но нет наследования, как же так?</sarcasm>. Теперь появилась возможность выделять так называемые «шаблонные селекторы» и расширять ими другие селекторы.

// style.scss
%equal-heights { // шаблонный селектор
    height: 100%;
}

%message { // шаблонный селектор
    padding: 10px;
}

.success {
  @extend %message; color: green;
}

.error {
  @extend %message; color: red;
}

// style.css
.success, .error {
    padding: 10px;
}

.success {
    color: green;
}

.error {
    color: red;
}

Прелесть шаблонных селекторов в том, что они не попадают в сгенерированные стили. Шаблонный селектор %equal-heights не был никак задействован в коде и не оставил никаких следов в CSS. Селектор же %message отразился в виде правил для селекторов, которые его расширили. Наследоваться можно и от обычных селекторов, но предпочтительнее использовать шаблонные, чтобы не оставалось лишнего мусора.

Форматирование


После того как код написан, его нужно отформатировать(для продакшена сжать). Можно делать это и с помощью сборщиков по типу webpack, а можно и через стандартные инструменты.

Всего в Sass есть 4 вида форматирования.

//expanded
span {
    color: gold; 
    display: block;
}

div {
    color: red;
}

//nested
span {
     color: gold; 
     display: block; }

div {
 color: red; }

//compact
span { color: gold; display: block; }
div { color: red; }

//compressed
span{color:gold;display:block}div{color:red}

expanded — Наиболее всего похож на код, написанный человеком.
nested — Приближен к формату старого синтаксиса. Читаемость не теряется, но это холиварный вопрос.
compact — Все еще сохраняет читаемость, но уже с трудом. Полезен для определения на глаз кол-ва селекторов в проекте.
compressed — Уже совершенно не читаемый формат. Все символы, которые можно удалить, удаляются. Подходит для «скармливания» браузеру.

Постскриптум


Я не разобрал некоторые возможности добавляемые Sass. Например циклы или особенности арифметических операторов. Я оставлю их вам на самостоятельное ознакомление.

Постпроцессоры


Разобравшись с препроцессорами переходим к постпроцессорам.

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

Объясню на конкретном примере работы PostCSS — единственного представителя постпроцессоров в контексте css.

PostCSS из коробки на самом деле не делает с CSS ничего. Он просто возвращает файл, который был дан ему на вход. Изменения начинаются, когда к PostCSS подключаются плагины.

Весь цикл работы PostCSS можно описать так:

  • Исходный файл дается на вход PostCSS и парсится
  • Плагин 1 что-то делает
  • ...
  • Плагин n что-то делает
  • Полученный результат преобразовывается в строку и записывается в выходной файл

Рассмотрим же основные плагины, которые есть в экосистеме PostCSS

Плагины


Autoprefixer


Этот плагин настолько популярен, что многие считают, что они используют этот плагин, но не используют PostCSS. Они не правы.

//in.css
div {
    display: flex
}

//out.css
div {
    display: -webkit-box;
    display: -webkit-flex;
    display: -moz-box;
    display: -ms-flexbox;
    display: flex
}

Autoprefixer добавляет браузерные префиксы к вашим правилам. Ничем не заменимый и один из самых важных плагинов, с которого и началась история PostCSS. Можно даже сказать, что имеет смысл поставить PostCss только ради этого плагина.

Preset Env


//in.css
@custom-media --med (width <= 50rem);

@media (--med) {
 a:hover {
    color: color-mod(black alpha(54%));
  }
}

//out.css
@media (max-width: 50rem) {
   a:hover  {
     color: rgba(0, 0, 0, 0.54);
   }
 }

PostCSS Preset Env добавляет возможности, которые только обсуждаются в черновиках разработчиков css. В данном примере была реализована директива @custom-media, а так же функция color-mod. Начни использовать css будущего уже сегодня!

CSS Modules


Все эти BEM не для вас, но проблема с конфликтами имен классов все еще стоит? Тогда PostCSS предлагает другое решение.

//in.css
.name {
 color: gray;
}

//out.css
.Logo__name__SVK0g {
 color: gray;
}

CSS Modules изменяет названия классов по некоторому паттерну(все настраивается). Теперь мы не знаем заранее имя класса, ибо оно определяется динамически. Как же теперь проставлять классы элементам, если мы не знаем их заранее? Объединяя PostCSS, Webpack и ES6 могу предложить такое решение:

import './style.css'; // раньше
import styles from './style.css'; // сейчас

Теперь мы не просто импортируем файл со стилями(например в файле React компонента) и подставляем заранее известные нам значения, а импортируем некий объект. Ключами этого объекта будут изначальные селекторы, а значениями — преобразованные. То есть в данном примере styles['name'] = 'Logo__name__SVK0g'.

Short


//in.css
.icon {
 size: 48px;
}

.canvas {
 color: #abccfc #212231;
}

//out.css
.icon {
 width: 48px;
 height: 48px;
}

.canvas {
 color: #abccfc;
 background-color: #212231;
}

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

Auto Reset


//in.css
div {
 margin: 10px;
}

a {
 color: blue;
}

//out.css
div, a {
 all: initial;
}

div {
 margin: 10px;
}

a {
 color: blue;
}

PostCSS Auto Reset позволяет нам не создавать отдельный файл со сбросом всех стилей. Плагин создает для всех селекторов один большой селектор, куда помещает правила, сбрасывающее все стили. По умолчанию создается лишь правило all со значением initial. Это полезно в комбинации с плагином postcss-initial, который в свою очередь превращает это правило в портянку правил на 4 экрана. Впрочем все можно настроить и сделать сброс например таким:

//out.css
div, a {
 margin: 0;
 padding: 0;
}
div {
 margin: 10px;
}
a {
 color: blue;
}

Помните в начале статьи я говорил что PostCSS не только постпроцессор?

PostCSS — препроцессор?


Рассмотрим один парсер и один плагин, после которых вы измените свое сложившееся мнение о PostCSS.

SugarSS


//in.sss
.parent
  color: white

.parent > .child
  color: black

//out.css
.parent {
  color: white
}

.parent > .child {
  color: black
}

SugarSS — парсер(не плагин!), который базируется на отступах, а не на фигурных скобках, как стандартный. Имеет отдельное расширение ".sss". Код написанный с помощью SugarSS по стилю схож со старым синтаксисом Sass, но без его примочек вроде переменных, миксинов, наследования и тд.

Вы ведь догадались что добавит следующий плагин?

PreCSS


//in.sss
$color: black

.parent
 .child
   color: $color

//Результат работы SugarSS
$color: black;

.parent {
 .child {
   color: $color
 }
}

//out.css
.parent .child {
   color: black
}

PreCSS как раз и добавляет те самые возможности препроцессоров о которых написано в первой половине статьи.

И чем же PostCSS теперь не препроцессор?

Stylelint


О Stylelint уже написано довольно много. Он попал в этот обзор, так как использует PostCSS, как парсер строк CSS файлов. Предположим у нас есть такой файл.

a {
 color: rgb(1, 1, 1)
}

div {
 color: rgb(0, 0, 0)
}

Вот его вывод для текущего файла:

 2:21    Expected a trailing semicolon              declaration-block-trailing-semicolon
 6:21    Expected a trailing semicolon              declaration-block-trailing-semicolon
 7:1     Unexpected missing end-of-source newline   no-missing-end-of-source-newline

Полезность этого инструмента довольно сложно переоценить.

Выводы


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

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

Никто не мешает использовать препроцессоры и PostCSS в связке. Вариант довольно неплох для проектов, которые уже используют препроцессоры и имеет место на жизнь.

Для новых же проектов я бы посоветовал использовать только PostCSS. Верстальщики привыкли к синтаксису препроцессора? Поставьте плагин PreCSS и парсер SugarSS. Нужна кроссбраузерность? Поставьте плагин Autoprefixer. Больше не нужна кроссбраузерность(например ваш проект обернули в электрон и он стал десктопным)? Просто удалите Autoprefixer! С PostCSS вы сможете, как с помощью конструктора, собрать именно то, что нужно вашему проекту.

Комментарии (9)


  1. CyberAP
    24.12.2018 01:52
    +2

    Странный обзор. А как же плюсы и минусы конкретных решений? Для новичка это самое важное: понять какую задачу решает инструмент и его ограничения. Вы перечислили очень много спорных инструментов и решений, которые подойдут явно не всем. Мне кажется, если какой-нибудь сферический джуниор в вакууме (в плане CSS) потащит это всё в проект (при условии что у него есть такая возможность конечно) через какое-то время он столкнётся с тем что это будет просто невозможно поддерживать, а переписывать уже поздно.


    Больше не нужна поддержка IE8? Просто удалите Autoprefixer!

    Autoprefixer не про IE, а про кроссбраузерность. Если нужна поддержка IE, то одного автопрефиксера может быть недостаточно. И наоборот, если поддержка IE не нужна это совсем не означает что от него надо отказаться.


    1. Tave Автор
      24.12.2018 20:00

      Наверное я неправильно донес свою мысль. Я хотел сделать не жесткий разбор с метриками, объяснениями по-хардкору за каждый плагин или инструмент, а именно, что обзор. Я хотел показать людям, которые только-только изучили CSS(например студенты 1-3 курсов или самоучки фронтендеры), что верстка чистым CSS'ом не заканчивается, что есть другие технологии и инструменты, которые также достойны изучения. Прочитав статью такие люди смогут понять «куда копать» и сами понять плюсы и минусы, а также нужность и ненужность(для себя и своих проектов) разбираемых технологий.


    1. Tave Автор
      24.12.2018 20:03

      А что касается IE8: Почти всегда, когда разговор касается кросссбраузерности, подразумевается поддержка IE. Я это написал на автомате, даже не подумав, что сделал что-то не так. Каюсь, исправил.


  1. fromrussia
    24.12.2018 04:02
    +1

    Миксин для border-radius — это из какого века?

    Вот тут опечатка:

    display: inlyne-block;


    1. Tave Автор
      24.12.2018 20:09

      Пример взял из курса Яндекса. По-моему он вполне прекрасно показывает, что делают миксины, даже не смотря на свое моральное устаревание.

      За опечатку спасибо поправил.


  1. dom1n1k
    24.12.2018 10:20

    Очень бессистемно и по верхам.


  1. Against-vegetables
    24.12.2018 16:28

    .chat-area {
        &__button {
        }
    }

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


    1. Tave Автор
      24.12.2018 20:20

      Если использовать правило «один файл стилей — один BEM блок», то поиск не сильно усложняется. Другое дело, что это действительно превращается в ребус, когда уровень вложенности увеличивается до неприличных размеров(даже 3-4 вполне хватит чтоб запутаться). Ну и надо понимать, что это пример, а не руководство к действию.


      1. kolyan222
        24.12.2018 23:46

        Да, зачастую обычный css, но разбитый по принципу «один файл стилей — один BEM блок» — намного лучше. Особенно если этот код был написан кем-то другим.