Au-delà des expressions régulières: amélioration de l'analyse des valeurs CSS dans les outils pour les développeurs Chrome

Philip Pfaffe
Ergün Erdogmus
Ergün Erdogmus

Avez-vous remarqué les propriétés CSS dans les outils pour les développeurs Chrome ? l'onglet Styles vous semble-t-il un peu plus soigné ces derniers temps ? Ces mises à jour, déployées entre Chrome 121 et 128, sont le résultat d'une amélioration significative de l'analyse et de la présentation des valeurs CSS. Cet article décrit les détails techniques de cette transformation, qui consiste à passer d'un système de correspondance des expressions régulières à un analyseur plus robuste.

Comparons les outils de développement actuels avec ceux de la version précédente:

En haut: il s'agit de la dernière version de Chrome, en bas: Chrome 121.

C’est une grande différence, n’est-ce pas ? Voici le détail des principales améliorations:

  • color-mix Aperçu pratique représentant visuellement les deux arguments de couleur dans la fonction color-mix.
  • pink Aperçu de couleur cliquable pour la couleur nommée pink. Cliquez dessus pour ouvrir un sélecteur de couleur et effectuer facilement des réglages.
  • var(--undefined, [fallback value]) Amélioration de la gestion des variables non définies : la variable non définie est grisée et la valeur de remplacement active (dans ce cas, une couleur TSL) s'affiche avec un aperçu cliquable de la couleur.
  • hsl(…): autre aperçu de couleur cliquable pour la fonction de couleur hsl, permettant d'accéder rapidement au sélecteur de couleur.
  • 177deg: horloge cliquable qui vous permet de faire glisser et de modifier la valeur de l'angle de manière interactive.
  • var(--saturation, …): lien cliquable vers la définition de la propriété personnalisée, permettant d'accéder facilement à la déclaration correspondante.

La différence est frappante. Pour ce faire, nous avons dû apprendre aux outils de développement à mieux comprendre les valeurs des propriétés CSS.

Ces aperçus n'étaient-ils pas déjà disponibles ?

Ces icônes d'aperçu peuvent vous sembler familières, mais elles n'ont pas toujours été affichées de manière cohérente, en particulier dans une syntaxe CSS complexe, comme dans l'exemple ci-dessus. Même dans les cas où ils travaillaient, des efforts importants étaient souvent nécessaires pour qu'ils fonctionnent correctement.

Cela s'explique par le fait que le système d'analyse des valeurs connaît une croissance organique depuis les premiers jours d'utilisation des outils de développement. Toutefois, il n'a pas réussi à suivre les nouvelles fonctionnalités incroyables que nous obtenons avec le CSS et l'augmentation correspondante de la complexité du langage. Le système a nécessité une refonte complète pour suivre l'évolution, et c'est exactement ce que nous avons fait !

Traitement des valeurs de propriété CSS

Dans les outils de développement, le processus d'affichage et de décoration des déclarations de propriété dans l'onglet Styles est divisé en deux phases distinctes:

  1. Analyse structurelle. Cette phase initiale analyse la déclaration de propriété pour identifier ses composants sous-jacents et leurs relations. Par exemple, dans la déclaration border: 1px solid red, il reconnaîtrait 1px comme une longueur, solid comme une chaîne et red comme une couleur.
  2. Rendu. En s'appuyant sur l'analyse structurelle, la phase de rendu transforme ces composants en une représentation HTML. Cela permet d'enrichir le texte de la propriété affiché avec des éléments interactifs et des repères visuels. Par exemple, la valeur de couleur red est affichée avec une icône de couleur cliquable qui, lorsque l'utilisateur clique dessus, affiche un sélecteur de couleur pour faciliter la modification.

Expressions régulières

Auparavant, nous nous appuyions sur des expressions régulières pour analyser les valeurs de propriétés en vue d'une analyse structurelle. Nous avons mis à jour une liste d'expressions régulières correspondant aux bits des valeurs de propriété que nous avons envisagé de décorer. Par exemple, il existe des expressions correspondant à des couleurs, des longueurs et des angles CSS, des sous-expressions plus complexes telles que les appels de fonction var, etc. Nous avons analysé le texte de gauche à droite pour analyser la valeur, en recherchant continuellement la première expression de la liste qui correspond à la partie suivante du texte.

