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
W DevTools CSS jest implementowany na 2 różne sposoby: jeden dla plików CSS używanych w starszej części DevTools, a drugi dla nowoczesnych komponentów internetowych używanych w DevTools.
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 poświęcić trochę czasu na zbadanie różnych potencjalnych rozwiązań, które mogłyby ostatecznie przekształcić się w skrypty modułu 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 (za pomocą elementów niestandardowych), początkowo zdecydowaliśmy się używać CSS za pomocą tagów wbudowanych <style>
w samych plikach komponentów. Wyzwania te były następujące:
- 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ł koszt wydajności, który można było wyeliminować, gdyby cały kod CSS był zapisany w samodzielnych plikach CSS.
- Problem z skompresowaniem. Nie udało się łatwo zminifikować kodu CSS w ciele elementu, więc nie zminifikowano żadnego kodu CSS. Rozmiar pliku wersji DevTools został również zwiększony przez duplikowany plik CSS wprowadzony przez wiele wystąpień tego samego komponentu internetowego.
Celem mojego projektu stażowego było znalezienie rozwiązania dla infrastruktury CSS, które działa zarówno z starszą infrastrukturą, jak i z nowymi komponentami internetowymi używanymi w DevTools.
Badanie potencjalnych rozwiązań
Problem można podzielić na 2 części:
- dowiedzieć się, 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 i uniknięcie powielonych arkuszy CSS w pliku HTML. Chcieliśmy też wybrać rozwiązanie, które umożliwiłoby nam przeniesienie naszych zmian do nowych standardów platform internetowych, takich jak skrypty modułów CSS.
Z tych powodów instrukcje @import i tagi nie wydawały się odpowiednim rozwiązaniem dla narzędzi deweloperskich. Nie byłyby one jednolite w przypadku importowania w pozostałych częściach Narzędzi deweloperskich i powodowałyby błyskawiczne wyświetlanie niesformatowanych treści (FOUC). Migracja do skryptów modułów CSS byłaby trudniejsza, ponieważ importy musiałyby być dodawane w wyraźny sposób i obsługiwane inaczej niż tagi <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 można było 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. Jako potencjalne narzędzia do tworzenia pakietów rozważaliśmy użycie 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 narzędzia do tworzenia pakietów nie integrują się dobrze z obecnym systemem kompilacji Chromium.
Zamiast tego rozważaliśmy użycie obecnego systemu kompilacji GN, który wykonałby tę przekształcenie 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 było to, że instrukcje import
wymagały zaimportowania pliku .css.js
. Aby umożliwić GN generowanie 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 registerRequiredCSS
i createShadowRootWithCoreStyles
. Ponieważ czynności związane z przeniesieniem tych wywołań były dość mechaniczne, uznaliśmy, że możemy użyć reguł ESLint do zastosowania poprawek i automatycznego przeniesienia starszego kodu. Narzędzia deweloperskie korzystają już z pewnej liczby niestandardowych reguł dotyczących kodu źródłowego tych narzędzi. Było to przydatne, ponieważ ESLint już przeanalizował kod w abstrakcyjnej strukturze drzewa składni(w dalszej części tekstu nazywanej. AST) i mogliśmy wysłać zapytanie do konkretnych węzłów wywołania, które były wywołaniami do rejestrowania usługi porównywania cen.
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.
Jedną z wad korzystania z reguł ESLint podczas migracji było to, że nie mogliśmy 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 naprawiać 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. Teraz wystarczy usunąć jak najwięcej plików module.json
, a potem przejść na obecną infrastrukturę, aby w przyszłości móc stosować skrypty modułów CSS.
Pobieranie kanałów podglądu
Rozważ użycie jako domyślnej przeglądarki deweloperskiej wersji Canary, Dev lub Beta przeglądarki Chrome. Te kanały wersji wstępnej zapewniają dostęp do najnowszych funkcji DevTools, umożliwiają testowanie najnowocześniejszych interfejsów API platformy internetowej i pomagają znaleźć problemy w witrynie, zanim zrobią to użytkownicy.
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.
- Przesyłaj opinie i prośby o dodanie funkcji na stronie crbug.com.
- Zgłoś problem z Narzędziami deweloperskimi, klikając Więcej opcji > Pomoc > Zgłoś problem z Narzędziami deweloperskimi w Narzędziach deweloperskich.
- Wyślij tweeta do @ChromeDevTools.
- Dodaj komentarze do filmów w YouTube z serii „Co nowego w Narzędziach deweloperskich” lub Wskazówki dotyczące Narzędzi deweloperskich.