När och hur man stöder flera versioner av Sass

Den andra dagen granskade jag Jeet gridsystemets Sass-kod, bara för det skull. Efter några kommentarer till GitHub-förvaret förstod jag att Jeets underhållare inte var redo att flytta till Sass 3.3 ännu. I själva verket är det mer korrekt att säga Jeet användare är inte redo att flytta till Sass 3.3, beroende på antalet problem som öppnades när Jeet började använda Sass 3.3-funktioner. 

Hur som helst, poängen är att Jeet inte kan få alla svala och glänsande saker från Sass 3.3. Eller kan det?

*-existerar funktioner

Om du är medveten om vilken version 3.3 som har kommit till Sass kanske du vet att ett par hjälparfunktioner har lagts till i kärnan, som syftar till att hjälpa ramutvecklare att stödja flera versioner av Sass samtidigt:

  • global variabel existerar ($ name): Kontrollerar om en variabel finns i det globala räckviddet
  • variabel existerar ($ name): Kontrollerar om det finns en variabel i nuvarande omfattning
  • funktions existerar ($ name): Kontrollerar om en funktion finns i globala räckvidd
  • mixin-exists ($ name): Kontrollerar om en mixin finns i globala räckvidd

Det finns även en funktions existerar ($ name) funktion, men jag är verkligen inte säker på vad det gör eftersom docs är ganska undvikande om det. Jag tog en titt på funktionskoden, men det gör inte mer änbool (Sass.has_feature? (feature.value)), vilket inte hjälper mycket.

Hur som helst, vi har ett par funktioner som kan kontrollera om en funktion, en mixin eller en variabel finns, och det är ganska trevligt. Dags att gå vidare.

Upptäcka Sass-versionen

Okej, nya funktioner, ganska coola. Men vad händer när vi använder en av dessa funktioner i en Sass 3.2.x miljö? Låt oss ta reda på med ett litet exempel.

// Definiera en variabel $ my-awesome-variabel: 42; // Någonstans annat i koden $ gör-min-awesome-variabel existerar: variabel-existerar ('my-awesome-variable'); // Sass 3.3 -> 'true' // Sass 3.2 -> 'variabel-exist (' my-awesome-variable ')' 

Som du kan se från resultaten, Sass 3.2 inte kraschar eller kasta något fel. Det analyserar variabel existerar (min-awesome-variabel) som en sträng, så i grunden "Variabel existerar (min-awesome-variabel)". För att kontrollera om vi har ett booleskt värde eller en sträng kan vi skriva ett mycket enkelt test:

$ return-type: typ av ($ gör-min-awesome-variabel existerar); // Sass 3.3 -> 'bool' // Sass 3.2 -> 'string' 

Vi kan nu upptäcka Sass-versionen från koden. Hur fantastisk är det? Faktum är att vi inte precis upptäcker Sass-versionen; Vi hittar snarare ett sätt att definiera om vi kör Sass 3.2 eller Sass 3.3, men det är allt vi behöver i det här fallet.

Progressiv förbättring

Låt oss se på att få progressiv förbättring av Sass-funktionerna. Till exempel kan vi använda inbyggda verktyg om de är tillgängliga (Sass 3.3), eller gå tillbaka till egna om de inte är (Sass 3.2). Det var vad jag föreslog för Jeet angående replace-n-te () funktion som används för att ersätta ett värde vid ett specifikt index.

Här är hur vi skulle kunna gör det:

@funktion replace-nth ($ list, $ index, $ value) // Om 'set-nth' existerar (Sass 3.3) @if funktion-existerar ('set-nth') == true @return set- nth ($ list, $ index, $ value);  // Annars är det Sass 3.2 $ resultat: (); $ index: om ($ 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;  

Och då antar du att du är som ...  vad är meningen med att göra detta om vi kan få det att fungera för Sass 3.2 ändå? Rättvis fråga. jag skulle säga prestanda. I vårat fall, set-n: te är en inbyggd funktion från Sass 3.3, vilket innebär att den fungerar i Ruby, vilket betyder att det är snabbare än en anpassad Sass-funktion. I grund och botten görs manipuleringar på Ruby-sidan istället för Sass-kompilatorns.

Ett annat exempel (fortfarande från Jeet) skulle vara a omvänd funktion, vända en lista över värden. När jag först släppte SassyLists, fanns det ingen Sass 3,3 så att omvända en lista skulle innebära att skapa en ny lista, looping bakåt över initiallistan, lägga till värden till den nya. Det gjorde jobbet bra. Men nu har vi tillgång till set-n: te funktion från Sass 3.3 finns det ett mycket bättre sätt att vända en lista: byta index.

