Modernizacja infrastruktury CSS w Narzędziach deweloperskich

Aktualizacja architektury Narzędzi deweloperskich: modernizacja infrastruktury CSS w Narzędziach deweloperskich

Ten post jest częścią cyklu postów na blogu, w których opisujemy wprowadzane przez nas zmiany w architekturze DevTools i sposób jej tworzenia. Wyjaśnimy, jak CSS działało w DevTools w przeszłości i jak zmodernizowaliśmy CSS w DevTools, aby przygotować się do (ostatecznej) migracji na standardowe rozwiązanie internetowe do wczytywania CSS w plikach JavaScript.

Poprzedni stan CSS w Narzędziach deweloperskich

Implementacja kodu CSS w Narzędziach deweloperskich została zaimplementowana w Narzędziach deweloperskich na 2 różne sposoby: jeden w przypadku plików CSS używanych w starszej części, a drugi z nowoczesnymi komponentami internetowymi, które są używane w Narzędziach deweloperskich.

Implementacja CSS w DevTools została zdefiniowana wiele lat temu i jest obecnie nieaktualna. W narzędziach dla programistów nadal używany jest wzór module.json, a usuwanie tych plików wymagało ogromnego wysiłku. Ostatnim elementem, który uniemożliwia usunięcie tych plików, jest sekcja resources, która służy do wczytywania plików CSS.

Chcieliśmy przyjrzeć się różnym potencjalnym rozwiązaniom, które w przyszłości mogą przekształcić się w skrypty modułów CSS. Celem było pozbycie się długu technologicznego spowodowanego przez starszy system, ale też ułatwienie procesu migracji do skryptów modułów CSS.

Wszystkie pliki CSS w DevTools zostały uznane za „starsze”, ponieważ były wczytywane za pomocą pliku module.json, który jest obecnie usuwany. Wszystkie pliki CSS musiały być wymienione 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 wypełniają globalną mapę obiektów o nazwie Root.Runtime.cachedResources, która mapuje ścieżkę do ich zawartości. Aby dodać style do DevTools, musisz wywołać registerRequiredCSS z dokładną ścieżką do pliku, który chcesz załadować.

Przykład registerRequiredCSS wywołania:

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

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

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

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 wprowadziliśmy nowoczesne komponenty sieciowe (korzystając z elementów niestandardowych), początkowo zdecydowaliśmy się użyć CSS za pomocą tagów wbudowanych <style> w samych plikach komponentów. Wiązało się to z pewnymi wyzwaniami:

  • Brak obsługi podświetlania składni. Wtyczki, które zapewniają wyróżnianie składni w przypadku wbudowanego CSS, nie są tak dobre jak funkcje wyróżniania składni i autouzupełniania w przypadku kodu CSS zapisanego w plikach .css.
  • Zwiększ obciążenie procesora. Wbudowany kod CSS wymagał też 2 przebiegów sprawdzania: jednego dla plików CSS i jednego dla wbudowanego kodu CSS. To był narzut na wydajność, który moglibyśmy wyeliminować, gdyby cały kod CSS był napisany w samodzielnych plikach CSS.
  • Problem z skompresowaniem. Nie udało się łatwo zminifikować kodu CSS w ciele elementu, więc nie został on zminifikowany. Rozmiar pliku wersji DevTools został również zwiększony przez duplikowany kod CSS wprowadzony przez wiele wystąpień tego samego komponentu internetowego.

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

Poszukiwanie potencjalnych rozwiązań

Problem można podzielić na 2 różne części:

  • ustalenie, jak system kompilacji obsługuje pliki CSS;
  • Dowiedz się, jak pliki CSS są importowane i wykorzystywane przez DevTools.

W przypadku każdego z tych elementów przeanalizowaliśmy różne potencjalne rozwiązania, które opisujemy poniżej.

Importowanie plików CSS

Celem importowania i wykorzystywania CSS w plikach TypeScript było jak największe dostosowanie się do standardów internetowych, ujednolicenie wszystkich DevTools oraz unikanie powielonego kodu CSS w pliku HTML. Chcieliśmy też wybrać rozwiązanie, które umożliwiłoby nam przeniesienie naszych zmian do nowych standardów platformy internetowej, takich jak skrypty modułu CSS.

Z tych powodów instrukcje @import i tagi nie wydawały się odpowiednim rozwiązaniem dla narzędzi deweloperskich. Nie będą one jednakowe w przypadku importów w pozostałych narzędziach deweloperskich i spowodują Flash Of Unstyled Content (FOUC). Migracja do skryptów modułu CSS będzie trudniejsza, ponieważ operacje importowania trzeba będzie w sposób jawny dodawać i przeprowadzać inaczej 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 za pomocą @import lub <link>.

Zamiast tego znaleźliśmy sposób na zaimportowanie pliku CSS jako obiektu CSSStyleSheet, aby móc dodać go do Shadow DOM (od kilku lat DevTools używa Shadow DOM) za pomocą jego właściwości adoptedStyleSheets.

Opcje pakietu

