Quand et comment prendre en charge plusieurs versions de Sass?

L'autre jour, je passais en revue le code Sass du système de grille de Jeet, juste pour le plaisir de le voir. Après quelques commentaires sur le référentiel GitHub, j'ai compris que les responsables de Jeet n'étaient pas encore prêts pour la migration vers Sass 3.3. En réalité, c'est plus juste de dire Jeet utilisateurs ne sont pas prêts à passer à Sass 3.3, en fonction du nombre de problèmes ouverts lorsque Jeet a commencé à utiliser les fonctionnalités de Sass 3.3. 

Quoi qu'il en soit, le fait est que Jeet ne peut pas obtenir tous les trucs cool et brillants de Sass 3.3. Ou peut-il?

* -existe Les fonctions

Si vous connaissez la version 3.3 apportée à Sass, sachez peut-être que plusieurs fonctions d'assistance ont été ajoutées au noyau, dans le but d'aider les développeurs d'infrastructure à prendre en charge plusieurs versions de Sass en même temps:

  • variable-globale-existe ($ nom): vérifie si une variable existe dans la portée globale
  • variable-existe ($ nom): vérifie si une variable existe dans la portée actuelle
  • fonction-existe ($ nom): vérifie si une fonction existe dans la portée globale
  • mixin-existe ($ name): vérifie si un mixin existe dans la portée globale

Il y a aussi fonctionnalité-existe ($ nom) fonctionne, mais je ne suis vraiment pas sûr de ce qu’il fait car les documents sont assez évasifs à ce sujet. J'ai même jeté un coup d'œil au code de la fonction, mais elle ne fait pas plus quebool (Sass.has_feature? (feature.value)), qui n'aide pas beaucoup.

Quoi qu'il en soit, nous avons quelques fonctions capables de vérifier si une fonction, un mixin ou une variable existe, et c'est très agréable. Il est temps de passer à autre chose.

Détection de la version Sass

OK, nouvelles fonctions, plutôt cool. Mais que se passe-t-il lorsque nous utilisons l'une de ces fonctions dans un environnement Sass 3.2.x? Voyons avec un petit exemple.

// Définition d'une variable $ my-awesome-variable: 42; // Quelque part ailleurs dans le code $ ne-mon-génial-variable-exist: variable-exist ('ma-génial-variable'); // Sass 3.3 -> 'true' // Sass 3.2 -> 'variable-existe (' my-awesome-variable ')' 

Comme vous pouvez le constater, Sass 3.2 ne plante pas et ne génère aucune erreur. Il analyse variable-existe ('ma-génial-variable') comme une chaîne, donc en gros "variable-existe ('ma-génial-variable')". Pour vérifier s'il s'agit d'une valeur booléenne ou d'une chaîne, nous pouvons écrire un test très simple:

$ return-type: type-of ($ ne-mon-génial-variable-existe); // Sass 3.3 -> 'bool' // Sass 3.2 -> 'string' 

Nous sommes maintenant en mesure de détecter la version Sass à partir du code. Comment est-ce génial? En fait, nous ne détectons pas exactement la version Sass; nous trouvons plutôt un moyen de définir si nous utilisons Sass 3.2 ou Sass 3.3, mais c'est tout ce dont nous avons besoin dans ce cas.

Amélioration progressive

Voyons comment apporter une amélioration progressive aux fonctions Sass. Par exemple, nous pourrions utiliser des outils natifs s'ils sont disponibles (Sass 3.3) ou utiliser des outils personnalisés s'ils ne le sont pas (Sass 3.2). C'est ce que j'ai suggéré à Jeet en ce qui concerne replace-nth () fonction, utilisée pour remplacer une valeur à un index spécifique.

Voici comment nous pourrait fais le:

@function replace-nth ($ list, $ index, $ value) // Si 'set-nth' existe (Sass 3.3) @if function-exist ('set-nth') == true @return set- nième ($ liste, $ index, $ valeur);  // Sinon c'est Sass 3.2 $ result: (); $ index: if ($ index < 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;  

Et puis je suppose que tu es comme…  quel est le point de le faire si nous pouvons le faire fonctionner pour Sass 3.2 de toute façon? Bonne question. Je dirais performance. Dans notre cas, set-nth est une fonction native de Sass 3.3, ce qui signifie qu’elle fonctionne en Ruby, ce qui signifie qu’elle est beaucoup plus rapide que la fonction Sass personnalisée. Fondamentalement, les manipulations sont faites du côté Ruby au lieu du compilateur Sass.

