Où je dis du bien du CSS-in-JS

Il n’y a que les imbéciles qui ne changent pas d’avis et c’est mon avis depuis toujours

Coluche

J’ai toujours regardé avec dédain les tentatives des dev JS pour contourner l’écriture de CSS mais je commence à considérer que les outils de CSS-in-JS type Emotion sont la bonne solution pour les webapp React.


J’ai été intégrateur, à faire de la belle CSS séparée du code HTML. On finit quand même vite par construire des monstres ou se prendre les pieds dans le tapis dès qu’on fait plus de quelques pages types.

Pour résoudre le problème, éliminons le. C’est ce que proposent les conventions comme BEM. Si je caricature, il s’agit principalement de retirer les sélecteurs CSS un attribuant une ou plusieurs classes spécifiques à chaque contexte. C’est franchement moche mais ça fonctionne.

CSS-Modules va un peu plus loin. Le principe est le même mais on permet au développeur d’utiliser un nommage plus agréable. C’est l’outil de génération qui gère la complexité au lieu du développeur.


J’avoue que j’aime bien CSS-modules. C’était mon favori jusqu’à présent.

Ça revient à juste gérer un fichier par composant en se limitant à des sélecteurs très simples pour ne pas créer de conflits de spécificité. On reste sur du CSS standard et sur une approche proche de mes habitudes historiques. Mieux : L’intégration peut se faire indépendamment du langage de développement de l’applicatif.

C’est top mais ça se base sur des composants qui ne bougent pas beaucoup, dont on connait à l’avance tous les états.

Dès qu’il s’agit de cumuler plusieurs états, le résultat dépend de l’ordre d’écriture dans la CSS. Parfois c’est bien prévu, parfois non.

Dès qu’il s’agit de rendre des choses très dynamiques, il faut de toutes façons sortir des CSS modules. Vous voulez que dans la vue large les items de navigation se colorent au survol en fonction de la catégorie destination déterminée dynamiquement mais qu’ils utilisent la couleur neutre dans la vue réduite destinée aux mobiles ? Vous êtes à poil et il va falloir composer avec d’autres façons d’injecter des CSS, peut-être même tâtonner sur les priorités entre classes.


Les classes utilitaires et CSS atomiques à la Tachyon sont là pour industrialiser en poussant encore plus loin.

J’ai une classe par valeur à appliquer : .ms7-ns applique la septième valeur du catalogue (7) comme taille horizontale maximum (ms pour max-width) si la fenêtre a une taille supérieure au point de rupture « small » (ns pour non-small).

Ça n’offre quasiment aucune abstraction utile (uniformiser les valeurs on a déjà plein d’outils plus efficaces). C’est vite cryptique, lourd, et monstrueux dès qu’on multiplie les valeurs et les points de rupture possibles.

Le seul intérêt par rapport à écrire directement les attributs style c’est que ça permet d’accéder aux media query et aux pseudo-sélecteurs.

Malheureusement non seulement ça ne résout pas les conflits de priorités mais ça les empire. Si je spécialise un composant existant en y ajoutant une classe liée à une directive déjà présente, je joue à la roulette russe. Il faut absolument que mon composant initial prévoit lui-même tous les cas possibles pour savoir quelle classe injecter et ou ne pas injecter. Pas d’alternative.

J’ai vraiment l’impression d’un retour en arrière monstrueux avec ces CSS atomiques, cumuler les défauts sans aucun avantage, et c’est probablement ce qui m’a fait rejeter par principe les CSS-in-JS jusqu’alors.


Les CSS-in-JS c’est finalement pousser la logique de Tachyons un cran plus loin. Quitte à décider de tout dans le code HTML, autant écrire directement les styles à cet endroit là en utilisant la vraie syntaxe CSS et en y ajoutant la possibilité d’accéder aux media query et aux pseudo-sélecteurs.

Emotion c’est ça. On est à la croisée entre le « j’écris tout dans un attribut style » et le « j’attache un module CSS ».

En fonctionnement basique c’est comme un CSS module sans le sélecteur. Je donne les directives en CSS on ne peut plus classiques et j’ai accès aux media query, aux pseudo-sélecteurs et aux animations avec une syntaxe proche de ce que font les préprocesseurs habituels (et en phase avec la direction que prend la syntaxe CSS elle-même).

const style = css`
padding: 32px;
background-color: hotpink;
font-size: 24px;
border-radius: 4px;
&:hover {
color: blue;
}
`

Je peux directement ajouter le résultat aux classes CSS de mon composant. Il se chargera de générer un nom de classe, de créer la CSS correspondante dans le document, et de lier les deux, comme avec CSS-Modules.

L’exemple est peu parlant. On a juste l’impression d’un CSS-Modules écrit dans le fichier JS.

