Obsługa CSS-in-JS w Narzędziach deweloperskich

Alex Rudenko
Alex Rudenko

W tym artykule omawiamy obsługę CSS-in-JS w Narzędziach deweloperskich, która pojawiła się w Narzędziach deweloperskich od wersji Chrome 85, oraz ogólnie o tym, czym jest „CSS-in-JS” i jak różni się od zwykłego kodu CSS, który jest obsługiwany przez Narzędzia deweloperskie od dłuższego czasu.

Co to jest CSS-in-JS?

Definicja CSS-in-JS jest dość niejasna. Ogólnie rzecz biorąc, jest to metoda zarządzania kodem CSS za pomocą JavaScriptu. Na przykład może to oznaczać, że zawartość CSS jest definiowana za pomocą JavaScriptu, a ostateczne dane wyjściowe CSS są generowane na bieżąco przez aplikację.

W kontekście Narzędzi deweloperskich kod CSS-in-JS oznacza, że treści CSS są wstawiane na stronie za pomocą interfejsów API CSSOM. Zwykły CSS jest wstrzykiwany za pomocą elementów <style> lub <link> i ma źródło statyczne (np. węzeł DOM lub zasób sieciowy). Natomiast kod CSS-in-JS często nie ma statycznego źródła. Szczególnym przypadkiem jest to, że zawartość elementu <style> może być aktualizowana za pomocą interfejsu CSSOM API, przez co źródło nie jest zsynchronizowane z rzeczywistym arkuszem stylów CSS.

Jeśli używasz jakiejkolwiek biblioteki CSS-in-JS (np. styled-component, Emotion, JSS), może ona wstrzyknąć style przy użyciu interfejsów API CSSOM API w zależności od trybu programowania i przeglądarki.

Przyjrzyjmy się kilku przykładom, jak wstrzyknąć arkusz stylów za pomocą interfejsu CSSOM API, podobnie jak w przypadku bibliotek CSS-in-JS.

// Insert new rule to an existing CSS stylesheet
const element = document.querySelector('style');
const stylesheet = element.sheet;
stylesheet.replaceSync('.some { color: blue; }');
stylesheet.insertRule('.some { color: green; }');

Możesz też utworzyć zupełnie nowy arkusz stylów:

// Create a completely new stylesheet
const stylesheet = new CSSStyleSheet();
stylesheet.replaceSync('.some { color: blue; }');
stylesheet.insertRule('.some { color: green; }');

// Apply constructed stylesheet to the document
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];

Obsługa CSS w Narzędziach deweloperskich

W Narzędziach deweloperskich najczęściej używaną funkcją do obsługi CSS jest panel Style. W panelu Style możesz sprawdzić, jakie reguły mają zastosowanie do konkretnego elementu, a także edytować je i przeglądać w czasie rzeczywistym zmiany wprowadzane na stronie.

Przed ubiegłym rokiem obsługa reguł CSS zmodyfikowanych za pomocą interfejsów CSSOM API była raczej ograniczona: widoczna było tylko zastosowane reguły, ale nie można było ich edytować. W zeszłym roku głównym celem było umożliwienie edycji reguł CSS-in-JS w panelu Style. Czasami nazywamy także style CSS-in-JS „constructed”, aby wskazać, że zostały utworzone za pomocą internetowych interfejsów API.

Przyjrzyjmy się bliżej działaniu edycji stylów w Narzędziach deweloperskich.

Mechanizm edycji stylu w Narzędziach deweloperskich

Mechanizm edycji stylu w Narzędziach deweloperskich

Gdy wybierzesz element w Narzędziach deweloperskich, wyświetli się panel Style. Panel Styles generuje polecenie CDP o nazwie CSS.getMatchedStylesForNode, które pobiera reguły CSS dotyczące elementu. CDP to skrót od Chrome DevTools Protocol (protokół Chrome DevTools Protocol). Jest to interfejs API, który umożliwia frontendowi Narzędzi deweloperskich uzyskanie dodatkowych informacji o sprawdzanej stronie.

Po wywołaniu CSS.getMatchedStylesForNode identyfikuje wszystkie arkusze stylów w dokumencie i analizuje je za pomocą parsera CSS przeglądarki. Następnie tworzy indeks, który wiąże każdą regułę CSS z pozycją w źródle arkusza stylów.

