Skip to main content

CSS препроцесори: SASS, SCSS та LESS

CSS препроцесори компілюють розширений синтаксис CSS зі змінними, вкладеністю, міксинами та функціями у звичайний CSS, який розуміє браузер.

Теорія

TL;DR

  • Препроцесор схожий на текстовий редактор з шаблонами: пишеш структуровані файли з багаторазовими блоками, компілюєш у plain CSS для браузера.
  • SCSS є строгою надмножиною CSS (будь-який валідний CSS є валідним SCSS); SASS прибирає фігурні дужки на користь відступів; LESS використовує @ для змінних і компілюється через JS.
  • Головний поділ: SCSS і LESS використовують фігурні дужки та крапки з комою, SASS - тільки відступи.
  • Правило вибору: SCSS для команд (міграція CSS без змін); LESS для динамічного JS-теміфікування; обидва можна пропустити, якщо вистачає CSS custom properties.
  • Bootstrap 5.3, Angular 18 та Bulma поставляються з SCSS.

Швидкий приклад

scss
// input.scss $blue: #007bff; .nav { background: $blue; .item { padding: 1rem; &:hover { background: lighten($blue, 10%); // обчислюється до #4dabf7 } } }
css
/* Скомпільований результат */ .nav { background: #007bff; } .nav .item { padding: 1rem; } .nav .item:hover { background: #4dabf7; }

Компілятор читає вкладену структуру, вирівнює її у стандартні селектори і підставляє всі змінні за один прохід.

Головна різниця

SCSS на 100% сумісний з CSS. Вставляєш будь-який існуючий stylesheet у .scss файл - він компілюється без жодних правок. Саме це робить міграцію простою. Оригінальний SASS замінює фігурні дужки та крапки з комою на відступи, що деяким розробникам подобається за мінімалістичний вигляд. LESS зовні схожий на SCSS, але використовує синтаксис @variable, і його JS-компілятор може давати несподівані результати у складних циклах на великих кодових базах. Більшість React-проектів, з якими я працював, зупинялись на SCSS щойно кількість компонентів переходила за 20 - просто тому, що наявний CSS вставляється без правок.

Коли що використовувати

  • Командний проект з наявним CSS: SCSS - мігруєш файли без змін.
  • Динамічне теміфікування через JS (як у Ant Design 5): LESS - його @var добре інтегрується з JS-конфігурацією.
  • Малий проект або прототип: пропускай препроцесори, використовуй CSS custom properties (--color: red;) - підтримуються всіма сучасними браузерами з 2016 року.
  • Angular або React/Vite: будь-який варіант - sass-loader або вбудована підтримка Vite впораються з компіляцією.
  • Ruby-стек: SASS - найглибша екосистема там.

Таблиця порівняння

ОсобливістьSASS (.sass)SCSS (.scss)LESS (.less)
СинтаксисТільки відступиФігурні дужки + крапки з комоюФігурні дужки + крапки з комою
Надмножина CSSНіТак (100%)Так (здебільшого)
Змінні$var$var@var
Міксини@mixin@mixin.mixin()
КомпіляціяRuby/NodeRuby/Node/DartJS/Node/браузер
Цикли@each, @for@each, @forGuards, рекурсивні міксини
Коли використовуватиRuby-розробникиКоманди, міграція CSSJS-проекти, динамічні змінні

Як працює компіляція

Ти пишеш .scss, .sass або .less файли. Компілятор (Dart Sass для SCSS/SASS починаючи з v1.77+, lessc для LESS v4.1+) розбирає вихідний код у AST (abstract syntax tree), підставляє всі змінні та виклики міксинів за один прохід і видає стандартний CSS. sass-loader для Webpack v14+ або вбудована підтримка Vite слідкують за файлами і перекомпіловують при збереженні, записуючи результат у /dist. Браузер ніколи не бачить вихідних файлів препроцесора.

Типові помилки

Вкладеність глибше 3-4 рівнів

scss
// Неправильно - компілюється у .a .b .c .d .e { color: red; } a { b { c { d { e { color: red; } } } } }

Глибока вкладеність роздуває скомпільований вивід і уповільнює пошук селекторів. Chrome DevTools позначає селектори глибше 6 рівнів. Тримай вкладеність до 3 рівнів або використовуй BEM: .nav__item--active { color: red; }.

Глобальне перевизначення змінних між файлами

scss
// _theme.scss $color: blue; // _component.scss імпортує _theme.scss, потім: $color: red; // перевизначає глобально

Часткова перекомпіляція у Webpack може пропустити залежності і дати непослідовний результат. Використовуй @use 'theme' as *; з Sass modules (v1.23+). Це виокремлює простір імен змінних і обчислює кожен файл лише один раз.

@import замість @use

@import скидає все у глобальний простір імен і повторно виконує файл при кожному імпорті. @use завантажує файл один раз і дає доступ через простір імен (theme.$primary). Dart Sass позначив @import як застарілий починаючи з v1.80.

Компіляція LESS у браузері

Стара практика підключення less.js безпосередньо у браузері не генерує source maps і ламається на сучасних міксинах. Завжди компілюй наперед за допомогою lessc або бандлера.

Ліниве обчислення у циклах LESS

less
// LESS - guard-based цикл може пропускати значення .loop(@n) when (@n > 0) { .w-@{n} { width: ~"@{n}%"; } .loop((@n - 5)); } .loop(25);

Для надійних циклів @for у SCSS поводиться передбачуваніше:

scss
@for $i from 1 through 5 { .w-#{$i * 5} { width: $i * 5%; } }

Де зустрічається у реальних проектах

  • Bootstrap 5.3: SCSS змінні та міксини для теміфікування ($primary, @mixin button-variant).
  • Ant Design 5: LESS для динамічного теміфікування з JS-конфігурацією змінних.
  • Angular 18: вбудована підтримка SCSS з вкладеністю для :host.
  • Bulma 1.0: чистий SCSS з CSS custom properties як fallback.
  • React/Vite: sass-loader плюс SCSS modules на рівні компонентів.

Питання на співбесіді

Q: Яка різниця між @mixin і @extend у SCSS?
A: @mixin копіює оголошені стилі у кожен селектор, який його підключає - це може роздути скомпільований вивід. @extend змушує кілька селекторів спільно використовувати один блок правил, що компактніше, але чутливо до порядку. Використовуй @extend для утилітарних класів на кшталт .btn; @mixin - коли потрібно передавати аргументи.

Q: Як обробляти vendor prefixes при роботі з препроцесорами?
A: Не безпосередньо через препроцесор. Стандартна практика - запускати Autoprefixer як PostCSS плагін після компіляції. Він читає налаштування browserslist і додає -webkit-, -moz- тощо автоматично.

Q: Яка різниця між @use і @import у Sass?
A: @import скидає все у глобальний простір імен і повторно виконує файл при кожному імпорті. @use завантажує файл один раз і виокремлює змінні через простір імен (theme.$primary). З'явився у Dart Sass v1.23; @import визнано застарілим з v1.80.

Q: Чим Dart Sass відрізняється від LibSass?
A: Dart Sass - офіційна реалізація (v1.77+ станом на 2024), точно слідує специфікації. LibSass - C++ порт, визнаний застарілим у 2021 через відставання від @use і @forward. Якщо ще використовуєш node-sass - мігруй на пакет sass.

Q (senior): У монорепо зі 100+ SCSS партіалів як уникнути каскадних перезбірок?
A: Використовуй @use замість @import, щоб кожен партіал обчислювався один раз. Поєднуй з Vite ?inline імпортами для HMR на рівні компонентів. Повільні збірки профілюй через sass --trace. Уникай glob-імпортів - вони ламають відстеження залежностей.

Приклади

Навбар з брейкпоінтами та темою

React-застосунок: навбар адаптується до розміру екрана і бере кольори зі спільного файлу теми.

scss
// styles/navbar.scss $primary: #0d6efd; $breakpoint-md: 768px; @mixin flex-center { display: flex; align-items: center; } .navbar { @include flex-center; padding: 1rem; background: $primary; @media (min-width: $breakpoint-md) { padding: 1.5rem; } .logo { font-size: 1.5rem; } &__link { color: white; text-decoration: none; &:hover { color: lighten($primary, 20%); } } }
css
/* Скомпільований результат (спрощено) */ .navbar { display: flex; align-items: center; padding: 1rem; background: #0d6efd; } @media (min-width: 768px) { .navbar { padding: 1.5rem; } } .navbar .logo { font-size: 1.5rem; } .navbar__link { color: white; text-decoration: none; } .navbar__link:hover { color: #6ea8fe; }

Міксин бере на себе flex-налаштування, $primary оголошено один раз, а медіа-запит знаходиться поруч з компонентом, якого він стосується, а не в окремому файлі брейкпоінтів.

Sass modules з @use

Цей підхід замінює @import у будь-якому Dart Sass проекті (v1.23+) і є поточною рекомендованою практикою.

scss
// _theme.scss $primary: #0d6efd; $danger: #dc3545; $font-size-base: 1rem;
scss
// button.scss @use 'theme'; .btn { font-size: theme.$font-size-base; background: theme.$primary; &--danger { background: theme.$danger; } }

Префікс theme. одразу показує, звідки береться кожна змінна. Не доведеться шукати по 50 партіалах, який файл востаннє встановив $primary.

Цикли: SCSS проти LESS

Генерація utility-класів для відступів - типове завдання. Ось як кожен препроцесор з ним справляється.

scss
// SCSS - передбачуваний @for цикл @for $i from 1 through 5 { .mt-#{$i} { margin-top: $i * 0.25rem; // 0.25, 0.5, 0.75, 1, 1.25rem } }
less
// LESS - рекурсивний міксин з guard (аналогічний результат) .spacing-loop(@n) when (@n > 0) { .mt-@{n} { margin-top: (@n * 0.25rem); } .spacing-loop((@n - 1)); } .spacing-loop(5);

Обидва варіанти генерують однакові п'ять класів. SCSS версія читається як звичайний цикл; LESS версія вимагає розуміння рекурсії через guards, перш ніж безпечно її змінювати.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?