Les compromis de CSS-in-JS

Photo par Artem Bali

Récemment, j'ai écrit un aperçu de niveau supérieur de CSS-in-JS, principalement sur les problèmes que cette approche tente de résoudre. Les auteurs de bibliothèque consacrent rarement du temps à décrire les compromis de leur solution. Parfois, c'est parce qu'ils sont trop biaisés, et parfois ils ne savent tout simplement pas comment les utilisateurs appliquent l'outil. Il s'agit donc d'une tentative de décrire les compromis que j'ai vus jusqu'à présent. Je pense qu'il est important de mentionner que je suis l'auteur de JSS, donc je dois être considéré comme partial.

Impact social

Il y a une couche de personnes qui travaillent sur la plate-forme Web et ne connaissent aucun JavaScript. Ces gens sont payés pour écrire du HTML et du CSS. CSS-in-JS a eu un impact énorme sur le flux de travail des développeurs. Un changement vraiment transformateur ne peut jamais être fait sans que certaines personnes soient laissées pour compte. Je ne sais pas si CSS-in-JS doit être le seul moyen, mais l'adoption massive est un signe clair de problèmes avec l'utilisation de CSS dans les applications modernes.

Une grande partie du problème est notre incapacité à communiquer avec précision les cas d'utilisation où CSS-in-JS brille et comment l'utiliser correctement pour une tâche. De nombreux passionnés de CSS-in-JS ont réussi à promouvoir la technologie, mais peu de critiques ont parlé des compromis de manière constructive, sans faire de sauts bon marché sur les outils. Par conséquent, nous avons laissé de nombreux compromis cachés et n'avons pas fait de gros efforts pour fournir l'explication et les solutions de contournement.

CSS-in-JS est une tentative de rendre les cas d'utilisation complexes plus faciles à gérer, alors ne le poussez pas là où il n'est pas nécessaire!

Coût d'exécution

Lorsque CSS est généré à partir de JavaScript lors de l'exécution, dans le navigateur, il y a une surcharge inhérente. Le temps d'exécution varie d'une bibliothèque à l'autre. C'est une bonne référence générique, mais assurez-vous de faire vos propres tests. Des différences majeures lors de l'exécution apparaissent selon la nécessité d'avoir une analyse CSS complète des chaînes de modèle, le nombre d'optimisations, les détails d'implémentation des styles dynamiques, l'algorithme de hachage et le coût des intégrations de framework. *

Outre le temps d'exécution potentiel, vous devez considérer 4 stratégies de regroupement différentes, car certaines bibliothèques CSS-in-JS prennent en charge plusieurs stratégies et il appartient à l'utilisateur de les appliquer. *

Stratégie 1: génération Runtime uniquement

La génération CSS d'exécution est une technique qui génère une chaîne CSS en JavaScript, puis injecte cette chaîne à l'aide d'une balise de style dans le document. Cette technique produit une feuille de style, PAS des styles en ligne.

Le compromis de la génération d'exécution est l'impossibilité de fournir un contenu stylisé à un stade précoce, lorsque le document commence à se charger. Cette approche convient généralement aux applications sans contenu qui peuvent être utiles immédiatement. Habituellement, ces applications nécessitent des interactions avec les utilisateurs avant de pouvoir vraiment devenir utiles à un utilisateur. Souvent, ces applications fonctionnent avec un contenu si dynamique qu'il devient obsolète dès que vous le chargez, vous devez donc établir un pipeline de mise à jour dès le début, par exemple Twitter. De plus, lorsqu'un utilisateur est connecté, il n'est pas nécessaire de fournir du HTML pour le référencement.

Si l'interaction nécessite JavaScript, le bundle doit être chargé avant que l'application ne soit prête. Par exemple, vous pouvez afficher le contenu d'un canal par défaut lors du chargement de Slack dans le document, mais il est probable que l'utilisateur veuille changer le canal juste après. Donc, si vous avez chargé le contenu initial juste pour le jeter immédiatement.

Les performances perçues de ces applications peuvent être améliorées avec des espaces réservés et d'autres astuces pour permettre à l'application de se sentir plus instantanée qu'elle ne l'est réellement. De telles applications sont généralement lourdes de toute façon, elles ne seront donc pas utiles aussi rapidement qu'un article.

Stratégie 2: génération d'exécution avec CSS critique

Le CSS critique est la quantité minimale de CSS requise pour styliser la page dans son état initial. Il est rendu à l'aide d'une balise de style dans la tête du document. Cette technique est largement utilisée avec et sans CSS-in-JS. Dans les deux cas, vous êtes susceptible de charger deux fois les règles CSS, une fois dans le cadre du CSS critique et une fois dans le cadre du bundle JavaScript ou CSS. La taille de CSS critique peut être assez grande en fonction de la quantité de contenu. Habituellement, le document n'est pas mis en cache.

