Moderniser l'infrastructure CSS dans les outils de développement

Actualisation de l'architecture DevTools: modernisation de l'infrastructure CSS dans DevTools

Cet article fait partie d'une série de posts de blog décrivant les modifications que nous apportons à l'architecture de DevTools et à sa création. Nous allons vous expliquer comment le CSS fonctionnait historiquement dans les outils pour les développeurs et comment nous avons modernisé notre CSS dans les outils pour les développeurs en vue d'une migration (éventuelle) vers une solution Web standard pour le chargement du CSS dans les fichiers JavaScript.

État précédent du CSS dans DevTools

DevTools a implémenté le CSS de deux manières différentes: l'une pour les fichiers CSS utilisés dans la partie ancienne de DevTools, l'autre pour les composants Web modernes utilisés dans DevTools.

L'implémentation du CSS dans DevTools a été définie il y a de nombreuses années et est désormais obsolète. DevTools a continué à utiliser le modèle module.json, et d'énormes efforts ont été déployés pour supprimer ces fichiers. Le dernier obstacle pour la suppression de ces fichiers est la section resources, utilisée pour charger les fichiers CSS.

Nous avons souhaité prendre le temps d'explorer différentes solutions potentielles qui pourraient éventuellement se transformer en scripts de module CSS. L'objectif était de supprimer la dette technique causée par l'ancien système, mais aussi de faciliter le processus de migration vers les scripts de module CSS.

Tous les fichiers CSS qui se trouvaient dans DevTools étaient considérés comme "anciens", car ils étaient chargés à l'aide d'un fichier module.json, qui est en cours de suppression. Tous les fichiers CSS devaient être listés sous resources dans un fichier module.json situé dans le même répertoire que le fichier CSS.

Exemple de fichier module.json restant :

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

Ces fichiers CSS rempliraient ensuite une carte d'objets globale appelée Root.Runtime.cachedResources en mappant un chemin d'accès à leur contenu. Pour ajouter des styles dans DevTools, vous devez appeler registerRequiredCSS avec le chemin exact du fichier que vous souhaitez charger.

Exemple d'appel registerRequiredCSS :

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

Cela permet de récupérer le contenu du fichier CSS et de l'insérer en tant qu'élément <style> dans la page à l'aide de la fonction appendStyle :

Fonction appendStyle qui ajoute du CSS à l'aide d'un élément de style intégré :

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

Lorsque nous avons lancé les composants Web modernes (à l'aide d'éléments personnalisés), nous avons initialement décidé d'utiliser le CSS via des balises <style> intégrées dans les fichiers de composants. Cela présentait ses propres défis:

  • La coloration syntaxique n'est pas prise en charge. Les plug-ins qui permettent de mettre en évidence la syntaxe du code CSS intégré ne sont généralement pas aussi performants que les fonctionnalités de coloration syntaxique et de saisie semi-automatique pour CSS écrites dans des fichiers .css.
  • Créer des coûts supplémentaires en termes de performances Le CSS intégré impliquait également que deux passes de linting étaient nécessaires : une pour les fichiers CSS et une pour le CSS intégré. Il s'agissait d'un surcoût de performances que nous pouvions supprimer si tout le CSS était écrit dans des fichiers CSS autonomes.
  • Défi de minimisation. Le CSS intégré n'a pas pu être facilement minimisé. Aucun CSS n'a donc été minimisé. La taille du fichier du build de DevTools a également augmenté en raison du CSS dupliqué introduit par plusieurs instances du même composant Web.

L'objectif de mon projet de stage était de trouver une solution pour l'infrastructure CSS qui fonctionne à la fois avec l'ancienne infrastructure et les nouveaux composants Web utilisés dans DevTools.

Rechercher des solutions potentielles

Le problème peut être divisé en deux parties:

  • Comprendre comment le système de compilation traite les fichiers CSS.
  • Comprendre comment les fichiers CSS sont importés et utilisés par les outils pour les développeurs.

Nous avons examiné différentes solutions potentielles pour chaque partie, qui sont décrites ci-dessous.

Importer des fichiers CSS

L'objectif de l'importation et de l'utilisation du CSS dans les fichiers TypeScript était de respecter au mieux les normes Web, de garantir la cohérence dans les outils pour les développeurs et d'éviter les doublons de CSS dans notre code HTML. Nous voulions également être en mesure de choisir une solution qui nous permettrait de migrer nos modifications vers les nouvelles normes de la plate-forme Web, telles que les scripts de module CSS.