För att jämföra prestanda mellan båda implementationerna försökte jag vända det latinska alfabetet (en lista med 26 artiklar) 500 gånger. Resultaten var mer eller mindre:

  • mellan 2s och 3s med "3.2 approach" (using bifoga)
  • aldrig över 2s med "3.3-tillvägagångssättet" (med set-n: te)

Skillnaden skulle vara ännu större med en längre lista, helt enkelt för att byta index är mycket snabbare än att lägga till värden. Så än en gång försökte jag se om vi kunde få ut det mesta i båda världarna. Här är vad jag kom med:

@funktion omvänd ($ list) // Om 'set-nth' existerar (Sass 3.3) @if funktion-existerar ('set-nth') == true @for $ i från 1 till golv (längd ($ lista) / 2) $ list: set-nth (set-nth ($ list, $ i, nth ($ list, - $ i)), - $ i, nth ($ list, $ i));  @return $ list;  // Annars är det Sass 3.2 $ resultat: (); @for $ i från längd ($ lista) * -1 till -1 $ resultat: lägg till ($ resultat, nth ($ list, abs ($ i)));  @return $ result;  

Där igen får vi mest ut av Sass 3.3 samtidigt som vi stöder Sass 3.2. Det här är ganska snyggt, tycker du inte? Naturligtvis kunde vi skriva funktionen tvärtom, först och främst med Sass 3.2. Det gör absolut ingen skillnad alls.

@funktion omvänd ($ list) // Om 'set-nth' existerar inte (Sass 3.2) @if funktion-existerar ('set-nth')! = true $ result: (); @for $ i från längd ($ lista) * -1 till -1 $ resultat: lägg till ($ resultat, nth ($ list, abs ($ i)));  @return $ result;  // Else det är Sass 3.3 @ för $ i från 1 till golv (längd ($ list) / 2) $ list: set-nth (set-nth ($ list, $ i, nth ($ list, - $ i )), - $ i, nth ($ list, $ i));  @return $ list;  

Notera: För att kontrollera om vi kör Sass 3.2 i sista exemplet kunde vi ha testat funktion-exist ("set-nth") == unquote ('function-exist ("set-nth")') också, men det är ganska lång och felaktigt.

Lagring av Sass-versionen i en variabel

För att undvika att kontrollera befintliga funktioner flera gånger, och eftersom vi bara hanterar två olika Sass-versioner här, kan vi lagra Sass-versionen i en global variabel. Så här gick jag till:

$ sass-version: om (funktion-existerar ("function-exist") == true, 3,3, 3,2); 

Jag ska ge dig det är typiskt knepigt. Låt mig förklara vad som händer här. Vi använder om() ternära funktion, utformad så här:

  • det första argumentet från om() funktionen är tillståndet; det utvärderas till Sann eller falsk
  • om villkoret utvärderas till Sann, det returnerar det andra argumentet
  • Annars returnerar den det tredje argumentet

Notera: Sass 3.2 är typ av buggy med ternära funktionen. Det utvärderar alla tre värden, inte bara den som ska returneras. Detta kan ibland leda till några oväntade fel.

Låt oss nu titta på vad som händer med Sass 3.3:

  • funktion-exists ( 'funktion-exists') avkastning Sann för uppenbarligen funktion-exists () existerar
  • sedan funktion-exist ('function-exist') == true är som sant == true vilket är Sann
  • så $ Sass-version är satt till 3,3

Och om vi kör Sass 3.2:

  • funktion-exists ( 'funktion-exists') är inte en funktion utan en sträng, så i grunden "Funktion-exists ( 'funktion-exists')"
  • funktion-exist ('function-exist') == true är falsk
  • så $ Sass-version är satt till 3,2

Om du är en funktion slags person kan du paketera dessa saker i en funktion.

@funktion sass-version () @return om (funktion-existerar ("funktion-exist") == true, 3.3, 3.2);  

Använd sedan det så här:

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

Naturligtvis kunde vi ha kontrollerat förekomsten av en annan 3,3 funktion som ring upp() eller map-get () men det kan eventuellt vara en version av Sass där *-existerar funktioner implementeras, men inte ring upp() eller kartor, så jag tycker att det är bättre att kontrollera att det finns en *-existerar fungera. Och sedan vi använder funktion-exists, låt oss testa den här!

Till framtiden!

