Quando e come supportare più versioni di Sass

L'altro giorno stavo rivedendo il codice Sass del sistema di grid Jeet, solo per il gusto di farlo. Dopo alcuni commenti sul repository GitHub, ho capito che i manutentori di Jeet non erano ancora pronti per passare a Sass 3.3. In realtà, è più corretto dire Jeet utenti non sono pronti per passare a Sass 3.3, in base al numero di problemi aperti quando Jeet ha iniziato a utilizzare le funzionalità di Sass 3.3. 

Ad ogni modo, il punto è che Jeet non può ottenere tutte le cose belle e brillanti di Sass 3.3. O può farlo?

* -exists funzioni

Se sei a conoscenza di quale versione 3.3 ha apportato a Sass, potresti sapere che un paio di funzioni di supporto sono state aggiunte al core, allo scopo di aiutare gli sviluppatori di framework a supportare più versioni di Sass allo stesso tempo:

  • esiste globale-variabile ($ nome): controlla se esiste una variabile nell'ambito globale
  • Esiste variabile ($ nome): controlla se esiste una variabile nell'ambito corrente
  • esiste la funzione-($ name): controlla se esiste una funzione nell'ambito globale
  • Esiste mixin-($ name): controlla se esiste un mixin nell'ambito globale

C'è anche un caratteristica-esistente ($ name) funzione, ma non sono davvero sicuro di ciò che fa dal momento che i documenti sono abbastanza evasivi su di esso. Ho anche dato uno sguardo al codice della funzione, ma non fa di piùbool (Sass.has_feature? (feature.value)), che non aiuta molto.

Ad ogni modo, abbiamo un paio di funzioni in grado di verificare se esiste una funzione, un mixin o una variabile, e questo è molto carino. Ora di andare avanti.

Rilevamento della versione di Sass

Ok, nuove funzioni, molto belle. Ma cosa succede quando usiamo una di quelle funzioni in un ambiente Sass 3.2.x? Scopriamolo con un piccolo esempio.

// Definizione di una variabile $ my-awesome-variable: 42; // Da qualche altra parte nel codice $ does-my-awesome-variable-exist: variable-exists ('my-awesome-variable'); // Sass 3.3 -> 'true' // Sass 3.2 -> 'variable-exists (' my-awesome-variable ')' 

Come puoi vedere dai risultati, Sass 3.2 non si blocca o genera errori. Analizza variabile esiste ( 'my-awesome-variabile') come una stringa, quindi fondamentalmente "Variabile esiste ( 'my-awesome-variabile')". Per verificare se abbiamo a che fare con un valore booleano o una stringa, possiamo scrivere un test molto semplice:

$ return-type: type-of ($ does-my-awesome-variable-exist); // Sass 3.3 -> 'bool' // Sass 3.2 -> 'string' 

Ora siamo in grado di rilevare la versione di Sass all'interno del codice. Quanto è fantastico? In realtà, non rileviamo esattamente la versione di Sass; piuttosto troviamo un modo per definire se stiamo eseguendo Sass 3.2 o Sass 3.3, ma questo è tutto ciò che ci serve in questo caso.

Miglioramento progressivo

Diamo un'occhiata al miglioramento progressivo delle funzioni di Sass. Ad esempio, potremmo usare strumenti nativi se disponibili (Sass 3.3), o ricorrere a quelli personalizzati se non lo sono (Sass 3.2). Questo è quello che ho suggerito a Jeet per quanto riguarda il sostituire-esimo () funzione, che viene utilizzata per sostituire un valore in un indice specifico.

Ecco come noi poteva fallo:

@function replace-nth ($ list, $ index, $ value) // Se 'set-nth' esiste (Sass 3.3) @if function-exists ('set-nth') == true @return set- ennesimo ($ lista, $ indice, valore $);  // Else it's Sass 3.2 $ result: (); $ indice: if ($ indice < 0, length($list) + $index + 1, $index); @for $i from 1 through length($list)  $result: append($result, if($i == $index, $value, nth($list, $i)));  @return $result;  

E poi suppongo che tu sia come ...  che senso ha farlo se possiamo farlo funzionare per Sass 3.2 comunque? Domanda giusta direi prestazione. Nel nostro caso, set-esimo è una funzione nativa di Sass 3.3, il che significa che funziona in Ruby, il che significa che è molto più veloce di una funzione Sass personalizzata. Fondamentalmente, le manipolazioni sono fatte sul lato Ruby al posto del compilatore Sass.

Un altro esempio (sempre di Jeet) sarebbe a inverso funzione, invertire una lista di valori. Quando ho rilasciato SassyList per la prima volta, non esisteva Sass 3.3, quindi invertire una lista significherebbe creare una nuova lista, scorrere all'indietro sulla lista iniziale, aggiungendo valori a quella nuova. Ha fatto bene il lavoro. Tuttavia, ora che abbiamo accesso al set-esimo funzione da Sass 3.3, c'è un modo molto migliore di invertire un elenco: scambiando gli indici.

