Wanneer en hoe meerdere versies van Sass te ondersteunen

Onlangs heb ik de Sass-code van het Jeet-rastersysteem herzien, gewoon ter wille van het feit. Na wat commentaar op de GitHub-repository, begreep ik dat de beheerders van Jeet nog niet klaar waren om naar Sass 3.3 te verhuizen. In feite is het juister om Jeet te zeggen gebruikers zijn niet klaar om naar Sass 3.3 te gaan, afhankelijk van het aantal problemen dat werd geopend toen Jeet Sass 3.3-functies begon te gebruiken. 

Hoe dan ook, het punt is dat Jeet niet alle coole en glanzende dingen van Sass 3.3 kan krijgen. Of kan het?

* -exists functies

Als u weet wat versie 3.3 aan Sass heeft opgeleverd, weet u misschien dat er een aantal helperfuncties aan de kern zijn toegevoegd, die erop gericht zijn ontwikkelaars van frameworks tegelijkertijd meerdere versies van Sass te ondersteunen:

  • global-variabele bestaat ($ name): controleert of een variabele bestaat in de globale scope
  • variabele bestaat ($ name): controleert of een variabele in het huidige bereik bestaat
  • functie-bestaat ($ name): controleert of een functie bestaat in de globale scope
  • mixin-bestaat ($ name): controleert of er een mix bestaat in de globale scope

Er is ook een feature-bestaat ($ name) functie, maar ik ben er echt niet zeker van wat het doet, omdat de documenten nogal ontwijkend zijn. Ik heb zelfs een blik geworpen op de code van de functie, maar het doet niet meer danbool (Sass.has_feature? (feature.value)), wat niet veel helpt.

Hoe dan ook, we hebben een paar functies die kunnen controleren of een functie, een mixin of een variabele bestaat, en dat is best aardig. Tijd om verder te gaan.

Sass-versie detecteren

Oké, nieuwe functies, best cool. Maar wat gebeurt er als we een van die functies in een Sass 3.2.x-omgeving gebruiken? Laten we het eens bekijken met een klein voorbeeld.

// Een variabele $ my-awesome-variabele definiëren: 42; // Ergens anders in de code bestaat $ does-my-awesome-variable: variable-exists ('my-awesome-variable'); // Sass 3.3 -> 'waar' // Sass 3.2 -> 'variable-exists (' my-awesome-variable ')' 

Zoals u kunt zien aan de resultaten, crasht of gooit Sass 3.2 geen fouten. Het parseert variabele bestaat ( 'mijn-awesome-variabele') als een string, dus eigenlijk "Variabele bestaat ( 'mijn-awesome-variabele')". Om te controleren of we te maken hebben met een booleaanse waarde of een tekenreeks, kunnen we een heel eenvoudige test schrijven:

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

We kunnen nu de Sass-versie detecteren vanuit de code. Hoe geweldig is dat? Eigenlijk detecteren we niet precies de Sass-versie; in plaats daarvan vinden we een manier om te bepalen of we Sass 3.2 of Sass 3.3 gebruiken, maar dat is alles wat we in dit geval nodig hebben.

Progressive Enhancement

Laten we kijken naar de progressieve verbetering van Sass-functies. We kunnen bijvoorbeeld native tools gebruiken als deze beschikbaar zijn (Sass 3.3), of terugvallen naar aangepaste tools als dat niet het geval is (Sass 3.2). Dat heb ik aan Jeet voorgesteld met betrekking tot de replace-n () functie, die wordt gebruikt om een ​​waarde op een specifieke index te vervangen.

Dit is hoe wij kon doe het:

@functie replace-nth ($ lijst, $ index, $ waarde) // Als 'set-nth' bestaat (Sass 3.3) @ if-function-exists ('set-nth') == true @return set- nth ($ lijst, $ index, $ waarde);  // Anders is het Sass 3.2 $ resultaat: (); $ 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;  

En dan denk ik dat je bent zoals ...  wat is het nut om dit te doen als we het toch voor Sass 3.2 kunnen laten werken? Terechte vraag. ik zou zeggen prestatie. In ons geval, set-n is een native functie van Sass 3.3, wat betekent dat het werkt in Ruby, wat betekent dat het veel sneller is dan een aangepaste Sass-functie. In principe worden manipulaties gedaan aan de Ruby-kant in plaats van de Sass-compilers.

Een ander voorbeeld (nog steeds van Jeet) zou een zijn omgekeerde functie, een zoeklijst omkeren. Toen ik SassyLists voor het eerst uitbracht, was er geen Sass 3.3, dus het omkeren van een lijst zou betekenen dat een nieuwe lijst wordt gemaakt, een omgekeerde over de oorspronkelijke lijst loopt, waarden worden toegevoegd aan de nieuwe lijst. Het deed het werk goed. Nu we echter toegang hebben tot de set-n functie van Sass 3.3 is er een veel betere manier om een ​​lijst om te keren: indexen omwisselen.