Bien que cela fonctionnait bien la plupart du temps, le nombre de cas où cela n'a pas continué à augmenter. Au fil des ans, nous avons reçu de nombreux rapports de bugs pour lesquels la correspondance n'était pas tout à fait correcte. Au fur et à mesure que nous les avons corrigés (certaines corrections simples, d'autres assez élaborées), nous avons dû repenser notre approche afin de réduire notre dette technique. Examinons certains de ces problèmes.

color-mix() correspondant

L'expression régulière que nous avons utilisée pour la fonction color-mix() était la suivante:

/color-mix\(.*,\s*(?<firstColor>.+)\s*,\s*(?<secondColor>.+)\s*\)/g

Ce qui correspond à sa syntaxe:

color-mix(<color-interpolation-method>, [<color> && <percentage [0,100]>?]#{2})

Essayez d'exécuter l'exemple suivant pour visualiser les correspondances.

const re = /color-mix\(.*,\s*(?<firstColor>.+)\s*,\s*(?<secondColor>.+)\s*\)/g;

// it works - simpler example
const simpler = re.exec('color-mix(in srgb, pink, hsl(127deg 100% 50%))');
console.table(simpler.groups);

re.exec('');

// it doesn't work - complex example
const complex = re.exec('color-mix(in srgb, pink, var(--undefined, hsl(127deg var(--saturation, 100%) 50%)))');
console.table(complex.groups);

Résultat de correspondance pour la fonction de mélange de couleurs.

L'exemple, plus simple, convient bien. Toutefois, dans l'exemple plus complexe, la correspondance <firstColor> est hsl(177deg var(--saturation et <secondColor> est 100%) 50%)), ce qui n'a aucun sens.

Nous savions que c'était un problème. Après tout, le CSS en tant que langage formel n'est pas standard. Nous avons donc déjà inclus un traitement spécial pour gérer des arguments de fonction plus complexes, comme les fonctions var. Toutefois, comme vous pouvez le voir sur la première capture d'écran, cela ne fonctionne toujours pas dans tous les cas.

tan() correspondant

L'un des bugs les plus hilarants signalés concernait la fonction trigonométrique tan() . L'expression régulière que nous utilisions pour faire correspondre les couleurs incluait une sous-expression \b[a-zA-Z]+\b(?!-) pour la mise en correspondance des couleurs nommées, comme le mot clé red. Nous avons ensuite vérifié si la partie correspondante était en fait une couleur nommée, et devinez quoi : tan est aussi une couleur nommée ! Nous avons donc interprété à tort les expressions tan() comme des couleurs.

var() correspondant

Examinons un autre exemple, les fonctions var() avec une solution de remplacement contenant d'autres références var(): var(--non-existent, var(--margin-vertical)).

Notre expression régulière pour var() correspondrait volontiers à cette valeur. En revanche, la correspondance arrête de correspondre à la première parenthèse fermante. Ainsi, le texte ci-dessus correspond à var(--non-existent, var(--margin-vertical). Il s'agit d'une limitation du manuel de la correspondance d'expression régulière. Les langues qui nécessitent la même parenthèse ne sont fondamentalement pas courantes.

Passer à un analyseur CSS

Lorsque l'analyse de texte à l'aide d'expressions régulières cesse de fonctionner (car la langue analysée n'est pas régulière), une étape canonique est nécessaire: utiliser un analyseur pour une grammaire de type supérieur. Dans le cas du CSS, il s'agit d'un analyseur de langues sans contexte. Ce système d'analyse existait déjà dans le codebase des outils de développement: le Lezer de CodeMirror, qui est à la base, par exemple, de la mise en surbrillance de la syntaxe dans CodeMirror, l'éditeur que vous trouverez dans le panneau Sources. L'analyseur CSS de Lezer nous a permis de produire des arbres syntaxiques (non abstraits) pour les règles CSS et était prêt à l'emploi. Victoire.

Arborescence de syntaxe pour la valeur de propriété &quot;hsl(177deg var(--saturation, 100%) 50%)&quot; Il s&#39;agit d&#39;une version simplifiée du résultat généré par l&#39;analyseur Lezer, sans nœuds purement syntaxiques pour les virgules et les parenthèses.

En revanche, il n'était pas possible de passer directement de la correspondance basée sur des expressions régulières à une correspondance basée sur l'analyseur, car les deux approches fonctionnent dans des directions opposées. Lors de la mise en correspondance de valeurs avec des expressions régulières, les outils de développement analysent l'entrée de gauche à droite, en essayant de trouver à plusieurs reprises la correspondance la plus ancienne dans une liste numérotée de modèles. Avec une arborescence syntaxique, la mise en correspondance commencera de bas en haut, par exemple en analysant d'abord les arguments d'un appel, avant d'essayer de faire correspondre l'appel de fonction. Considérez cela comme une évaluation d'une expression arithmétique, où vous considéreriez d'abord les expressions entre parenthèses, puis les opérateurs multiplicatifs, et enfin les opérateurs additifs. Dans ce cadre, la correspondance basée sur des expressions régulières correspond à l'évaluation de l'expression arithmétique de gauche à droite. Nous n'avions absolument pas envie de réécrire entièrement le système de mise en correspondance: il y avait 15 paires de mise en correspondance et de moteur de rendu différentes, accompagnées de milliers de lignes de code, il était donc peu probable que nous puissions l'envoyer en une seule étape.

Nous avons donc trouvé une solution qui nous a permis d'apporter des modifications incrémentales, que nous décrirons plus en détail ci-dessous. En bref, nous avons gardé l'approche en deux phases, mais dans la première phase, nous essayons de faire correspondre les sous-expressions de bas en haut (ce qui entraîne une interruption avec le flux d'expression régulière). Dans la deuxième phase, nous affichons un rendu descendant. Dans les deux phases, nous avons pu utiliser les résultats et les outils de mise en correspondance basés sur des expressions régulières, pratiquement inchangés, et ainsi les migrer un par un.

Phase 1: Mise en correspondance ascendante

La première phase correspond plus ou moins précisément et exclusivement à ce qui est indiqué sur la couverture. Nous balayons l'arborescence de bas en haut en essayant de faire correspondre les sous-expressions au niveau de chaque nœud de l'arborescence syntaxique que nous consultons. Pour établir une correspondance avec une sous-expression spécifique, un outil de mise en correspondance peut utiliser une expression régulière de la même manière que dans le système existant. Depuis la version 128, nous le faisons encore dans certains cas, par exemple pour les longueurs correspondantes. Un outil de mise en correspondance peut également analyser la structure de la sous-arborescence en racine du nœud actuel. Cela lui permet de détecter les erreurs de syntaxe et d'enregistrer les informations structurelles en même temps.

Prenons l'exemple d'arborescence de syntaxe ci-dessus:

Phase 1: correspondance ascendante sur l&#39;arborescence syntaxique.

Pour cet arbre, nos outils de mise en correspondance s'appliqueraient dans l'ordre suivant:

  1. hsl(177degvar(--saturation, 100%) 50%): nous découvrons d'abord le premier argument de l'appel de fonction hsl, l'angle de teinte. Nous l'associons à un outil de mise en correspondance des angles, afin de pouvoir décorer la valeur de l'angle avec l'icône d'angle.
  2. hsl(177degvar(--saturation, 100%)50%): nous découvrons ensuite l'appel de fonction var avec un outil de mise en correspondance de variables. Pour de tels appels, nous souhaitons principalement effectuer les deux opérations suivantes: <ph type="x-smartling-placeholder">
      </ph>
    • Recherchez la déclaration de la variable et calculez sa valeur, puis ajoutez un lien et une fenêtre pop-up au nom de la variable pour vous y connecter, respectivement.
    • Décorez l'appel avec une icône de couleur si la valeur calculée est une couleur. Il y a en fait une troisième chose, mais nous en reparlerons plus tard.
  3. hsl(177deg var(--saturation, 100%) 50%): enfin, nous mettons en correspondance l'expression d'appel pour la fonction hsl afin de pouvoir la décorer avec l'icône de couleur.

En plus de rechercher des sous-expressions que nous aimerions décorer, il existe une deuxième fonctionnalité que nous exécutons dans le cadre du processus de mise en correspondance. Notez qu'à l'étape 2, nous avons indiqué que nous recherchions la valeur calculée pour un nom de variable. En fait, nous allons encore plus loin et propageons les résultats dans l'arborescence. Et pas seulement pour la variable, mais aussi pour la valeur de remplacement. Lors de la visite d'un nœud de fonction var, ses enfants ont été consultés au préalable. Nous connaissons donc déjà les résultats de toutes les fonctions var qui pourraient apparaître dans la valeur de remplacement. Par conséquent, nous pouvons facilement et à moindre coût remplacer les fonctions var par leurs résultats à la volée, ce qui nous permet de répondre de manière triviale à des questions telles que "Le résultat de cet élément var appelle-t-il une couleur ?", comme nous l'avons fait à l'étape 2.

Phase 2: Affichage de haut en bas

Pour la deuxième phase, nous inverserons la direction. Après avoir obtenu les résultats de la phase 1, nous affichons l'arborescence au format HTML en la faisant glisser du haut vers le bas. Pour chaque nœud visité, nous vérifions s'il correspond et, le cas échéant, nous appelons le moteur de rendu correspondant de l'outil de mise en correspondance. Nous évitons d'avoir à traiter spécialement les nœuds qui ne contiennent que du texte (comme le NumberLiteral "50 %") en incluant un outil de mise en correspondance et un moteur de rendu par défaut pour les nœuds de texte. Les moteurs de rendu génèrent simplement des nœuds HTML qui, une fois assemblés, produisent la représentation de la valeur de la propriété, y compris ses décorations.

Phase 2: Rendu descendant dans l&#39;arborescence syntaxique

Pour l'exemple d'arborescence, voici l'ordre dans lequel la valeur de la propriété est affichée:

  1. Consultez l'appel de fonction hsl. Comme il correspond, appelez le moteur de rendu de fonction de couleur. Il a deux fonctions: <ph type="x-smartling-placeholder">
      </ph>
    • Calcule la valeur de couleur réelle à l'aide du mécanisme de substitution à la volée pour tous les arguments var, puis affiche une icône de couleur.
    • Affiche les enfants de CallExpression de manière récursive. Cela se charge automatiquement d'afficher le nom de la fonction, les parenthèses et les virgules, qui ne sont que du texte.
  2. Consultez le premier argument de l'appel hsl. Comme il correspond, appelez le moteur de rendu d'angle, qui dessine l'icône d'angle et le texte de l'angle.
  3. Accédez au deuxième argument, qui est l'appel var. Comme la correspondance est établie, appelez la variable renderer, qui génère la sortie suivante: <ph type="x-smartling-placeholder">
      </ph>
    • Texte var( au début.
    • Le nom de la variable est décoratif avec un lien vers sa définition ou avec du texte en gris pour indiquer qu'elle n'était pas définie. Une fenêtre contextuelle est également ajoutée à la variable pour afficher des informations sur sa valeur.
    • La virgule affiche la valeur de remplacement de manière récursive.
    • Parenthèse fermante.
  4. Accédez au dernier argument de l'appel hsl. Comme il ne correspond pas, il suffit de renvoyer son contenu textuel.

Avez-vous remarqué que dans cet algorithme, un rendu contrôle entièrement l'affichage des enfants d'un nœud correspondant ? Le rendu récursif des éléments enfants est proactif. Cette astuce a permis de migrer progressivement le rendu basé sur des expressions régulières vers l'affichage basé sur une arborescence syntaxique. Pour les nœuds mis en correspondance avec un ancien moteur de rendu d'expression régulière, le moteur de rendu correspondant peut être utilisé dans sa forme d'origine. En termes d'arborescence syntaxique, il serait responsable du rendu de l'intégralité de la sous-arborescence, et son résultat (un nœud HTML) pourrait être correctement intégré au processus de rendu environnant. Cela nous a permis de les échanger les uns après les autres, avec les outils de mise en correspondance et les moteurs de rendu.

Autre fonctionnalité intéressante qui permet aux moteurs de rendu de contrôler le rendu des enfants de leurs nœuds correspondants : cela nous permet de comprendre les dépendances entre les icônes que nous ajoutons. Dans l'exemple ci-dessus, la couleur produite par la fonction hsl dépend évidemment de sa valeur de teinte. Cela signifie que la couleur affichée par l'icône de couleur dépend de l'angle indiqué par l'icône d'angle. Si l'utilisateur ouvre l'éditeur d'angle via cette icône et modifie l'angle, nous pouvons désormais mettre à jour la couleur de l'icône de couleur en temps réel:

Comme vous pouvez le voir dans l'exemple ci-dessus, nous utilisons également ce mécanisme pour d'autres associations d'icônes, par exemple pour color-mix() et ses deux canaux de couleur, ou pour les fonctions var qui renvoient une couleur à partir de sa création de remplacement.

Impact sur la performance

Lorsque nous avons examiné ce problème afin d'améliorer la fiabilité et de corriger ceux qui existent depuis longtemps, nous nous attendions à une régression des performances, étant donné que nous avons commencé à utiliser un analyseur complet. Pour tester cela, nous avons créé une analyse comparative qui affiche environ 3,5 000 déclarations de propriété et profilé les versions basées sur des expressions régulières et sur l'analyseur avec une limitation x6 sur une machine M1.

Comme prévu, l'approche basée sur l'analyse s'est avérée 27% plus lente que celle basée sur les expressions régulières. Avec l'approche basée sur les expressions régulières, le rendu a pris 11 secondes, tandis que l'approche basée sur l'analyseur a pris 15 secondes.

Compte tenu des avantages que présente cette nouvelle approche, nous avons décidé de l'adopter.

Remerciements

Nous remercions Sofia Emelianova et Jecelyn Yeen pour leur précieuse aide à la rédaction de ce post.

Télécharger les canaux de prévisualisation

Vous pouvez utiliser Chrome Canary, Dev ou Bêta comme navigateur de développement par défaut. Ces versions preview vous permettent d'accéder aux dernières fonctionnalités des outils de développement, de tester des API de plates-formes Web de pointe et de 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 et des modifications dans l'article, ou de tout autre sujet lié aux outils de développement.

  • Envoyez-nous une suggestion ou un commentaire via crbug.com.
  • Signalez un problème lié aux outils de développement en cliquant sur Autres options   Plus > Aide > Signalez un problème dans les outils de développement.
  • Tweetez à l'adresse @ChromeDevTools.
  • Faites-nous part de vos commentaires sur les vidéos YouTube sur les nouveautés des outils de développement ou sur les vidéos YouTube de conseils pour les outils de développement.