Per confrontare le prestazioni tra entrambe le implementazioni, ho provato a invertire l'alfabeto latino (un elenco di 26 elementi) per 500 volte. I risultati erano, più o meno:

  • tra 2s e 3s con "3.2 approach" (usando aggiungere)
  • mai sopra 2 secondi con "3.3 approccio" (usando set-esimo)

La differenza sarebbe ancora più grande con una lista più lunga, semplicemente perché scambiare gli indici è molto più veloce dei valori aggiunti. Quindi, ancora una volta, ho cercato di vedere se potevamo ottenere il massimo da entrambi i mondi. Ecco cosa mi è venuto in mente:

@function reverse ($ list) // Se 'set-nth' esiste (Sass 3.3) @if function-exists ('set-nth') == true @for $ i da 1 a floor (length ($ list) / 2) $ list: set-nth (set-en ($ list, $ i, nth ($ list, - $ i)), - $ i, nth ($ list, $ i));  @return $ list;  // Else it's Sass 3.2 $ result: (); @for $ i da length ($ list) * -1 a -1 $ result: append ($ result, nth ($ list, abs ($ i)));  @return $ result;  

Anche in questo caso, stiamo ottenendo il massimo da Sass 3.3 pur continuando a supportare Sass 3.2. Questo è abbastanza pulito, non credi? Ovviamente potremmo scrivere la funzione al contrario, occupandoci prima di Sass 3.2. Non fa assolutamente nessuna differenza.

@function reverse ($ list) // Se 'set-nth' non esiste (Sass 3.2) @if function-exists ('set-nth')! = true $ result: (); @for $ i da length ($ list) * -1 a -1 $ result: append ($ result, nth ($ list, abs ($ i)));  @return $ result;  // Else è Sass 3.3 @for $ i da 1 a pavimento (length ($ list) / 2) $ list: set-nth (set-nth ($ list, $ i, nth ($ list, - $ i )), - $ i, nth ($ list, $ i));  @return $ list;  

Nota: per verificare se stiamo eseguendo Sass 3.2 nell'ultimo esempio, avremmo potuto testarlo function-exists ("set-nth") == unquote ('function-exists ("set-nth")') pure, ma è piuttosto lungo e soggetto a errori.

Memorizzazione della versione Sass in una variabile

Per evitare di verificare le funzionalità esistenti più volte, e poiché qui trattiamo solo due versioni diverse di Sass, possiamo memorizzare la versione di Sass in una variabile globale. Ecco come sono andato su di esso:

$ sass-version: if (function-exists ("function-exists") == true, 3.3, 3.2); 

Ti darò che è un po 'complicato. Permettetemi di spiegare cosa sta succedendo qui. Stiamo usando il Se() funzione ternaria, progettata in questo modo:

  • il primo argomento del Se() la funzione è la condizione; valuta a vero o falso
  • se la condizione è valutata a vero, restituisce il secondo argomento
  • altrimenti restituisce il terzo argomento

Nota: Sass 3.2 è un tipo di bachi con la funzione ternaria. Valuta tutti e tre i valori, non solo quello da restituire. Questo a volte può portare ad alcuni errori imprevisti.

Ora, diamo un'occhiata a cosa succede con Sass 3.3:

  • Funzione-esistente ( 'funzione-esiste') ritorna vero perché ovviamente Funzione-exists () esiste
  • poi function-exists ('function-exists') == true è come true == true che è vero
  • così $ Sass-versione è impostato per 3.3

E se stiamo usando Sass 3.2:

  • Funzione-esistente ( 'funzione-esiste') non è una funzione ma una stringa, quindi fondamentalmente "Funzione-esistente ( 'function-esiste')"
  • function-exists ('function-exists') == true è falso
  • così $ Sass-versione è impostato per 3.2

Se sei un tipo di funzione, puoi avvolgere questa roba in una funzione.

@function sass-version () @return if (function-exists ("function-exists") == true, 3.3, 3.2);  

Quindi usalo in questo modo:

@if sass-version () == 3.3 // Sass 3.3 @if sass-version () == 3.2 // Sass 3.2 @if sass-version () < 3.3  // Sass 3.2  

Ovviamente, avremmo potuto verificare l'esistenza di un'altra funzione 3.3 simile chiamata() o mappa-get () ma potrebbe esserci potenzialmente una versione di Sass dove * -exists le funzioni sono implementate, ma non chiamata() o mappe, quindi mi sento meglio controllare l'esistenza di a * -exists funzione. E dal momento che usiamo Funzione-esiste, proviamo questo!

Verso il futuro!

