Modernizacja infrastruktury CSS w Narzędziach deweloperskich

Odświeżona architektura DevTools: modernizacja infrastruktury CSS w Narzędziach deweloperskich

Ten post jest częścią serii postów na blogu, w których opisujemy zmiany, jakie wprowadzamy w architekturze DevTools i sposobie jego budowy. Wyjaśnimy, jak działało CSS w Narzędziach deweloperskich w przeszłości i w jaki sposób zmodernizowaliśmy nasze usługi porównywania cen w tych narzędziach, przygotowując się do (ostatecznie) migracji do standardowego rozwiązania internetowego do wczytywania plików CSS w plikach JavaScript.

Poprzedni stan CSS w Narzędziach deweloperskich

Narzędzia deweloperskie implementowały kod CSS na 2 różne sposoby: jeden w plikach CSS używanych w starszej części Narzędzi deweloperskich, a drugi w nowoczesnych komponentach internetowych używanych w tych narzędziach.

Implementacja CSS w Narzędziach deweloperskich została zdefiniowana wiele lat temu i jest już nieaktualna. Narzędzia deweloperskie nadal używają wzorca module.json i włożono wiele wysiłku w ich usunięcie. Ostatnią blokadą przed usunięciem tych plików jest sekcja resources, która służy do wczytywania plików CSS.

Chcieliśmy poświęcić czas na analizę różnych potencjalnych rozwiązań, które z czasem można przekształcić w skrypty modułu CSS. Celem było usunięcie długu technicznego powodowanego przez starszy system, a jednocześnie ułatwienie migracji do skryptów modułu CSS.

Wszystkie pliki CSS w Narzędziach deweloperskich zostały uznane za starsze, ponieważ zostały wczytane za pomocą pliku module.json, który jest w trakcie usuwania. Wszystkie pliki CSS musiały znajdować się w sekcji resources w pliku module.json w tym samym katalogu, co plik CSS.

Przykład pozostałego pliku module.json:

{
  "resources": [
    "serviceWorkersView.css",
    "serviceWorkerUpdateCycleView.css"
  ]
}

Te pliki CSS uzupełnią następnie globalną mapę obiektów o nazwie Root.Runtime.cachedResources w postaci mapowania ze ścieżki do ich zawartości. Aby dodać style do Narzędzi deweloperskich, musisz wywołać registerRequiredCSS z dokładną ścieżką do pliku, który chcesz wczytać.

Przykładowe wywołanie funkcji registerRequiredCSS:

constructor() {
  …
  this.registerRequiredCSS('ui/legacy/components/quick_open/filteredListWidget.css');
  …
}

Spowoduje to pobranie zawartości pliku CSS i wstawienie go na stronie jako elementu <style> za pomocą funkcji appendStyle:

Funkcja appendStyle, która dodaje kod CSS za pomocą wbudowanego elementu stylu:

const content = Root.Runtime.cachedResources.get(cssFile) || '';

if (!content) {
  console.error(cssFile + ' not preloaded. Check module.json');
}

const styleElement = document.createElement('style');
styleElement.textContent = content;
node.appendChild(styleElement);

Gdy wprowadzaliśmy nowoczesne komponenty internetowe (z elementami niestandardowymi), postanowiliśmy używać CSS za pomocą wbudowanych tagów <style> w plikach komponentu. Wiązało się to z różnymi wyzwaniami:

  • Brak obsługi wyróżnienia składni. Wtyczki, które wyróżniają składnię we wbudowanym kodzie CSS, zwykle nie są tak dobre jak podświetlanie składni i funkcje autouzupełniania w przypadku CSS napisanych w plikach .css.
  • Zwiększ narzut dotyczących wydajności. Wbudowany kod CSS wymagał też 2 przebiegów na lintowanie: jednego dla plików CSS i drugiego dla wbudowanego arkusza CSS. Był to narzut dotyczący wydajności, który moglibyśmy usunąć, gdyby wszystkie elementy CSS były zapisywane w samodzielnych plikach CSS.
  • Wyzwanie w minifikacji. Nie udało się łatwo minifikować wbudowanego kodu CSS, dlatego żaden kod CSS nie został zminifikowany. Rozmiar pliku kompilacji Narzędzi deweloperskich również został zwiększony przez zduplikowany kod CSS w wyniku kilku wystąpień tego samego komponentu internetowego.

Celem mojego projektu dla stażu było znalezienie rozwiązania dla infrastruktury usług porównywania cen, które będzie współdziałać zarówno ze starszą infrastrukturą, jak i nowymi komponentami internetowymi używanymi w Narzędziach deweloperskich.

