Więcej niż wyrażenia regularne: ulepszenie analizy wartości CSS w Narzędziach deweloperskich w Chrome

Philip Pfaffe
Ergün Erdogmus
Ergün Erdogmus

Czy widzisz właściwości CSS w Narzędziach deweloperskich w Chrome? Karta Style wygląda ostatnio trochę lepiej? Wprowadzone w wersjach Chrome 121 i 128 aktualizacje są wynikiem znacznego poprawienia sposobu analizowania i prezentowania wartości CSS. W tym artykule omawiamy techniczne szczegóły przekształcenia, które pozwala przejść od systemu dopasowywania wyrażeń regularnych do bardziej wydajnego parsera.

Porównajmy bieżące Narzędzia deweloperskie z poprzednią wersją:

U góry: jest najnowsza wersja Chrome, na dole: Chrome 121.

Duża różnica, prawda? Oto omówienie najważniejszych ulepszeń:

  • color-mix Przydatny podgląd, który przedstawia 2 argumenty koloru w funkcji color-mix.
  • pink Klikalny podgląd koloru o nazwie pink. Kliknij go, aby otworzyć selektor kolorów i łatwo go dopasować.
  • var(--undefined, [fallback value]) Ulepszona obsługa niezdefiniowanych zmiennych – niezdefiniowana zmienna jest wyszarzona, a aktywna wartość zastępcza (w tym przypadku kolor HSL) jest wyświetlana z klikalnym podglądem koloru.
  • hsl(…): kolejny klikalny podgląd funkcji koloru hsl, który zapewnia szybki dostęp do selektora kolorów.
  • 177deg: klikalny zegar kątowy, który umożliwia interaktywne przeciąganie i modyfikowanie wartości kąta.
  • var(--saturation, …): klikalny link do definicji właściwości niestandardowej, który ułatwia przejście do odpowiedniej deklaracji.

Różnica jest uderzająca. Aby to osiągnąć, musieliśmy nauczyć Narzędzi deweloperskich lepiej rozumieć wartości właściwości CSS znacznie lepiej niż dotychczas.

Czy te podglądy nie były już dostępne?

Chociaż te ikony podglądu mogą wydawać się znajome, nie zawsze były wyświetlane spójnie, zwłaszcza w złożonej składni CSS, takiej jak w przykładzie powyżej. Nawet jeśli rozwiązania okażą się skuteczne, ich prawidłowe działanie często wymagało wiele wysiłku.

Dzieje się tak, ponieważ od pierwszych dni korzystania z Narzędzi deweloperskich system analizowania wartości rozrasta się w naturalny sposób. Nie nadążaliśmy jednak za nowymi funkcjami, które otrzymujemy z CSS, ani powiązanym z nimi wzrostem złożoności języka. Aby nadążyć za ewolucją, system wymagał pełnego zmodyfikowania. To właśnie zrobiliśmy!

Jak przetwarzane są wartości właściwości CSS

W Narzędziach deweloperskich proces renderowania i dekorowania deklaracji właściwości na karcie Style dzieli się na 2 fazy:

  1. Analiza strukturalna. Na tym początkowym etapie deklaracja właściwości jest badana w celu identyfikacji powiązanych z nimi komponentów i relacji. Na przykład deklaracja border: 1px solid red rozpozna 1px jako długość, solid jako ciąg znaków i red jako kolor.
  2. Renderowanie. W oparciu o analizę strukturalną etap renderowania przekształca te komponenty w reprezentację HTML. Wzbogaca to wyświetlany tekst właściwości o elementy interaktywne i wskazówki wizualne. Na przykład wartość koloru red jest renderowana z klikalną ikoną koloru, która po kliknięciu wyświetla selektor kolorów, który można łatwo zmienić.

Wyrażenia regularne

Wcześniej badaliśmy wartości właściwości na potrzeby analizy struktury za pomocą wyrażeń regularnych. Zachowaliśmy listę wyrażeń regularnych, które pasują do wartości właściwości, które uważaliśmy za dekorowanie. Były na przykład wyrażenia pasujące do kolorów, długości i kątów CSS, bardziej złożone wyrażenia podrzędne, takie jak wywołania funkcji var itd. Zeskanowaliśmy tekst od lewej do prawej, aby przeprowadzić analizę wartości, stale w poszukiwaniu pierwszego wyrażenia na liście, które pasuje do następnego fragmentu tekstu.

Sprawdzało się to w większości przypadków, ale liczba przypadków nie rosła. Na przestrzeni lat otrzymywaliśmy wiele raportów o błędach, w których dopasowywanie nie było zgodne z oczekiwaniami. Gdy je naprawialiśmy – niektóre proste poprawki, inne – dość zaawansowane – musieliśmy zmienić nasze podejście, aby pozbyć się długu technologicznego. Przyjrzyjmy się niektórym problemom.