Sans Critical CSS, une application d'une seule page chargée de contenu statique avec CSS-in-JS d'exécution devra afficher des espaces réservés au lieu du contenu. C'est mauvais car cela aurait pu être utile à un utilisateur beaucoup plus tôt, améliorant l'accessibilité sur les appareils bas de gamme et pour les connexions à faible bande passante.

Avec CSS critique, la génération CSS d'exécution peut être effectuée à un stade ultérieur, sans bloquer l'interface utilisateur dans la phase initiale. Soyez averti cependant, sur les appareils mobiles bas de gamme, qui ont environ 5 ans et plus, la génération CSS à partir de JavaScript peut avoir un impact négatif sur les performances. Cela dépend fortement de la quantité de CSS générée et de la bibliothèque utilisée, elle ne peut donc pas être généralisée.

Le compromis de cette stratégie est le coût de l'extraction CSS critique et le coût de la génération CSS d'exécution.

Stratégie 3: extraction au moment de la construction uniquement

Cette stratégie est la stratégie par défaut sur le Web sans CSS-in-JS. Certaines bibliothèques CSS-in-JS vous permettent d'extraire du CSS statique au moment de la construction. * Dans ce cas, aucune surcharge d'exécution n'est impliquée, le CSS est rendu sur la page à l'aide d'une balise de lien. Le coût de la génération CSS est payé une fois à l'avance.

Il y a 2 compromis majeurs ici:

  1. Vous ne pouvez pas utiliser certaines des API dynamiques proposées par CSS-in-JS au moment de l'exécution, car vous n'avez pas accès à l'état. Souvent, vous ne pouvez toujours pas utiliser les propriétés personnalisées CSS, car elles ne sont pas prises en charge dans tous les navigateurs et ne peuvent pas être remplies par nature au moment de la création. Dans ce cas, vous devrez contourner le thème dynamique et le style basé sur l'état. *
  2. Sans CSS critique et avec un cache vide, vous bloquerez la première peinture, jusqu'à ce que votre bundle CSS soit chargé. Un élément de lien dans l'en-tête du document bloque le rendu du HTML.
  3. Spécificité non déterministe avec fractionnement de paquets basé sur les pages dans les applications d'une seule page. *

Stratégie 4: extraction au moment de la construction avec CSS critique

Cette stratégie n'est pas non plus propre à CSS-in-JS. L'extraction statique complète avec CSS critique offre les meilleures performances lorsque vous travaillez avec une application plus statique. Cette approche a toujours les compromis susmentionnés d'un CSS statique, sauf que la balise de lien de blocage peut être déplacée vers le bas du document.

Il existe 4 stratégies principales de rendu CSS. Seuls 2 d'entre eux sont spécifiques à CSS-in-JS et aucun ne s'applique à toutes les bibliothèques.

Accessibilité

CSS-in-JS peut réduire l'accessibilité lorsqu'il est utilisé de manière incorrecte. Cela se produit lorsqu'un site de contenu en grande partie statique est mis en œuvre sans extraction CSS critique, de sorte que le HTML ne peut pas être peint avant le chargement et l'évaluation du bundle JavaScript. Cela peut également se produire lorsqu'un énorme fichier CSS est rendu à l'aide d'une balise de lien de blocage dans la tête du document, ce qui est le problème actuel le plus populaire avec l'incorporation traditionnelle et non spécifique à CSS-in-JS.

Les développeurs doivent assumer la responsabilité de l'accessibilité. Il existe toujours une forte idée erronée selon laquelle une connexion Internet instable est un problème des pays économiquement faibles. Nous avons tendance à oublier que nous avons des problèmes de connectivité chaque jour lorsque nous entrons dans un système ferroviaire souterrain ou dans un grand bâtiment. Une connexion mobile sans câble stable est un mythe. Il n'est même pas facile d'avoir une connexion WiFi stable, par exemple, un réseau WI-FI 2,4 GHz peut être perturbé par un four à micro-ondes!

Le coût de CSS critique avec le rendu côté serveur

Pour obtenir une extraction CSS critique pour CSS-in-JS, nous avons besoin de SSR. SSR est un processus de génération du HTML final pour un état donné d'une application sur le serveur. En fait, cela peut être un processus assez complexe et coûteux. Il nécessite un certain nombre de cycles CPU sur le serveur pour chaque requête HTTP.

CSS-in-JS exploite généralement le fait qu'il est connecté au pipeline de rendu HTML. * Il sait de quel HTML a été rendu et de quel CSS il a besoin pour pouvoir en produire la quantité minimale absolue. Le CSS critique ajoute une surcharge supplémentaire au rendu HTML sur le serveur car ce CSS doit également être compilé dans une chaîne CSS finale. Dans certains scénarios, il est difficile, voire impossible, de mettre en cache sur le serveur.

