Il y a peu on a vu fleurir des liens vers des propositions au groupe de travail CSS. Et en particulier une proposition de variables CSS. Ces variables peuvent être changées dynamiquement en javascript. Le rendu est alors refait avec la nouvelle valeur, partout où la variable était utilisée.
La question des performances
Si vous gérez plusieurs CSS sur une même page vous avez le choix entre faire un javascript qui va parcourir toutes vos feuilles de style à chaque changement pour reporter le changement (et faire attention à ne pas le faire pour les CSS externes que vous ne contrôlez pas), ou imbriquer vos feuilles de styles les unes dans les autres à coups de @import
.
S’en suit quelques commentaires sur le blog de l’excellent Laurent Jouanneau à propos du coût en performance de @import
. C’est une syntaxe déconseillée un peu partout du point de vue des performances et j’ai entendu des choses étranges, comme le fait que les CSS en @import
seraient téléchargées après tout le contenu, voire après le onload de la page, ou que ça bloque tout le navigateur au même titre qu’un <script>
.
Les tests
Laurent étant dubitatif, les avis sur la question étant surtout des on-dits, me voilà à faire des tests. J’ai fait une page HTML qui charge deux CSS en <link>
(A1 et B1). Chaque CSS en link en charge une autre en @import
(A2 et B2) et tente d’appliquer quelques règles. Viennent ensuite du contenu HTML neutre mais suffisamment gros pour tenter de forcer le rendu du navigateur, et quelques images.
Du PHP me permet de régler des temps d’attente entre chaque ligne pour simuler des téléchargements lents et des délais aux diverses étapes. Un fichier journal me permet de savoir à tout moment quand une requête arrive sur mon serveur mais aussi quand un téléchargement s’arrête. Il y a d’autres outils pour ça mais le panneau réseau de Firebug donne des résultats totalement faussés, en tout cas sur les dernières versions beta. Préférez les vieilles bonnes solutions en fichiers de log.
Les résultats
Les résultats ne sont pas ceux que j’attendais, et pas ceux qu’on décrit dans les on-dits, mais je ne suis pas sûr qu’ils puissent être considérés comme meilleurs. J’ai testé la beta 5 de Firefox 3 et Microsoft Internet Explorer 6 dans leur configuration par défaut, les deux navigateurs ont réagit de la même façon.
- Le rendu de la page est mis en attente le temps que toutes les feuilles de styles soient téléchargées entièrement et analysé. J’ai eu beau mettre 100ko de HTML divers et 6 secondes de délai pour télécharger mes CSS, histoire de tenter de forcer la main au navigateur, mais rien à faire : le navigateur n’affiche rien tant qu’une feuille de style est en téléchargement. Je suis resté donc scotché sur la page précédente pendant presque 7 secondes, avant de voir toute la page s’afficher d’un coup.
- La page s’affiche ensuite d’un coup. J’entend par là que dans mon journal je vois que le navigateur demande les images et la seconde CSS en
<link>
au serveur. Il n’attend pas pour ça la fin de la feuille de style. Le rendu est bloqué mais le navigateur analyse tout de même le HTML et lance les téléchargements nécessaires en tâche de fond. C’est déjà moins grave. - Les feuilles de style A1 et B1 (celles déclarées en
<link>
) sont téléchargées en parallèle, quand bien même le rendu est bloqué. On est dans le même cas que les images. Le navigateur attend patiemment la fin du téléchargement et de l’analyse de A1 et B1 pour respectivement initier les téléchargements de A2 et B2 (celles chargée via@import
à partir des premières). Au lieu de faire du parallèle, nous voilà à faire du séquentiel. On double le temps d’attente et le rendu est toujours bloqué pendant ce temps là. - Pire, mes deux A2 et B2 sont ajoutées à la queue des téléchargements sans aucune priorité. C’est à dire que si A1 et B1 sont grosses ou si votre serveur met longtemps à répondre, la queue de téléchargement sera déjà encombrée d’une multitude d’images et d’objets externes. Il faudra télécharger tous ceux là avant de pouvoir télécharger A2 et B2. Pendant ce temps votre rendu est toujours bloqué, en attente.
La conséquence
Pour l’exemple : Vous avez une grosse CSS de 30ko qui en charge une autre plus petite de 5ko. Viennent ensuite une douzaine d’images de 3ko, des avatars par exemple. Vous souhaitez garder deux fichiers CSS parce que la grosse est spécifique à cette page et il serait dommage de pourrir la petite CSS générique juste pour ça. Vous accédez à tout ça avec une latence réseau honnête de 40ms, une liaison 2mb/s et Firefox 2 (donc deux fils de téléchargements d’1mb/s chacun).
Si vous utilisez @import
vous aurez un rendu qui commencera après 325ms :
- Premier fil de téléchargement : la première CSS (0 à 65ms), l’image 3 (65 à 110ms), l’image 5 (110 à 155ms), l’image 7 (155 à 190ms), l’image 9 (190 à 235ms), l’image 11 (235 à 275ms), la seconde CSS (275 à 325ms)
- Second fil de téléchargement : l’image 1 (0 à 45ms), l’image 2 (45 à 90ms), l’image 4 (90 à 135ms), l’image 6 (135 à 175ms), l’image 8 (175 à 215ms), l’image 10 (215 à 255ms), l’image 12 (255 à 290ms)
Avec deux <link>
et sans @import
, les deux téléchargements se font en parallèle. On commence donc à 65ms au lieu de 325ms. Les avatars se chargeront au fur et à mesure. Sachant que la différence de perception entre « attente » et « instantané » se situe vers les 100ms, c’est justement ce qu’on vient de gagner.
Encore une fois, de plus, le calcul est très idéal : latence faible, connexion exploitée à fond, temps d’analyse et délais de réaction nuls, etc. En pratique il est courant d’avoir quelques fichiers qui dépassent les 150ms et une latence plus faible. Sur le blog de Laurent justement, qui contient justement plein de fichiers CSS et une dizaine d’images, j’ai une demi-seconde au total. Et un rendu qui serait bloqué une demi-seconde (ce qui n’est pas le cas chez lui) ça se perçoit, ça a une influence non négligeable.
En vitesse
Il manque à regarder ce que donne une feuille de style chargée par @import
directement dans le HTML mais la conclusion du premier test est sans appel : pas de @import
à l’intérieur d’une feuille de style.
Si d’aucuns veulent tester d’autres navigateurs où on testé le cas du @import
à partir du HTML source, n’hésitez pas à poser un lien ou un commentaire.
Intéressant… Donc, si j’ai bien compris, dans le cas où celà est possible, il vaut mieux utiliser deux balises link : une pour la css principale et une autre pour celle que l’on aurait éventuellement importée dans la première, si celle-ci est assez lourde. J’ai bon ?
PS : l’adresse du flux RSS contient un feed// qui empêche la détection.
Exactement, deux balises <link>.
Par contre pour le RSS je ne vois pas. Je serai heureux de résoudre un problème mais il n’y a pas de double slash, et la détection fonctionne chez moi pour Firefox, Safari et Google Reader. Quel logiciel utilises tu ? quel est le problème exact ?
@Eric > voici un copié-collé de mon clic-droit sur l’url de ton flux RSS en bas de la page : feed://http//performance.survol.fr/feed/
le feed:// est de trop, et en plus il manque les : (deux-points) après les deux // du http
Idem pour le flux des commentaires
C’est corrigé, merci. Le tout venait des gabarits par défaut de wordpress.
Merci beaucoup pour ce benchmark !
PS: Avec des images c’est peut-être plus parlant (surtout en ce qui concerne l’enchaînement des téléchargements).
Hello,
Une solution intéressante:
1. utiliser des @import tant qu’on veut, appelés depuis une feuille de styles principale (ou plusieurs feuilles de styles principales, par exemple screen.css et print.css qui appelleraient toutes deux commun.css), afin de segmenter le code CSS en «zones» ou «principaux éléments»;
2. avec un script qui va bien, concaténer tous les CSS appelés depuis screen.css en un seul fichier, et tous les CSS appelés depuis print.css en un seul fichier.
Bref, séparer développement (avec fichiers multiples pour l’organisation du code) et production.
Une chose que je garde à l’esprit chaque fois que je veux changer certaines habitudes de travail, c’est d’ajouter le temps de maintenance d’un site : séparer les feuilles de styles est peut-être « ressourcessophage », mais c’est beaucoup plus simple à maintenir dans le temps et ça permet de mettre à jour certaines parties (comme une CSS typography.css ou layout.css) sans prendre le risque de tout chambouler.
La solution suggérée par Florent V. est séduisante, mais bon, à la limite, pourquoi ne pas travailler en local avec des imports, et une fois que tout fonctionne et que le site passe en prod, on met tout d’une une CSS unique, et on en profite pour passer le tout à la moulinette pour avoir les déclarations sur une ligne et en supprimant les lignes vides (certains outils font ça merveilleusement), et hop ! :))
Logiquement faire un processus qui aggrège plein de fichiers CSS ensemble avec un bête « make » c’est super rapide à faire. SI vous voulez faire plus sioux un bête fichier PHP avec des include() va très bien aussi. Pas besoin d’imaginer un moteur complexe qui parse les @import pour ça.