Dopasowanie: color-mix()

Wyrażenie regularne użyte w funkcji color-mix() było następujące:

/color-mix\(.*,\s*(?<firstColor>.+)\s*,\s*(?<secondColor>.+)\s*\)/g

Co pasuje do jego składni:

color-mix(<color-interpolation-method>, [<color> && <percentage [0,100]>?]#{2})

Spróbuj uruchomić poniższy przykład, aby zwizualizować dopasowania.

const re = /color-mix\(.*,\s*(?<firstColor>.+)\s*,\s*(?<secondColor>.+)\s*\)/g;

// it works - simpler example
const simpler = re.exec('color-mix(in srgb, pink, hsl(127deg 100% 50%))');
console.table(simpler.groups);

re.exec('');

// it doesn't work - complex example
const complex = re.exec('color-mix(in srgb, pink, var(--undefined, hsl(127deg var(--saturation, 100%) 50%)))');
console.table(complex.groups);

Dopasuj wynik dla funkcji mieszania kolorów.

Dobry przykład jest prostszy. Jednak w bardziej złożonym przykładzie dopasowanie <firstColor> to hsl(177deg var(--saturation, a dopasowanie <secondColor> to 100%) 50%)), co jest całkowicie bez znaczenia.

Wiedzieliśmy, że to problem. W końcu CSS jako język formalny nie jest zwykły, dlatego załączyliśmy specjalną obsługę do obsługi bardziej złożonych argumentów funkcji, takich jak funkcje var. Jak jednak widać na pierwszym zrzucie ekranu, nie zawsze zadziałało.

Dopasowanie: tan()

Jednym z najzabawniejszych zgłoszonych błędów był funkcja trygonometryczna tan() . Wyrażenie regularne użyte do dopasowywania kolorów zawierało wyrażenie podrzędne \b[a-zA-Z]+\b(?!-) umożliwiające dopasowanie nazwanych kolorów, np. słowo kluczowe red. Następnie sprawdziliśmy, czy pasująca część faktycznie ma nazwany kolor, i zgadujemy! tan to również nazwany kolor! Dlatego błędnie zinterpretowaliśmy wyrażenia tan() jako kolory.

Dopasowanie: var()

Przyjrzyjmy się innemu przykładowi funkcji var() z wartością zastępczą zawierającą inne odwołania do var(): var(--non-existent, var(--margin-vertical)).

