Кратко
СкопированоПрепроцессор — это инструмент, который расширяет стандартные возможности CSS с помощью новых синтаксических конструкций, таких как миксины, циклы, переменные и другие. Препроцессоры так называются потому, что принимают данные (ваш код в формате stylus, sass, scss или less) и потом компилируют (преобразуют) их в обычный CSS-код. Этим они отличаются от постпроцессоров, которые улучшают именно CSS. К примеру, подставляют вендорные префиксы.
Основные препроцессоры — это Sass, Less и Stylus. У них различные синтаксисы, а Sass поддерживает два: sass и SCSS.
С чего начать?
СкопированоУстановка препроцессоров происходит, как правило, через npm или pnpm. Потом вы дописываете конфигурационные файлы (конфиги) по инструкции к нужному препроцессору и настраиваете под свой проект.
Хорошая поддержка препроцессоров у сборщика Vite. У него есть встроенная поддержка файлов .scss, .sass, .less, .styl и .stylus. Для этого не нужно устанавливать специфичные для Vite плагины, но сам препроцессор должен быть установлен через команду npm
. Установка препроцессоров в связке с Webpack сложнее, лучше обратиться к документации сборщика.
Зачем знать о препроцессорах?
СкопированоСейчас можно обойтись без препроцессоров, так как у CSS больше возможностей, чем раньше 😇 Хотя препроцессоры постепенно теряют популярность, вы всё равно найдёте проекты, в которых не используют чистый CSS. Так что, при очередной смене работы, можете столкнуться с проектом, написанным на Less или SCSS.
Ещё одна причина — в CSS пока ещё нет поддержки миксинов, с помощью которых можно добавлять адаптивность сайту, вынося отдельные стили и «примешивая» их потом. Переписывание большого количества стилей с ними отнимет много времени, из-за чего рефакторинг может затянуться или вообще быть отложен до лучших времён.
Ситуация с CSS-функциями для работы с цветами тоже пока нестабильная: их не так много и у них пока нет хорошей браузерной поддержки. Так что без препроцессоров с цветами по-прежнему сложно работать.
Немного истории
СкопированоПрепроцессоры появились по нескольким причинам. Раньше CSS не был таким развитым и гибким, как сейчас. В нём не было переменных, операторов, циклов, условий, нельзя было писать вложенные конструкции. Одновременно была проблема в довольно большом расхождении между разными браузерами, особенно между Safari и Internet Explorer. Для того чтобы сгладить разницу в отображении интерфейсов сайтов в браузерах, использовали препроцессорные миксины. Примерно в начале 2010-х годов появилось много новых устройств, помимо компьютеров и ноутбуков, и сайты на них тоже должны были выглядеть хорошо. Экраны также отличались друг от друга по техническим характеристикам, например, по размерам и особенностям передачи цветов, так что появилась необходимость работать со сложными цветами. Все эти проблемы решали препроцессоры, а за счёт повторного использования переменных и специфического синтаксиса, код был изящнее и проще для чтения.
Вы можете спросить, а почему вообще важно количество строк в файлах для стилей? Раньше, где-то между 2013 и 2019, React-компоненты писали на громоздких классах с большим количеством свойств. Это приводило к длинному полотну из CSS-классов и правил, которое хотелось сократить для удобства чтения и для лучшей поддержки. Постепенно появилась тенденция к декомпозиции (разбиению) компонентов на более мелкие, чтобы улучшить их переиспользуемость и ускорить навигацию по стилям. Это вызвало запрос на минималистичный CSS. Сейчас декомпозиция считается лучшей практикой, но большие запутанные компоненты всё ещё встречаются в легаси-коде.
С тех пор многое изменилось. В CSS появились кастомные свойства, вложенность и другие нововведения. Также браузерные команды начали совместно работать над исправлением проблем совместимости браузеров (Interop), а Internet Explorer вообще перестал поддерживаться.
Синтаксис и дополнительные возможности
СкопированоLess и SCSS похожи синтаксисом на современный CSS, различий немного. К примеру, если не использовать дополнительные фичи SCSS, трудно найти отличия между ним и CSS:
@media screen and (max-width: 600px) { .error { width: 100%; }}.error { width: 300px;}
@media screen and (max-width: 600px) { .error { width: 100%; } } .error { width: 300px; }
Этот же код, но на Less:
@media { screen { and { (max-width { &:600px) { .error { width: 100%; } } } }}.error { width: 300px;}
@media { screen { and { (max-width { &:600px) { .error { width: 100%; } } } } } .error { width: 300px; }
Синтаксисы Sass и Stylus напоминают синтаксис языка программирования Python. В них тоже можно опускать фигурные скобки и знаки :
(двоеточие) и ;
(точка с запятой), поэтому важны правильные отступы. Если отступы в Stylus и Sass расставлены неправильно, препроцессорные стили не скомпилируются в CSS, а вы получите консольную ошибку.
Простой пример синтаксиса Sass:
@media screen and (max-width: 600px) .error width: 100%.error width: 300px
@media screen and (max-width: 600px) .error width: 100% .error width: 300px
Больше подробностей о тонкостях синтаксиса препроцессоров найдёте в официальной документации или в шпаргалках, например, Stylus cheatsheet. Можете также почитать обзорную статью про разные препроцессоры и их фичи, которых довольно много.
Дополнительные возможности препроцессоров часто называют синтаксическим «сахаром» (syntactic sugar). Они не изменяют существующие возможности CSS и нужны для повышения читаемости кода и ускорения его написания.
@extend
СкопированоВо всех препроцессорах есть интересная фича — @extend
. С её помощью расширяют классы за счёт других. С одной стороны, это удобно и сокращает количество написанного кода, с другой, если неправильно использовать фичу, это приведёт к хаосу в организации кода. Станет трудно разобраться, какие CSS-стили получатся в итоге. Дело в том, что из-за @extend
возникает неявная связанность между оригинальными классами и теми классами, которые их расширяют. Изменяя стили из первоначального класса, мы автоматически вносим изменения и во все другие, которые содержат расширения. Так что, при большом количестве расширений, стили могут измениться сразу в нескольких местах. Важно следить за тем, есть ли у родительских стилей зависимость от дочерних. Таким образом, при использовании @extend
стоит сначала подумать, будет ли это хорошим решением для конкретного проекта, и действительно ли оно упрощает вашу работу.
Давайте посмотрим на неудачный пример использования @extend
. Предположим, у нас есть класс .button
со свойствами для ширины, высоты, цвета фона и какой-то анимации. Этот класс расширяет другие в разных местах, создавая связь между ними. Теперь, внося изменения в первоначальный класс .button
, нужно следить за стилями у других классов, которые расширяются за его счёт. А что, если один класс расширяют несколько, и в них повторяются одни и те же свойства и их значения? Или расширений слишком много? В таких случаях сложно понять, какие стили применятся к элементам интерфейса в итоге, и в такой код трудно вносить новые изменения.
С другой стороны, если @extend
содержит не так много стилей и расширения не навешиваются хаотично на один и тот же класс, то это не запутывает связи между ними. Это хороший пример использования фичи.
Рассмотрим @extend
на примере Stylus. Расширим стили класса .warning
за счёт стилей .message
:
.message padding: 10px border: 1px solid #EEEEEE.warning @extend .message color: #E2E21E
.message padding: 10px border: 1px solid #EEEEEE .warning @extend .message color: #E2E21E
Этот код скомпилируется в такой CSS:
.message,.warning { padding: 10px; border: 1px solid #EEEEEE;}.warning { color: #E2E21E;}
.message, .warning { padding: 10px; border: 1px solid #EEEEEE; } .warning { color: #E2E21E; }
Обратите внимание, что конвертеры плохо справляются с расширением классов в препроцессорах. Лучше периодически заглядывать в официальную документацию, чтобы понять принцип работы этой фичи. Например, в документацию к Stylus.
А вот так @extend
выглядит в Sass:
.error border: 2px #FFA500 background-color: #DDDDDD &--serious @extend .error border-width: 5px
.error border: 2px #FFA500 background-color: #DDDDDD &--serious @extend .error border-width: 5px
Миксины
СкопированоМиксины (mixins) похожи на функции в языках программирования. В них передают аргументы, задают им значения по умолчанию и так далее. Миксины помогают группировать нужные стили и повторно использовать их в нескольких местах кода или в разных CSS-файлах. Это пригодится, когда в проекте ну очень много стилей и строк кода, сложная логика вычисления значений для CSS-свойств, а ещё когда хотите подстраховаться и не споткнуться об разные особенности отрисовки стилей в браузерах.
Попробуем при помощи SCSS-миксина сократить время на написание медиавыражений для минимальной ширины экрана, которую поддерживаем на сайте. Вот нужные нам брейкпоинты (контрольные точки):
$breakpoints: ( xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px);
$breakpoints: ( xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, xxl: 1400px );
Теперь напишем миксин для @media
:
@mixin media($widthName) { $width: map-get($breakpoints, $widthName); @media (width >= $width) { @content; }}
@mixin media($widthName) { $width: map-get($breakpoints, $widthName); @media (width >= $width) { @content; } }
В нужном месте используем его с помощью специальной директивы @include
:
.card { padding: 8px; @include media(md) { padding: 16px; }}
.card { padding: 8px; @include media(md) { padding: 16px; } }
Скомпилированный CSS выглядит таким образом:
.card { padding: 8px;}@media(width >= 768px) { .card { padding: 16px; }}
.card { padding: 8px; } @media(width >= 768px) { .card { padding: 16px; } }
Давайте теперь напишем миксин для класса со стилями фокуса в Less. Предположим, во всех случаях нам подходит чёрный цвет (#000000
) обводки. В зависимости от размеров и формы элементов, обводка бывает разной толщины (outline
) и находится на разном расстоянии от краёв элемента (outline
).
.focus(@width; @offset; @color: #000000) { outline-width: @width; outline-offset: @offset; outline-color: @color;}
.focus(@width; @offset; @color: #000000) { outline-width: @width; outline-offset: @offset; outline-color: @color; }
Применим миксин к кнопкам и ссылкам и зададим нужные стили обводке:
.button:focus-visible { .focus(2px; 2px);}.link:focus-visible { .focus(3px; 3px);}
.button:focus-visible { .focus(2px; 2px); } .link:focus-visible { .focus(3px; 3px); }
Так будет выглядеть получившийся CSS:
.button:focus-visible { outline-width: 2px; outline-offset: 2px; outline-color: #000000;}.link:focus-visible { outline-width: 3px; outline-offset: 3px; outline-color: #000000;}
.button:focus-visible { outline-width: 2px; outline-offset: 2px; outline-color: #000000; } .link:focus-visible { outline-width: 3px; outline-offset: 3px; outline-color: #000000; }
Возможности Stylus
СкопированоОбсудим другие удобства препроцессоров на примере Stylus. К примеру, вы можете выносить медиавыражения в отдельные импорты. Это делает код короче и проще при правильной организации.
Также можно использовать сокращённую запись значений через ссылки на ранее написанные значения свойств. Например, высота блока равна значению ранее заданной ширины:
.button width 200px height @width
.button width 200px height @width
Получится такой CSS:
.button { width: 200px; height: 200px;}
.button { width: 200px; height: 200px; }
Другой способ сокращённой записи — шорткат @block
. С его помощью блок кода определяют в переменной, которую можно вызвать, передать как аргумент или повторно использовать другим способом. Есть несколько способов написания:
foo = width: 20px height: 20pxfoo = @block { width: 20px height: 20px}
foo = width: 20px height: 20px foo = @block { width: 20px height: 20px }
И, если нужно отрисовать блок foo
, можно вызвать переменную внутри интерполяции:
.icon {foo}
.icon {foo}
Код из предыдущего примера скомпилируется в такой CSS:
.icon { width: 20px; height: 20px;}
.icon { width: 20px; height: 20px; }
Получившийся CSS-код можно проверить в конвертере Stylus to CSS.
Возможности Sass
СкопированоПрепроцессор Sass в любом из своих синтаксисов позволяет вкладывать одни селекторы в другие. Это называют вложенностью стилей (nesting).
.button { color: #000000; background-color: #FFFFFF; .button-icon { width: 10px; height: 10px; }}
.button { color: #000000; background-color: #FFFFFF; .button-icon { width: 10px; height: 10px; } }
Скомпилированный CSS:
.button { color: #000000; background-color: #FFFFFF;}.button .button-icon { width: 10px; height: 10px;}
.button { color: #000000; background-color: #FFFFFF; } .button .button-icon { width: 10px; height: 10px; }
Также можно использовать оператор &
, чтобы связать родительский селектор с псевдоклассом. К примеру, у нас есть кнопка и стили при наведении на неё:
.button { color: #FFFFFF; background-color: #000000; &:hover { color: #000000; background-color: #FFFFFF; }}
.button { color: #FFFFFF; background-color: #000000; &:hover { color: #000000; background-color: #FFFFFF; } }
На выходе получится такой CSS-код:
.button { color: #FFFFFF; background-color: #000000;}.button:hover { color: #000000; background-color: #FFFFFF;}
.button { color: #FFFFFF; background-color: #000000; } .button:hover { color: #000000; background-color: #FFFFFF; }
Оператор &
удобно использовать при именовании классов по методологии БЭМ (Блок, Элемент, Модификатор). В этом примере объединяем блок .button
с классом элемента .button
и с классом модификатора .button
:
.button { color: #FFFFFF; background-color: #000000; &_inactive { opacity: 0.7; } &__icon { width: 20px; height: 20px; }}
.button { color: #FFFFFF; background-color: #000000; &_inactive { opacity: 0.7; } &__icon { width: 20px; height: 20px; } }
Этот код скомпилируется в CSS таким образом:
.button { color: #FFFFFF; background-color: #000000;}.button_inactive { opacity: 0.7;}.button__icon { width: 20px; height: 20px;}
.button { color: #FFFFFF; background-color: #000000; } .button_inactive { opacity: 0.7; } .button__icon { width: 20px; height: 20px; }
Когда используете вложенность стилей, не злоупотребляйте этой возможностью. Если вложить друг в друга много классов, такой код будет трудно читать и обновлять (особенно после отпуска). Обычно рекомендуют не создавать больше двух уровней вложенности.
Посмотрим на плохой пример и попробуем мысленно скомпилировать CSS 😅
.navigation { .link { padding: 10px 20px; &:hover { background: #000000; color: #FFFFFF; } &__icon { width: 20px 20px; margin-right: 5px; &_big { width: 60px 60px; &_secondary { color: #DDDDDD; } } } }}
.navigation { .link { padding: 10px 20px; &:hover { background: #000000; color: #FFFFFF; } &__icon { width: 20px 20px; margin-right: 5px; &_big { width: 60px 60px; &_secondary { color: #DDDDDD; } } } } }
Теперь сверьте свой мысленный результат компиляции с реальным:
.navigation .link { padding: 10px 20px;}.navigation .link:hover { background: #000000; color: #FFFFFF;}.navigation .link__icon { width: 20px 20px; margin-right: 5px;}.navigation .link__icon_big { width: 60px 60px;}.navigation .link__icon_big_secondary { color: #DDDDDD;}
.navigation .link { padding: 10px 20px; } .navigation .link:hover { background: #000000; color: #FFFFFF; } .navigation .link__icon { width: 20px 20px; margin-right: 5px; } .navigation .link__icon_big { width: 60px 60px; } .navigation .link__icon_big_secondary { color: #DDDDDD; }
Поиграть с разными возможностями SCSS и сравнить со вторым синтаксисом препроцессора Sass можно в Sass Playground.
Возможности Less
СкопированоПопробуем поработать с препроцессором Less. Для класса .block
добавляем к части от ранее заданной ширины дополнительную величину. Примерно для того же сейчас используется CSS-функция calc
. Для другого класса .block1
задаём переменным значения, а потом, с помощью математических вычислений, умножаем их между собой и делим на 3. Это тоже работает как calc
.
.block { width: 10% + 30px;}@w1 = 10%;@w2 = 30px;.block1 { width: (@w1 * @w2) / 3;}
.block { width: 10% + 30px; } @w1 = 10%; @w2 = 30px; .block1 { width: (@w1 * @w2) / 3; }
Благодаря возможностям Less и других препроцессоров можно задавать значения переменным и оперировать ими как математическими.
Мы уже упоминали про переменные в препроцессорах. С помощью них удобно хранить повторяющиеся значения свойств в одном месте. В Less для переменных используют знак @
(коммерческое at). При этом можно ссылаться в одних переменных на другие. В этом примере храним в переменных значения для свойств width
и height
. Внутри переменной @height
переиспользуем переменную @width
и прибавляем к её значению дополнительные 10 пикселей:
@width: 100px;@height: @width + 10px;.card { width: @width; height: @height;}
@width: 100px; @height: @width + 10px; .card { width: @width; height: @height; }
На практике
СкопированоРазобраться в препроцессорах поможет официальная документация. Ещё есть много конвертеров, которые конвертируют код из одних препроцессоров в другие, в CSS и наоборот.
Примеры конвертеров:
Можно найти конвертеры и в виде npm- и pnpm-пакетов, например, npm-конвертер для Stylus. Однако у пакетов может быть немного скачиваний, и их сложнее использовать, чем просто онлайн-конвертер. Сложность в основном в том, что любой новый пакет создаёт свои зависимости, включая препроцессоры. Чем больше зависимостей, тем больше вероятность, что появятся сложности в версионировании.
Учитывайте, что конвертеры работают не так хорошо. Переменные в них нужно задавать отдельно, они не обрабатывают все фичи препроцессоров и расширения классов. Их можно использовать для общего понимания, но лучше всегда перепроверять итоговый код. На скомпилированный CSS можно посмотреть в браузерных инструментах разработчика во вкладке со стилями.