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

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