Sass 3.3 är den första versionen som ska implementeras *-existerar funktioner, så vi måste kontrollera om * -Exists ($ param)returnerar faktiskt en booleska eller analyseras som en sträng, vilket är typ av hacky.

Låt oss nu säga att Sass 3.4 släpps imorgon med a enhörning() funktion, vilket ger awesomeness och regnbågar till världen. Funktionen att upptäcka Sass-versionen skulle förmodligen se ut så här:

@function sass-versionen () @if funktion-existerar ('enhörning') == true @return 3.4;  @else om funktionen existerar ('enhörning') == false @return 3.3;  @else @return 3.2;  

Neapolitan Unicorn av Erin Hunting

Och sedan om Sass 3.5 ger en regnbåge() funktionen, skulle du uppdatera sass-version () den här vägen:

@function sass-versionen () @if funktion-existerar ('regnbåge') == true @return 3.5;  @else om funktionen existerar ('enhörning') == sann och funktion-existerar ('regnbåge') == false @return 3.4;  @else om funktionen existerar ('enhörning') == false @return 3.3;  @else @return 3.2;  

Och så vidare.

Talar om Unicorns och Rainbows ...

Vad skulle vara verkligen awesome skulle vara förmågan att importera en fil inom ett villkorligt uttalande. Tyvärr är det inte möjligt just nu. Med det sagt är det planerat för Sass 4.0, så låt oss inte förlora hoppet än.

Hur som helst, tänk oss att vi kunde importera en fil baserat på resultatet av sass-version () fungera. Detta skulle göra det lätt att Polyfill Sass 3.3 funktioner för Sass 3.2.

Till exempel kan vi få en fil inklusive alla Sass 3.2-versioner av kartfunktioner med hjälp av tvådimensionella listor istället (som vad Lu Nelson gjorde med Sass-List-Maps) och importera den endast när det gäller Sass 3.2, så här:

// Tyvärr fungerar det inte :( @if sass-version () < 3.3  @import "polyfills/maps";  

Då kan vi använda alla dessa funktioner (som map-get) i vår kod utan att oroa dig för Sass-versionen. Sass 3.3 skulle använda inbyggda funktioner medan Sass 3.2 skulle använda polypyfills. 

Men det fungerar inte.

Man kan komma med idén att definiera funktioner i ett villkorligt uttalande, istället för att importera en hel fil. Då kan vi bara definiera kartrelaterade funktioner om de inte existerar ännu (med andra ord: Sass 3.2). Tyvärr fungerar det inte heller: funktioner och mixins kan inte definieras i ett direktiv.

Funktioner kan inte definieras i kontrolldirektiv eller andra mixins.

Det bästa vi kan göra för tillfället är att definiera både en Sass 3.2 och en Sass 3.3-version i varje funktion som vi har sett ovanpå denna artikel. Men det är inte bara det mer komplicerat att behålla, men det kräver också att varje Sass 3,3-inbyggd funktion lindas i en anpassad funktion. Ta en titt tillbaka på vår replace-n: te funktion från tidigare: vi kan inte namnge det set-n-te (), eller det kommer att bli oändligt rekursivt när du använder Sass 3.3. Så vi måste hitta ett eget namn (i det här fallet replace-n: te).

Att kunna definiera funktioner eller importera filer inom villkorliga direktiv skulle göra det möjligt att behålla inbyggda funktioner som det är medan de genererar polyfills för äldre versioner av Sass. Tyvärr kan vi inte. Det suger.

Under tiden antar vi att vi kan använda detta för att varna användaren när han använder en föråldrad Sass-kompilator. Till exempel, om ditt Sass-bibliotek / ramverk / vad som än använder Sass, kan du lägga till detta ovanpå ditt huvudsakliga stilark:

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

Där. Om koden kraschar eftersom du använder ostödda funktioner som kartor och saker, kommer användaren att varnas varför när han eller hon kontrollerar utgången.

Slutgiltiga tankar

Fram till nu har Sass varit ganska långsam för att gå vidare med versioning standpoint. Jag minns att läsa någonstans att Sass-underhållare önskade gå vidare något snabbare, vilket innebär att vi skulle kunna hantera flera Sass-versioner helst snart.

Lär dig hur du upptäcker Sass-versionen och utnyttjar *-existerar funktionen - enligt min mening - är viktig en dag, åtminstone för vissa projekt (ramverk, nätsystem, bibliotek ...). Fram till dess, fortsätt Sassing killar!

Vidare läsning

  • Sass 3.3 (Maptastic Maple) av John W. Long
  • Sass 3.3 är släppt på Sass Blog