Compatibilité CSS-in-JS dans les outils de développement

Alex Rudenko
Alex Rudenko

Cet article présente la prise en charge du CSS dans JavaScript dans les outils pour les développeurs depuis Chrome 85. Il explique également en quoi consiste le CSS dans JavaScript et en quoi il diffère du CSS standard, qui est pris en charge par les outils pour les développeurs depuis longtemps.

Qu'est-ce que CSS-in-JS ?

La définition de CSS-in-JS est assez vague. De manière générale, il s'agit d'une approche permettant de gérer le code CSS à l'aide de JavaScript. Par exemple, cela peut signifier que le contenu CSS est défini à l'aide de JavaScript et que la sortie CSS finale est générée instantanément par l'application.

Dans le contexte de DevTools, CSS-in-JS signifie que le contenu CSS est injecté dans la page à l'aide d'API CSSOM. Le code CSS standard est injecté à l'aide des éléments <style> ou <link> et possède une source statique (par exemple, un nœud DOM ou une ressource réseau). En revanche, le CSS-in-JS n'a souvent pas de source statique. Il s'agit d'un cas particulier : le contenu d'un élément <style> peut être mis à jour à l'aide de l'API CSSOM, ce qui entraîne une désynchronisation de la source avec la feuille de style CSS réelle.

Si vous utilisez une bibliothèque CSS-in-JS (par exemple, styled-component, Emotion ou JSS), la bibliothèque peut injecter des styles à l'aide d'API CSSOM en fonction du mode de développement et du navigateur.

Voyons quelques exemples d'injection de feuille de style à l'aide de l'API CSSOM, comme le font les bibliothèques CSS-in-JS.

// Insert new rule to an existing CSS stylesheet
const element = document.querySelector('style');
const stylesheet = element.sheet;
stylesheet.replaceSync('.some { color: blue; }');
stylesheet.insertRule('.some { color: green; }');

Vous pouvez également créer une feuille de style complètement nouvelle:

// Create a completely new stylesheet
const stylesheet = new CSSStyleSheet();
stylesheet.replaceSync('.some { color: blue; }');
stylesheet.insertRule('.some { color: green; }');

// Apply constructed stylesheet to the document
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];

Compatibilité des CSS dans les outils de développement

Dans les outils de développement, la fonctionnalité la plus couramment utilisée pour les CSS est le volet Styles. Dans le volet Styles, vous pouvez voir les règles qui s'appliquent à un élément particulier, les modifier et voir les modifications apportées à la page en temps réel.

Avant l'année dernière, la prise en charge des règles CSS modifiées à l'aide d'API CSSOM était plutôt limitée: vous ne pouviez voir que les règles appliquées, mais pas les modifier. L'objectif principal de l'année dernière était de permettre la modification des règles CSS-in-JS à l'aide du volet "Styles". Nous appelons parfois les styles CSS-in-JS "construits" pour indiquer qu'ils ont été créés à l'aide d'API Web.

Découvrons en détail le fonctionnement de la modification des styles dans DevTools.

Mécanisme de modification des styles dans DevTools

Mécanisme de modification du style dans les outils de développement

Lorsque vous sélectionnez un élément dans DevTools, le volet Styles s'affiche. Le volet Styles émet une commande CDP appelée CSS.getMatchedStylesForNode pour obtenir les règles CSS qui s'appliquent à l'élément. CDP signifie Chrome DevTools Protocol. Il s'agit d'une API qui permet au frontend DevTools d'obtenir des informations supplémentaires sur la page inspectée.

Lorsqu'il est appelé, CSS.getMatchedStylesForNode identifie toutes les feuilles de style du document et les analyse à l'aide de l'analyseur CSS du navigateur. Il crée ensuite un index qui associe chaque règle CSS à une position dans la source de la feuille de style.

Vous vous demandez peut-être pourquoi il doit analyser à nouveau le CSS. Le problème est que, pour des raisons de performances, le navigateur lui-même ne se soucie pas des positions sources des règles CSS et, par conséquent, ne les stocke pas. Toutefois, les outils de développement ont besoin des positions sources pour permettre la modification des CSS. Nous ne voulons pas que les utilisateurs Chrome ordinaires paient le prix de cette pénalité de performances, mais nous voulons que les utilisateurs des outils pour les développeurs aient accès aux positions source. Cette approche de réanalyse répond aux deux cas d'utilisation avec un minimum d'inconvénients.

Ensuite, l'implémentation de CSS.getMatchedStylesForNode demande au moteur de style du navigateur de fournir des règles CSS correspondant à l'élément donné. Enfin, la méthode associe les règles renvoyées par le moteur de style au code source et fournit une réponse structurée sur les règles CSS afin que DevTools sache quelle partie de la règle est le sélecteur ou les propriétés. Il permet aux outils de développement de modifier le sélecteur et les propriétés indépendamment.

Passons maintenant à la modification. N'oubliez pas que CSS.getMatchedStylesForNode renvoie les positions source pour chaque règle. C'est essentiel pour le montage. Lorsque vous modifiez une règle, DevTools émet une autre commande CDP qui met à jour la page. La commande inclut la position d'origine du fragment de la règle qui est mis à jour et le nouveau texte à utiliser pour le remplacer.

