use e forward, come cambia SASS e i nostri progetti

Non c'è dubbio che Sass sia uno strumento indispensabile per chiunque si occupi di sviluppo frontend e uno dei punti di forza è quello di permetterci di suddividere il progetto in vari file, con la logica che preferiamo, referenziandoli tra di loro usando la direttiva @import, per poi compilarli in un unico file di output.

Come specificato anche dalla documentazione ufficiale però @import non è necessariamente il metodo appropriato per includere un file (chiaramente era anche l'unico fino all'introduzione di @use e @forward), tanto che dal 1 Ottobre 2021 è stata avviata la prima fase di deprecazione di @import che si concluderà con il drop definitivo il prossimo 1 Ottobre 2022 (questo riguarda le librerie Dart Sass e LibSass).

In sostituzione di @import avremo ben due nuove direttive, @use e @forward, che rendono più strutturato il meccanismo di importazione di file e introducono diverse novità.

Differenze tra use e forward

Entrambe le direttive in qualche modo "importano" un file ma in modi diversi, è importante quindi capire bene come e quando usarle.

Use

Sulle prime @use mi era sembrato potesse essere un rimpiazzo di @import tanto che la prima cosa che ho fatto è stata quella di sostituire gli @import del mio progetto con @use salvo rendermi conto molto rapidamente che non funzionava proprio allo stesso modo.

Facciamo un esempio molto semplice; abbiamo un file con alcune regole tipografiche e vogliamo importarlo nel file principale chiamato style.scss, quello che andremo a scrivere sarà:

@use "typography";

Il risultato compilato in style.css sarà qualcosa del genere:

h1 { font-size: 48px }
h2 { font-size: 32px }
...

Questo è quello che sarebbe successo anche con @import giusto? In questo caso specifico si, non ci sono differenze, ma facciamo un esempio diverso; sempre nello stesso file di tipografia andiamo a mettere anche un mixin (che con grande fantasia chiameremo appunto mixin) e una variabile che cercheremo poi di riutilizzare nel file style.scss all’interno di una nuova regola:

@use "typography";

.selettore {
  @include mixin;
  color: $variabile;
}

Se proviamo a compilare questo file il sistema genererà un errore, questo perché @use introduce il concetto di <namespace> che vedremo più avanti essere molto importante.

Nell'esempio che abbiamo appena visto, il <namespace> viene assegnato automaticamente ed è corrispondente al nome del file, quindi dovremo richiamare mixin o variabili in questo modo:

@use "typography";

.selettore {
  @include typography.mixin;
  color: typography.$variabile;
}

Questo è chiaramente un esempio molto semplice (e anche poco credibile) ma dovrebbe aiutarvi a farvi un'idea di come funziona @use.

Forward

La direttiva @forward a sua volta ci permette di importare un file, ma funziona in modo leggermente diverso rispetto a @use. Prendiamo l'esempio precedente e sostituiamo @use con @forward sempre nel nostro file style.scss:

@forward "typography";

Questo verrà compilato senza problemi come anche nel primo esempio di @use ma se proviamo a richiamare il mixin o la variabile il compilatore genererà un errore.

Questo perché @forward si occupa diciamo di "trasportare" il contenuto di un file ma non renderlo "interattivo", possiamo immaginare come se @use fosse un @import "attivo" e @forward "passivo".

Anche @forward a sua volta supporta il <namespace> e può chiaramente essere usato in combinata con @use come vedremo più avanti.

L'importanza del namespace

In PHP come anche in altri linguaggi il <namespace> è abbastanza comune ma in Sass è una "novità" (tralasciando che è in giro da metà 2019).

Per chi è magari alle prime armi magari potrebbe non essere così ovvio ma avere la possibilità di utilizzare un <namespace> ci permette di rendere più sicuro il nostro codice e sopratutto riutilizzabile con più facilità.

Facciamo qualche esempio per rendere più chiara l'utilità del <namespace>; immaginiamo di avere un file di mixins che vogliamo importare nel file style.scss per poterli applicare, con il precedente sistema @import avremmo scritto una cosa del genere:

@import "mixins";

.selettore {
  @include nome-mixin;
}

