Når og hvordan støtter du flere versjoner av Sass

Den andre dagen, jeg gjennomgikk Jeet grid systemets Sass kode, bare for det skyldes det. Etter noen kommentarer til GitHub-depotet forstod jeg at Jeets vedlikeholdere ikke var klare til å flytte til Sass 3.3 ennå. Faktisk er det mer nøyaktig å si Jeet brukere er ikke klar til å flytte til Sass 3.3, i henhold til antall problemer åpnet da Jeet begynte å bruke Sass 3.3-funksjoner. 

Uansett, poenget er at Jeet ikke kan få alle de kule og skinnende stoffene fra Sass 3.3. Eller kan det?

* -exists funksjoner

Hvis du er klar over hvilken versjon 3.3 som er levert til Sass, kan du kanskje vite at et par hjelpefunksjoner har blitt lagt til i kjerne, som har til formål å hjelpe rammeprodusenter til å støtte flere versjoner av Sass samtidig:

  • global-variabel finnes ($ navn): Kontrollerer om det finnes en variabel i det globale omfanget
  • variabel finnes ($ navn): Kontrollerer om det finnes en variabel i gjeldende rekkefølge
  • funksjons finnes ($ navn): Kontrollerer om en funksjon eksisterer i det globale omfanget
  • mixin-exists ($ navn): Kontrollerer om en mixin finnes i globalt omfang

Det er også en funksjons finnes ($ navn) funksjon, men jeg er virkelig ikke sikker på hva det gjør siden docs er ganske unnvikende om det. Jeg tok en titt på funksjonens kode, men det gjør ikke mer ennbool (Sass.has_feature? (feature.value)), som ikke hjelper mye.

Uansett har vi et par funksjoner som kan kontrollere om en funksjon, en mixin eller en variabel eksisterer, og det er ganske fint. På tide å komme seg videre.

Registrere Sass Versjon

Ok, nye funksjoner, ganske kule. Men hva skjer når vi bruker en av disse funksjonene i et Sass 3.2.x miljø? La oss finne ut med et lite eksempel.

// Definere en variabel $ my-awesome-variabel: 42; // Et eller annet sted i koden $ gjør-min-kjempebra-variabel eksisterer: variabel-eksisterer ('my awesome-variable'); // Sass 3.3 -> 'true' // Sass 3.2 -> 'variabel eksisterer (' my awesome-variable ')' 

Som du kan se fra resultatene, krasjer Sass 3.2 ikke eller kaster noen feil. Det analyserer variabel finnes ( 'min-awesome-variabel') som en streng, så i utgangspunktet "Variabel finnes ( 'min-awesome-variabel')". For å sjekke om vi har en boolsk verdi eller en streng, kan vi skrive en veldig enkel test:

$ return-type: type-of ($ gjør-min-awesome-variabel finnes); // Sass 3.3 -> 'bool' // Sass 3.2 -> 'streng' 

Vi er nå i stand til å oppdage Sass-versjonen fra koden. Hvor fantastisk er det? Faktisk oppdager vi ikke nøyaktig Sass-versjonen; heller vi finner en måte å definere om vi kjører Sass 3.2 eller Sass 3.3, men det er alt vi trenger i dette tilfellet.

Progressiv forbedring

La oss se på å bringe progressiv forbedring til Sass-funksjoner. For eksempel kan vi bruke innfødte verktøy hvis de er tilgjengelige (Sass 3.3), eller falle tilbake til egendefinerte seg hvis de ikke er (Sass 3.2). Det var det jeg foreslo til Jeet angående erstatte-n-te () funksjon, som brukes til å erstatte en verdi på en bestemt indeks.

Her er hvordan vi kunne gjør det:

@function replace-nth ($ list, $ index, $ value) // Hvis 'set-nth' eksisterer (Sass 3.3) @if funksjon eksisterer ('set-nth') == true @return set- nth ($ list, $ index, $ verdi);  // Ellers er det Sass 3.2 $ resultat: (); $ indeks: hvis ($ indeks < 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;  

Og så antar jeg at du er som ...  hva er poenget med å gjøre dette hvis vi kan få det til å fungere for Sass 3.2 uansett? Fair spørsmål. jeg ville ha sagt opptreden. I vårt tilfelle, set-n-te er en innfødt funksjon fra Sass 3.3, som betyr at den fungerer i Ruby, noe som betyr at det er raskere enn en tilpasset Sass-funksjon. I utgangspunktet gjøres manipulasjoner på Ruby-siden i stedet for Sass-kompilatoren.

Et annet eksempel (fortsatt fra Jeet) ville være a omvendt funksjon, reversere en liste over verdier. Da jeg først lanserte SassyLists, var det ingen Sass 3.3, så reversering av en liste ville bety å skape en ny liste, looping bakover over opprinnelig liste, ved å legge til verdier til den nye. Det gjorde jobben bra. Men nå har vi tilgang til set-n-te funksjon fra Sass 3.3, er det en mye bedre måte å reversere en liste: bytte indekser.