Om de prestaties tussen beide implementaties te vergelijken, probeerde ik 500 keer het Latijnse alfabet (een lijst van 26 items) om te keren. De resultaten waren min of meer:

  • tussen 2s en 3s met de "3.2 approach" (met toevoegen)
  • nooit boven de 2s met de "3.3 benadering" (met set-n)

Het verschil zou nog groter zijn bij een langere lijst, simpelweg omdat het omwisselen van indexen veel sneller gaat dan het toevoegen van waarden. Dus nogmaals, ik probeerde te zien of we het beste uit beide werelden konden halen. Dit is wat ik bedacht:

@function reverse ($ list) // Als 'set-nth' bestaat (Sass 3.3) @if function-exists ('set-nth') == true @for $ i van 1 tot en met verdieping (lengte ($ lijst) / 2) $ lijst: set-nth (set-nth ($ lijst, $ i, nth ($ lijst, - $ i)), - $ i, nth ($ lijst, $ i));  @return $ lijst;  // Anders is het Sass 3.2 $ resultaat: (); @ voor $ i van lengte ($ lijst) * -1 tot -1 $ resultaat: toevoegen ($ resultaat, nde ($ lijst, abs ($ i)));  @return $ resultaat;  

Ook hier halen we het beste uit Sass 3.3 terwijl we Sass 3.2 nog steeds ondersteunen. Dit is behoorlijk netjes, vind je niet? Natuurlijk kunnen we de functie andersom schrijven, eerst met Sass 3.2. Het maakt absoluut geen enkel verschil.

@function reverse ($ list) // Als 'set-nth' niet bestaat (Sass 3.2) @if function-exists ('set-nth')! = true $ result: (); @ voor $ i van lengte ($ lijst) * -1 tot -1 $ resultaat: toevoegen ($ resultaat, nde ($ lijst, abs ($ i)));  @return $ resultaat;  // Anders is het Sass 3.3 @for $ i van 1 tot en met verdieping (lengte ($ lijst) / 2) $ lijst: set-nth (set-nth ($ lijst, $ i, nth ($ lijst, - $ i )), - $ i, nth ($ lijst, $ i));  @return $ lijst;  

Notitie: om te controleren of we Sass 3.2 in het laatste voorbeeld gebruiken, hadden we kunnen testen function-exists ("set-nth") == unquote ('function-exists ("set-nth")') ook, maar dat is vrij lang en foutgevoelig.

De Sass-versie opslaan in een variabele

Om te voorkomen dat meerdere keren wordt gecontroleerd op bestaande functies en omdat we hier alleen twee verschillende Sass-versies behandelen, kunnen we de Sass-versie opslaan in een globale variabele. Hier is hoe ik het deed:

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

Ik zal je dat een beetje lastig noemen. Staat u mij toe om uit te leggen wat hier gebeurt. We gebruiken de als() ternaire functie, ontworpen als volgt:

  • het eerste argument van de als() functie is de voorwaarde; het evalueert naar waar of vals
  • als de conditie evalueert naar waar, het geeft het tweede argument als resultaat
  • anders geeft het het derde argument terug

Notitie: Sass 3.2 is een soort van buggy met de ternaire functie. Het evalueert alle drie de waarden, niet alleen degene die moet worden geretourneerd. Dit kan soms leiden tot enkele onverwachte fouten.