Potrzebowaliśmy sposobu na konwersję plików CSS na obiekt CSSStyleSheet, aby można było łatwo nimi manipulować w pliku TypeScript. Aby przeprowadzić tę transformację, uwzględniliśmy zarówno Rollup, jak i webpack. Narzędzie DevTools korzysta już z Rollup w wersji produkcyjnej, ale dodanie dowolnego z bundlerów do wersji produkcyjnej może spowodować problemy z wydajnością podczas pracy z obecnym systemem kompilacji. Nasza integracja z systemem kompilacji GN w Chromium utrudnia tworzenie pakietów, dlatego pakietatory nie integrują się dobrze z obecnym systemem kompilacji Chromium.

Postanowiliśmy jednak skorzystać z obecnego systemu kompilacji GN, aby wykonać tę transformację za nas.

Nowa infrastruktura używania CSS w Narzędziach deweloperskich

Nowe rozwiązanie polega na użyciu adoptedStyleSheets do dodawania stylów do konkretnego Shadow DOM, a systemu kompilacji GN do generowania obiektów CSSStyleSheet, które mogą być używane przez 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 adoptedStyleSheets ma wiele zalet, m.in.:

  • Jest on obecnie standardem w internecie.
  • zapobiega zduplikowaniu kodu CSS;
  • Stosuje style tylko do DOM-u cieniowanego, co pozwala uniknąć problemów spowodowanych przez zduplikowane nazwy klas lub selektory identyfikatorów w plikach CSS.
  • Łatwe przejście na przyszłe standardy internetowe, takie jak skrypty modułu CSS i asercje importu

Jedynym zastrzeżeniem rozwiązania było to, że instrukcje import wymagały zaimportowania pliku .css.js. Aby umożliwić GN wygenerowanie pliku CSS podczas kompilacji, 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 świetne, bo możemy zaimportować plik CSS i łatwo go dostosować. Co więcej, teraz możemy łatwo zminimalizować kompilację produkcyjną, oszczędzając miejsce na dysku:

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: iconButton.css.js wygenerowany na podstawie skryptu.

Migracja starszego kodu za pomocą reguł ESLint

Chociaż komponenty internetowe można było łatwo przenieść ręcznie, proces migracji zastosowań registerRequiredCSS w starszych wersjach był bardziej skomplikowany. Dwie główne funkcje, które zarejestrowały starsze style, to registerRequiredCSScreateShadowRootWithCoreStyles. Uznaliśmy, że skoro migracja tych wywołań jest dość mechaniczna, możemy zastosować reguły ESLint, aby zastosować poprawki i automatycznie przenieść starszy kod. Narzędzia deweloperskie korzystają już z pewnej liczby niestandardowych reguł dotyczących kodu źródłowego tych narzędzi. Było to pomocne, ponieważ ESLint już analizuje kod do drzewa składni abstrakcyjnej(skrót. AST), możemy więc wysyłać zapytania do konkretnych węzłów wywołań, które są wywołaniami rejestracji CSS.

Największym problemem, z którym mieliśmy do czynienia podczas pisania reguł ESLint na potrzeby migracji, było uwzględnienie przypadków szczególnych. Chcieliśmy mieć pewność, że wiemy, które przypadki szczególne warto uwzględnić, a które należy przenieść ręcznie. Chcieliśmy też mieć możliwość poinformowania użytkownika, gdy zaimportowany plik .css.js nie jest automatycznie generowany przez system kompilacji, ponieważ zapobiega to błędom związanym z brakiem pliku w czasie wykonywania.

Wadą korzystania z reguł ESLint podczas migracji było to, że nie można było zmienić wymaganego pliku kompilacji GN w systemie. Użytkownik musiał wprowadzić te zmiany ręcznie w każdym katalogu. Chociaż wymagało to więcej pracy, było to dobry sposób na potwierdzenie, że każdy importowany plik .css.js jest faktycznie generowany przez system kompilacji.

Ogólnie rzecz biorąc, użycie reguł ESLint w celu przeprowadzenia tej migracji było bardzo pomocne, ponieważ mogliśmy szybko przenieść stary kod do nowej infrastruktury. Dzięki temu, że AST był łatwo dostępny, mogliśmy też obsłużyć wiele szczególnych przypadków w regułach i automatycznie je poprawić za pomocą interfejsu API do naprawiania błędów ESLint.

Co dalej?

Do tej pory wszystkie komponenty internetowe w narzędziach Chromium DevTools zostały przeniesione do korzystania z nowej infrastruktury CSS zamiast stylów wbudowanych. Większość starszych zastosowań registerRequiredCSS została również przeniesiona do nowego systemu. Pozostaje Ci już tylko usunąć jak najwięcej plików module.json i przenieść bieżącą infrastrukturę w celu zaimplementowania w przyszłości skryptów modułu CSS.

Pobieranie kanałów podglądu

Rozważ użycie przeglądarki Chrome Canary, Dev lub Beta jako domyślnej przeglądarki deweloperskiej. Te kanały wersji testowej dają dostęp do najnowszych funkcji Narzędzi deweloperskich, umożliwiają testowanie najnowocześniejszych interfejsów API platform internetowych i pomagają w wykrywaniu problemów w witrynie przed użytkownikami.

Kontakt z zespołem Narzędzi deweloperskich w Chrome

Aby omówić nowe funkcje, aktualizacje lub inne kwestie związane z Narzędziami deweloperskimi, skorzystaj z tych opcji.