Questo è perfettamente valido, ma ci espone ad alcuni problemi;

  1. essendo disponibili globalmente è necessario aggiungere un prefisso ai nostri mixin o funzioni per evitare collisioni di nomi
  2. problemi di specificità di inclusione, in parole povere, se un altro file dichiara dopo il nostro un mixin con lo stesso nome andrà a rimpiazzare il precedente, questo come potrete immaginare rende abbastanza imprevedibile la gestione su progetti grandi.

Come ci aiuta quindi il <namespace>?

Mi piace vederlo come una specie di categorizzazione, in pratica quello che dobbiamo fare è organizzare in modo logico i nostri mixin e funzioni per potergli associare un <namespace> appropriato.

Prima abbiamo visto come il <namespace> venga generato in automatico da Sass in funzione del nome del file, questo però può andar bene in casi molto semplici, ma se iniziamo ad utilizzare nomi più lunghi diventa tutto più scomodo, facciamo qualche esempio:

@use "utilities/typography";

// Il namespace generato sarà typography
h1 { @include typography.h1 }

Più andiamo a complicare i nomi e più diventa verbosa la sintassi, diventando molto scomodo da scrivere, quello che possiamo fare quindi è creare un alias:

@use "utilities/typography" as t;

// Il namespace è diventato t
h1 { @include t.h1 }

Nel caso in cui non ci interessi utilizzare il <namespace> (in alcuni contesti specifici potrebbe non essere indispensabile) ci viene incontro la wildcard * che ci permette in qualche modo di "annullare" il <namespace> e dire a Sass di prendere la funzione o il mixin così come sono stati definiti, ricadendo chiaramente in tutti i problemi legati all'@import che abbiamo visto poco più sopra:

@use "utilities/typography" as *;

// Il namespace viene annullato
h1 { @include h1 }

Come possiamo utilizzarli in un progetto?

Ora che abbiamo compreso i concetti base vediamo come possiamo utilizzare le nuove direttive in un progetto o quanto meno l’approccio che sto seguendo nei miei progetti.

Solitamente suddivido il progetto in tre parti, layout, componenti e utilities (come base di partenza, da adattare chiaramente alla complessità del progetto), sfruttando le convenzioni di Sass utilizzo un file _index.scss dentro ogni cartella incaricato di importare i vari file che si trovano all’interno della sua cartella (utilizzando @forward) per renderli disponibili nel file principale, solitamente style.scss.

Una struttura di esempio dei file di un progetto potrebbe essere qualcosa del genere:

// Struttura files di progetto
style.scss
_config.scss
layout
|-- _index.scss
|-- blog.scss
|-- post.scss
components
|-- _index.scss
|-- componente.scss
utilities
|-- _index.scss
|-- mixins.scss
|-- functions.scss

Il file _index.scss della cartella layout:

// _index.scss di layout
@forward "blog";
@forward "post";
…

Nel file style.scss ci basterà richiamare con @forward il nome della cartella e Sass per convenzione andrà ad includere automaticamente il primo file index che trova al suo interno.

In testa al file invece utilizzando @use andremo a caricare e rendere disponibile globalmente le variabili di configurazione presenti nel nostro file di configurazione in modo che i mixin o funzioni che devono leggere o operare sulla configurazione abbiano sempre disponibile il dato:

@use "_config";

@forward "layout";
@forward "components";

Infine abbiamo le utilities, che essendo tipicamente mixins o funzioni verranno richiamate utilizzando @use all'interno dei singoli file di componenti o layout quando ci servono.

Moduli built-in di Sass

Abbiamo visto come il <namespace> ci porti a "compartimentare" delle funzionalità, evitare conflitti e di riflesso creare dei "moduli" di funzioni o utilità potenzialmente riutilizzabili (creandoci quindi un nostro framework).

Parlando di moduli di funzionalità, Sass ne include diversi che sono:

  • sass:color
  • sass:list
  • sass:map
  • sass:math
  • sass:meta
  • sass:selector
  • sass:string

I moduli che ritengo indispensabili sono Math e Map ma potete trovare maggiori dettagli su tutti i moduli nella documentazione ufficiale.

Conclusioni

Possiamo dire che la deprecazione di @import di fatto sia stata una buona cosa, le nuove direttive ci permettono di creare progetti più solidi, ripagandoci rapidamente dello sforzo di adeguamento. Il mio consiglio è di iniziare a sperimentare con la nuova sintassi in un progetto nuovo, perché non sempre è possibile (ed economicamente sostenibile) aggiornare progetti già completati.

Blog