Skip to content
← Wszystkie wpisy
5 min czytania Dawid Skłodowski

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 @extend ma 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.