Parte III — Il front-end JavaScript · Capitolo 11

SASS e SCSS per i fogli di stile

~40 min di lettura3 widget interattivi2 tavole

In questo capitolo

  1. CSS dal 1996 e le sue limitazioni
  2. La soluzione: preprocessor e transpiler
  3. SASS: definizione e installazione
  4. SASS contro SCSS e la linea di comando
  5. Nested rules e parent selector
  6. Variabili e tipi supportati
  7. Operazioni e interpolazione
  8. DRY: @import, @extend e placeholder
  9. Direttive di controllo
  10. Mixin e funzioni
  11. Verifica le tue conoscenze

1. CSS dal 1996 e le sue limitazioni

CSS è il linguaggio che descrive la presentazione delle pagine Web — colori, layout e font — e la definizione W3C citata dalle slide ne ricorda il principio fondante: la separazione tra struttura (o contenuto) e presentazione, che rende più facile mantenere i siti e condividere i fogli di stile tra le pagine; CSS si può usare con qualsiasi linguaggio di markup basato su XML.

Ma le limitazioni sono tante, e le più comuni sono tre: il committente cambia idea a CSS completato; file pieni di commenti; probabili molte chiamate HTTP per caricare lo stile della pagina Web, quando ci sono molteplici file CSS esterni. La terza è il problema architetturale: ogni file CSS esterno è una richiesta, e il peso di un'applicazione si misura anche in numero di round trip.

2. La soluzione: preprocessor e transpiler

I preprocessor e i transpiler si usano per tre scopi: aggiungere feature non disponibili nel linguaggio di partenza, mantenere il codice DRY e manutenibile, ed eseguire controlli a tempo di "compilazione". Esistono per ogni linguaggio del web: per HTML (HAML, Pug, Slim), per CSS (SASS, Less, Stylus, PostCSS) e per JavaScript (TypeScript, Dart — che abbiamo visto nel capitolo precedente).

Definizione

Un CSS Preprocessor serve a definire fogli di stile con una forma più semplice, completa e potente rispetto ai CSS e a generare file CSS ottimizzati, aggregando le strutture definite anche in modo complesso. Il punto chiave è lo stesso del transpiling TypeScript: si scrive in un linguaggio più espressivo, si distribuisce il prodotto compilato.

3. SASS: definizione e installazione

SASS sta per Syntactically Awesome Style Sheets. La definizione ufficiale riportata dalle slide dice tre cose: è un'estensione di CSS che aggiunge potenza ed eleganza al linguaggio di base; permette di usare variabili, nested rules, mixins e inline imports; aiuta a mantenere grandi fogli di stile ben organizzati e a far partire rapidamente fogli di stile piccoli.

L'installazione è possibile in quattro modi. Standalone: si scarica il pacchetto specifico per il proprio sistema operativo da GitHub e si aggiunge il percorso alla variabile d'ambiente PATH — non ci sono dipendenze esterne. Con npm: npm install -g sass — potrebbe essere più lento delle altre soluzioni. Su Windows con Chocolatey: choco install sass (Dart Sass). Su macOS con Homebrew: brew install sass/sass/sass.

Le caratteristiche tecniche di SASS si riassumono in quattro gruppi: estensioni del linguaggio (variables, nesting, import, mixins, eccetera); direttive di controllo (if, for, each, while); funzioni ausiliarie (colori, numeri, liste, eccetera); output formattato e personalizzabile.

4. SASS contro SCSS e la linea di comando

La sintassi di SASS si discosta dalle regole base del CSS — niente parentesi, niente punti e virgola, indentazione significativa — e questo rende la vita difficile agli sviluppatori che devono riconvertire il loro modo di lavorare. Per questo esiste SCSS, Sassy-CSS, fully CSS-compatible: ogni CSS valido è anche un SCSS valido. La stessa regola nelle tre sintassi:

#sidebar
  width: 30%
  background-color: #faa