L’avantage c’est que je ne suis pas limité aux valeurs en dur. Je peux avoir des valeurs dynamiques venant de mon Javascript ou de mon thème, et je n’en limite pas les effets à ce que me permettent les variables CSS.

Je peux aussi réutiliser, composer ou surcharger un élément ou un bloc de styles avec un autre sans risque de conflit de priorité.


Tachyons me donnait l’impression de cumuler les inconvénients, ici j’ai vraiment l’impression de cumuler les avantages.

La seule contrainte c’est que mon code CSS se retrouve dans mes fichiers JS. C’est moche quand c’est dit ainsi mais pour une app en React, on a de toutes façons un fichier par composant HTML et ça a du sens de grouper HTML, JS et CSS lié au composant ensemble quand ils sont fortement liés. C’est d’ailleurs le choix de VueJS.

Ce n’est forcément pas adapté à tout, et si vous voulez rester génériques les CSS-Modules sont à mon avis l’option la plus saine, mais pour un code React je crois que c’est là que je commencerai par défaut désormais.

Rejoindre la conversation

2 commentaires

  1. Je suis relativement d’accord sur la partie css-in-js, mais pas du tout sur les classes utilitaires :p.

    Tachyons fait 14kb, c’est… pas lourd du tout. Si on y ajoute l’overhead des multiples classes dans les composants, on reste plus léger que du css modules par exemple. Et on doit etre vaguement identique a du css-in-js.
    Je comprend qu’au premier abord les noms de classes paraissent cryptiques, mais en réalité on comprend rapidement la logique de nomage et après ca roule. Et les problèmes de priorité, dans les faits j’en ai jamais rencontré avec Tachyons. On peut aussi avoir des classes utilitaires qui utilisent les variables CSS du thème, c’est pas un problème.

    Le fait d’uniformiser les valeurs n’est a mon sens pas un point mineur du tout. Si on veux la même chose dans css-in-js, on se retrouve avec des

    const style = css`
    padding: ${paddingSize2};
    background-color: ${hotpink};
    font-size: ${fontSize3};
    border-radius: ${borderRadius1};
    `
    … et c’est pas franchement plus pratique que des classes utilitaires. Le fait de pouvoir injecter des valeurs dynamiques est limite un inconvénient pour moi. Ca va forcement élargir la palette des valeurs utilisées ce qui n’est généralement pas désirable dans une app. A mon sens ca doit rester un besoin exceptionnel, et ne pas rendre ca simple est un avantage.

    Pour moi, CSS modules permet d’esquiver les problèmes de conflits que l’on a avec du CSS classique mais impose d’écrire du nouveau CSS pour chaque module. Ca fait grossir la code base, avec du CSS de qualité variable et des problèmes d’homogénéité dans les valeurs. Et en plus ca pèse lourd.
    On fait globalement les mêmes compromis avec css-in-js, dans la lourdeur du css généré et avec quelques possibilités en plus.
    Alors qu’avec des classes utilitaires, on sort de la logique « un bout de css par composant » et tous les problèmes que ca entraîne, et on n’a pas de problèmes de conflits parce que toutes les règles ont la même priorité. Le compromis, c’est l’apprentissage des noms de classes… pour moi ca vaux le coup :-)

    1. Je ne cherche pas forcément à avoir des valeurs dynamiques partout. C’est parfois utile mais ce n’est pas ce que je reproche aux classes utilitaires.

      Si j’ai un composant [Button] qui utilise .c-blue pour la couleur du bouton, que je tente de le spécialiser en créant un [MyButton] qui dérive de [Button] mais en rouge… je ne peux pas. Je peux ajouter .c-red dans [MyButton] mais dans ce cas il aura .c-blue et .c-red. Une fois sur deux ce ne sera pas la valeur que je veux qui aura la priorité.

      Essentiellement ce que les classes utilitaires m’apportent c’est de définir une liste de valeurs standards. Or franchement, je n’ai déjà l’embarras du choix sur comment définir cette liste de valeurs. Je peux la définir dans les variables CSS, dans les variables du préprocesseur, éventuellement dans un import JS si je fais mes styles en JS. Je n’ai aucunement besoin de définir ça via un inventaire de noms de classe.

      Bref, pour cet avantage très contestable, je viens de m’empêcher de faire des surcharges simples. Franchement le ratio bénéfices/problèmes me parait faible, très faible.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

À propos de ce site, du contenu, de l'auteur
Je poste parfois ici des humeurs ou des pensées. Parfois je change, parfois je me trompe, parfois j'apprends, et souvent le contexte lui-même évolue avec le temps. Les contenus ne sont représentatifs que de l'instant où ils ont été écrits. J'efface peu les contenus de ce site, merci de prendre du recul quand les textes sont anciens. Merci

À toutes fins utiles, ce site est hébergé par OVH SAS, joignable par téléphone au +33 (0)9 72 10 10 07 et dont le siège social est au 2 rue Kellermann, 59100 Roubaix, France.