Нужны ли CSS-препроцессоры в 2018 году, или насколько мы близки к ванильному CSS

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

Начнём с того, что классические препроцессоры вроде Sass/Less/Stylus свой век почти отжили — им на смену пришли современные возможности CSS, а также PostCSS с большой экосистемой плагинов. PostCSS по сути тоже препроцессор, только модульный — это главная причина его популярности и нужности. Подключаете и используете только нужные плагины, а если нужно что-то нестандартное, всегда можно написать свой плагин.

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

В идеале хотелось бы избавиться от PostCSS и оставить только ванильный CSS (#usetheplatform). Как дела с ключевыми возможностями препроцессоров в веб-платформе?

Математические выражения. Поддерживаются нативно в виде функции calc() в CSS. Более того, calc() поддерживается подавляющим большинством браузеров — проблемы есть только в IE9-, Android Browser 4.3- и Opera Mini. Есть плагин postcss-calc, заранее вычисляющий на этапе сборки всё, что можно.

Переменные. Реализованы в CSS в виде кастомных свойств, и имеют даже более мощную функциональность, чем в препроцессорах — они доступны в JS, а также могут менять свои значения динамически с помощью медиавыражений:

:root {
  --base-font-size: 16px;
}

@media (min-width: 1280px) {
  :root {
    --base-font-size: 18px;
  }
}

Кастомные свойства не поддерживаются в IE 11- и в большинстве мобильных браузеров. Базовая функциональность кастомных свойств реализуется плагином postcss-custom-properties — он на этапе сборки вычисляет все значения переменных и подставляет их в места использования, на выходе получается обычный CSS-файл с захардкоженными значениями. Динамическое переопределение значений переменных в медиавыражениях плагином не поддерживается.

Вложенность. Есть черновик Таба Аткинса с предложением по добавлению вложенности в CSS, но о полноценной спецификации и поддержке браузерами задумываться, судя по всему, сильно рано. И, на мой взгляд, вложенность в CSS должна поддерживаться только для медиавыражений, чтобы не дублировать селекторы, то есть предыдущий пример я бы хотел написать так:

:root {
  --base-font-size: 16px;

  @media (min-width: 1280px) {
    --base-font-size: 18px;
  }
}

Вложенность селекторов, на мой взгляд, не нужна, потому что она поощряет увеличение специфичности и затрудняет чтение кода.

Примеси (миксины). И здесь не обошлось без черновика Таба Аткинса. Его предложение заключалось в том, чтобы реиспользовать функциональность кастомных свойств и разрешить хранить в них не только значения, но и целые блок стилей, а затем применять эти блоки с помощью нового правила @apply:

.popup {
  --heading-style: {
    font-weight: bold;
    text-transform: uppercase;
  }
}

.popup__title {
  @apply(--heading-style);
}

На первый взгляд круто, но в таком решении есть проблемы из-за смешения обычных переменных, используемых через var(), и блоков правил, используемых через @apply() — Таб Аткинс подробно описал эти проблемы и сразу же предложил новое решение на основе Shadow DOM — псевдоэлемент ::part(). Из статьи с описанием проблем мне стало ясно, что у Таба изначально было немного другое видение предназначения примесей.

Таб заметил, что кастомные свойства не дают достаточно гибкости для стилизации веб-компонентов. В качестве примера рассмотрим попап — изолированный компонент, к внутренностям которого доступ мы не имеем. Кастомные свойства позволяют настроить внешний вид попапа, например задать цвет его подложки или размер заголовка. Ограничение в том, что внешний пользователь компонента не может доопределить или переопределить стили его частей — например, того же заголовка. Именно эту задачу Таб решил, предложив @apply, и, к счастью, пришёл к лучшему решению с ::part().

Вроде здорово, но есть проблема. Основная задача, которую решают примеси — не до- или переопределение стилей, а абстракция. Есть замечательная статья, объясняющая, что CSS — неэффективный язык, потому что в нём нет средств абстракции и комбинирования. Примеси в первую очередь полезны для абстрагирования стилей и комбинирования этих абстракций. Только примеси позволяют добиться настоящего разделения разметки и стилей:

Разделение разметки и стилей с помощью примесей

В общем, так как Таба Аткинса унесло в другую степь, перспектива появления примесей в CSS пока что туманна.

Итого

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

В простых проектах можно начать использовать только нативные возможности CSS с postcss-плагинами, выступающими в роли полифилов на этапе сборки. Такой подход я попробовал на одном из проектов. Нужно было сверстать небольшой лендинг. Все стили я написал в одном файле с использованием CSS-переменных, причём на время разработки даже не пришлось настраивать никакую сборку — было достаточно подключить в разметке исходный файл со стилями, Хром нативно поддерживает CSS-переменные. Сборка потребовалась только для вычисления переменных, простановки вендорных префиксов и минификации — это заняло всего 17 строчек кода.

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