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 Chrome od wersji 85, oraz to, co rozumiemy przez CSS-in-JS i czym różni się od zwykłego arkusza CSS, który jest obsługiwany przez Narzędzia deweloperskie od dłuższego czasu.

Co to jest CSS-in-JS?

Definicja obiektu CSS-in-JS jest dość ogólnikowa. Ogólnie rzecz biorąc, jest to sposób zarządzania kodem CSS za pomocą języka JavaScript. Może to na przykład oznaczać, że treść CSS jest definiowana za pomocą JavaScriptu, a ostateczne dane CSS są generowane przez aplikację na bieżąco.

W kontekście narzędzi deweloperskich atrybut CSS-in-JS oznacza, że treści CSS są wstrzykiwane na stronie za pomocą interfejsów CSSOM API. Zwykły kod CSS jest wstrzykiwany przy użyciu elementów <style> lub <link> i zawiera źródło statyczne (np. węzeł DOM lub zasób sieciowy). W przeciwieństwie do tego CSS-in-JS często nie ma statycznego źródła. Szczególnym przypadkiem jest to, że zawartość elementu <style> można aktualizować przy użyciu interfejsu CSSOM API, co powoduje brak synchronizacji źródła z rzeczywistym arkuszem stylów CSS.

Jeśli używasz dowolnej biblioteki CSS-in-JS (np. styled-component, Emotion, JSS), może ona wstrzykiwać style za pomocą wewnętrznych interfejsów CSSOM API w zależności od trybu programowania i przeglądarki.

Przyjrzyjmy się kilku przykładom, jak wstrzykiwać arkusz stylów za pomocą interfejsu CSSOM API, podobnie jak robią to biblioteki 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 wykorzystywaną funkcją w przypadku obsługi stylów CSS jest panel Style. W panelu Style możesz zobaczyć, które reguły mają zastosowanie do konkretnego elementu, a także je edytować i śledzić zmiany na stronie w czasie rzeczywistym.

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

Przyjrzyjmy się bliżej funkcjom edytowania 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, pojawi się panel Style. Panel Style generuje polecenie CDP o nazwie CSS.getMatchedStylesForNode, aby pobrać reguły CSS, które mają zastosowanie do danego elementu. CDP oznacza protokół Chrome DevTools Protocol. Jest to interfejs API, który umożliwia frontendowi Narzędzi deweloperskich uzyskanie dodatkowych informacji o kontrolowanej 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 konieczne jest ponowne analizowanie CSS? Problem polega na tym, że ze względu na wydajność sama przeglądarka nie bierze pod uwagę pozycji źródłowych reguł CSS i dlatego ich nie przechowuje. Aby umożliwić edycję CSS, Narzędzia deweloperskie potrzebują pozycji źródeł. Nie chcemy, aby stali użytkownicy Chrome musieli płacić karę za wydajność, ale chcemy, aby użytkownicy Narzędzi deweloperskich mieli dostęp do pozycji źródłowych. Ta analiza obejmuje oba przypadki użycia z minimalnymi wadami.

Następnie implementacja CSS.getMatchedStylesForNode prosi silnik stylów przeglądarki o podanie reguł CSS pasujących do danego elementu. Na koniec metoda wiąże reguły zwracane przez mechanizm stylów z kodem źródłowym i udostępnia ustrukturyzowaną odpowiedź na temat reguł CSS, tak aby Narzędzia deweloperskie wiedziały, która część reguły zawiera selektor lub właściwości. Umożliwia ono Narzędziach deweloperskich niezależne edytowanie selektora i właściwości.

Przyjrzyjmy się teraz edytowaniu. Pamiętaj, że funkcja CSS.getMatchedStylesForNode zwraca pozycje źródłowe dla każdej reguły? To kluczowe do montażu. Gdy zmienisz regułę, Narzędzia deweloperskie wydają kolejne polecenie CDP, które faktycznie aktualizuje stronę. Polecenie zawiera pierwotną pozycję fragmentu aktualizowanej reguły oraz nowy tekst, którego fragment ma zostać zaktualizowany.

Podczas obsługi wywołania edycji w backendzie Narzędzia deweloperskie aktualizuje docelowy arkusz stylów. Zaktualizuje także kopię źródła arkusza stylów, które przechowuje, oraz pozycje źródłowe dla zaktualizowanej reguły. W odpowiedzi na wywołanie edycji frontend Narzędzi deweloperskich otrzymuje zaktualizowane pozycje zaktualizowanego właśnie fragmentu tekstu.

Wyjaśnia to, dlaczego edycja kodu CSS-in-JS w Narzędziach deweloperskich nie udała się na nowo: CSS-in-JS nie ma rzeczywistego źródła zapisanego w żadnym miejscu, a reguły CSS znajdują się w pamięci przeglądarki w strukturach danych CSSOM.

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

Dlatego, aby móc edytować reguły CSS-in-JS, uznaliśmy, że najlepszym rozwiązaniem będzie utworzenie źródła skonstruowanych arkuszy stylów, które można edytować za pomocą opisanego powyżej mechanizmu.

