@use

Założenia wstępne

Jeśli czytając ten wpis nie chcesz się czuć zakręcony jak bąk w tulipanie to musisz umieć sprawnie poruszać się w  projektach z kilkoma plikami SCSS, które mogą być między sobą importowane i korzystają z syntaxu SASS-a. Dodatkowo - zakladam, że wiesz w jaki sposób kompliować SCSS na CSS.

Intro

Czasami odnoszę wrażenie, że bardzo łatwo przyzwyczaić się do pewnych rozwiązań związanych z SASS-em. Najpierw uczymy się z dokumentacji czegoś nowego, implementujemy to w projekcie, potem w drugim, trzecim, dziesiątym, piętnastym. Struktura naszych projektów jest podobna, style organizujemy podobnie. Życie jest wspaniałe.

Jeśli jakieś zmiany w SASS są wprowadzane, zazwyczaj są dość "delikatne" i łatwe do przetrawienia. Nie czujemy takiej presji jak w przypadku JS-a i związanaj z nim bezustannej gonitwy frameworków i ich updatów. Takie podejście jest dość zgubne i usypia naszą czujność. Prawda jest taka, ze SASS nieustannie się rozwija i czasami można łatwo przegapić zmiany, które są niezwykle pomocne i rozwiązują uciążliwe problemy. Zwłaszcza jeśli pracujemy w projekcie, który ma zdecydowanie za mało styli żeby implementacja SASS-a miała sens albo jeśli customowych styli w projekcie nie ma wcale. W takiej sytuacji nie mamy dodatkowego "przymusu" żeby od czasu do czasu sprawdzić co się zmieniło.

Uświadomiłem to sobie gdy podczas przeglądania dokumentacji Sass-a natknąłem się na poniższy fragment:

"The Sass team discourages the continued use of the @import rule. Sass will gradually phase it out over the next few years, and eventually remove it from the language entirely. Prefer the @use rule instead."

sass-lang.com/documentation/at-rules/import

Pierwsze zderzenie z teraźniejszością

Ze co ? Deprecated ? @import, ktory widnieje w 3/4 moich projektów i repozytoriów ?

Szperasz dalej i dowiadujesz się, żę Twoja ukochana wtyczka do VSC kompilująca SCSS na CSS zaczyna sypać warningami, i straszy Cię informacjami w stylu "@import deprecated ".

Nagle zaczynasz się czuć jakbyś obudził się ze snu zimowego albo jakby Twoi rodzice powiedzieli Ci, że jesteś adoptowany / adoptowana. Zastanawiasz się jak to do cholery możliwe, że Ci to umknęło i masz wrażenie jakbyś przegapił 10 lat rozwoju ludzkości. Znasz to uczucie ? :)

Co więcej, po kilku rozmowach z moimi znajomymi okazało się, że nie jestem odosobnionym przypadkiem i nie tylko ja to przespałem.

@use vs @import

Czemu akurat przyczepili się do starego dobrego @import-a? Tak się składa, że miał kilka dolegliwości, które nieco utrudniały pracę ze stylami. Oto niektóre z nich, które wyszczególnione w oficjalnej dokumentacji SASS-a:

  • Wszystko globalne! Zmienne, mixiny, funkcje. Każdy z tych bajerów automatycznie stawał się globalnie dostępny. Sprawia to, że zlokalizowanie miejsca deklaracji danego kawałka kodu staje się znacznie trudniejsze.
  • Przez to, że wszystko jest globalne możemy mieć problemy z przestrzeniami nazw (namespace)
  • Nie mamy możliwości zadeklarowania prywatnych właściwości. Przecież nie wszystkim w danym pliku musimy chcieć się dzielić.

@use jest oficjalną alternatywą dla @import-a. Korzystając z niej jesteśmy w stanie wyeliminować  powyższe mankamenty.

Importowanie i global scope

Przejdźmy do konkretów. Jak zastąpić @import naszą "nową" zabawką ? Przeanalizujmy poniższy przykład. Przy okazji zrozumiemy jeszcze problem globalnego scope-u zmiennych w plikach SCSS.