Nasze wyrażenie regularne dla atrybutu var() pasuje do tej wartości. Z wyjątkiem tego, że dopasowanie przestaje być dopasowane w pierwszym nawiasie zamykającym. Powyższy tekst jest więc dopasowany jako var(--non-existent, var(--margin-vertical). Jest to podręcznikowe ograniczenie dopasowania wyrażeń regularnych. Języki, które wymagają dopasowania w nawiasach, zasadniczo nie są regularne.

Przejście na parser CSS

Kiedy analiza tekstu przy użyciu wyrażeń regularnych przestanie działać (ponieważ analizowany język nie jest regularny), można wybrać następny krok kanoniczny: użyć parsera do obsługi gramatyki wyższego typu. W przypadku CSS oznacza to parser języków bez kontekstu. W bazie kodu Narzędzi deweloperskich istniał już taki system parserów: aplikacja Lezer firmy CodeMirror, która jest podstawą np. podświetlania składni w narzędziu CodeMirror, który jest dostępny w panelu Sources (Źródła). Parser CSS Lezer pozwolił nam utworzyć (nieabstrakcyjne) drzewa składni dla reguł CSS i było gotowe do użycia. Zwycięstwo.

Drzewo składni wartości właściwości „hsl(177deg var(--saturation, 100%) 50%)”. Jest to uproszczona wersja wyniku wygenerowanego przez parser Lezer, pomijającą czysto składniowe węzły dla przecinków i nawiasów.

Poza tym okazało się, że bezpośrednie przejście z dopasowania opartego na wyrażeniach na dopasowywanie oparte na parserze jest niemożliwe: oba podejścia działają w przeciwnych kierunkach. Dopasowując fragmenty wartości do wyrażeń regularnych, Narzędzia deweloperskie skanują dane wejściowe od lewej do prawej, wielokrotnie próbując znaleźć najwcześniejsze dopasowanie na podstawie uporządkowanej listy wzorców. W przypadku drzewa składni dopasowywanie zaczyna się od dołu do góry, np. najpierw analizuje argumenty wywołania, a dopiero potem próbuje dopasować wywołanie funkcji. Jest to funkcja opracowana jak obliczanie wyrażenia arytmetycznego, w którym trzeba najpierw brać pod uwagę wyrażenia w nawiasach, potem operatory mnożne, a następnie operatory addytywne. W tej ramce dopasowanie oparte na wyrażeniach regularnych odpowiada sprawdzaniu wyrażenia arytmetycznego od lewej do prawej. Naprawdę nie chcieliśmy od nowa pisać całego systemu dopasowywania: było 15 różnych par dopasowań i mechanizmów renderowania zawierających tysiące wierszy kodu, dlatego mało prawdopodobne jest, że zdążymy je wysłać w ramach jednego etapu.

Opracowaliśmy więc rozwiązanie, które umożliwiło nam stopniowe wprowadzanie zmian, które omówimy bardziej szczegółowo poniżej. Krótko mówiąc, zachowaliśmy podejście dwufazowe, ale w pierwszej fazie staramy się dopasować wyrażenia podrzędne od dołu do góry (co oznacza odejście od przepływu wyrażeń regularnych), a w drugiej fazie renderujemy od góry. W obu fazach mogliśmy korzystać z istniejących dopasowań i renderowań opartych na wyrażeniach regularnych, praktycznie bez zmian, dzięki czemu mogliśmy je przenosić pojedynczo.

Etap 1. Dopasowywanie od dołu

Pierwszy etap dotyczy mniej więcej dokładnie tego, co jest na okładce. Przemierzamy drzewo w kolejności od dołu do góry i staramy się dopasować wyrażenia podrzędne w każdym węźle drzewa składni, który odwiedzamy. Aby dopasować konkretne wyrażenie podrzędne, funkcja ta może użyć wyrażenia regularnego tak samo jak w dotychczasowym systemie. Od wersji 128 nadal robimy to w kilku przypadkach, na przykład w przypadku pasujących długości. Narzędzie dopasowujące może też przeanalizować strukturę poddrzewa ukierunkowanego na bieżący węzeł. Dzięki temu może wychwytywać błędy składni i jednocześnie rejestrować informacje strukturalne.

Przeanalizujmy przykład drzewa składni przedstawionym powyżej:

Etap 1. Od dołu dopasowania w drzewie składni.

W przypadku tego drzewa nasze dopasowania zostaną zastosowane w następującej kolejności:

  1. hsl(177degvar(--saturation, 100%) 50%): najpierw znajdujemy pierwszy argument wywołania funkcji hsl, czyli kąt barwy. Łączymy go z dopasowaniem kąta, dzięki czemu możemy udekorować wartość kąta ikoną kąta.
  2. hsl(177degvar(--saturation, 100%)50%): Po drugie odkrywamy wywołanie funkcji var z dopasowaniem zmiennej. W przypadku takich połączeń chcemy skupić się na 2 czynnościach:
    • Wyszukaj deklarację zmiennej i oblicz jej wartość, a potem dodaj do niej link oraz okienko wyskakujące, aby się z nimi połączyć.
    • Jeśli obliczona wartość jest kolorem, oznacz połączenie ikoną koloru. Jest jeszcze trzecia rzecz, którą omówimy później.
  3. hsl(177deg var(--saturation, 100%) 50%): na koniec dopasowujemy wyrażenie wywołujące funkcji hsl, by można było ozdobić ją ikoną koloru.

Oprócz wyszukiwania wyrażeń podrzędnych, które chcemy udekorować, w ramach procesu dopasowywania uruchamiamy drugą funkcję. Zwróć uwagę, że w kroku 2 wspomnieliśmy, że należy wyszukać obliczoną wartość dla nazwy zmiennej. Idziemy o krok dalej i rozpowszechniamy wyniki po drzewie. I nie tylko w przypadku zmiennej, ale też wartości kreacji zastępczej. Gwarantujemy, że podczas odwiedzania węzła funkcji var jego elementy podrzędne zostały wcześniej odwiedzone, więc znamy już wyniki wszystkich funkcji var, które mogą pojawić się w wartości zastępczej. Dzięki temu możemy łatwo i niedrogo zastąpić funkcje var ich wynikami na bieżąco, dzięki czemu możemy poznać proste odpowiedzi na pytania w rodzaju „Czy wynik funkcji var wywołuje kolor?”, tak jak w kroku 2.

Faza 2. Renderowanie odgórne

W drugiej fazie robimy to w odwrotnym kierunku. Uwzględniając wyniki dopasowania z fazy 1, renderujemy drzewo w kodzie HTML, przemierzając go w kolejności od góry do dołu. W przypadku każdego odwiedzonego węzła sprawdzamy, czy jest zgodny, a jeśli tak, wywołujemy odpowiedni mechanizm renderowania. Unikamy specjalnej obsługi węzłów zawierających tylko tekst (np. NumberLiteral „50%”), udostępniając dla nich domyślny mechanizm dopasowywania i mechanizmu renderowania. Mechanizmy renderowania po prostu wyświetlają węzły HTML, które po połączeniu reprezentują wartość właściwości, w tym jej dekoracje.

Etap 2. Renderowanie odgórne w drzewie składni.

W przykładowym drzewie wartość właściwości wygląda tak:

  1. Otwórz wywołanie funkcji hsl. Pasuje, więc wywołaj mechanizm renderowania funkcji koloru. Robi 2 czynności:
    • Oblicza rzeczywistą wartość koloru za pomocą mechanizmu zastępowania w locie dla dowolnych argumentów var, a następnie rysuje ikonę koloru.
    • Rekursywnie renderuje elementy podrzędne elementu CallExpression. Automatycznie renderuje to nazwę funkcji, nawiasy i przecinki, które są tylko tekstem.
  2. Otwórz pierwszy argument wywołania hsl. Dopasuje, więc wywołaj mechanizm renderowania kąta, który rysuje ikonę kąta i jego tekst.
  3. Przejdź do drugiego argumentu, który jest wywołaniem var. Pasuje, więc wywołaj zmienną renderer, która zwraca to:
    • Tekst var( na początku.
    • Nazwa zmiennej i dodawanie do niej linku do jej definicji lub szarego koloru tekstu oznaczającego, że nie zdefiniowano tej zmiennej. Powoduje też dodanie do zmiennej wyskakującego okienka z informacjami o jej wartości.
    • Przecinek, a następnie rekurencyjnie renderuje wartość zastępczą.
    • Nawias zamykający.
  4. Wyświetl ostatni argument wywołania hsl. Nie pasuje, więc wyświetla się tylko tekst.

Czy zauważyłeś, że w tym algorytmie renderowanie w pełni kontroluje sposób renderowania elementów podrzędnych dopasowanego węzła? Rekurencyjne renderowanie elementów podrzędnych działa proaktywnie. Ta sztuczka umożliwiła stopniową migrację z renderowania opartego na wyrażeniach regularnych na renderowanie oparte na drzewie składni. W przypadku węzłów dopasowanych ze starszą wersją dopasowywania wyrażeń regularnych można użyć odpowiedniego mechanizmu renderowania w oryginalnej postaci. W przypadku drzewa składniowego odpowiadałoby to za wyrenderowanie całego drzewa podrzędnego, a jego wynik (węzeł HTML) zostałby dobrze podłączony do otaczającego procesu renderowania. Dzięki temu mogliśmy przenosić moduły dopasowania i mechanizmy renderowania w parach, a potem je zamieniać.

Inną ciekawą funkcją mechanizmów renderowania, które kontrolują renderowanie elementów podrzędnych pasujących węzłów, jest to, że umożliwia nam to rozważanie zależności między dodawanych ikon. W przykładzie powyżej kolor wygenerowany przez funkcję hsl oczywiście zależy od wartości barwy. Oznacza to, że kolor pokazany przez ikonę koloru zależy od kąta wskazywanego przez ikonę kąta. Jeśli użytkownik otworzy edytor kątów za pomocą tej ikony i zmieni kąt, możemy w czasie rzeczywistym zmienić kolor ikony koloru:

Jak widać w powyższym przykładzie, korzystamy z niego również do łączenia ikon, np. w przypadku parametru color-mix() z 2 kanałami kolorów lub funkcji var zwracających kolor z wartości zastępczej.

Wpływ na wydajność

Gdy zagłębialiśmy się w ten problem w celu zwiększenia niezawodności i rozwiązywania długotrwałych problemów, spodziewaliśmy się spadku wydajności, ponieważ zaczęliśmy korzystać z w pełni obsługiwanego parsera. Aby to sprawdzić, przygotowaliśmy test porównawczy, który renderuje około 3,5 tys.deklaracji właściwości i profiluje wersje oparte na wyrażeniach regularnych i analizie parsera z 6-krotnym ograniczeniem na komputerze M1.

Tak jak się spodziewaliśmy, metoda oparta na analizie okazała się w tym przypadku wolniejsza o 27% od metody opartej na wyrażeniach regularnych. Renderowanie oparte na wyrażeniach regularnych potrzebowało 11 sekund, a renderowanie oparte na parserze wymagało 15 s.

Biorąc pod uwagę nasze korzyści, jakie przynosi nowe podejście, zdecydowaliśmy się kontynuować jego pracę.

Podziękowania

Dziękujemy Sofii Emelianova i Jecelyn Yeen za nieocenioną pomoc w edytowaniu tego posta.

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

Użyj 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 w Narzędziach deweloperskich.
  • 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.