Indentazione al posto delle parentesi, niente punti e virgola: è la sintassi originale di SASS, più "pulita" ma lontana dal CSS.

#sidebar {
  width: 30%;
  background-color: #faa;
}

Il CSS che il browser esegue davvero: è il punto di arrivo della compilazione.

#sidebar {
  width: 30%;
  background-color: #faa;
}

SCSS è CSS con le estensioni: stessa struttura, e in più variabili, nesting e direttive. È la sintassi consigliata per chi parte da CSS.

La linea di comando è semplice: sass input.scss output.css compila una volta; sass --watch input.scss:output.css ricompila a ogni modifica del file; sass --watch app/sass:public/stylesheets osserva un'intera cartella. SASS consente inoltre di definire lo stile dell'output con il flag --style, con quattro opzioni: nested, expanded, compact e compressed. Ad esempio sass --watch style.scss:style.css --style compressed produce un'unica riga di CSS minimizzato. La stessa regola nelle quattro forme:

/* nested */               /* expanded */
#main {                    #main {
  color: #fff;               color: #fff;
  background: #000; }        background: #000; }
  #main p {                #main p {
    width: 10em; }            width: 10em; }

/* compact */
#main { color: #fff; background: #000; }
#main p { width: 10em; }

/* compressed */
#main{color:#fff;background:#000;}#main p{width:10em;}

5. Nested rules e parent selector

Le nested rules permettono di scrivere i selettori annidati come nella struttura HTML che stilizzano: #main p { color: #0f0; width: 97%; .redbox { background-color: #f00; color: #000; } } viene compilato in #main p e #main p .redbox. Il parent selector & referenzia il selettore padre e si usa in tre forme tipiche:

// &:hover — lo pseudo-stato del selettore corrente
a {
  font-weight: bold;
  text-decoration: none;
  &:hover { color: #FFF; }
}

// body.firefox & — un contesto esterno che precede il padre
body.firefox & {
  font-weight: normal;
}

// &-sidebar — il suffisso (stile BEM-like)
#main {
  color: black;
  &-sidebar {
    border: 1px solid;
  }
}

Il compilato è rispettivamente a:hover, body.firefox a e #main-sidebar: tre usi diversi dello stesso simbolo, tutti basati sul fatto che & viene sostituito dal selettore del blocco in cui compare.

6. Variabili e tipi supportati

Le variabili si dichiarano con $ e possono essere locali o globali: $width: 5em !global; indica una variabile che può essere usata globalmente, e una nuova assegnazione ne modifica il valore da quel punto in poi — il punto in poi è importante: le slide lo mostrano con #main che usa il valore corrente e #sidebar che lo riusa dopo.

I tipi supportati nativamente da SASS sono sette: numeri, stringhe, colori, booleani, null, liste e mappe chiave-valore.

$numero: 5;
$stringa: "Stringa di prova";
$colore: #ff00ff;
$boolean: true;
$valoreNulla: null;
$lista: ("elem1", "elem2", "elem3");
$mappa: (k1: v1, k2: v2, k3: v3);

7. Operazioni e interpolazione

SASS supporta le operazioni + - * /, ma con una regola fondamentale: l'espressione viene valutata solo se è presente sintassi SCSS. La divisione in particolare è ambigua, perché in CSS font: 10px/8px è una notazione valida (l'altezza di riga); SASS la esegue solo quando usa una variabile (width: $width/2), una funzione (width: round(1.5)/2), le parentesi (height: (500px/2)) o un'operazione mista (margin-left: 5px + 8px/2px).

Attenzione anche agli spazi nelle sottrazioni: $marg: 10px -5px; è una lista di due valori (e margin: 10px -5px resta tale), mentre $marg: 10px-5px; è una sottrazione che produce margin: 5px. Quando invece si vuole mantenere la sintassi CSS usando variabili, occorre l'interpolazione #{}: font: #{$font-size}/#{$line-height} produce font: 12px/30px senza eseguire la divisione. L'interpolazione serve anche nei nomi: p.#{$myvar} e a##{myneid} compilano in p.superclass e a#mynewid, e content: hello + -world produce content: hello-world.

