Ricordi quando il CSS veniva scritto a manina ed era un’impresa non da poco? Per i progetti di un certo livello le righe di codice erano migliaia e potevamo scegliere tra scrivere l’intero codice in un unico file o suddividerlo in più file a seconda delle pagine o dei blocchi. Inoltre per i selettori annidati occorreva sempre ripetere per intero i nomi dei selettori genitore. Per risolvere questi problemi e aggiungere tutte quelle possibilità che il CSS non era ancora in grado di fornire furono creati i preprocessori CSS - gli ideatori meriterebbero come minimo una statua per le ore (miliardi?) di lavoro risparmiate. Oggi, però, con tutti gli ultimi aggiornamenti al CSS, i preprocessori sono ancora utili? Spoilero la risposta: sì. Vediamo di comprenderne il perché ripercorrendo un po’ di storia, evidenziando i vantaggi dei preprocessori CSS, analizzando come si sta evolvendo il CSS, e scoprendo come unire i vantaggi dei preprocessori CSS alle novità del CSS.
Perché sono nati i preprocessori CSS?
Il CSS nel corso degli anni si è evoluto e non poco; la sua crescita però non era in grado di rispondere velocemente alle esigenze di sviluppo, in particolare non era in linea con il concetto della programmazione ad oggetti. Per carità, il CSS era nato per dare un’anima grafica alle pagine web separandola dall’HTML ed era un po’ difficile prevedere agli albori tutte le sue potenzialità. I browser, inoltre, facevano fatica a tenere il passo dei cambiamenti seppur pochi. E i meno giovani ricorderanno oltretutto quante “gioie” ci ha regalato Internet Explorer in termini di compatibilità! Quanti workaround, - a pensarci vengono i brividi!
Scrivere CSS prima dell’arrivo dei preprocessori significava ripetere il codice con il rischio di commettere errori e allungare i tempi di interventi. Il cliente cambiava idea, ad esempio, sulla tavolozza colori, o voleva cambiare delle spaziature? Lascio immaginare, o ricordare ai diversamente giovani, cosa significava.
Qualche buon’anima quindi portò il concetto della programmazione ad oggetti dentro i fogli di stile, potenziandoli e rendendoli più semplici da scrivere e meglio organizzati. I nuovi super poteri erano le variabili, le funzioni, l’annidamento del codice e i mixin. Nacquero così i preprocessori CSS, chiamati anche precompilatori, programmi che hanno il compito di automatizzare la sostituzione del codice, in questo caso del CSS. Il risultato finale della compilazione è un unico CSS correttamente leggibile dai browser, come se fosse stato scritto interamente da zero. Per noi sviluppatori front-end l’arrivo di SASS, Less e fratelli ha significato migliore organizzazione, più flessibilità, interventi nettamente più rapidi. E se il cliente cambiava idea, bastava un magico schiocco delle dita.
I preprocessori più diffusi
In ordine sia di nascita che di diffusione, i preprocessori che ci hanno aiutato a velocizzare la scrittura dei fogli di stile sono: SASS, Less, Stylus, PostCSS. Le loro caratteristiche comuni sono: le variabili, i mixin, le funzioni e l’annidamento.
SASS è il primo ad entrare in gioco ed è anche quello che ha avuto la maggiore diffusione - entrando nelle mie grazie preferendolo agli altri sia per la sua potenza che per la sua similitudine con il CSS tradizionale (nella variante SCSS), aspetto importante quando si lavora in team multidisciplinari. (Per questa ragione in questo articolo preferirò riportare degli esempi in SCSS). Questo preprocessore CSS ha come punto di forza le estensioni (@extend
). La sua popolarità e una comunità molto attiva hanno significato una documentazione molto ampia e tante risorse. Può però risultare più complesso soprattutto quando si utilizzano le estensioni e i mixin annidati.
// Stilizzazione di un pulsante
.button {
border: 1px solid #ccc;
border-radius: 5px;
padding: 10px 20px;
}
// Esempio di utilizzo di @extend in SCSS
.button-primary {
@extend .button;
background-color: blue;
color: white;
}
Less invece ha meno funzionalità rispetto a SASS - mancano le estensioni - ma è più semplice da studiare, il che lo rende adatto ai nuovi utenti o per i piccoli progetti. È stato progettato per funzionare con JavaScript, ed è quindi ideale per i progetti basati su Node.js e JS.
Stylus si è ritagliato la sua fetta di mercato puntando sulla flessibilità con una sintassi senza punti e virgola, due punti e rinunciando perfino alle parentesi graffe. Un altro suo punto di forza è la manipolazione del colore.
// Funzione in Stylus per creare una scala di colori a partire da un colore base
create-color-scale(base-color, steps)
colors = []
for i in (0..steps)
colors.push(lighten(base-color, i * 100 / steps))
return colors
// Utilizzo della funzione
$primary-color = #336699
$colors = create-color-scale($primary-color, 5)
.box
for color in $colors
&.box-#{i}
background-color: color
L’ultimo a nascere è PostCSS. È un preprocessore modulare, basato sui plugin. Si possono scegliere quindi quali funzionalità includere. Questa sua particolarità lo rende il più veloce in termini di prestazioni.
Sviluppare i fogli di stile con il piede sull’acceleratore
L’obiettivo dei preprocessori CSS era ridurre gli sforzi di sviluppo aumentandone la velocità. Hanno magistralmente centrato il risultato automatizzando molte delle operazioni ripetitive. Introducendo quelli esistenti, abbiamo visto quali sono i loro superpoteri: le variabili, i mixin, le funzioni e l’annidamento. Rivediamoli velocemente.
Le variabili, fondamentali in tutti i linguaggi di sviluppo, sono dei preziosi “contenitori” univoci cui vengono assegnati dei valori. Nei fogli di stile li utilizziamo per assegnare loro, per esempio, un colore o una dimensione. Il loro vantaggio sta indubbiamente nel fatto che se desideriamo cambiare un colore basta farlo direttamente al livello della variabile, senza doverlo cercare in tutto il foglio di stile.
// Esempio di definizione dei colori in SCSS
$primary-color: #333;
$secondary-color: #f0f0f0;
.button {
background-color: $primary-color;
color: $secondary-color;
}
// Esempio di definizione delle dimensioni in SCSS
$base-font-size: 16px;
$container-width: 960px;
body {
font-size: $base-font-size;
}
.container {
max-width: $container-width;
margin: 0 auto;
}
I mixin consentono di definire degli stile di base per un elemento, per esempio un bottone, per poterlo poi riutilizzare attraverso l’inclusione in vari punti del foglio di stile.
// Esempio di un pulsante personalizzato in SCSS
@mixin button($color, $background-color) {
background-color: $background-color;
color: $color;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
}
.primary-button {
@include button(white, #333);
}
.secondary-button {
@include button(#333, white);
}
L'annidamento, in inglese nesting, consente di strutturare i selettori in modo gerarchico, o ad “albero”. Si spiega meglio con l’esempio che segue:
// Esempio di annidamento di una card in SCSS
.card {
background-color: #fff;
border: 1px solid #ddd;
border-radius: 5px;
padding: 20px;
.card-header {
font-weight: bold;
margin-bottom: 10px;
}
.card-body {
p {
margin: 0;
}
}
}
Le funzioni, come per gli altri linguaggi di sviluppo, svolgono dei calcoli o manipolano dei valori e restituiscono un risultato.
// Esempio di funzione per il calcolo di un colore scuro
@function darken-color($color, $percentage) {
@return darken($color, $percentage);
}
// Utilizzo della funzione
$primary-color: #3498db;
.button {
background-color: $primary-color;
border-color: darken-color($primary-color, 10%);
color: white;
}
//Output CSS
.button {
background-color: #3498db;
border-color: #2c80b6;
color: white;
}
// Esempio di funzione per calcolare un valore dinamico basato su una scala
@function scale-size($base-size, $factor) {
@return $base-size * $factor;
}
// Utilizzo della funzione
$base-font-size: 16px;
h1 {
font-size: scale-size($base-font-size, 2); // Raddoppia la dimensione base
}
p {
font-size: scale-size($base-font-size, 1); // Usa la dimensione base
}
h1 {
font-size: 32px;
}
p {
font-size: 16px;
}
Un altro indubbio vantaggio dei preprocessori CSS sta nel fatto di poter organizzare il codice in più file ed anche in sotto cartelle. Così organizzati, possiamo infatti scrivere il codice inerente una porzione del sito o uno specifico contenuto in un file separato. È come già facciamo per esempio in PHP o in React, separando porzioni di codice in più file. Questa soluzione è ottima anche per avere davanti a se solo quella sezione e concentrarci su di essa.
La velocità di scrittura e la migliore organizzazione del codice portano di conseguenza ad una migliore manutenzione del progetto. Perché proprio grazie alle funzionalità come le variabili o i mixin è possibile modificare in un istante uno stile senza dover rimettere mani all’intero foglio di stile. E la suddivisione di quest’ultimo in più file, ci consente di trovare ciò che cerchiamo molto più rapidamente.
Nota informativa: in realtà il CSS consente di importare i CSS dentro altri CSS così da poter organizzare le istruzioni in più file o in sottocartelle. L’approccio storico dell’importazione, tuttavia, ha delle forti limitazioni, come spiegherò successivamente, e può impattare sulle prestazioni generali del sito.
Come si sta evolvendo il nuovo CSS
Laddove i preprocessori avevano introdotto ciò che “mancava” già a partire dal lontano 2007, il nuovo CSS sta colmando il divario, ora un po’ più velocemente. E lo stanno anche facendo i browser, ora più reattivi ai nuovi standard del W3C.
Da qualche anno possiamo utilizzare direttamente in CSS le variabili, chiamate “Custom Properties”. Queste possono rivelarsi anche più potenti di quelle dei preprocessori in quanto possono essere manipolate con JavaScript e quindi aggiornate dinamicamente. In CSS sono state anche introdotte le funzioni, predefinite, come calc() o il più recente clamp(). Ed è anche possibile annidare in modo nativo i selettori.
// Esempio di utilizzo delle variabili native (o Custom Properties) del CSS
:root {
--primary-color: #4CAF50; /* Verde */
--hover-color: #388E3C; /* Verde più scuro */
}
.my-button {
background-color: var(--primary-color);
color: white;
padding: 10px 20px;
border-radius: 5px;
&:hover {
background-color: var(--hover-color);
}
}
In CSS troviamo ulteriori altri potenziamenti introdotti di recente, come per esempio la regola @property
, una proprietà personalizzata, o variabile, che presenta una sintassi avanzata utile a definire il tipo di proprietà (es. colore, percentuale), l'impostazione dei valori predefiniti e se una proprietà personalizzata può ereditare o meno valori. Altra recente introduzione è la regola @scope
che ci consente di applicare degli stili a specifiche aree senza dover scrivere ulteriori stili che sovrascrivono quelli dei selettori genitori o figli.
// Esempio di utilizzo di @property
@property --custom-color {
syntax: '<color>';
inherits: true;
initial-value: black;
}
:root {
--custom-color: red;
}
div {
color: var(--custom-color);
transition: color 0.5s ease;
}
div:hover {
--custom-color: blue;
}
<!-- Esempio di utilizzo di @scope-->
<section>
<header>
<h1>Questo titolo è scuro</h1>
</header>
<p>Questo paragrafo è chiaro.</p>
@scope (header) {
h1 {
color: darkblue;
}
}
@scope (section:not(header)) {
p {
color: lightgray;
}
}
</section>
In virtù dei tanti miglioramenti implementati in CSS, personalmente ritengo che non sia più necessario utilizzare ancora il nome CSS3. Riconosco però che con questo termine, ancora ampiamente utilizzato, vengono indicate tutte le evoluzioni presentate dopo il CSS2.1. Giusto a titolo informativo, CSS ora ha un nuovo logo che ha abbandonato il numero 3 e graficamente ricorda un il logo di JavaScript.
I preprocessori CSS stanno per diventare obsoleti?
Direi proprio di no! Potremo utilizzare i preprocessori CSS ancora per diverso tempo e non è detto che si possano rimpiazzare facilmente e nel breve tempo. Se da un lato in CSS sono state introdotte tante nuove funzionalità, queste presentano ancora delle limitazioni.
Le nuove funzioni che il CSS ci ha messo a disposizione sono funzioni predefinite, cioè già integrate e ben definite. Al contrario, con i preprocessori CSS possiamo scrivere funzioni personalizzate.
// Esempio di funzione nativa in CSS
:root {
--responsive-size: clamp(1rem, 2vw, 3rem);
}
h1 {
font-size: var(--responsive-size);
}
Un altro vantaggio a favore del continuo utilizzo dei preprocessori CSS è la modularità del codice. Il CSS nativo consente di dividere il foglio di stile in più file e di importarli all’interno di un file “indice”. Questo approccio però compromette le prestazioni generali del sito web, in particolare quando sono presenti molti file CSS. Il browser infatti si ritrova a dover scaricare tutti i file CSS importati causando un blocco del rendering della pagina. Nei preprocessori CSS, invece, la suddivisione dei file avviene solo in fase di sviluppo. Il browser invece leggerà un unico file CSS generato dalla compilazione.
I mixin dei preprocessori sono invece delle funzionalità interessanti ma non indispensabili. Si possono infatti rimpiazzare scrivendo delle regole per dei selettori specifici. Utilizzarli, però, migliora l’organizzazione e la lettura del codice, ed evita nelle pagine HTML di avere una lunga sfilza di classi assegnate ad un elemento (come avviene quando si utilizzano i framework CSS, come Bootstrap o Tailwind CSS).
I preprocessori CSS, in conclusione, rimangono fondamentali per la maggior parte dei progetti, e sicuramente indispensabili per quelli molto complessi. Per le “one page” possiamo invece considerare l’uso del CSS nativo. Questa scelta rimane comunque personale e la valutazione deve essere fatta in base al progetto.
La chiave è la combinazione
Il miglior approccio per continuare a sviluppare i fogli di stile è di unire la potenza e le unicità dei preprocessori CSS, che tutt’oggi vengono aggiornati con vari miglioramenti, alle novità del CSS puro. Possiamo tranquillamente sfruttare le proprietà personalizzate avanzate native del CSS ma allo stesso tempo sfruttare le variabili dei preprocessori per scrivere il codice in modo più efficiente. Possiamo sfruttare le nuove funzioni CSS ma se dovessimo aver bisogno di scriverne una personalizzata lo possiamo fare utilizzando il preprocessore scelto.
// Esempio di uso combinato CSS e SCSS
// Variabili SCSS
$default-padding: 1rem;
// Mixin per il supporto responsive
@mixin responsive-padding($factor) {
padding: clamp(0.5rem, $factor * 1vw, $default-padding * $factor);
}
// Annidamento e uso di mixin
.card {
// Variabile CSS registrata con SCSS
@property --card-color {
syntax: '<color>';
inherits: true;
initial-value: lightgray;
}
background-color: var(--card-color);
border: 1px solid darkgray;
@include responsive-padding(2); // Usa il mixin per padding responsive
&:hover {
--card-color: skyblue;
transition: background-color 0.3s ease;
}
h1 {
color: darkblue;
font-size: clamp(1.5rem, 3vw, 2.5rem);
}
p {
color: gray;
font-size: clamp(1rem, 2vw, 1.5rem);
}
}
Studiare gli aggiornamenti del nostro preprocessore preferito e del CSS puro, sperimentare per trovare il miglior equilibrio in termini di manutenibilità e prestazioni, ci aiuterà a migliorare le nostre scelte per il successo del progetto al quale si lavora.