À ses débuts, Glean a choisi d'utiliser des styles en ligne. Cette approche n'a pas bien évolué avec notre croissance, c'est pourquoi nous avons décidé d'examiner les options de CSS-in-JS. vanilla-extract, la solution que nous avons retenue, optimise les performances (aucune surcharge au moment de l'exécution) et l'expérience développeur, au prix d'une courbe d'apprentissage légèrement plus raide.
Lisez cet article si vous :
- Souhaitez découvrir les compromis entre performances et expérience développeur des frameworks CSS modernes et courants
- Êtes intéressé par les solutions de CSS-in-JS sans surcharge d'exécution
{{richtext-banner-component}}
Notre examen des options de frameworks CSS
Nous avons entamé notre recherche d'une solution CSS en consultant des tendances et des enquêtes comme State of CSS (2021). D'après l'utilisation ci-dessous, nous avons constaté que Styled Components et CSS Modules occupaient des positions assez proches, aux rangs n° 1 et n° 2. Notez que l'utilisation de vanilla-extract se situait presque en bas, à 1 %. Cela s'explique par le fait que sa première version stable est sortie en mai 2021.

Cependant, du côté de la satisfaction des développeurs, nous avons constaté que vanilla-extract était en tête, suivi de très près par CSS Modules.

Pour départager nos 3 options en tête — Styled Components, CSS Modules et vanilla-extract — nous avons lu des blogs de développeurs afin d'explorer les avantages et les inconvénients.
Pourquoi nous avons écarté Styled Components
Styled Components constituent un mécanisme opinionné d'intégration de propriétés de style (sans classes) dans un composant React. Notre principale préoccupation concernant Styled Components était sa surcharge au moment de l'exécution. Nous n'avons pas réalisé de prototype avec Styled Components, mais les remarques d'autres développeurs étaient convaincantes. En bref, Styled Components entraîne une surcharge de la taille du bundle au moment de l'exécution en raison de son loader. Son rendu, comme les styles en ligne, est également bloqué jusqu'à l'exécution du JS.
CSS Modules étaient prometteurs
Les CSS Modules sont un mécanisme d'obfuscation des classes CSS permettant de simuler un périmètre local dans le CSS. Nous avons apprécié sa séparation philosophique des responsabilités entre les fichiers CSS et les fichiers de composants React. De plus, les CSS Modules se transpilent en assets CSS natifs, qui sont :
- Plus parallélisables : le CSS ne bloque que le rendu (pas l'analyse), alors que le JS bloque l'analyse HTML.
- Plus rapides à appliquer par le navigateur : voyez la surcharge liée au style JS dans ce benchmark.
Cependant, les CSS Modules ne sont pas une solution de CSS-in-JS, et nécessiteraient une migration de TS vers CSS ou vers un préprocesseur.
Pourquoi nous avons choisi vanilla-extract
L'argumentaire de vanilla-extract en tant que solution offrant le meilleur des deux mondes (CSS-in-JS et CSS Modules) était suffisamment prometteur pour justifier un prototype. Rappelons que vanilla-extract est un préprocesseur de TS vers CSS modules au moment du build. Il convient également de noter que l'auteur de vanilla-extract est le cocréateur de CSS Modules, qui se classe deuxième derrière Styled Components en utilisation actuelle.
Nous pensons que Styled Components affiche une utilisation élevée mais une satisfaction relativement plus faible parce qu'il est facile de démarrer pour des projets de petite à moyenne taille (pas de préprocesseur CSS). En revanche, CSS Modules et vanilla-extract conviennent mieux aux projets de plus grande envergure grâce à leurs avantages en matière de performances, d'organisation des fichiers et de facilité de débogage.
vanilla-extract vs. CSS Modules
CSS Modules et vanilla-extract prennent tous deux en charge l'absence de surcharge au moment de l'exécution. Autrement dit, le CSS natif transpilé laisse le navigateur faire ce qu'il fait de mieux en code natif, plutôt que de s'exécuter dans l'« espace utilisateur » JS, dont l'analyse est bien plus lente et qui se trouve bloqué pendant le chargement.
Cependant, nous estimons que l'approche CSS-in-JS de vanilla-extract est supérieure à la transpilation des CSS Module, car l'intégration de TS offre l'intellisense de l'IDE, le découpage de bundle de Webpack, le tree shaking (élimination du code mort), etc. En bref : un refactoring plus simple et de meilleures performances web grâce à une taille d'asset minimale.
Cela dit, nos réserves quant à l'adoption de vanilla-extract restent valables. Nous avons adopté ce projet relativement tôt. Par exemple, la solution de CSS-in-JS de Facebook, Stylex, pourrait être publiée en open source et gagner en popularité. Cette préoccupation est en partie atténuée par la similarité de Stylex. Toutes deux transpilent le TS en CSS Modules. En cas de nécessité, remplacer des solutions similaires ne serait peut-être pas si terrible. Enfin, d'après l'article lié, Stylex aurait dû être publié fin 2021, mais n'est toujours pas disponible en septembre 2022. Cela peut être un signe que Stylex cède du terrain face à la dynamique open source de vanilla-extract. Nous mesurons cette dynamique par : l'adoption croissante mesurée par les téléchargements npm et les commits Github.
Migration des styles en ligne vers vanilla-extract
Bien que nous ayons adopté vanilla-extract pour les nouvelles fonctionnalités, nous devions encore migrer les styles en ligne existants dans la base de code. Cela s'est également révélé être une refonte d'assez grande ampleur, car nous avions plus de ~300 fichiers (10 000 lignes de code) à convertir vers vanilla-extract.
En migrant manuellement certaines des feuilles de style, nous avons remarqué que l'effort est assez mécanique, puisque nous convertissons un style de code en un autre ; nous avons donc décidé d'écrire un codemod pour faire le gros du travail.
Refonte à l'aide de codemods
Un codemod est un ensemble de transformations permettant d'automatiser des modifications qui constitueraient essentiellement une refonte. L'idée est de traiter le code comme des données pour un autre programme capable de comprendre sa structure et d'appliquer des transformations spécifiques. Avec la bonne configuration, les codemods peuvent réduire énormément le temps et les efforts que nécessitent les refontes à grande échelle.
En examinant l'implémentation, nos scripts de codemod effectueraient les opérations suivantes pour chaque feuille de style :
Étape 1. Convertir les règles de style en ligne en styles vanilla-extract
Pour chaque fichier, nous devions convertir les règles de style en déclarations vanilla-extract style() correspondantes. Nous utilisons bien une déclaration Stylesheet() personnalisée pour nos styles en ligne existants, ce qui constitue un bon point de départ pour le script de codemod. À un niveau élevé, nous envisageons quatre transformations distinctes :
- Trouver toutes les déclarations Stylesheet() dans le fichier.
- Pour chaque Stylesheet(), extraire chaque propriété et expression afin de créer une nouvelle déclaration de variable.
Par exemple : `container: { …styleRules }` → `export const container = style({ ...styleRules })` - Ajouter un import de `style` depuis vanilla-extract.
- Supprimer la déclaration et l'import Stylesheet().

Étape 2. Enfin, mettre à jour les props « style » en « className » dans les composants JSX
Une fois les styles transformés, nous devions également convertir les composants qui utilisaient ces styles en ligne.
Les localiser était simple, car la plupart de nos composants partagent une convention de nommage avec leurs styles.
Une fois les fichiers localisés, quelques transformations supplémentaires nous attendent :
- Convertir les JSXAttributes style en className et référencer les styles transformés.
- Mettre à jour l'import de styles par défaut en imports nommés, puisque nous nous sommes éloignés d'un export Stylesheet par défaut ci-dessus.
Étape 3. Ajouter .css.ts au nom du fichier
À ce stade, la feuille de style et le composant ont tous deux été transformés ; il ne reste donc plus qu'à renommer le fichier et à ajouter .css.ts au nom du fichier.
Outils
Nous avons utilisé ASTExplorer pour explorer et visualiser l'arbre syntaxique abstrait (AST) du code et déterminer où appliquer les transformations. Nous avons également utilisé JSCodeshift, qui fournit une API élégante pour appliquer des transformations au code.
Vanilla-extract en pratique
Après le prototypage et une adoption à grande échelle, nous avons réalisé que les classNames pour le débogage constituaient notre plus grand gain de productivité. L'arbre des composants React et l'arbre DOM diffèrent souvent considérablement. Un className=$fileName_$elementClass_$moduleHash (ex : ProfilePage_mainPersonCard__1bmv6no1) est bien plus facile à déboguer que de contourner l'absence de sourcemaps propre aux styles en ligne. Par exemple, nous chargions auparavant à chaud (hotloaded) des textNodes traceurs chaque fois que nous devions associer les styles rendus à la source TS :
<div style={{...inlineStylesToDebug}}> {/* no sourcemaps */}
Test : est-ce le div que je débogue ? {/* tracer textNode */}
<SomeComponent />
</div>
Les coûts liés à l'adoption de vanilla-extract étaient ponctuels :
- Il existe une courbe d'apprentissage pour l'adoption des classNames. Ex : résolution arbitraire de la spécificité des classNames. Les développeurs devaient apprendre de nouvelles pratiques, comme privilégier un maximum de 1 className par élément DOM :
// Quasi-équivalent des mixins SCSS
const deepMergedClassName = style([baseStyle, otherBase, componentOverrides])
- Bugs de migration initiaux. La migration des styles en ligne vers les classNames échoue lorsque les sélecteurs sont à égalité, et lorsque des propriétés !important auparavant courantes sont présentes. Détecter les bugs manuellement était fastidieux, mais ces bugs ont motivé des tests de comparaison de captures d'écran Storybook plus automatisés, qui préviennent les régressions futures
À retenir
Parmi les avantages de travailler dans une start-up à croissance rapide :
- Le travail à fort impact ne manque pas lorsqu'il y a beaucoup de gains faciles à portée de main.
- Si vous repérez une amélioration, vous pouvez souvent prendre pleinement en charge les solutions.
Si concevoir ou utiliser un produit de recherche de premier plan vous intéresse, contactez-nous !