Sur le backend, lors de la gestion de l'appel de modification, les outils de développement mettent à jour la feuille de style cible. Il met également à jour la copie de la source de la feuille de style qu'il gère et les positions de la source pour la règle mise à jour. En réponse à l'appel de modification, l'interface des outils de développement récupère les positions mises à jour pour le fragment de texte qui vient d'être mis à jour.

Cela explique pourquoi la modification du CSS-in-JS dans DevTools ne fonctionnait pas immédiatement: CSS-in-JS n'a pas de source réelle stockée nulle part et les règles CSS résident dans la mémoire du navigateur dans des structures de données CSSOM.

Comment nous avons ajouté la compatibilité avec le CSS-in-JS

Pour permettre la modification des règles CSS-in-JS, nous avons donc décidé que la meilleure solution était de créer une source pour les feuilles de style construites qui peuvent être modifiées à l'aide du mécanisme existant décrit ci-dessus.

La première étape consiste à créer le texte source. Le moteur de style du navigateur stocke les règles CSS dans la classe CSSStyleSheet. C'est celle dont vous pouvez créer des instances à partir de JavaScript, comme indiqué précédemment. Le code permettant de créer le texte source est le suivant:

String InspectorStyleSheet::CollectStyleSheetRules() {
  StringBuilder builder;
  for (unsigned i = 0; i < page_style_sheet_->length(); i++) {
    builder.Append(page_style_sheet_->item(i)->cssText());
    builder.Append('\n');
  }
  return builder.ToString();
}

Il itère sur les règles trouvées dans une instance CSSStyleSheet et en crée une seule chaîne. Cette méthode est appelée lorsqu'une instance de la classe InspectorStyleSheet est créée. La classe InspectorStyleSheet encapsule une instance CSSStyleSheet et extrait les métadonnées supplémentaires requises par les outils de développement:

void InspectorStyleSheet::UpdateText() {
  String text;
  bool success = InspectorStyleSheetText(&text);
  if (!success)
    success = InlineStyleSheetText(&text);
  if (!success)
    success = ResourceStyleSheetText(&text);
  if (!success)
    success = CSSOMStyleSheetText(&text);
  if (success)
    InnerSetText(text, false);
}

Dans cet extrait, CSSOMStyleSheetText appelle CollectStyleSheetRules en interne. CSSOMStyleSheetText est appelé si la feuille de style n'est pas intégrée ou si elle est une feuille de style de ressources. En gros, ces deux extraits permettent déjà de modifier de manière basique les feuilles de style créées à l'aide du constructeur new CSSStyleSheet().

Les feuilles de style associées à une balise <style> qui ont été modifiées à l'aide de l'API CSSOM constituent un cas particulier. Dans ce cas, la feuille de style contient le texte source et des règles supplémentaires qui ne sont pas présentes dans la source. Pour gérer ce cas, nous introduisons une méthode permettant de fusionner ces règles supplémentaires dans le texte source. Ici, l'ordre est important, car les règles CSS peuvent être insérées au milieu du texte source d'origine. Par exemple, imaginons que l'élément <style> d'origine contenait le texte suivant:

/* comment */
.rule1 {}
.rule3 {}

La page a ensuite inséré de nouvelles règles à l'aide de l'API JS, ce qui a généré l'ordre de règles suivant : .rule0, .rule1, .rule2, .rule3, .rule4. Le texte source obtenu après l'opération de fusion doit être le suivant:

.rule0 {}
/* comment */
.rule1 {}
.rule2 {}
.rule3 {}
.rule4 {}

La conservation des commentaires et de l'indentation d'origine est importante pour le processus de modification, car les positions du texte source des règles doivent être précises.

Autre aspect particulier des feuilles de style CSS-in-JS : elles peuvent être modifiées par la page à tout moment. Si les règles CSSOM réelles ne sont plus synchronisées avec la version textuelle, la modification ne fonctionnera pas. Pour cela, nous avons lancé un test, qui permet au navigateur d'informer la partie backend des outils de développement lorsqu'une feuille de style est modifiée. Les feuilles de style mutées sont ensuite synchronisées lors du prochain appel de CSS.getMatchedStylesForNode.

Avec tous ces éléments en place, l'édition CSS-in-JS fonctionne déjà, mais nous souhaitions améliorer l'interface utilisateur pour indiquer si une feuille de style a été créée. Nous avons ajouté un nouvel attribut appelé isConstructed à CSS.CSSStyleSheetHeader de CDP, que le frontend utilise pour afficher correctement la source d'une règle CSS:

Feuille de style constructible

Conclusions

Pour résumer, nous avons examiné les cas d'utilisation pertinents liés au CSS-in-JS que DevTools ne prenait pas en charge et avons présenté la solution pour les prendre en charge. L'aspect intéressant de cette implémentation est que nous avons pu exploiter les fonctionnalités existantes en créant un texte source standard pour les règles CSSOM, ce qui nous a évité de repenser complètement l'édition de style dans DevTools.

Pour en savoir plus, consultez notre proposition de conception ou le bug de suivi de Chromium, qui fait référence à tous les correctifs associés.

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.