Dostajesz zadanie. Musisz zmienić kolor tła na stronie na czerwony. Tworzysz PR i Twoje zmiany wygladają tak:

$main-red: red;

body {
  background-color: $main-red;
}

// main.scss

Wszystko działa, jednak jeden ze sprawdzających Twój kod zwrócił uwagę, że lepiej wrzucić zmienne z kolorami do osobnego pliku. To nie problem. Zróbmy to.

$main-red: red;

//colors.scss

Oczywiscie main musi wiedzieć o tej zmiennej z nowego pliku. Zaimportujmy ją.

@import "./colors.scss";

body {
  background-color: $main-red;
}

// main.scss

Tło staje sie czerwone. Po naszym małym refactorze nadal szystko działa sprawnie.

Tygodnie mijają, projekt się rozwija. Po jakimś czasie chcemy zaimportować kolejny plik do main.scss. Plik ten będzie miał za zadanie dodanie kilku zmian stylistycznych (no shit Sherlock). Powiedzmy, że chcemy zmienić kolor tekstu na stronie na niebieski. Na potrzeby przykładu załóżmy, że w pliku z kolorami colors.scss ktoś dodał dodatkową zmienną z kolorem niebieskim.

body {
  color: $main-blue; //używamy zmiennej, do której ten plik teoretycznie nie ma dostępu
}

// fontColor.scss

Oczywiście nowy plik musimy zaimportować w main.scss

@import "./colors.scss";
@import "./fontColor.scss";

body {
  background-color: $main-red;
}

// main.scss

Użyliśmy zmiennej $main-blue, którą znaleźliśmy w pliku colors.scss (załóżmy, że tam była). Po chwili zdaliśmy sobie sprawę, że przecież w pliku fontColor.scss nie ma żadnego importu. Jakim cudem zmiana zadziałała i kolor tesktu zmienił sie na niebieski ?

To jest właśnie jeden z problemów, o których wspominałem wcześniej. Globalne zmienne. Przeanalizujmy jeszcze raz plik main.scss. Mamy tam dwa importy, jeden pod drugim. Problem polega na tym, że plik fontColor.scss automatycznie ma dostęp do zmiennych z pliku colors.scss. Newet jeśli ich jawnie nie importował. Niby nic wielkiego ale musisz przyznać, że rodzi to pewne zagrożenia i komplikacje, przykładowo związane z konfliktami nazw.

Przestrzenie nazw (namespaces)

Postarajmy sie naprawic ten problem korzystajac z @use

Po pierwsze zmieniamy nasze @import na @use (kapitan oczywisty strikes again)

@use './colors.scss';
@use './fontColor.scss';

body {
  background-color: $main-red;
}

// main.scss

Zapisujemy zmiany. Kompilacja styli zacznie wyrzucać bląd.

Error: Undefined variable: color: $main-blue; fontColor.scss

No tak. To ma sens. Korzystając z @use musimy najpierw zaimportować to czego chcemy użyć. W przypadku pliku fontColor potrzebujemy zawartości pliku colors.scss. Wciągnijmy go.

@use 'colors';

body {
  color: $main-blue;
}

// fontColor.scss

Zrobione. Ale zaraz, to nadal nie działa bo kompilacja cały czas krwawi... O co chodzi ?

Korzystając z @use musimy pamiętać, że nasze zmienne zostają przypisane do konkretnej przestrzeni nazw - namespace-a . Nasz plik nazywa się colors.scss więc jego namespacem w tym przypadku będzie colors

@use 'colors';

body {
  color: colors.$main-blue;
}

// fontColor.scss

Zapisujemy. Nareszcie plik fontColor.scss nie wyrzuca żadnych błędow. Teraz analogicznie musimy poprawić main.scss

@use './colors.scss';
@use './fontColor.scss';

body {
  background-color: colors.$main-red;
}

// main.scss