Sass 3.3 è la prima versione da implementare * -exists funzioni, quindi dobbiamo verificare se * -Exists ($ param)restituisce effettivamente un valore booleano o viene analizzato come una stringa, che è una specie di hacky.

Ora, diciamo che Sass 3.4 verrà rilasciato domani con a unicorno() funzione, portando awesomeness e arcobaleni al mondo. La funzione per rilevare la versione di Sass sarebbe probabilmente simile a questa:

@function sass-version () @if function-exists ('unicorn') == true @return 3.4;  @else if function-exists ('unicorn') == false @return 3.3;  @else @return 3.2;  

Unicorno napoletano di Erin Hunting

E poi se Sass 3.5 porta a arcobaleno() funzione, si aggiornerebbe sass-versione () per di qua:

@function sass-version () @if function-exists ('rainbow') == true @return 3.5;  @else if function-exists ('unicorn') == true e function-exists ('rainbow') == false @return 3.4;  @else if function-exists ('unicorn') == false @return 3.3;  @else @return 3.2;  

E così via.

Parlando di unicorni e arcobaleni ...

Cosa sarebbe veramente fantastico sarebbe la possibilità di importare un file all'interno di un'istruzione condizionale. Sfortunatamente, questo non è possibile al momento. Detto questo, è programmato per Sass 4.0, quindi non perdiamo ancora la speranza.

Ad ogni modo, immagina di poter importare un file basato sul risultato del sass-versione () funzione. Ciò renderebbe maledettamente polifilare le funzioni di Sass 3.3 per Sass 3.2.

Ad esempio, potremmo avere un file che include tutte le versioni di Sass 3.2 delle funzioni di mappa usando invece elenchi bidimensionali (come quello che Lu Nelson ha fatto con Sass-List-Maps) e importarlo solo quando si ha a che fare con Sass 3.2, in questo modo:

// Purtroppo, questo non funziona :( @if sass-version () < 3.3  @import "polyfills/maps";  

Quindi, potremmo usare tutte quelle funzioni (come map-get) nel nostro codice senza preoccuparsi della versione di Sass. Sass 3.3 userebbe funzioni native mentre Sass 3.2 userebbe polyfill. 

Ma questo non funziona.

Si potrebbe arrivare con l'idea di definire funzioni in un'istruzione condizionale, invece di importare un intero file. Quindi, potremmo definire le funzioni relative alla mappa solo se non esistono ancora (in altre parole: Sass 3.2). Sfortunatamente, questo non funziona neanche: le funzioni e i mixin non possono essere definiti in una direttiva.

Le funzioni non possono essere definite all'interno di direttive di controllo o altri mixin.

Il meglio che possiamo fare al momento è definire sia una versione Sass 3.2 che una versione Sass 3.3 in ciascuna funzione, come abbiamo visto in cima a questo articolo. Ma non solo è più complicato da mantenere, ma richiede anche che tutte le funzioni native di Sass 3.3 siano racchiuse in una funzione con nome personalizzato. Dai un'occhiata al nostro replace-esimo funzione da prima: non possiamo nominarlo set-esimo (), o sarà ricorsivo all'infinito quando si usa Sass 3.3. Quindi dobbiamo trovare un nome personalizzato (in questo caso replace-esimo).

Essere in grado di definire funzioni o importare file all'interno di direttive condizionali renderebbe possibile mantenere le funzionalità native così come sono, mentre si generano i polyfill per le versioni precedenti di Sass. Sfortunatamente, non possiamo. Questo fa schifo.

Nel frattempo, suppongo che potremmo usarlo per avvertire l'utente ogni volta che sta usando un compilatore Sass obsoleto. Ad esempio, se la tua libreria / framework Sass / qualunque cosa stia usando Sass, puoi aggiungerla sopra il tuo foglio di stile principale:

@if sass-version () < 3.3  @warn "You are using a version of Sass prior to 3.3. Unfortunately for you, Sass 3.3 is required for this tool to work. Please make sure to upgrade your Sass compiler.";  

Là. Nel caso in cui il codice si arresti in modo anomalo perché si utilizzano funzionalità non supportate come mappe e roba, l'utente verrà avvisato perché quando controlla l'output.

Pensieri finali

Fino ad ora, Sass è stato piuttosto lento a passare dal punto di vista delle versioni. Ricordo di aver letto da qualche parte che i manutentori di Sass volevano andare avanti un po 'più velocemente, il che significa che potremmo trovarci a occuparci di più versioni di Sass in qualsiasi momento presto.

Imparare come rilevare la versione Sass e fare uso di * -exists la funzione - a mio parere - sarà importante un giorno, almeno per alcuni progetti (quadri, sistemi a griglia, biblioteche ...). Fino ad allora, continua con i ragazzi di Sassing!

Ulteriori letture

  • Sass 3.3 (Maptastic Maple) di John W. Long
  • Sass 3.3 è stato rilasciato sul blog di Sass