Architektura SASS: od spaghetti do design systemu
SASS daje ci moc; architektura jest tym, co powstrzymuje cię przed jej nadużywaniem. Deep-dive o strukturyzowaniu arkuszy stylów tak, by się skalowały — tokeny projektowe, wzorzec 7-1, BEM, zarządzanie specyficznością i myślenie komponentami zamiast stronami.
Jakiś czas temu pisałem o funkcjach, które daje SASS — zmienne, miksiny,
zagnieżdżanie. Ten wpis jest o rzeczy, która decyduje, czy te funkcje pomagają, czy
szkodzą: architekturze. SASS bez planu produkuje coś gorszego niż czysty CSS —
plątaninę głęboko zagnieżdżonych reguł, wojny !important i czterotysięczną linię
arkusza, którego nikt nie śmie tknąć. SASS z planem produkuje utrzymywalny design
system, który zespół może rozszerzać latami. Różnicą nie jest narzędzie; jest to, jak
je organizujesz. Oto struktura, do której doszliśmy.
Cel: system, nie sterta stron
Zmianą myślową, która naprawia większość problemów z CSS, jest przestać myśleć
stronami, a zacząć myśleć komponentami. Stary sposób pisze
.homepage .sidebar .promo h2 { ... } — style ograniczone do miejsca, gdzie się
pojawiają, co oznacza, że każda nowa strona rozwiązuje te same problemy od nowa, a
arkusz rośnie liniowo z witryną. Sposób komponentowy pisze .card raz i reużywa go
na stronie głównej, stronie produktu i dashboardzie. Twój CSS przestaje być opisem
stron, a staje się biblioteką reużywalnych części — design systemem. Wszystko
poniżej służy tej zmianie.
Tokeny projektowe: jedno źródło prawdy
Fundamentem jest warstwa zmiennych nazywających twoje decyzje projektowe — kolory, odstępy, skalę typografii, punkty łamania — w jednym miejscu, do którego wszystko inne się odnosi:
// _tokens.scss
$color-brand: #b00020;
$color-ink: #1a1a1a;
$color-muted: #6b7280;
$space-unit: 0.5rem;
$space-1: $space-unit; // 0.5rem
$space-2: $space-unit * 2; // 1rem
$space-3: $space-unit * 3; // 1.5rem
$font-scale: 1.25;
$breakpoint-m: 48rem;
$breakpoint-l: 64rem;
To tokeny projektowe — słownictwo twojego systemu. Żaden komponent nie powinien
nigdy wpisywać na sztywno #b00020 ani 17px; odnosi się do $color-brand i tokenu
odstępu. Zysk jest ogromny: rebranding to edycja jednego pliku, twoje odstępy
pozostają na spójnym rytmie, bo wszystko jest wielokrotnością jednej jednostki, a
projekt staje się spójny z konstrukcji, a nie dzięki temu, że każdy pamięta właściwy
kod hex. Pochodna warstwa tokenów semantycznych ($color-danger: $color-brand) dodaje
poziom znaczenia na wierzchu, więc komponenty odnoszą się do intencji, a nie surowych
wartości.
Wzorzec 7-1: miejsce na wszystko
Gdy arkusz rośnie, potrzebujesz struktury folderów, a szeroko przyjęty wzorzec „7-1” to sensowny domyślny wybór — partiale pogrupowane w foldery, wszystkie zebrane przez jeden manifest:
styles/
├── abstracts/ _tokens.scss, _mixins.scss, _functions.scss
├── base/ _reset.scss, _typography.scss
├── components/ _card.scss, _button.scss, _form.scss
├── layout/ _header.scss, _footer.scss, _grid.scss
├── pages/ _home.scss (tylko nadpisania per strona)
├── themes/ _dark.scss
└── application.scss (importuje wszystko, w kolejności)
Kolejność importów ma znaczenie i podąża za rosnącą specyficznością: abstracts (bez
wyjścia, tylko narzędzia), potem base (domyślne dla elementów), potem layout i
komponenty, potem rzadkie nadpisanie per strona. Większość twojego realnego CSS żyje
w components/ — jeden plik na komponent — a pages/ pozostaje niemal pusty, bo
dobrze zbudowany system potrzebuje stylów per strona tylko na krawędziach. Gdy
pages/ zaczyna się zapełniać, to zapach, że znów stylujesz stronami zamiast budować
komponenty.
BEM: nazewnictwo trzymające specyficzność płaską
Najtrudniejszym problemem w CSS przy skali jest specyficzność — kaskada oznacza,
że bardziej specyficzny selektor wygrywa, więc gdy reguły się kumulują, kończysz
walcząc z własnym arkuszem, eskalując selektory i ostatecznie sięgając po
!important. BEM (Block, Element, Modifier) pokonuje to konwencją nazewniczą
trzymającą niemal każdy selektor pojedynczą, płaską klasą:
// Block: komponent. Element: jego część. Modifier: wariant.
.card { /* ... */ }
.card__title { /* ... */ }
.card__body { /* ... */ }
.card--featured { /* ... */ } // wariant całej karty
.card.card--featured
%h2.card__title= title
.card__body= body
Ponieważ każda reguła to jedna klasa, każdy selektor ma tę samą niską specyficzność,
więc kolejność i nadpisywanie stają się przewidywalne — klasa modyfikatora po prostu
dodaje się do klasy bazowej, bez bitwy o specyficzność. BEM wygląda gadatliwie, a
pierwszą reakcją zawsze jest „te nazwy klas są brzydkie”, ale kupuje najcenniejszą
właściwość, jaką może mieć duży arkusz: możesz przeczytać nazwę klasy i wiedzieć
dokładnie, co styluje i gdzie żyje, i możesz ją nadpisać bez walki. W połączeniu z &
z SASS źródło pozostaje schludnie zgrupowane, a wyjście płaskie:
.card {
padding: $space-3;
&__title { color: $color-ink; }
&__body { color: $color-muted; }
&--featured { border: 2px solid $color-brand; }
}
Reguły trzymające to zdrowym
Kilka dyscyplin trzyma całość razem w czasie:
- Trzymaj zagnieżdżanie płytkie. Zagnieżdżaj dla wzorca modyfikatora
&, nigdy by odwzorować DOM. Selektor głębszy niż dwa poziomy to przyszły problem specyficzności; spłaszcz go, nadając elementowi własną klasę BEM. - Żadnych magicznych liczb, żadnych wartości na sztywno. Każdy kolor, odstęp i
punkt łamania to token. Jeśli łapiesz się na wpisywaniu
margin-top: 23px, zatrzymaj się — albo należy to do skali, albo projekt potrzebuje decyzji, a nie jednorazowej liczby. - Jeden komponent, jeden plik, jedna odpowiedzialność. Plik komponentu styluje swój blok i nic więcej. Nie powinien sięgać i stylować sąsiadów.
- Wolej miksiny od
@extend. Jak omówiono wcześniej, grupowanie selektorów przez@extendma zaskakujące efekty specyficzności; miksiny kopiują deklaracje przewidywalnie. Rezerwuj@extend(z placeholderami) dla naprawdę statycznych współdzielonych kawałków. - Dokumentuj system. Żywy style guide — strona renderująca każdy komponent w jego wariantach — zamienia twój CSS z wiedzy plemiennej w przeglądalny katalog i jest artefaktem, który czyni go prawdziwym design systemem, a nie tylko dobrze zorganizowanymi plikami.
Werdykt
Funkcje SASS są łatwe; architektura jest tym, co czyni je opłacalnymi. Zakotwicz wszystko w tokenach projektowych, zorganizuj komponenty w jasną strukturę folderów, trzymaj selektory płaskie i przewidywalne dzięki BEM i myśl reużywalnymi komponentami zamiast stronami. Zrób to, a twój arkusz przestaje rosnąć liniowo z witryną i zaczyna zachowywać się jak system — taki, gdzie dodanie funkcji oznacza komponowanie istniejących części, a nie doczepianie kolejnego tysiąca linii. To linia między CSS jako obciążeniem a CSS jako aktywem, a w 2015, gdy design systemy stają się sposobem pracy poważnych zespołów, to dokładnie dyscyplina warta budowania teraz.