Znalezienie potencjalnych rozwiązań

Zadanie można podzielić na 2 części:

  • Omówienie sposobu, w jaki system kompilacji postępuje z plikami CSS.
  • Importowanie plików CSS i wykorzystywanie ich w Narzędziach deweloperskich.

Przyjrzeliśmy się różnym potencjalnym rozwiązaniom w przypadku każdej z nich, które przedstawiamy poniżej.

Importowanie plików CSS

Importowanie i wykorzystywanie CSS w plikach TypeScript było przestrzeganiem standardów internetowych oraz zapewnienia spójności w Narzędziach deweloperskich i unikania powielonego kodu CSS w kodzie HTML. Chcieliśmy też znaleźć rozwiązanie, które pozwoliłoby przenieść nasze zmiany do nowych standardów platform internetowych, takich jak skrypty modułu CSS.

Dlatego instrukcje @import i tagi nie wydają się odpowiednie dla Narzędzi deweloperskich. Nie będą one jednakowo identyczne z importowanymi w pozostałych narzędziach deweloperskich, a w rezultacie da to błąd Flash Of Unstyled Content (FOUC). Migracja do skryptów modułu CSS byłaby trudniejsza, ponieważ importowanie musi zostać jawnie dodane i rozwiązane w inny sposób niż w przypadku tagów <link>.

const output = LitHtml.html`
<style> @import "css/styles.css"; </style>
<button> Hello world </button>`
const output = LitHtml.html`
<link rel="stylesheet" href="styles.css">
<button> Hello World </button>`

Możliwe rozwiązania wykorzystujące pola @import lub <link>.

Zamiast tego znaleźliśmy sposób na zaimportowanie pliku CSS jako obiektu CSSStyleSheet, abyśmy mogli dodać go do Shadow Dom (DevTools od kilku lat korzysta z modelu Shadow DOM) przy użyciu właściwości adoptedStyleSheets.

Opcje pakietów

Potrzebowaliśmy sposobu konwertowania plików CSS na obiekt CSSStyleSheet, abyśmy mogli łatwo manipulować nimi w pliku TypeScript. Rozważyliśmy zarówno podsumowanie, jak i pakiet webpack jako potencjalne pakiety SDK, które pozwolą nam wykonać tę transformację. Narzędzia deweloperskie korzystają już z usługi o pełnym zakresie w swojej kompilacji produkcyjnej, ale dodanie któregokolwiek pakietu pakietu do kompilacji produkcyjnej może powodować problemy z wydajnością podczas pracy z obecnym systemem kompilacji. Integracja z systemem kompilacji GN w Chromium utrudnia łączenie w pakiety, przez co pakiety zwykle nie integrują się dobrze z obecnym systemem kompilacji Chromium.

Zamiast tego sprawdziliśmy możliwość wykonania tej transformacji za pomocą obecnego systemu kompilacji GN.

Nowa infrastruktura umożliwiająca korzystanie z CSS w Narzędziach deweloperskich

Nowe rozwiązanie obejmuje użycie elementu adoptedStyleSheets w celu dodania stylów do określonego modelu Shadow DOM oraz użycie systemu kompilacji GN do generowania obiektów CSSStyleSheet, które mogą zostać przyjęte za pomocą document lub ShadowRoot.

// CustomButton.ts

// Import the CSS style sheet contents from a JS file generated from CSS
import customButtonStyles from './customButton.css.js';
import otherStyles from './otherStyles.css.js';

export class CustomButton extends HTMLElement{
  …
  connectedCallback(): void {
    // Add the styles to the shadow root scope
    this.shadow.adoptedStyleSheets = [customButtonStyles, otherStyles];
  }
}

Korzystanie z usługi adoptedStyleSheets ma wiele zalet, w tym:

  • Stopniowo staje się nowoczesnym standardem internetowym
  • Zapobiega duplikowaniu kodu CSS
  • Umożliwia stosowanie stylów tylko w modelu Shadow DOM, co pozwala uniknąć problemów powodowanych przez zduplikowane nazwy klas lub selektory identyfikatorów w plikach CSS.
  • Łatwą migrację do przyszłych standardów internetowych, takich jak skrypty modułu CSS i asercje importu