8. DRY: @import, @extend e placeholder

«Don't Repeat Yourself»: SCSS offre un insieme di direttive che consentono di rendere il codice più ordinato, modulare e scalabile rispetto a CSS. La prima è @import, che importa file SCSS esterni per usare le regole definite al loro interno:

@import "foo.scss";
@import "foo" screen;
@import "http://foo.com/bar";
@import url(foo);

Una convenzione importante: se non si vuole che venga creato un file .css corrispondente, si aggiunge un underscore al nome del file — _variables.scss — così il file è importabile ma non viene compilato singolarmente. L'organizzazione tipica dei sorgenti è una cartella sass/ con un file principale e una serie di file parziali, come nella tavola seguente.

@media può usare variabili e interpolazione: @media #{$media} and ($feature: $value) compila in @media screen and (-webkit-min-device-pixel-ratio: 1.5). E @extend permette a un selettore di ereditare le regole di un altro, aggregando i selettori nel compilato:

L'esempio @extend più avanzato mostra l'interazione con i selettori composti: se .error.intrusion definisce una regola e .seriousError estende .error, il compilato aggrega .error.intrusion, .intrusion.seriousError — l'estensione vale anche per il contesto composto. Esiste infine il placeholder selector %: a differenza di una classe normale, un placeholder non produce regole CSS finché non viene esteso — %bordered viene "consumato" da .fancy-box { @extend %bordered; } e solo allora genera .fancy-box { border: 1px solid #CCC; }.

9. Direttive di controllo

Le direttive di controllo aggiungono un ulteriore livello di dinamicità a SCSS, consentendo di includere regole solo al verificarsi di determinate condizioni. Sono quattro: @if, @for, @each e @while.

// @if — scelta condizionale
$type: monster;
p {
  @if $type == ocean { color: blue; }
  @else if $type == monster { color: green; }
  @else { color: black; }
}

// @for — iterazione numerica (from 1 through 3, estremi inclusi)
@for $i from 1 through 3 {
  .item-#{$i} { width: 2em * $i; }
}

// @each — iterazione su una lista
@each $animal in puma, lion, salamander {
  .#{$animal}-icon { background-image: url('/#{$animal}.png'); }
}

// @while — ciclo con condizione
$i: 6;
@while $i > 0 {
  .item-#{$i} { width: 2em * $i; }
  $i: $i - 2;
}

Il compilato del @for produce .item-1 { width: 2em; }, .item-2 { width: 4em; } e .item-3 { width: 6em; }; il @while con passo -2 genera .item-6, .item-4 e .item-2. Come per TypeScript, il controllo avviene a tempo di compilazione: nel CSS distribuito resta solo il risultato.

10. Mixin e funzioni

I mixin consentono di definire "stili" (regole) riutilizzabili, ma a differenza di @extend accettano argomenti in input, col fine di definire procedure che aggiungano un buon livello di variabilità al risultato — e la presenza di argomenti cambia completamente la visione del riuso del codice in un linguaggio come CSS, in cui il riuso è di fatto assente. Si definiscono con @mixin e si usano con @include:

@mixin rounded($radius: 4px) {
  border-radius: $radius;
  -webkit-border-radius: $radius;
}

.box {
  @include rounded;            // usa il default: 4px
}
.pill {
  @include rounded(999px);     // valore posizionale
}
.card {
  @include rounded($radius: 8px); // valore per nome argomento
}

I mixin supportano i valori di default, l'assegnazione per nome argomento in chiamata, e un numero di argomenti sconosciuto sia in definizione (con ...) sia in chiamata. Esiste anche un insieme di funzioni messe a disposizione da Sass per la manipolazione dei dati — colori, stringhe (quote, str-length, to-upper-case, …), numeri (max, min, random, round, …) — e si possono definire funzioni personalizzate con @function/@return.

Verifica le tue conoscenze