Un autre exemple (toujours de Jeet) serait un sens inverse fonction, en inversant une liste de valeurs. Lorsque j'ai publié SassyLists pour la première fois, il n'y avait pas de Sass 3.3, donc inverser une liste reviendrait à créer une nouvelle liste, à revenir en arrière sur la liste initiale et à ajouter des valeurs à la nouvelle. Cela a bien fait le travail. Cependant, maintenant que nous avons accès au set-nth fonction de Sass 3.3, il existe un bien meilleur moyen d’inverser une liste: échanger des index.

Pour comparer les performances entre les deux implémentations, j'ai essayé d'inverser 500 fois l'alphabet latin (une liste de 26 éléments). Les résultats ont été plus ou moins:

  • entre 2 et 3 avec "l'approche 3.2" (en utilisant ajouter)
  • jamais au-dessus de 2s avec "l'approche 3.3" (en utilisant set-nth)

La différence serait encore plus grande avec une liste plus longue, tout simplement parce que l'échange d'index est beaucoup plus rapide que l'ajout de valeurs. Alors encore une fois, j'ai essayé de voir si nous pouvions tirer le meilleur parti des deux mondes. Voici ce que je suis venu avec:

@function reverse ($ list) // Si 'set-nth' existe (Sass 3.3) @if function-existe ('set-nth') == true @for $ i de 1 à l'étage (longueur ($ liste) / 2) $ list: set-nth (set-nth ($ list, $ i, nth ($ list, - $ i)), - $ i, nth ($ list, $ i));  @return $ list;  // Sinon c'est Sass 3.2 $ result: (); @for $ i from length ($ list) * -1 à -1 $ result: append ($ result, nth ($ list, abs ($ i)));  @return $ result;  

Là encore, nous tirons le meilleur parti de Sass 3.3 tout en continuant à soutenir Sass 3.2. C'est plutôt chouette, vous ne pensez pas? Bien sûr, nous pourrions écrire la fonction dans l’inverse, en commençant par Sass 3.2. Cela ne fait absolument aucune différence.

@function reverse ($ list) // Si 'set-nth' n'existe pas (Sass 3.2) @if si la fonction-existe ('set-nth')! = true $ result: (); @for $ i from length ($ list) * -1 à -1 $ result: append ($ result, nth ($ list, abs ($ i)));  @return $ result;  // Sinon, c'est Sass 3.3 @for $ i de 1 à étage (longueur ($ liste) / 2) $ list: set-nth (set-nth ($ list, $ i, nth ($ list, - $ i )), - $ i, nth ($ liste, $ i));  @return $ list;  

Remarque: pour vérifier si nous utilisons Sass 3.2 dans le dernier exemple, nous aurions pu tester function-exist ("set-nth") == unquote ('function-exist ("set-nth")') aussi, mais c'est plutôt long et sujet aux erreurs.

Stocker la version Sass dans une variable

Pour éviter de vérifier plusieurs fois les fonctionnalités existantes et, comme nous ne traitons ici que deux versions Sass différentes, nous pouvons stocker la version Sass dans une variable globale. Voici comment je suis allé à ce sujet:

$ sass-version: if (function-exist ("function-exist") == vrai, 3.3, 3.2); 

Je vais vous dire que c'est un peu délicat. Permettez-moi d'expliquer ce qui se passe ici. Nous utilisons le si() fonction ternaire, conçue comme ceci:

  • le premier argument de la si() la fonction est la condition; il évalue à vrai ou faux
  • si la condition est évaluée à vrai, il retourne le deuxième argument
  • sinon il retourne le troisième argument

Remarque: Sass 3.2 est une sorte de buggy avec la fonction ternaire. Il évalue les trois valeurs, pas seulement celle à renvoyer. Cela peut parfois conduire à des erreurs inattendues.

Voyons maintenant ce qui se passe avec Sass 3.3:

  • fonction-existe ('fonction-existe') résultats vrai car évidemment fonction-existe () existe
  • puis fonction-existe ('fonction-existe') == vrai est comme vrai == vrai lequel est vrai
  • alors $ sass-version est réglé sur 3.3

Et si nous utilisons Sass 3.2:

  • fonction-existe ('fonction-existe') n'est pas une fonction mais une chaîne, donc en gros "function-existe ('function-existe')"
  • fonction-existe ('fonction-existe') == vrai est faux
  • alors $ sass-version est réglé sur 3.2

Si vous êtes une sorte de personne fonctionnelle, vous pouvez envelopper cette substance dans une fonction..

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

Alors utilisez-le de cette façon:

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

Bien sûr, nous aurions pu vérifier l’existence d’une autre fonction 3.3 comme appel() ou map-get () mais il pourrait y avoir une version de Sass où * -existe les fonctions sont implémentées, mais pas appel() ou des cartes, alors je me sens comme il est préférable de vérifier l'existence d'un * -existe une fonction. Et depuis que nous utilisons la fonction existe, testons celui-ci!