For å sammenligne ytelsen mellom begge implementeringer prøvde jeg å reversere det latinske alfabetet (en liste med 26 elementer) 500 ganger. Resultatene var mer eller mindre:

  • mellom 2s og 3s med "3.2-tilnærming" (ved bruk av føyer)
  • aldri over 2s med "3.3-tilnærmingen" (ved bruk av set-n-te)

Forskjellen ville bli enda større med en lengre liste, ganske enkelt fordi bytteindekser er mye raskere enn å legge til verdier. Så igjen prøvde jeg å se om vi kunne få mest mulig ut av begge verdener. Her er hva jeg kom opp med:

@function reverse ($ list) // Hvis 'set-nth' eksisterer (Sass 3.3) @if funksjon eksisterer ('set-nth') == true @for $ i fra 1 til gulv (lengde liste) / 2) $ list: set-nth (set-nth ($ list, $ i, nth ($ list, - $ i)), - $ i, nth ($ list, $ i));  @return $ list;  // Ellers er det Sass 3.2 $ resultat: (); @for $ i fra lengde ($ liste) * -1 til -1 $ resultat: legg til ($ resultat, nth ($ list, abs ($ i)));  @return $ result;  

Der igjen får vi mest mulig ut av Sass 3.3 mens de fortsatt støtter Sass 3.2. Dette er ganske pent, tror du ikke? Selvfølgelig kunne vi skrive funksjonen omvendt, som først og fremst omhandler Sass 3.2. Det gjør absolutt ingen forskjell overhodet.

@function reverse ($ list) // Hvis 'set-nth' eksisterer ikke (Sass 3.2) @if funksjon eksisterer ('set-nth')! = true $ result: (); @for $ i fra lengde ($ liste) * -1 til -1 $ resultat: legg til ($ resultat, nth ($ list, abs ($ i)));  @return $ result;  // Ellers er det Sass 3.3 @ for $ i fra 1 til gulv (lengde ($ list) / 2) $ list: set-nth (set-nth ($ list, $ i, nth ($ list, - $ i )), - $ i, nth ($ list, $ i));  @return $ list;  

Merk: For å sjekke om vi kjører Sass 3.2 i siste eksempel, kunne vi ha testet funksjon-eksisterer ("set-nth") == unquote ('function-exist ("set-nth")') også, men det er ganske lang og feilaktig.

Lagring av Sass-versjonen i en variabel

For å unngå å se etter eksisterende funksjoner flere ganger, og fordi vi bare behandler to forskjellige Sass-versjoner her, kan vi lagre Sass-versjonen i en global variabel. Her er hvordan jeg gikk om det:

$ sass-versjon: hvis (funksjon-eksisterer ("funksjon-eksisterer") == sann, 3,3, 3,2); 

Jeg gir deg det er litt vanskelig. Tillat meg å forklare hva som skjer her. Vi bruker hvis() ternær funksjon, utformet slik:

  • det første argumentet til hvis() funksjon er tilstanden; det vurderer til ekte eller falsk
  • hvis tilstanden vurderes til ekte, det returnerer det andre argumentet
  • Ellers returnerer den det tredje argumentet

Merk: Sass 3.2 er slags buggy med ternærfunksjonen. Det evaluerer alle tre verdiene, ikke bare den som skal returneres. Dette kan noen ganger føre til noen uventede feil.

Nå, la oss ta en titt på hva som skjer med Sass 3.3:

  • funksjons eksisterer ( 'funksjons finnes') avkastning ekte fordi åpenbart funksjons eksisterer () finnes
  • deretter funksjon-eksisterer ('funksjon-eksisterer') == true er som sant == sant som er ekte
  • så $ Sass-versjon er satt til 3.3

Og hvis vi kjører Sass 3.2:

  • funksjons eksisterer ( 'funksjons finnes') er ikke en funksjon, men en streng, så i utgangspunktet "Funksjons eksisterer ( 'funksjons finnes')"
  • funksjon-eksisterer ('funksjon-eksisterer') == true er falsk
  • så $ Sass-versjon er satt til 3.2

Hvis du er en funksjon slags person, kan du pakke inn disse tingene i en funksjon.

@function sass-versjonen () @return hvis (funksjon-eksisterer ("funksjon-eksisterer") == sann, 3,3, 3,2);  

Bruk så det på denne måten:

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

Selvfølgelig kunne vi ha sjekket eksistensen av en annen 3,3-funksjon som anrop() eller kart-get () men det kan potensielt være en versjon av Sass hvor * -exists funksjoner er implementert, men ikke anrop() eller kart, så jeg føler at det er bedre å se etter eksistensen av a * -exists funksjon. Og siden vi bruker funksjons eksisterer, la oss teste denne!

Til fremtiden!

