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 tenta­tives des dev JS pour contour­ner l’écri­ture de CSS mais je commence à consi­dé­rer que les outils de CSS-in-JS type Emotion sont la bonne solu­tion pour les webapp React.


J’ai été inté­gra­teur, à faire de la belle CSS sépa­ré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, élimi­nons le. C’est ce que proposent les conven­tions comme BEM. Si je cari­ca­ture, il s’agit prin­ci­pa­le­ment de reti­rer les sélec­teurs CSS un attri­buant une ou plusieurs classes spéci­fiques à chaque contexte. C’est fran­che­ment moche mais ça fonc­tionne.

CSS-Modules va un peu plus loin. Le prin­cipe est le même mais on permet au déve­lop­peur d’uti­li­ser un nommage plus agréable. C’est l’ou­til de géné­ra­tion qui gère la complexité au lieu du déve­lop­peur.


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

Ça revient à juste gérer un fichier par compo­sant en se limi­tant à des sélec­teurs très simples pour ne pas créer de conflits de spéci­fi­cité. On reste sur du CSS stan­dard et sur une approche proche de mes habi­tudes histo­riques. Mieux : L’in­té­gra­tion peut se faire indé­pen­dam­ment du langage de déve­lop­pe­ment de l’ap­pli­ca­tif.

C’est top mais ça se base sur des compo­sants qui ne bougent pas beau­coup, dont on connait à l’avance tous les états.

Dès qu’il s’agit de cumu­ler plusieurs états, le résul­tat dépend de l’ordre d’écri­ture dans la CSS. Parfois c’est bien prévu, parfois non.

Dès qu’il s’agit de rendre des choses très dyna­miques, il faut de toutes façons sortir des CSS modules. Vous voulez que dans la vue large les items de navi­ga­tion se colorent au survol en fonc­tion de la caté­go­rie desti­na­tion déter­mi­née dyna­mique­ment mais qu’ils utilisent la couleur neutre dans la vue réduite desti­née aux mobiles ? Vous êtes à poil et il va falloir compo­ser avec d’autres façons d’injec­ter des CSS, peut-être même tâton­ner sur les prio­ri­tés entre classes.


Les classes utili­taires et CSS atomiques à la Tachyon sont là pour indus­tria­li­ser en pous­sant encore plus loin.

J’ai une classe par valeur à appliquer : .ms7-ns applique la septième valeur du cata­logue (7) comme taille hori­zon­tale maxi­mum (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 quasi­ment aucune abstrac­tion utile (unifor­mi­ser les valeurs on a déjà plein d’ou­tils plus effi­caces). C’est vite cryp­tique, lourd, et mons­trueux dès qu’on multi­plie les valeurs et les points de rupture possibles.

Le seul inté­rêt par rapport à écrire direc­te­ment les attri­buts style c’est que ça permet d’ac­cé­der aux media query et aux pseudo-sélec­teurs.

Malheu­reu­se­ment non seule­ment ça ne résout pas les conflits de prio­ri­tés mais ça les empire. Si je spécia­lise un compo­sant exis­tant en y ajou­tant une classe liée à une direc­tive déjà présente, je joue à la roulette russe. Il faut abso­lu­ment que mon compo­sant initial prévoit lui-même tous les cas possibles pour savoir quelle classe injec­ter et ou ne pas injec­ter. Pas d’al­ter­na­tive.

J’ai vrai­ment l’im­pres­sion d’un retour en arrière mons­trueux avec ces CSS atomiques, cumu­ler les défauts sans aucun avan­tage, et c’est proba­ble­ment ce qui m’a fait reje­ter par prin­cipe les CSS-in-JS jusqu’a­lors.


Les CSS-in-JS c’est fina­le­ment pous­ser la logique de Tachyons un cran plus loin. Quitte à déci­der de tout dans le code HTML, autant écrire direc­te­ment les styles à cet endroit là en utili­sant la vraie syntaxe CSS et en y ajou­tant la possi­bi­lité d’ac­cé­der aux media query et aux pseudo-sélec­teurs.

Emotion c’est ça. On est à la croi­sée entre le « j’écris tout dans un attri­but style » et le « j’at­tache un module CSS ».

En fonc­tion­ne­ment basique c’est comme un CSS module sans le sélec­teur. Je donne les direc­tives en CSS on ne peut plus clas­siques et j’ai accès aux media query, aux pseudo-sélec­teurs et aux anima­tions avec une syntaxe proche de ce que font les prépro­ces­seurs habi­tuels (et en phase avec la direc­tion 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 direc­te­ment ajou­ter le résul­tat aux classes CSS de mon compo­sant. Il se char­gera de géné­rer un nom de classe, de créer la CSS corres­pon­dante dans le docu­ment, et de lier les deux, comme avec CSS-Modules.

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

L’avan­tage c’est que je ne suis pas limité aux valeurs en dur. Je peux avoir des valeurs dyna­miques venant de mon Javas­cript ou de mon thème, et je n’en limite pas les effets à ce que me permettent les variables CSS.

Je peux aussi réuti­li­ser, compo­ser ou surchar­ger un élément ou un bloc de styles avec un autre sans risque de conflit de prio­rité.


Tachyons me donnait l’im­pres­sion de cumu­ler les incon­vé­nients, ici j’ai vrai­ment l’im­pres­sion de cumu­ler les avan­tages.

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 compo­sant HTML et ça a du sens de grou­per HTML, JS et CSS lié au compo­sant ensemble quand ils sont forte­ment 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’op­tion la plus saine, mais pour un code React je crois que c’est là que je commen­ce­rai par défaut désor­mais.

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 Scaleway, ONLINE SAS, joignable par téléphone au +33 (0)1 84 13 00 00 et joignable par courrier à l'adresse BP 438 - 75366 Paris Cedex 08.