Jedynym zastrzeżeniem było to, że instrukcje import wymagały zaimportowania pliku .css.js. Aby umożliwić GN wygenerowanie pliku CSS podczas kompilowania, napisaliśmy skrypt generate_css_js_files.js. System kompilacji przetwarza teraz każdy plik CSS i przekształca go w plik JavaScript, który domyślnie eksportuje obiekt CSSStyleSheet. To świetnie, ponieważ możemy zaimportować plik CSS i łatwo go wdrożyć. Ponadto można teraz łatwo zmniejszyć kompilację produkcyjną, oszczędzając rozmiar pliku:

const styles = new CSSStyleSheet();
styles.replaceSync(
  // In production, we also minify our CSS styles
  /`${isDebug ? output : cleanCSS.minify(output).styles}
  /*# sourceURL=${fileName} */`/
);

export default styles;

Przykład wygenerowany iconButton.css.js ze skryptu.

Migracja starszego kodu przy użyciu reguł ESLint

Komponenty internetowe można łatwo przenieść ręcznie, ale bardziej wymagał procesu migracji starszych zastosowań registerRequiredCSS. Dwie główne funkcje rejestrujące starsze style to registerRequiredCSS i createShadowRootWithCoreStyles. Uznaliśmy, że skoro kroki potrzebne do przeniesienia tych wywołań są dość mechaniczne, możemy użyć reguł ESLint, aby wprowadzić poprawki i automatycznie przenieść starszy kod. Narzędzia deweloperskie korzystają już z kilku reguł niestandardowych przeznaczonych dla bazy kodu Narzędzi deweloperskich. Było to pomocne, ponieważ narzędzie ESLint analizuje już kod w abstrakcyjnym drzewie składni(abbr. AST) i możemy wysłać zapytanie do konkretnych węzłów wywołań, które były wywołaniami rejestracji CSS.

Największym problemem, który napotkaliśmy podczas tworzenia reguł ESLint, była przechwytywanie przypadków skrajnych. Chcieliśmy mieć pewność, że zachowujemy równowagę między tym, które przypadki skrajne warto zarejestrować, a które należy przenieść ręcznie. Chcieliśmy też mieć pewność, że możemy powiadamiać użytkownika o tym, że zaimportowany plik .css.js nie jest automatycznie generowany przez system kompilacji, ponieważ zapobiega to błędom związanym ze znalezieniem plików w czasie działania.

Wadą użycia reguł ESLint do migracji jest to, że nie mogliśmy zmienić wymaganego pliku kompilacji GN w systemie. Te zmiany musiał wprowadzić ręcznie użytkownik w każdym katalogu. Wymagało to więcej pracy, ale stanowiło dobry sposób na potwierdzenie, że każdy importowany plik .css.js rzeczywiście został wygenerowany przez system kompilacji.

Ogólnie rzecz biorąc, użycie reguł ESLint było bardzo pomocne w tej migracji, ponieważ mogliśmy szybko przenieść starszy kod do nowej infrastruktury, a dzięki temu, że platforma AST była łatwo dostępna, mogliśmy też obsługiwać wiele przypadków brzegowych w regule i niezawodnie automatycznie naprawiać je za pomocą interfejsu API eliminacji ESLint.

Co dalej?

Do tej pory wszystkie komponenty internetowe w Narzędziach deweloperskich w Chromium zostały przeniesione, aby korzystały z nowej infrastruktury CSS, a nie ze stylów wbudowanych. Większość starszych zastosowań registerRequiredCSS również została przeniesiona do nowego systemu. Wystarczy usunąć jak najwięcej plików module.json, a następnie przenieść bieżącą infrastrukturę i w przyszłości zaimplementować skrypty modułu CSS.

Pobieranie kanałów podglądu

Jako domyślnej przeglądarki dla programistów możesz używać Chrome Canary, Dev lub Beta. Te kanały podglądu dają dostęp do najnowszych funkcji Narzędzi deweloperskich, umożliwiają testowanie najnowocześniejszych interfejsów API platform internetowych oraz wykrywanie problemów w witrynie, zanim zdołają zrobić użytkownicy.

Kontakt z zespołem Narzędzi deweloperskich w Chrome

Użyj poniższych opcji, aby omówić nowe funkcje i zmiany w poście lub wszelkie inne kwestie związane z Narzędziami dla deweloperów.

  • Prześlij nam sugestię lub opinię 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.
  • zatweetuj na @ChromeDevTools.
  • Napisz komentarz o nowościach w filmach w YouTube dostępnych w Narzędziach deweloperskich lub z poradami dotyczącymi narzędzi dla deweloperów w filmach w YouTube.