Quali sono le tre limitazioni più comuni di CSS citate dalla lezione?

Il committente cambia idea a CSS completato; file pieni di commenti; probabili molte chiamate HTTP per caricare lo stile della pagina Web quando ci sono molteplici file CSS esterni. La soluzione proposta è il preprocessor/transpiler, che aggiunge feature non disponibili, mantiene il codice DRY e manutenibile ed esegue controlli a tempo di "compilazione".

Definite SASS e SCSS, e le differenze tra le due sintassi.

SASS (Syntactically Awesome Style Sheets) è un'estensione di CSS che aggiunge potenza ed eleganza: variabili, nested rules, mixins, inline imports, direttive di controllo, funzioni ausiliarie e output personalizzabile. La sintassi originale di SASS si discosta dal CSS (indentazione, niente parentesi e punti e virgola). SCSS (Sassy-CSS) è fully CSS-compatible: ogni CSS valido è anche SCSS valido, ed è la sintassi consigliata a chi parte da CSS.

Come si compila un file SCSS da linea di comando?

sass input.scss output.css compila una volta; sass --watch input.scss:output.css osserva il file; sass --watch app/sass:public/stylesheets osserva una cartella. Il flag --style sceglie lo stile dell'output: nested, expanded, compact o compressed.

Che cosa sono le nested rules e il parent selector &?

Le nested rules permettono di scrivere i selettori annidati come la struttura HTML che stilizzano: #main p { .redbox { ... } } compila in #main p .redbox. Il parent selector & referenzia il selettore del blocco corrente: a { &:hover } compila in a:hover, body.firefox & in body.firefox a, e #main { &-sidebar } in #main-sidebar.

Quali tipi supporta SASS e come funziona la visibilità delle variabili con !global?

Numeri, stringhe, colori, booleani, null, liste e mappe chiave-valore. La dichiarazione $width: 5em !global; rende la variabile usabile globalmente, e una nuova assegnazione ne modifica il valore da quel punto in poi.

Quando SASS esegue la divisione e quando invece la mantiene come sintassi CSS?

La divisione viene eseguita solo se è presente sintassi SCSS: con una variabile (width: $width/2), una funzione (round(1.5)/2), le parentesi ((500px/2)) o un'operazione mista (5px + 8px/2px). In font: 10px/8px (plain CSS) non viene eseguita. Per mantenere la sintassi CSS con variabili si usa l'interpolazione: font: #{$font-size}/#{$line-height}.

Quali direttive servono al riuso del codice in SCSS?

Due principali: @import (importa un file SCSS, con la convenzione dell'underscore _variables.scss per non generare un .css per il parziale) e @extend (estende una porzione di codice precedentemente definita, aggregando i selettori nel compilato; il placeholder % non produce regole finché non viene esteso). A queste si aggiungono i mixin con argomenti e le funzioni @function/@return.

Quali sono le direttive di controllo di SCSS? Fate un esempio.

@if (scelta condizionale, con @else if e @else), @for (iterazione numerica from 1 through 3), @each (iterazione su una lista) e @while (ciclo con condizione). Esempio: @for $i from 1 through 3 { .item-#{$i} { width: 2em * $i; } } genera .item-1, .item-2 e .item-3 con larghezze 2em, 4em e 6em.

Che differenza c'è tra @extend e @mixin?

@extend fa ereditare a un selettore le regole di un altro, aggregando i selettori nel CSS compilato (e il placeholder % evita regole "orfane"). I mixin definiscono procedure di regole riutilizzabili e, a differenza di @extend, accettano argomenti in input — con valori di default, assegnazione per nome e numero di argomenti sconosciuto — portando nel CSS il concetto di variabilità che prima non esisteva.

Riepilogo delle funzioni ausiliarie di Sass.

Sass mette a disposizione funzioni per la manipolazione dei dati: colori, stringhe (quote, str-length, to-upper-case), numeri (max, min, random, round) e altro. È inoltre possibile definire funzioni personalizzate con @function e @return.