Laten we nu eens kijken naar wat er met Sass 3.3 aan de hand is:

  • functiespecifieke bestaat ( 'functiespecifieke bestaat) komt terug waar want natuurlijk functiespecifieke bestaat () bestaat
  • dan function-exists ('function-exists') == true is als true == true welke is waar
  • zo $ Sass-versie ingesteld op 3.3

En als we Sass 3.2 gebruiken:

  • functiespecifieke bestaat ( 'functiespecifieke bestaat) is geen functie maar een reeks, dus eigenlijk "Functie-bestaat ( 'functie-exists')"
  • function-exists ('function-exists') == true is vals
  • zo $ Sass-versie ingesteld op 3.2

Als je een soort van persoon bent, kun je dit spul in een functie plaatsen.

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

Gebruik het dan op deze manier:

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

Natuurlijk hadden we het bestaan ​​van nog eens een 3.3-functie kunnen controleren () aanroepen of kaart-get () maar er kan mogelijk een versie van Sass zijn waar * -exists functies zijn geïmplementeerd, maar niet () aanroepen of kaarten, dus ik heb het gevoel dat het beter is om te controleren op het bestaan ​​van een * -exists functie. En omdat we gebruiken functiespecifieke bestaat, laten we deze testen!

Naar de toekomst!

Sass 3.3 is de eerste versie die wordt geïmplementeerd * -exists functies, dus we moeten controleren of * -Exists ($ param)retourneert eigenlijk een booleaanse waarde of wordt geparseerd als een tekenreeks, wat een beetje hacky is.

Laten we nu zeggen dat Sass 3.4 morgen wordt vrijgegeven met een eenhoorn() functioneren, ontzagwekkendheid brengen en regenbogen ter wereld brengen. De functie om de Sass-versie te detecteren zou er waarschijnlijk als volgt uitzien:

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

Napolitaanse eenhoorn door Erin Hunting

En als Sass 3.5 dan een brengt regenboog() functie, zou je updaten sass-versie () op deze manier:

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

Enzovoorts.

Over Unicorns en Rainbows gesproken ...

Wat zou zijn werkelijk geweldig is de mogelijkheid om een ​​bestand te importeren binnen een voorwaardelijke verklaring. Helaas is dit op dit moment niet mogelijk. Dat gezegd hebbende, het staat gepland voor Sass 4.0, dus laten we de hoop nog niet verliezen.

Hoe dan ook, stel je voor dat we een bestand konden importeren op basis van het resultaat van de sass-versie () functie. Dit zou het eenvoudig maken om Sass 3.3-functies voor Sass 3.2 te polyfillen.

We zouden bijvoorbeeld een bestand kunnen hebben met alle Sass 3.2-versies van kaartfuncties door middel van tweedimensionale lijsten (zoals wat Lu Nelson met Sass-List-Maps heeft gedaan) en het alleen importeren als het met Sass 3.2 te maken heeft, zoals:

// Helaas werkt dit niet :( @if sass-version () < 3.3  @import "polyfills/maps";  

Dan kunnen we al die functies gebruiken (zoals map-get) in onze code zonder zich zorgen te hoeven maken over de Sass-versie. Sass 3.3 zou native-functies gebruiken, terwijl Sass 3.2 polyfills zou gebruiken. 

Maar dat werkt niet.

Men zou kunnen komen met het idee om functies in een voorwaardelijke verklaring te definiëren, in plaats van een volledig bestand te importeren. Vervolgens kunnen we kaartgerelateerde functies alleen definiëren als ze nog niet bestaan ​​(met andere woorden: Sass 3.2). Helaas werkt dit ook niet: functies en mixins kunnen niet in een richtlijn worden gedefinieerd.

Functies kunnen niet worden gedefinieerd binnen besturingsrichtlijnen of andere mixins.

Het beste wat we op dit moment kunnen doen, is het definiëren van zowel een Sass 3.2 als een Sass 3.3-versie in elke functie zoals we bovenaan dit artikel hebben gezien. Maar het is niet alleen ingewikkelder om te onderhouden, maar het vereist ook dat elke Sass 3.3 native-functie in een aangepaste functie wordt gewikkeld. Kijk eens naar onze replace-n functie van vroeger: we kunnen het niet noemen set-n (), of het zal eindeloos recursief zijn wanneer je Sass 3.3 gebruikt. Dus we moeten een aangepaste naam vinden (in dit geval replace-n).

Het kunnen definiëren van functies of het importeren van bestanden binnen voorwaardelijke richtlijnen zou het mogelijk maken om oorspronkelijke functies te behouden zoals het is, terwijl het polyfills genereert voor oudere versies van Sass. Helaas kunnen we dat niet. Dat is balen.

Ondertussen denk ik dat we dit kunnen gebruiken om de gebruiker te waarschuwen wanneer hij een verouderde Sass-compiler gebruikt. Als uw Sass-bibliotheek / framework / wat dan ook Sass gebruikt, kunt u dit bijvoorbeeld bovenop uw hoofdstijlblad toevoegen:

@if sass-versie () < 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.";  

Er. Als de code crasht omdat u niet-ondersteunde functies zoals kaarten en dergelijke gebruikt, wordt de gebruiker gewaarschuwd waarom hij de uitvoer controleert.

Laatste gedachten

Tot nu toe was Sass vrij traag om het standpunt van versiebeheer te veranderen. Ik kan me herinneren ergens te hebben gelezen dat Sass-beheerders iets sneller wilden gaan werken, wat betekent dat we snel met verschillende Sass-versies te maken zouden kunnen krijgen.

Leren hoe de Sass-versie te detecteren en te gebruiken * -exists functie zal - naar mijn mening - op een dag belangrijk zijn, althans voor sommige projecten (kaders, rastersystemen, bibliotheken ...). Tot die tijd, blijf Sassing jongens!

Verder lezen

  • Sass 3.3 (Maptastic Maple) door John W. Long
  • Sass 3.3 is uitgebracht op de Sass-blog