Modernisierung der CSS-Infrastruktur in Entwicklertools

Aktualisierung der Architektur der Entwicklertools: CSS-Infrastruktur in Entwicklertools modernisieren

Dieser Post ist Teil einer Reihe von Blogposts, in denen die Änderungen beschrieben werden, die wir an der Architektur der Entwicklertools vornehmen. Wir erläutern, wie CSS in der Vergangenheit in den Entwicklertools funktionierte und wie wir unser CSS in den Entwicklertools modernisiert haben, um uns auf die (letztendliche) Migration zu einer Webstandardlösung für das Laden von CSS in JavaScript-Dateien vorzubereiten.

Bisheriger Status von CSS in den Entwicklertools

In den Entwicklertools wurde CSS auf zwei Arten implementiert: einmal für CSS-Dateien, die im alten Teil der Entwicklertools verwendet wurden, und zum anderen für die modernen Webkomponenten, die in den Entwicklertools verwendet werden.

Die CSS-Implementierung in den Entwicklertools wurde vor vielen Jahren definiert und ist jetzt veraltet. Die Entwicklertools verwenden weiterhin das module.json-Muster und es gab viel Mühe, diese Dateien zu entfernen. Der letzte Blocker zum Entfernen dieser Dateien ist der Abschnitt resources, der zum Laden von CSS-Dateien verwendet wird.

Wir wollten verschiedene mögliche Lösungen kennenlernen, die sich in CSS-Modulskripts verwandeln lassen. Ziel war es, technische Altlasten zu beseitigen, die durch das alte System verursacht wurden, und gleichzeitig die Migration zu CSS-Modulskripts zu erleichtern.

Alle CSS-Dateien aus den Entwicklertools wurden als „veraltet“ eingestuft, da sie mit einer module.json-Datei geladen wurden, die gerade 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"
  ]
}

Diese CSS-Dateien werden dann in eine globale Objektzuordnung namens Root.Runtime.cachedResources als Zuordnung von einem Pfad zu ihrem Inhalt eingefügt. Wenn du Stile in die Entwicklertools hinzufügen möchtest, musst du registerRequiredCSS mit dem genauen Pfad zu der Datei aufrufen, die du laden möchtest.

Beispiel für einen registerRequiredCSS-Aufruf:

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 mithilfe eines Inline-Stilelements 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 haben wir uns zu Beginn dafür entschieden, CSS über Inline-<style>-Tags in den Komponentendateien selbst zu verwenden. Das war mit Herausforderungen verbunden:

  • Keine Unterstützung für Syntaxhervorhebungen. Plug-ins, die Syntaxhervorhebung für Inline-CSS nutzen, sind tendenziell weniger gut als die Funktionen für Syntaxhervorhebung und automatische Vervollständigung für CSS, die in .css-Dateien geschrieben sind.
  • Leistungsaufwand steigern: Inline-CSS bedeutete auch, dass zwei Karten/Tickets für Linting erforderlich waren: eine für CSS-Dateien und eine für Inline-CSS. Dies war ein Leistungsaufwand, den wir entfernen könnten, wenn der gesamte CSS-Code in eigenständigen CSS-Dateien geschrieben wäre.
  • Herausforderung bei der Reduzierung Inline-CSS konnte nicht einfach reduziert werden, daher wurde keiner der CSS-Ressourcen reduziert. Die Dateigröße des Release-Builds der Entwicklertools wurde auch durch dupliziertes CSS erhöht, das durch mehrere Instanzen derselben Webkomponente eingefügt wurde.

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

Mögliche Lösungen recherchieren

Das Problem könnte in zwei verschiedene Teile aufgeteilt werden:

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

Wir haben uns verschiedene mögliche Lösungen für die einzelnen Teile angesehen, die im Folgenden erläutert werden.

CSS-Dateien importieren

Ziel beim Importieren und Verwenden von CSS in TypeScript-Dateien war es, den Webstandards so nah wie möglich zu entsprechen, Konsistenz in den Entwicklertools durchzusetzen und doppelte CSS in unserem HTML-Code zu vermeiden. Außerdem wollten wir eine Lösung auswählen, die es uns ermöglicht, unsere Änderungen zu neuen Webplattformstandards wie CSS-Modulskripts zu migrieren.

Aus diesen Gründen schienen die @import-Anweisungen und -Tags nicht für die Entwicklertools geeignet zu sein. Sie wären in den übrigen Entwicklertools nicht einheitlich und würden zu einem Flash of Unstyled Content (FOUC) führen. Die Migration zu CSS-Modulskripts 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 den Import der CSS-Datei als CSSStyleSheet-Objekt entschieden, um sie mithilfe der adoptedStyleSheets-Eigenschaft dem Shadow Dom hinzuzufügen. Die Entwicklertools verwenden Shadow DOM jetzt schon seit einigen Jahren.

Bundler-Optionen

Wir brauchten eine Möglichkeit, CSS-Dateien in ein CSSStyleSheet-Objekt zu konvertieren, damit wir sie in der TypeScript-Datei einfach bearbeiten können. Wir haben sowohl Rollup als auch Webpack als potenzielle Bundler für diese Umwandlung in Betracht gezogen. Die Entwicklertools verwenden Rollup bereits im Produktions-Build, aber wenn man einen der Bundler zum Produktions-Build hinzufügt, kann es bei der Arbeit mit unserem aktuellen Build-System zu Leistungsproblemen kommen. Durch unsere Integration in das GN-Build-System von Chromium wird das Bündeln erschwert. Aus diesem Grund lassen sich Bundler oft nicht so gut in das aktuelle Chromium-Build-System integrieren.