Pour ces raisons, les instructions @import et les balises ne semblaient pas adaptées aux outils de développement. Elles ne seraient pas uniformes avec les importations dans le reste de DevTools et entraîneraient un Flash of Unstyled Content (FOUC). La migration vers les scripts de module CSS serait plus difficile, car les importations devraient être ajoutées explicitement et traitées différemment que les balises <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>`

Solutions potentielles à l'aide de @import ou <link>.

Nous avons plutôt choisi de trouver un moyen d'importer le fichier CSS en tant qu'objet CSSStyleSheet afin de pouvoir l'ajouter au Shadow DOM (DevTools utilise Shadow DOM depuis quelques années) à l'aide de sa propriété adoptedStyleSheets.

Options du bundleur

Nous avions besoin d'un moyen de convertir les fichiers CSS en objet CSSStyleSheet afin de pouvoir les manipuler facilement dans le fichier TypeScript. Nous avons envisagé Rollup et webpack comme des outils de regroupement potentiels pour effectuer cette transformation. Les outils de développement utilisent déjà Rollup dans son build de production, mais l'ajout de l'un ou l'autre bundler au build de production peut entraîner des problèmes de performances potentiels avec notre système de compilation actuel. Notre intégration au système de compilation GN de Chromium rend le regroupement plus difficile. Par conséquent, les outils de regroupement ont tendance à ne pas s'intégrer correctement au système de compilation Chromium actuel.

Nous avons plutôt exploré la possibilité d'utiliser le système de compilation GN actuel pour effectuer cette transformation à notre place.

Nouvelle infrastructure d'utilisation du CSS dans DevTools

La nouvelle solution consiste à utiliser adoptedStyleSheets pour ajouter des styles à un Shadow DOM particulier, tout en utilisant le système de compilation GN pour générer des objets CSSStyleSheet pouvant être adoptés par un document ou un 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];
  }
}

L'utilisation de adoptedStyleSheets présente plusieurs avantages, dont les suivants:

  • Il est en passe de devenir une norme Web moderne
  • Empêche le CSS en double
  • Applique les styles uniquement à un Shadow DOM, ce qui évite les problèmes causés par des noms de classe ou des sélecteurs d'ID en double dans les fichiers CSS
  • Facile à migrer vers les futures normes Web telles que les scripts de module CSS et les assertions d'importation

Le seul inconvénient de cette solution était que les instructions import nécessitaient l'importation du fichier .css.js. Pour permettre à GN de générer un fichier CSS lors de la compilation, nous avons écrit le script generate_css_js_files.js. Le système de compilation traite désormais chaque fichier CSS et le transforme en fichier JavaScript qui exporte par défaut un objet CSSStyleSheet. C'est très pratique, car nous pouvons importer le fichier CSS et l'adopter facilement. De plus, nous pouvons désormais facilement réduire la taille de la version de production :

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;

Exemple de iconButton.css.js généré à partir du script.

Migrer l'ancien code à l'aide de règles ESLint

Bien que les composants Web puissent être facilement migrés manuellement, le processus de migration des anciennes utilisations de registerRequiredCSS était plus complexe. Les deux principales fonctions qui enregistraient les anciens styles étaient registerRequiredCSS et createShadowRootWithCoreStyles. Étant donné que les étapes de migration de ces appels étaient relativement mécaniques, nous avons décidé d'utiliser des règles ESLint pour appliquer des correctifs et migrer automatiquement le code ancien. Les outils de développement utilisent déjà un certain nombre de règles personnalisées spécifiques à leur codebase. Cela a été utile, car ESLint analyse déjà le code en arbre syntaxique abstrait (abr. AST) et nous pouvions interroger les nœuds d'appel spécifiques qui étaient des appels d'enregistrement de CSS.

Le plus gros problème que nous avons rencontré lors de l'écriture des règles ESLint de migration était la capture des cas extrêmes. Nous voulions trouver le bon équilibre entre savoir quels cas limites valent la peine d'être capturés et lesquels doivent être migrés manuellement. Nous voulons également nous assurer de pouvoir indiquer à un utilisateur lorsqu'un fichier .css.js importé n'est pas généré automatiquement par le système de compilation, car cela évite toute erreur de fichier introuvable lors de l'exécution.

L'un des inconvénients des règles ESLint pour la migration était que nous ne pouvions pas modifier le fichier de compilation GN requis dans le système. Ces modifications devaient être effectuées manuellement par l'utilisateur dans chaque répertoire. Bien que cela ait nécessité plus de travail, c'était un bon moyen de vérifier que chaque fichier .css.js importé est effectivement généré par le système de compilation.

Dans l'ensemble, l'utilisation des règles ESLint pour cette migration a été très utile, car nous avons pu migrer rapidement l'ancien code vers la nouvelle infrastructure. La disponibilité immédiate de l'AST nous a également permis de gérer plusieurs cas particuliers dans la règle et de les corriger automatiquement de manière fiable à l'aide de l'API de correction d'ESLint.

Et maintenant ?

À ce jour, tous les composants Web des outils pour les développeurs Chromium ont été migrés pour utiliser la nouvelle infrastructure CSS au lieu des styles intégrés. La plupart des anciens usages de registerRequiredCSS ont également été migrés vers le nouveau système. Il ne vous reste plus qu'à supprimer autant de fichiers module.json que possible, puis à migrer cette infrastructure actuelle pour implémenter des scripts de module CSS à l'avenir.

Télécharger les canaux de prévisualisation

Envisagez d'utiliser Chrome Canary, Dev ou Bêta comme navigateur de développement par défaut. Ces canaux de prévisualisation vous donnent accès aux dernières fonctionnalités de DevTools, vous permettent de tester les API de plates-formes Web de pointe et vous aident à détecter les problèmes sur votre site avant vos utilisateurs.

Contacter l'équipe des outils pour les développeurs Chrome

Utilisez les options suivantes pour discuter des nouvelles fonctionnalités, des mises à jour ou de tout autre élément lié aux outils pour les développeurs.