Pierwszym krokiem jest utworzenie tekstu źródłowego. Mechanizm stylu przeglądarki przechowuje reguły CSS w klasie CSSStyleSheet. Ta klasa to klasa, której instancje możesz tworzyć za pomocą JavaScriptu, jak omówiliśmy wcześniej. Kod do utworzenia tekstu źródłowego wygląda tak:

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 iterację nad regułami znalezionymi w instancji CSSStyleSheet i tworzy na jej podstawie pojedynczy ciąg znaków. Ta metoda jest wywoływana po utworzeniu instancji klasy InspectorStyleSheet. Klasa InspectorStyleSheet zapakowuje instancję CSSStyleSheet i wyodrębnia dodatkowe metadane, które są 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 kodu widzimy, że funkcja CSSOMStyleSheetText wywołuje wewnętrznie funkcję CollectStyleSheetRules. Pole CSSOMStyleSheetText jest wywoływane, jeśli arkusz stylów nie jest wbudowany lub jeśli nie jest to arkusz stylów zasobu. Te dwa fragmenty kodu umożliwiają już podstawową edycję arkuszy stylów utworzonych za pomocą konstruktora new CSSStyleSheet().

Szczególnym przypadkiem są arkusze stylów powiązane z tagiem <style>, które zostały zmienione przy użyciu 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. W tym przypadku wprowadzamy metodę łączenia tych dodatkowych reguł z tekstem źródłowym. W tym przypadku kolejność ma znaczenie, ponieważ reguły CSS można wstawić w środku oryginalnego tekstu źródłowego. Na przykład wyobraź sobie, że pierwotny element <style> zawierał taki tekst:

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

Następnie strona wstawiła nowe reguły przez interfejs API JavaScriptu, tworząc reguły w takiej kolejności: .rule0, .rule1, .rule2, .rule3, .rule4. Po scaleniu tekst źródłowy powinien wyglądać tak:

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

Zachowanie oryginalnych komentarzy i wcięć jest ważne w przypadku procesu edycji, ponieważ pozycje tekstu źródłowego w regułach muszą być dokładne.

Arkusze stylów CSS-in-JS wyróżniają się też tym, że strona może je w każdej chwili zmienić. Jeśli rzeczywiste reguły CSSOM nie będą zsynchronizowane z wersją tekstową, edycja nie zadziała. W tym celu wprowadziliśmy tak zwaną sondę, która umożliwia przeglądarce powiadamianie backendowej części Narzędzi deweloperskich o zmienianiu arkusza stylów. Arkusze stylów zmutowane są synchronizowane podczas następnego wywołania CSS.getMatchedStylesForNode.

Mając wszystkie te elementy, już działa edycja CSS-in-JS, ale chcieliśmy ulepszyć interfejs użytkownika, by wskazać, czy stworzono arkusz stylów. Do arkusza CSS.CSSStyleSheetHeader CDP dodaliśmy nowy atrybut o nazwie isConstructed, który jest używany przez frontend do prawidłowego wyświetlania źródła reguły CSS:

Arkusz stylów z możliwością konstruowania

Podsumowanie

Aby je podsumować, przeanalizowaliśmy przypadki użycia kodu CSS-in-JS, których Narzędzia deweloperskie nie były obsługiwane, i omówiliśmy odpowiednie rozwiązanie, żeby je wykorzystać. Co ciekawe, udało nam się wykorzystać istniejące funkcje, dzięki czemu reguły CSSOM CSSOM miały standardowy tekst źródłowy, co eliminowało konieczność zmiany architektury w Narzędziach deweloperskich.

Więcej informacji znajdziesz w naszej propozycji projektu oraz w artykule o błędzie związanym ze śledzeniem Chromium, które odnoszą się do wszystkich powiązanych poprawek.

Pobierz kanały podglądu

Zastanów się, czy nie ustawić Chrome w wersji Canary, Dev lub beta jako domyślnej przeglądarki do programowania. Te kanały wersji testowej dają dostęp do najnowszych funkcji Narzędzi deweloperskich, umożliwiają testowanie najnowocześniejszych interfejsów API platformy internetowej i wykrywanie problemów w witrynie, zanim użytkownicy ją zobaczą.

Kontakt z zespołem ds. Narzędzi deweloperskich w Chrome

Skorzystaj z poniższych opcji, aby porozmawiać o nowych funkcjach i zmianach w poście lub o innych kwestiach związanych z Narzędziami deweloperskimi.

  • Prześlij nam sugestię lub opinię na crbug.com.
  • Aby zgłosić problem z Narzędziami deweloperskimi, kliknij Więcej opcji   Więcej   > Pomoc > Zgłoś problemy z Narzędziami deweloperskimi.
  • Opublikuj tweeta na stronie @ChromeDevTools.
  • Napisz komentarz pod filmem dotyczącym nowości w Narzędziach deweloperskich w Narzędziach deweloperskich w YouTube lub filmach w YouTube ze wskazówkami dotyczącymi Narzędzi deweloperskich.