Sass 3.3 er den første versjonen som skal implementeres * -exists funksjoner, så vi må sjekke om * -Exists ($ param)faktisk returnerer en boolsk eller blir analysert som en streng, noe som er litt hacky.

Nå, la oss si at Sass 3.4 blir sluppet i morgen med en Enhjørning() funksjon, bringe awesomeness og rainbows til verden. Funksjonen til å oppdage Sass-versjonen vil sannsynligvis se slik ut:

@function sass-versjon () @if funksjon-eksisterer ('enhjørning') == true @return 3.4;  @else hvis funksjon eksisterer ('enhjørning') == false @return 3.3;  @else @return 3.2;  

Napolitaen Enhjørning av Erin Jakt

Og så hvis Sass 3.5 bringer en regnbue() funksjonen, vil du oppdatere sass-versjon () denne måten:

@function sass-versjon () @if funksjon-eksisterer ('regnbue') == true @return 3.5;  @else hvis funksjon eksisterer ('enhjørning') == sann og funksjon-eksisterer ('regnbue') == falsk @return 3.4;  @else hvis funksjon eksisterer ('enhjørning') == false @return 3.3;  @else @return 3.2;  

Og så videre.

Snakker om Unicorns og Rainbows ...

Hva ville være egentlig fantastisk ville være muligheten til å importere en fil innenfor en betinget uttalelse. Dessverre er dette ikke mulig for øyeblikket. Det blir sagt, det er planlagt for Sass 4.0, så la oss ikke miste håpet ennå.

Uansett, tenk at vi kunne importere en fil basert på resultatet av sass-versjon () funksjon. Dette ville gjøre det enkelt å polyfillere Sass 3.3 funksjoner for Sass 3.2.

For eksempel kan vi ha en fil, inkludert alle Sass 3.2-versjoner av kartfunksjoner ved hjelp av todimensjonale lister i stedet (som hva Lu Nelson gjorde med Sass-List-Maps) og bare importere det når det gjelder Sass 3.2, slik som:

// Dessverre virker dette ikke :( @if sass-versjon () < 3.3  @import "polyfills/maps";  

Da kunne vi bruke alle disse funksjonene (som map-get) i vår kode uten å bekymre deg for Sass-versjonen. Sass 3.3 ville bruke innfødte funksjoner mens Sass 3.2 ville bruke polyfilter. 

Men det virker ikke.

Man kan komme med ideen om å definere funksjoner i en betinget setning, i stedet for å importere en hel fil. Da kunne vi bare definere kartrelaterte funksjoner hvis de ikke eksisterer ennå (med andre ord: Sass 3.2). Dessverre fungerer dette heller ikke: funksjoner og mixins kan ikke defineres i et direktiv.

Funksjoner kan ikke defineres i kontrolldirektiv eller andre mixins.

Det beste vi kan gjøre for øyeblikket er å definere både en Sass 3.2 og en Sass 3.3 versjon i hver funksjon som vi har sett på toppen av denne artikkelen. Men ikke bare er det mer komplisert å vedlikeholde, men det krever også at alle Sass 3.3-innfødte funksjoner skal pakkes inn i en egendefinert funksjon. Ta en titt tilbake på vår erstatte-n-te Funksjon fra tidligere: vi kan ikke nevne det set-n-te (), eller det kommer til å bli uendelig rekursiv når du bruker Sass 3.3. Så vi må finne et egendefinert navn (i dette tilfellet erstatte-n-te).

Å kunne definere funksjoner eller importere filer innenfor betingede direktiver, ville gjøre det mulig å beholde innfødte funksjoner som det er, samtidig som de genererer polypyfills for eldre versjoner av Sass. Dessverre kan vi ikke. Det suger.

I mellomtiden antar vi at vi kunne bruke dette til å advare brukeren når han bruker en utdatert Sass-kompilator. For eksempel, hvis ditt Sass-bibliotek / rammeverk / hva som helst som bruker Sass, kan du legge til dette på toppen av hovedformatarket ditt:

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

Der. Hvis koden krasjer fordi du bruker ustøttede funksjoner som kart og ting, blir brukeren advart om hvorfor når han eller hun sjekker utgangen.

Siste tanker

Inntil nå har Sass vært ganske sakte å bevege seg på versjonsstandard. Jeg husker å lese et sted at Sass-vedlikehavere ønsket å bevege seg litt raskere, noe som betyr at vi kunne finne oss selv å håndtere flere Sass-versjoner helst snart.

Lær hvordan du oppdager Sass-versjonen og bruk av * -exists funksjonen vil etter min mening være viktig en dag, i hvert fall for enkelte prosjekter (rammer, nettverkssystemer, biblioteker ...). Inntil da, fortsett med Sassing gutter!

Videre lesning

  • Sass 3.3 (Maptastic Maple) av John W. Long
  • Sass 3.3 er utgitt på Sass bloggen