Stattdessen haben wir die Option untersucht, das aktuelle GN-Build-System für diese Transformation zu verwenden.

Die neue Infrastruktur der Verwendung von CSS in den Entwicklertools

Bei der neuen Lösung werden mit adoptedStyleSheets Stile zu einem bestimmten Shadow-DOM hinzugefügt und mithilfe des GN-Build-Systems werden CSSStyleSheet-Objekte generiert, die von einem document oder ShadowRoot ü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 hat mehrere Vorteile:

  • Er entwickelt sich zu einem modernen Webstandard.
  • Verhindert doppelte CSS
  • Stile werden nur auf ein Schatten-DOM angewendet. So werden Probleme vermieden, die durch doppelte Klassennamen oder ID-Selektoren in CSS-Dateien verursacht werden.
  • Einfache Migration zu zukünftigen Webstandards wie CSS-Modulskripts und Import Assertions

Der einzige Nachteil der Lösung war, dass für die import-Anweisungen der Import der Datei .css.js erforderlich war. 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 nun jede CSS-Datei und wandelt sie in eine JavaScript-Datei um, die standardmäßig ein CSSStyleSheet-Objekt exportiert. Das ist großartig, weil wir die CSS-Datei importieren und leicht übernehmen können. Darüber hinaus können wir den Produktions-Build jetzt einfach reduzieren und so die Dateigröße einsparen:

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;

Das Beispiel hat aus dem Skript iconButton.css.js generiert.

Legacy-Code mithilfe von ESLint-Regeln migrieren

Die Webkomponenten lassen sich zwar leicht manuell migrieren, die Migration von Legacy-Nutzungen von registerRequiredCSS war jedoch aufwendiger. Die beiden Hauptfunktionen, für die Legacy-Stile registriert wurden, waren registerRequiredCSS und createShadowRootWithCoreStyles. Da die Schritte zur Migration dieser Aufrufe ziemlich mechanisch waren, haben wir beschlossen, dass wir mithilfe von ESLint-Regeln Korrekturen anwenden und Legacy-Code automatisch migrieren konnten. Die Entwicklertools verwenden bereits eine Reihe von benutzerdefinierten Regeln, die für die Codebasis der Entwicklertools spezifisch sind. Dies war hilfreich, da ESLint den Code bereits in einen abstrakten Syntaxbaum(abbr. AST) und könnten die jeweiligen Aufrufknoten abfragen, die zur Registrierung von CSS aufgerufen wurden.

Das größte Problem, das wir beim Verfassen der ESLint-Regeln für die Migration hatten, war das Erfassen von Grenzfällen. Wir wollten herausfinden, welche Grenzfälle erfasst werden sollten und welche manuell migriert werden sollten. Außerdem wollten wir Nutzer darüber informieren können, wenn eine importierte .css.js-Datei nicht automatisch vom Build-System generiert wird. So wird verhindert, dass während der Laufzeit Fehler vom Typ „Datei nicht gefunden“ auftreten.

Ein Nachteil der Verwendung von ESLint-Regeln für die Migration bestand darin, dass wir die erforderliche GN-Build-Datei im System nicht ändern konnten. Diese Änderungen mussten vom Nutzer in jedem Verzeichnis manuell vorgenommen werden. Dies erforderte zwar mehr Arbeit, war aber eine gute Methode, um sicherzustellen, dass jede importierte .css.js-Datei auch wirklich vom Build-System generiert wurde.

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

Was kann ich jetzt für Sie tun?

Bisher wurden alle Webkomponenten in den Chromium-Entwicklertools auf die neue CSS-Infrastruktur migriert und nicht mehr auf Inline-Styles. Der Großteil der alten Nutzungen von registerRequiredCSS wurde ebenfalls auf das neue System migriert. Jetzt müssen nur noch so viele module.json-Dateien wie möglich entfernt und dann die aktuelle Infrastruktur migriert werden, um später CSS-Modulskripts zu implementieren.

Vorschaukanäle herunterladen

Sie können Chrome Canary, Dev oder Beta als Standardbrowser für die Entwicklung verwenden. Über diese Vorschaukanäle erhältst du Zugriff auf die neuesten Entwicklertools-Funktionen, kannst neue Webplattform-APIs testen und Probleme auf deiner Website erkennen, bevor deine Nutzer es tun.

Chrome-Entwicklertools-Team kontaktieren

Verwende die folgenden Optionen, um die neuen Funktionen und Änderungen im Beitrag oder andere Themen im Zusammenhang mit den Entwicklertools zu besprechen.

  • Sende uns über crbug.com Vorschläge oder Feedback.
  • Wenn du ein Problem mit den Entwicklertools melden möchtest, klicke in den Entwicklertools auf Weitere Optionen   Mehr   > Hilfe > Probleme mit den Entwicklertools melden.
  • Senden Sie einen Tweet an @ChromeDevTools.
  • Hinterlasse Kommentare zu den Neuheiten in den Entwicklertools YouTube-Videos oder YouTube-Videos in den Entwicklertools-Tipps.