Modernisierung der CSS-Infrastruktur in Entwicklertools

Aktualisierung der DevTools-Architektur: Modernisierung der CSS-Infrastruktur in den DevTools

Dieser Beitrag ist Teil einer Reihe von Blogposts, in denen wir die Änderungen an der Architektur der Entwicklertools und deren Aufbau beschreiben. Wir erläutern, wie CSS in der Vergangenheit in Entwicklertools funktioniert hat und wie wir unsere CSS in den Entwicklertools modernisiert haben, um die Migration auf eine Web-Standardlösung zum Laden von CSS in JavaScript-Dateien vorzubereiten.

Vorheriger CSS-Status in den Entwicklertools

Die Entwicklertools haben CSS auf zwei verschiedene Arten implementiert: eine für CSS-Dateien, die im alten Teil der Entwicklertools verwendet werden, und eine für die modernen Webkomponenten, die in den Entwicklertools verwendet werden.

Die CSS-Implementierung in DevTools wurde vor vielen Jahren definiert und ist jetzt veraltet. In den Entwicklertools wird nur noch das Muster module.json verwendet und der Aufwand, diese Dateien zu entfernen, wurde umfangreich. Der letzte Blocker zum Entfernen dieser Dateien ist der Abschnitt resources, der zum Laden von CSS-Dateien verwendet wird.

Wir wollten uns Zeit nehmen, um verschiedene potenzielle Lösungen zu untersuchen, die sich schließlich in CSS-Modulscripts verwandeln könnten. Ziel war es, die durch das alte System verursachten technischen Altlasten zu beseitigen und gleichzeitig die Migration zu CSS-Modulscripts zu vereinfachen.

Alle CSS-Dateien in den DevTools wurden als „alt“ eingestuft, da sie über eine module.json-Datei geladen wurden, die derzeit entfernt wird. Alle CSS-Dateien mussten unter resources in einer module.json-Datei im selben Verzeichnis wie die CSS-Datei aufgeführt werden.

Beispiel für eine verbleibende module.json-Datei:

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

Mit diesen CSS-Dateien wird dann eine globale Objektzuordnung namens Root.Runtime.cachedResources als Zuordnung eines Pfads zu ihren Inhalten dargestellt. Wenn Sie Stile in den DevTools hinzufügen möchten, müssen Sie registerRequiredCSS mit dem genauen Pfad zur Datei aufrufen, die Sie laden möchten.

Beispiel für einen registerRequiredCSS Anruf:

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

Dadurch wird der Inhalt der CSS-Datei abgerufen und mithilfe der Funktion appendStyle als <style>-Element in die Seite eingefügt:

appendStyle-Funktion, die CSS mit einem Inline-Style-Element hinzufügt:

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);

Bei der Einführung moderner Webkomponenten mit benutzerdefinierten Elementen entschieden wir uns anfangs, CSS über Inline-<style>-Tags in den Komponentendateien selbst zu verwenden. Dies führte zu Herausforderungen:

  • Fehlende Unterstützung für Syntaxhervorhebungen. Plugins, die Syntaxhervorhebung für Inline-CSS bieten, sind in der Regel nicht so gut wie die Syntaxhervorhebung und die automatische Vervollständigung für CSS, das in .css-Dateien geschrieben wurde.
  • Leistungsoverhead verursachen. Inline-CSS bedeutete auch, dass zwei Durchläufe für das Linting erforderlich waren: einer für CSS-Dateien und einer für Inline-CSS. Dies war ein Leistungsoverhead, den wir entfernen konnten, wenn das gesamte CSS in eigenständigen CSS-Dateien geschrieben wurde.
  • Herausforderung bei der Minimierung Inline-CSS konnte nicht einfach minimiert werden, daher wurde kein CSS minimiert. Die Dateigröße des Release-Builds von DevTools wurde auch durch das duplizierte CSS erhöht, das durch mehrere Instanzen derselben Webanwendung eingeführt wurde.