Au futur!

Sass 3.3 est la première version à implémenter * -existe fonctions, nous devons donc vérifier si * -exists ($ param)renvoie en fait un booléen ou est analysé comme une chaîne, ce qui est une sorte de hacky.

Maintenant, supposons que Sass 3.4 soit publié demain avec un Licorne() fonction, apportant au monde l’impressionnante et les arcs-en-ciel. La fonction permettant de détecter la version Sass ressemblerait probablement à ceci:

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

Licorne Napolitaine par Erin Hunting

Et puis si Sass 3.5 apporte une arc en ciel() fonction, vous mettriez à jour sass-version () par ici:

@function sass-version () @if function-exist ('rainbow') == true @return 3.5;  @else si fonction-existe ('licorne') == vrai et fonction-existe ('arc-en-ciel') == faux @return 3.4;  @else if function-exist ('unicorn') == false @return 3.3;  @else @return 3.2;  

Etc.

En parlant de licornes et de Rainbows…

Quel serait vraiment awesome serait la possibilité d'importer un fichier dans une instruction conditionnelle. Malheureusement, ce n'est pas possible pour le moment. Cela étant dit, il est prévu pour Sass 4.0, alors ne perdons pas espoir pour le moment.

Quoi qu’il en soit, imaginons que nous puissions importer un fichier en fonction du résultat de la sass-version () une fonction. Cela rendrait sacrément facile à polyfiller les fonctions Sass 3.3 pour Sass 3.2..

Par exemple, nous pourrions avoir un fichier contenant toutes les versions Sass 3.2 de fonctions de carte utilisant des listes bidimensionnelles (comme ce que Lu Nelson a fait avec Sass-List-Maps) et l'importer uniquement avec Sass 3.2, comme ceci:

// Malheureusement, cela ne fonctionne pas :( @if sass-version () < 3.3  @import "polyfills/maps";  

Ensuite, nous pourrions utiliser toutes ces fonctions (comme map-get) dans notre code sans se soucier de la version Sass. Sass 3.3 utiliserait des fonctions natives tandis que Sass 3.2 utiliserait polyfills. 

Mais ça ne marche pas.

On pourrait avoir l’idée de définir des fonctions dans une instruction conditionnelle, au lieu d’importer un fichier complet. Ensuite, nous pourrions définir des fonctions liées à la carte uniquement si elles n'existent pas encore (en d’autres termes: Sass 3.2). Malheureusement, cela ne marche pas non plus: les fonctions et mixins ne peuvent pas être définis dans une directive.

Les fonctions ne peuvent pas être définies dans les directives de contrôle ou d'autres mixins.

Le mieux que nous puissions faire pour le moment est de définir une version de Sass 3.2 et une version de Sass 3.3 dans chaque fonction, comme nous l’avons vu en haut de cet article. Cependant, non seulement la maintenance est compliquée, mais chaque fonction native de Sass 3.3 doit également être encapsulée dans une fonction personnalisée. Retournez à notre remplacer fonction de plus tôt: on ne peut pas le nommer set-nth (), ou cela va être sans fin récursif lors de l'utilisation de Sass 3.3. Nous devons donc trouver un nom personnalisé (dans ce cas, remplacer).

Pouvoir définir des fonctions ou importer des fichiers dans des directives conditionnelles permettrait de conserver les fonctionnalités natives telles quelles, tout en générant des polyfill pour les anciennes versions de Sass. Malheureusement, nous ne pouvons pas. Ça craint.

En attendant, je suppose que nous pourrions utiliser cela pour avertir l'utilisateur lorsqu'il utilise un compilateur Sass obsolète. Par exemple, si votre bibliothèque / framework / tout ce qui utilise Sass, vous pouvez ajouter ceci au dessus de votre feuille de style 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à. Si le code se bloque parce que vous utilisez des fonctionnalités non prises en charge, telles que les cartes et d'autres éléments, l'utilisateur sera averti de la raison pour laquelle il vérifie la sortie..

Dernières pensées

Jusqu'à présent, Sass a été assez lent pour passer au point de vue des versions. Je me souviens d'avoir lu quelque part que les responsables de Sass souhaitaient passer à la vitesse supérieure, ce qui signifie que nous pourrions nous retrouver bientôt avec plusieurs versions de Sass..

Apprendre à détecter la version Sass et à utiliser * -existe À mon avis, cette fonction sera importante un jour, du moins pour certains projets (cadres, systèmes de grille, bibliothèques, etc.). Jusque-là, continuez à Sassing les gars!

Lectures complémentaires

  • Sass 3.3 (Maptastic Maple) par John W. Long
  • Sass 3.3 est publié sur le blog Sass