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.
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).
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.
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.
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;}
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.
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);
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.
«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; }.
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.
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.
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".
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.
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.
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.
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.
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}.
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.
@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.
@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.
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.