HAML i SASS: markup i style, które pozostają DRY
Dwa narzędzia, po które sięgamy w każdym projekcie Rails. Jak HAML czyni szablony czytelnymi, a SASS utrzymuje arkusze stylów w ryzach — ze wzorcami, które działają, i pułapkami (nadmierne zagnieżdżanie, @extend), które nie.
Dwa narzędzia siedzą na froncie niemal każdego projektu Rails, który budujemy: HAML do szablonów i SASS do arkuszy stylów. Żadne nie jest niezbędne — ERB i czysty CSS działają — ale oba zwracają się w ciągu tygodnia, usuwając kategorię szumu i powtórzeń, które inaczej narastają, aż twoje widoki i arkusze stylów stają się tą częścią bazy kodu, której nikt nie chce dotykać. Oto jak ich używamy i jakich błędów nauczyliśmy się unikać.
HAML: markup znika
ERB to HTML z wmieszanym Ruby, co oznacza, że piszesz — i czytasz — każdy nawias
kątowy i każdy tag zamykający. HAML wyrzuca to wszystko. Wcięcie definiuje
zagnieżdżenie, więc nie ma tagów zamykających do pomylenia; %tag deklaruje
element, a skróty w stylu CSS .class i #id sprawiają, że typowy przypadek czyta
się niemal jak selektory, które później wystylujesz:
.card
.card__header
%h2= post.title
%span.card__meta= post.published_on.to_s(:long)
.card__body
= simple_format(post.body)
- if post.comments.any?
%footer.card__footer
= pluralize(post.comments.count, "komentarz")
Odpowiednik w ERB jest o jedną trzecią dłuższy i znacznie bardziej zaszumiony, a
łatwiej go pomylić — brakujący </div> w głęboko zagnieżdżonym ERB to klasyczny
zabójca popołudnia, który po prostu nie może się zdarzyć w HAML, bo nie ma tagów
zamykających. Dyscyplina, którą HAML narzuca, jest też realna: ponieważ wcięcie
jest strukturą, niechlujne zagnieżdżenie staje się natychmiast widoczne, a twoje
szablony niemal siłą wychodzą płytsze i lepiej rozłożone.
Kilka rzeczy wartych wiedzy, gdy miniesz podstawy:
=wypisuje wyrażenie,-uruchamia Ruby bez wypisywania.%h2= post.titledrukuje;- if ...steruje przepływem.- Atrybuty to hash Ruby:
%a{ href: post_path(post), class: "btn" }, a hash współgra z helperami Rails i atrybutami warunkowymi. - Filtry czysto obsługują osadzoną treść —
:javascript,:css,:markdown— więc nie escapujesz się przez bloki inline. - HAML nagradza wydzielanie partiali i helperów, bo szablon będący samą strukturą sprawia, że zabłąkana grudka logiki rzuca się w oczy. Gdy widok zaczyna kiełkować warunkami, to HAML mówi ci, byś przeniósł logikę do helpera albo presentera.
Zarzut, który ludzie podnoszą, to wrażliwość na białe znaki, i jest słuszny: spartolone wcięcie to realny błąd. Ale to błąd, który widzisz od razu, w miejscu, gdzie się dzieje, a nie zagadka zniekształconego DOM, którą debugujesz w przeglądarce. Tę wymianę bierzemy za każdym razem.
SASS: arkusze stylów, które się skalują
CSS nie pozwala abstrahować. Nie nazwiesz koloru, nie podzielisz bloku deklaracji, nie zagnieździsz powiązanych reguł — więc rosnący arkusz staje się długą płaską listą z tym samym kodem hex skopiowanym czterdzieści razy i selektorami powtórzonymi dla każdego wariantu. SASS (używamy składni SCSS, będącej nadzbiorem CSS, więc każdy poprawny CSS jest poprawnym SCSS) daje brakujące narzędzia: zmienne, zagnieżdżanie, miksiny, dziedziczenie i partiale.
Zmienne i partiale: jedno źródło prawdy
// _variables.scss
$brand: #b00020;
$ink: #1a1a1a;
$space: 1rem;
$breakpoint-m: 48rem;
// application.scss
@import "variables";
@import "components/card";
@import "components/buttons";
Nazwanie palety i odstępów raz, w partialu, który wszystko importuje, to rzecz o
najwyższej dźwigni, jaką daje SASS. Rebranding z jednego pliku; zmiana rytmu
odstępów w jednym miejscu. Partiale @import (nazwy plików z prefiksem _)
pozwalają też podzielić monolityczny arkusz na plik na komponent, dzięki czemu
utrzymujesz go żeglownym, gdy rośnie.
Miksiny do reużycia, z argumentami
Miksin to reużywalny blok deklaracji, opcjonalnie parametryzowany — idealny do zupy prefiksów producentów z 2014 i do punktów łamania responsywności:
@mixin respond-to($min) {
@media (min-width: $min) { @content; }
}
.card {
padding: $space;
@include respond-to($breakpoint-m) {
padding: $space * 2;
}
}
Blok @content jest tym, co sprawia, że responsywne miksiny wskakują na miejsce:
nazywasz punkty łamania raz i piszesz media queries, które wszędzie indziej czyta
się jak po angielsku.
Zagnieżdżanie: użyteczne, dopóki nie jest
Zagnieżdżanie to funkcja, po którą ludzie sięgają pierwszą i nadużywają najmocniej. Odzwierciedla strukturę markupu i trzyma powiązane reguły razem:
.card {
&__header { font-weight: 600; }
&__body { color: $ink; }
&:hover { box-shadow: 0 2px 8px rgba(0,0,0,.1); }
}
& (selektor rodzica) to dobra część — w połączeniu z konwencją nazewniczą jak BEM
(block__element--modifier) produkuje płaskie selektory o niskiej specyficzności,
trzymając źródło schludnie zgrupowane. Pułapką jest zagnieżdżanie podążające za
drzewem DOM kilka poziomów w głąb:
// NIE: kompiluje się do .sidebar .widget ul li a — kruche i nadmiernie specyficzne
.sidebar { .widget { ul { li { a { color: $brand; } } } } }
To generuje głęboko potomne, mocno specyficzne selektory, które są kruche (psują
się, gdy markup się przesunie), trudne do nadpisania (kończysz w wojnie ze
specyficznością, sięgając po !important) i wolne do czytania. Nasza zasada:
zagnieżdżaj dla wzorca modyfikatora &, nie po to, by odwzorować HTML. Jeśli
selektor jest głębszy niż dwa–trzy poziomy, spłaszcz go, zwykle nadając elementowi
własną klasę.
@extend vs miksiny: wybierz miksin
SASS oferuje dwa sposoby współdzielenia deklaracji, a wybór liczy się bardziej, niż
wygląda. @extend sprawia, że jeden selektor dziedziczy reguły innego, grupując
selektory w wyniku; miksin kopiuje deklaracje. @extend wygląda elegancko i
produkuje mniej CSS, ale ma zaskakujące efekty: może przestawić twój wynik,
wyciągnąć selektory przez kaskadę w sposób zmieniający, która reguła wygrywa, i
tworzyć rozległe listy przez przecinki, które trudno przewidzieć. Po ugryzieniu przez
niespodzianki specyficzności wywołane przez @extend, naszym domyślnym wyborem jest
miksin (albo selektor placeholder %foo z @extend tylko dla naprawdę
współdzielonych, statycznych kawałków). Kilka dodatkowych bajtów jest warte
przewidywalności.
Działają razem
Powodem, dla którego te dwa narzędzia tak dobrze się parują w projekcie Rails, jest
to, że pchają w tym samym kierunku: struktura ponad szum. Element .card__header w
HAML siedzi wprost nad regułą .card { &__header { ... } } w SASS, nazwy się
zgadzają, a asset pipeline kompiluje oba bez ceremonii. Komponent to partial HAML
plus partial SCSS, które dzielą nazwę i korzeń BEM — jednostka, którą czytasz,
przenosisz i ogarniasz jako całość.
Żadne z narzędzi nie jest magią i oba da się nadużyć — HAML w sprytne jednolinijkowce,
których nikt nie przeczyta, SASS w piramidy zagnieżdżeń i spaghetti @extend.
Używane z odrobiną dyscypliny usuwają jednak harówkę z frontu i zostawiają ci
szablony i arkusze stylów, które wciąż przyjemnie otworzyć rok później. W naszych
projektach to cała gra: kod, którego nie boisz się zmieniać, to kod, który dalej
trafia na produkcję.