Możesz zapytać, dlaczego musi ponownie przeanalizować CSS? Problem polega na tym, że ze względu na wydajność przeglądarka nie interesuje się położeniem źródłowym reguł CSS i dlatego ich nie przechowuje. Jednak narzędzia deweloperskie potrzebują pozycji źródła do obsługi edycji CSS. Nie chcemy, aby zwykli użytkownicy Chrome płacili za spadek wydajności, ale chcemy, aby użytkownicy Narzędzi deweloperskich mieli dostęp do pozycji źródłowych. Ta metoda ponownej analizy dotyczy obu przypadków użycia z minimalnymi wadami.

Następnie implementacja CSS.getMatchedStylesForNode prosi mechanizm stylu przeglądarki o podanie reguł CSS pasujących do danego elementu. Na koniec wiąże się ona z powiązaniem reguł zwróconych przez mechanizm stylów z kodem źródłowym i zapewnia ustrukturyzowaną odpowiedź na temat reguł CSS, dzięki czemu Narzędzia deweloperskie wiedzą, która część reguły jest selektorem lub właściwościami. Pozwala on Narzędziom deweloperskim osobno edytować selektor i właściwości.

Teraz przyjrzyjmy się edytowaniu. Pamiętaj, że funkcja CSS.getMatchedStylesForNode zwraca pozycje źródłowe w przypadku każdej reguły? To bardzo ważne przy montażu. Gdy zmieniasz regułę, Narzędzia deweloperskie wydają inne polecenie CDP, które faktycznie aktualizuje stronę. Polecenie to zawiera pierwotną pozycję fragmentu uaktualnionej reguły oraz nowy tekst, za pomocą którego fragment należy zaktualizować.

Podczas obsługi wywołania edycji w backendzie Narzędzia deweloperskie aktualizują docelowy arkusz stylów. Aktualizuje również kopię źródłowego arkusza stylów, którą utrzymuje, i aktualizuje pozycje źródłowe dla zaktualizowanej reguły. W odpowiedzi na wywołanie edycji interfejs Narzędzi deweloperskich pobiera zaktualizowane pozycje dla zaktualizowanego fragmentu tekstu.

To wyjaśnia, dlaczego edytowanie kodu CSS-in-JS w Narzędziach deweloperskich nie działa od razu: CSS-in-JS nie ma rzeczywistego źródła zapisanego nigdzie, a reguły CSS znajdują się w pamięci przeglądarki w strukturach danych CSSOM.

Jak dodaliśmy obsługę CSS-in-JS

W związku z tym, aby umożliwić edycję reguł CSS-in-JS, uznaliśmy, że najlepszym rozwiązaniem będzie utworzenie źródła arkuszy stylów, które można edytować za pomocą opisanego powyżej mechanizmu.

Pierwszym krokiem jest utworzenie tekstu źródłowego. Mechanizm stylów przeglądarki przechowuje reguły CSS w klasie CSSStyleSheet. Ta klasa ma instancje, które możesz utworzyć z JavaScriptu, jak omówiliśmy wcześniej. Oto kod do kompilacji tekstu źródłowego:

String InspectorStyleSheet::CollectStyleSheetRules() {
  StringBuilder builder;
  for (unsigned i = 0; i < page_style_sheet_->length(); i++) {
    builder.Append(page_style_sheet_->item(i)->cssText());
    builder.Append('\n');
  }
  return builder.ToString();
}

Wykonuje iteracje nad regułami znalezionymi w elemencie CSSStyleSheet i tworzy z niego pojedynczy ciąg znaków. Ta metoda jest wywoływana po utworzeniu instancji klasy InspectorStyleSheet. Klasa InspectorStyleSheet pakuje wystąpienie CSSStyleSheet i wyodrębnia dodatkowe metadane wymagane przez Narzędzia deweloperskie:

void InspectorStyleSheet::UpdateText() {
  String text;
  bool success = InspectorStyleSheetText(&text);
  if (!success)
    success = InlineStyleSheetText(&text);
  if (!success)
    success = ResourceStyleSheetText(&text);
  if (!success)
    success = CSSOMStyleSheetText(&text);
  if (success)
    InnerSetText(text, false);
}