Boîte noire de rendu

Vous devez savoir comment une bibliothèque CSS-in-JS que vous utilisez rend votre CSS. Par exemple, les gens ne savent souvent pas comment les composants stylisés et l'émotion implémentent des styles dynamiques. Les styles dynamiques sont une syntaxe qui permet l'utilisation de fonctions JavaScript à l'intérieur de votre déclaration de styles. Ces fonctions acceptent les accessoires et renvoient un bloc CSS.

Afin de garder la spécificité de l'ordre source cohérente, les deux bibliothèques nommées ci-dessus génèrent une nouvelle règle CSS si elle contient une déclaration dynamique et le composant se met à jour avec de nouveaux accessoires. Pour démontrer ce que je veux dire, j'ai créé ce bac à sable. Dans JSS, nous avons décidé de faire un compromis différent, ce qui nous permet de mettre à jour les propriétés dynamiques sans générer de nouvelles règles CSS. *

Courbe d'apprentissage abrupte

Pour les personnes familiarisées avec CSS, mais novices en JavaScript, la quantité de travail initiale pour se familiariser avec CSS-in-JS peut être assez importante.

Vous n'avez pas besoin d'être un développeur JavaScript professionnel pour écrire CSS-in-JS, jusqu'au point où une logique complexe est impliquée. Nous ne pouvons pas généraliser la complexité du style, car cela dépend vraiment du cas d'utilisation. Dans les cas où CSS-in-JS devient complexe, il est probable que l'implémentation avec CSS vanilla soit encore plus complexe.

Pour le style CSS-in-JS de base, il faut savoir comment déclarer des variables, comment utiliser des chaînes de modèle et interpoler des valeurs JavaScript. Si la notation d'objet est utilisée, il faut savoir comment travailler avec les objets JavaScript et la syntaxe basée sur les objets spécifique à la bibliothèque. Si le style dynamique est impliqué, il faut savoir comment utiliser les fonctions et les conditions JavaScript.

Globalement, il y a une courbe d'apprentissage, on ne peut pas la nier. Cependant, cette courbe d'apprentissage n'est généralement pas beaucoup plus longue que l'apprentissage de Sass. En fait, j'ai créé ce cours egghead pour le démontrer.

Pas d'interopérabilité

La plupart des bibliothèques CSS-in-JS ne sont pas interopérables. Cela signifie que les styles écrits à l'aide d'une bibliothèque ne peuvent pas être rendus à l'aide d'une autre bibliothèque. Concrètement, cela signifie que vous ne pouvez pas facilement basculer l'intégralité de votre application d'une implémentation à une autre. Cela signifie également que vous ne pouvez pas facilement partager votre interface utilisateur sur NPM sans apporter votre bibliothèque CSS-in-JS de choix dans le bundle du consommateur, sauf si vous avez une extraction statique au moment de la construction pour votre CSS.

Nous avons commencé à travailler sur le format ISTF qui est censé résoudre ce problème, mais malheureusement, nous n'avons pas encore eu le temps de le mettre en état de production. *

Je pense que le partage de composants d'interface utilisateur agnostiques dans un cadre réutilisable dans le domaine public est toujours un problème généralement difficile à résoudre.

Risques de sécurité

Il est possible d'introduire des fuites de sécurité avec CSS-in-JS. Comme pour toutes les applications côté client, vous devez toujours échapper à l'entrée utilisateur avant de la restituer.

Cet article vous donnera plus d'informations et quelques exemples de dégradation.

Noms de classe illisibles

Certaines personnes pensent toujours qu'il est important de conserver des noms de classe lisibles et significatifs sur le Web. Actuellement, de nombreuses bibliothèques CSS-in-JS fournissent des noms de classe significatifs basés sur le nom de la déclaration ou le nom du composant en mode développement. Certains d'entre eux vous permettent même de personnaliser la fonction de génération de nom de classe.

En mode production cependant, la plupart d'entre eux génèrent des noms plus courts pour une charge utile plus petite. Il s'agit d'un compromis que l'utilisateur de la bibliothèque doit faire et personnaliser la bibliothèque si nécessaire.

Conclusion

Des compromis existent, et je ne les ai probablement pas tous mentionnés. Mais la plupart d'entre eux ne s'appliquent pas à tous les CSS-in-JS. Ils dépendent de la bibliothèque que vous utilisez et de la façon dont vous l'utilisez.

* Il faudra un article dédié pour expliquer cette phrase. Faites-moi savoir sur Twitter (@ oleg008) sur lequel vous souhaitez en savoir plus.