Udało się. Kompilacja zielona. Brak kolejnych blędow. Strona wygląda tak jak powinna.

Custom namespace

Co jeśli z jakiegoś powodu chcemy mieć kompletnie inny namespace ? Czy jesteśmy uwiązani z nazwą pliku ? Na szczęście nie. SASS umożliwia nam stworzenie customowej przestrzeni nazw. Wygląda to mniej więcej w ten sposob:

@use './colors.scss' as kolorki; // nowy namespace

body {
  background-color: kolorki.$main-red; // nowy namespace w akcji !
}

Możliwości konfiguracyjne

Kolejną przydatną ciekawostką płynącą z @use jest możliwość importowania pliku z dodatkowymi parametrami. Najlepiej zrozumieć to patrząc na przykład. Wróćmy na moment do pliku colors.scss

$main-red: red !default;
$main-blue: blue !default;

// colors.scss

Co się zmieniło ? Jak już zapewne zauważyłeś, dodałem do każdej wartości dodatkową flagę !default. Tutaj zaczyna się zabawa. Korzystając z tej flagi mówimy "Chciałbym być w stanie zmienić te wartości podczas importowania mojego modułu ze stylami!"

W jaki sposób tego użyć? Jest to bardzo proste.

@use './colors.scss' with (
    $main-red: teal,
    $main-blue: orangered
);

// main.scss

Wiem, ze powyższy zapis może się wydawać dziwny ale jego działanie jest bardzo proste. Pamietasz flagi !default z poprzedniego pliku ? One pozwliły nam zmienić wartość podczas importu. Dokladnie to robimy w powyższym snippecie.

Kolor zmiennej $main-red chcemy zmienić na teal

Kolor zmiennej $main-blue chcemy zmienić na orangered

Kod kompiluje się bez problemu. Kolory zostaly zamienione na teal i orangered.

Co w przypadku, gdy mamy dodaną flagę !default do kilku wartości ale podczas importu nie chcemy nadpisac jednej z nich? To rownież jest banalnie proste - wystarczy ją olać podczas importu modułu. Wówczas zastosowana zostanie domyślna wartość z pliku colors.scss widniejąca przed flagą !default. Rozważmy poniższy przykład:

@use './colors.scss' with (
    $main-red: teal,
);

// main.scss

Nadpisujemy wartość zmiennej main-red wartoscią teal (teal to taki zielonopodobny kolor). Main-blue rownież moglibyśmy nadpisać ale tego nie zrobilismy. W tym przypadku - jak już wspomniałem, zastosowany zostanie kolor widniejący obok flagi !default.

Innymi słowy - $main-red: red !default; można rozumieć jako -> "Jesli podczas importu nie zostanie podana inna wartość - defaultuj do red"

Prywatne właściwości

Co jeśli mamy plik z różnymi zmiennymi ale nie chcemy udostępniać niektórych na zewnątrz ? Co jeśli jakaś zmienna jest przydatna tylko i wyłącznie w jednym pliku i inne jej nie potrzebują?

@use rozwiazuje rownież ten problem. Konfiguracja jest bardzo skomplikowana więc się skup. Przed nazwą zmiennej dodaj - lub _

Już. To wszystko : )

$-main-red: red; // prywatna zmienna

// colors.scss
@use './colors.scss';

body {
  background-color: colors.$-main-red;
}

// main.scss

Rezultat: Error: Private members can't be accessed from outside their modules.

Podsumowanie

@use jest zdecydowanie dobrym krokiem w kierunku rozwoju SASS-a. Nie dość, że rozwiązuje on dość istotne problemy, z którymi borykaliśmy się do tej pory to jeszcze jest bardzo prosty w implementacji i myślę, że migracja mniejszych (pod kątem styli) aplikacji nie powinna być bolesna. Być może warto sprawdzić narzędzie do migracji ?

sass-lang.com/documentation/cli/migrator

Źródła

youtube.com/watch?v=CR-a8upNjJ0

sass-lang.com/documentation/at-rules/import

sass-lang.com/documentation/at-rules/use