W tym fragmencie znajduje się kod CSSOMStyleSheetText, który wywołuje wewnętrznie element CollectStyleSheetRules. Jeśli arkusz stylów nie jest wbudowany lub nie jest arkuszem stylów zasobów, jest wywoływana funkcja CSSOMStyleSheetText. Zasadniczo te 2 fragmenty umożliwiają już podstawową edycję arkuszy stylów utworzonych przy użyciu konstruktora new CSSStyleSheet().

Szczególnym przypadkiem są arkusze stylów powiązane z tagiem <style>, które zostały zmutowane za pomocą interfejsu CSSOM API. W takim przypadku arkusz stylów zawiera tekst źródłowy i dodatkowe reguły, których nie ma w źródle. Aby radzić sobie z tym przypadkiem, wprowadzamy metodę scalania tych dodatkowych reguł z tekstem źródłowym. W tym przypadku kolejność ma znaczenie, ponieważ reguły CSS można wstawiać w środku oryginalnego tekstu źródłowego. Załóżmy np., że pierwotny element <style> zawierał następujący tekst:

/* comment */
.rule1 {}
.rule3 {}

Następnie strona wstawiła nowe reguły za pomocą interfejsu JS API tworzące taką kolejność reguł: .rule0, .rule1, .rule2, .rule3, .rule4. Tekst źródłowy po scaleniu powinien wyglądać tak:

.rule0 {}
/* comment */
.rule1 {}
.rule2 {}
.rule3 {}
.rule4 {}

Zachowanie oryginalnych komentarzy i wcięć jest ważne przy edytowaniu, ponieważ teksty źródłowe w regułach muszą być dokładne.

Kolejną cechą wyjątkową w przypadku arkuszy stylów CSS-in-JS jest to, że strona może w dowolnym momencie je zmienić. Jeśli rzeczywiste reguły CSSOM nie byłyby zsynchronizowane z wersją tekstową, edycja nie zadziałała. W tym celu wprowadziliśmy tzw. sondę, która umożliwia przeglądarce powiadamianie backendu narzędzia o wprowadzaniu mutacji w arkuszu stylów. Zmutowane arkusze stylów są następnie synchronizowane podczas następnego wywołania CSS.getMatchedStylesForNode.

Po wykonaniu tych wszystkich czynności edycja z użyciem CSS-in-JS już działa, ale chcieliśmy ulepszyć interfejs użytkownika, aby wskazać, czy arkusz stylów został utworzony. Dodaliśmy nowy atrybut o nazwie isConstructed do atrybutu CSS.CSSStyleSheetHeader interfejsu CDP, z którego korzysta interfejs użytkownika do prawidłowego wyświetlania źródła reguły CSS:

Konstruktalny arkusz stylów

Podsumowanie

Podsumowując, omówiliśmy przypadki użycia związane z CSS-in-JS, których nie obsługują Narzędzia deweloperskie, i zastosowaliśmy odpowiednie rozwiązanie. Ciekawą częścią tej implementacji jest to, że udało nam się wykorzystać istniejącą funkcjonalność, tworząc reguły CSSOM CSS, używając zwykłego tekstu źródłowego, dzięki czemu nie trzeba było całkowicie zmieniać architektury w Narzędziach deweloperskich.

Więcej informacji znajdziesz w naszej propozycji projektu lub o błędzie śledzenia w Chromium, który odwołuje się do wszystkich powiązanych poprawek.

Pobieranie kanałów podglądu

Jako domyślnej przeglądarki programistycznej możesz użyć Chrome Canary, Dev lub Beta. Te kanały podglądu dają dostęp do najnowszych funkcji Narzędzi deweloperskich, testują nowoczesne interfejsy API platform internetowych oraz wykrywają problemy w witrynie, zanim zrobią to użytkownicy.

Kontakt z zespołem Narzędzi deweloperskich w Chrome

Użyj tych opcji, aby omówić nowe funkcje i zmiany w poście lub wszelkich innych sprawach związanych z Narzędziami dla programistów.

  • Sugestię lub opinię możesz przesłać na stronie crbug.com.
  • Aby zgłosić problem z Narzędziami deweloperskimi, kliknij Więcej opcji   Więcej   > Pomoc > Zgłoś problemy z Narzędziami deweloperskimi.
  • zatweetować na @ChromeDevTools.
  • Komentarze do filmów o narzędziach dla deweloperów w YouTube lub filmach w YouTube ze wskazówkami dotyczącymi Narzędzi deweloperskich.