Mein Praktikumsprojekt war es, eine Lösung für die Preisvergleichsportal-Infrastruktur zu finden, die sowohl mit der alten Infrastruktur als auch mit den neuen Webkomponenten, die in den Entwicklertools verwendet werden, funktioniert.

Mögliche Lösungen recherchieren

Das Problem lässt sich in zwei Teile unterteilen:

  • Herausfinden, wie das Build-System mit CSS-Dateien umgeht.
  • Herausfinden, wie die CSS-Dateien von DevTools importiert und verwendet werden.

Wir haben uns verschiedene potenzielle Lösungen für jeden Teil angesehen. Diese werden unten beschrieben.

CSS-Dateien importieren

Das Ziel beim Importieren und Verwenden von CSS in den TypeScript-Dateien war es, sich so nah wie möglich an Webstandards wie möglich zu halten, Konsistenz in den Entwicklertools zu erzwingen und doppelte CSS-Elemente im HTML-Code zu vermeiden. Außerdem wollten wir eine Lösung auswählen, die es uns ermöglicht, unsere Änderungen an neuen Webplattformstandards wie CSS-Modulskripts zu übertragen.

Aus diesem Grund schienen die @import-Anweisungen und die -Tags nicht für die Entwicklertools geeignet zu sein. Sie würden nicht mit den Importen im Rest von DevTools übereinstimmen und zu einem Flash of Unstyled Content (FOUC) führen. Die Migration zu CSS-Modulscripts wäre schwieriger, da die Importe explizit hinzugefügt und anders behandelt werden müssten als bei <link>-Tags.

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>`

Mögliche Lösungen mit @import oder <link>.

Stattdessen haben wir uns für eine Möglichkeit entschieden, die CSS-Datei als CSSStyleSheet-Objekt zu importieren, damit wir sie mithilfe ihrer adoptedStyleSheets-Eigenschaft zum Shadow Dom hinzufügen können. DevTools verwendet seit einigen Jahren Shadow DOM.

Optionen für den Bündelungsdienst

Wir brauchten eine Möglichkeit, CSS-Dateien in ein CSSStyleSheet-Objekt umzuwandeln, damit wir sie in der TypeScript-Datei einfach bearbeiten konnten. Wir haben sowohl Rollup als auch webpack als potenzielle Bundler für diese Transformation in Betracht gezogen. In DevTools wird bereits Rollup im Produktionsbuild verwendet. Wenn Sie dem Produktionsbuild jedoch einen der beiden Bundler hinzufügen, kann es bei der Verwendung unseres aktuellen Build-Systems zu Leistungsproblemen kommen. Die Integration in das GN-Build-System von Chromium erschwert das Bündeln von Paketen. Bundler lassen sich deshalb in der Regel nicht gut in das aktuelle Chromium-Build-System einbinden.

Stattdessen haben wir die Möglichkeit untersucht, diese Transformation mit dem aktuellen GN-Buildsystem durchzuführen.

Die neue Infrastruktur für die Verwendung von CSS in den Entwicklertools

Bei der neuen Lösung werden mit adoptedStyleSheets Stile zu einem bestimmten Shadow DOM hinzugefügt, während mithilfe des GN-Build-Systems CSSStyleSheet-Objekte generiert werden, die von einem document- oder ShadowRoot-Element übernommen werden können.

// 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];
  }
}

Die Verwendung von adoptedStyleSheets bietet mehrere Vorteile:

  • Es wird zum modernen Webstandard.
  • Verhindert doppelte Preisvergleichsportale
  • Wendet Stile nur auf ein Shadow DOM an. Dadurch werden Probleme vermieden, die durch doppelte Klassennamen oder ID-Selektoren in CSS-Dateien verursacht werden.
  • Einfache Migration zu zukünftigen Webstandards wie CSS-Modulscripts und Importaussagen

Der einzige Nachteil der Lösung war, dass die import-Anweisungen die Importierung der .css.js-Datei erforderten. Damit GN während der Erstellung eine CSS-Datei generieren kann, haben wir das Skript generate_css_js_files.js geschrieben. Das Build-System verarbeitet jetzt jede CSS-Datei und wandelt sie in eine JavaScript-Datei um, die standardmäßig ein CSSStyleSheet-Objekt exportiert. Das ist großartig, da wir die CSS-Datei importieren und einfach anpassen können. Außerdem können wir den Produktions-Build jetzt ganz einfach minimieren und so die Dateigröße reduzieren:

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;

Beispiel hat iconButton.css.js aus dem Script generiert.

Legacy-Code mit ESLint-Regeln migrieren

Während die Webkomponenten einfach manuell migriert werden konnten, war die Migration der bisherigen Verwendungen von registerRequiredCSS aufwendiger. Die beiden Hauptfunktionen, die Legacy-Stile registriert haben, waren registerRequiredCSS und createShadowRootWithCoreStyles. Da die Schritte zur Migration dieser Aufrufe ziemlich mechanisch waren, entschieden wir uns, ESLint-Regeln zu verwenden, um Fehler zu beheben und den alten Code automatisch zu migrieren. In den Entwicklertools werden bereits eine Reihe benutzerdefinierter Regeln verwendet, die speziell für die Codebasis der Entwicklertools gelten. Das war hilfreich, da ESLint den Code bereits in einen abstrakten Syntaxbaum(AST, AST) und wir konnten die bestimmten Aufrufknoten abfragen, die Aufrufe zur Registrierung von Preisvergleichsportalen waren.

Die größte Herausforderung beim Erstellen der ESLint-Regeln für die Migration bestand darin, Grenzfälle zu berücksichtigen. Wir wollten das richtige Gleichgewicht zwischen den Grenzfällen finden, die es wert waren, erfasst zu werden, und denen, die manuell migriert werden sollten. Außerdem wollten wir Nutzern mitteilen können, wenn eine importierte .css.js-Datei nicht automatisch vom Build-System generiert wird, da dies Laufzeitfehler verhindert.

Ein Nachteil der Verwendung von ESLint-Regeln für die Migration war, dass wir die erforderliche GN-Builddatei im System nicht ändern konnten. Diese Änderungen mussten vom Nutzer in jedem Verzeichnis manuell vorgenommen werden. Das erforderte zwar mehr Arbeit, war aber eine gute Möglichkeit, zu bestätigen, dass jede importierte .css.js-Datei tatsächlich vom Build-System generiert wird.

Insgesamt war die Verwendung von ESLint-Regeln für diese Migration sehr hilfreich, da wir den alten Code schnell in die neue Infrastruktur migrieren konnten. Da der AST sofort verfügbar war, konnten wir auch mehrere Grenzfälle in der Regel behandeln und sie mithilfe der Fixer API von ESLint zuverlässig automatisch beheben.

Nächster Schritt

Bisher wurden alle Webkomponenten in den Chromium DevTools auf die neue CSS-Infrastruktur umgestellt, anstatt Inline-Styles zu verwenden. Die meisten bisherigen Verwendungen von registerRequiredCSS wurden ebenfalls auf das neue System umgestellt. Jetzt müssen Sie nur noch so viele module.json-Dateien wie möglich entfernen und dann diese Infrastruktur migrieren, um CSS-Modulskripts in Zukunft zu implementieren.

Vorschaukanäle herunterladen

Verwenden Sie als Standard-Entwicklungsbrowser Chrome Canary, Chrome Dev oder Chrome Beta. Diese Vorabversionen bieten Zugriff auf die neuesten DevTools-Funktionen, ermöglichen den Test moderner Webplattform-APIs und helfen Ihnen, Probleme auf Ihrer Website zu finden, bevor Ihre Nutzer sie bemerken.

Chrome-Entwicklertools-Team kontaktieren

Mit den folgenden Optionen können Sie über neue Funktionen, Updates oder andere Themen im Zusammenhang mit den DevTools sprechen.