Quelques optimisations sur le front

Une fois n'est pas coutume, je vais parler de développement front sur ce blog. Rien de révolutionnaire pour une personne du métier mais venant du monde java, j'ai suffisamment galéré pour en parler :-)

J'ai commencé par un nettoyage visuel pour poursuivre sur la réduction du temps de chargement de la page. Le plus gros du travail a concerné les images. Les outils utilisés sont très répandus, leurs préconisations très simples à suivre sur un environnement où l'on a la main mais moins chez un hébergeur gratuit comme Free.

Contraintes du site

  • le moteur de blog Dotclear : en particulier pour la webanalyse. N'ayant aucune notion fiable de web autre que mes gouts personnels, avoir des possibilités d'A/B testing m'aurait beaucoup aidé sur les arbitrages à effectuer entre fonctionnalité à proposer / temps de chargement à récupérer.
  • l'hébergement sur www.free.fr : impossible de spécifier des paramètres de cache sur le htaccess -> une erreur 500 s'affiche, oui monsieur. Au niveau du chargement des pages, j'ai dû effectuer quelques hacks coté PHP à défault.
  • Last but not least, la mise à jour par FTP. Je travaille trop occasionnellement sur la partie code du site pour avoir un hébergement local iso, mais j'aurais peut être du prendre le temps de le faire. J'ai profité de la période creuse pour casser régulièrement l'IHM. J'avais aussi un fichier de test bidon pour tester certaines mises en pages.

Je ne parle pas de l'angoisse de perdre les modifications et les commentaires dans tous les sens pour signaler les modifs en cas de besoin de rollbacker. J'ai finalement mis les sources sur github pour sécuriser un peu le code.

Nous pouvons maintenant rentrer dans le vif du sujet.

Opération vide-grenier

J'hésite toujours à enlever des éléments car "on ne sait jamais", "cela pourrait servir". Du coup, on ne voit plus rien. Histoire d'en avoir le coeur net et avant de tout faire sauter, j'ai crée un compte basic sur XITI pour voir quel point d'entrée était réellement utilisé en créant des chapitres associés. L'interface est beaucoup plus accessible que google analytics, où sans se pencher réellement dessus, il me semble difficile de s'y retrouver.

XITI chapitres

Je me suis ainsi rendu compte que mon super nuage de tags n'était absolument pas utilisé, les articles non plus (mais ils datent alors je les laisse pour le moment) et la recherche un tout petit peu.

J'aurais aimé faire de l'A/B Testing sur deux versions de menu par exemple, mais je ne vois pas comment proposer deux versions de pages sur dotclear, à moins de la dupliquer...

Mobile

Dans la même logique, quand la largeur d'écran est réduite, les menus latéraux disparaissent.

@media screen and (max-width: 2000px) {
  #blogextra {
    display:none;
  }
  #sidebar {
    display:none;
  }
  #content {
	margin: 0 0;
  }  
}

Pour les terminaux mobiles, il faut également ajouter dans le head

<meta name="viewport" content="width=device-width, initial-scale=1.0" />

Sur d'autres sites, le menu redescend très joliment quand la résolution est plus petite. J'ai considéré que c'était good enough pour moi sur un mobile : l'essentiel est de voir les billets et de pouvoir défiler, pas de maintenir la navigation via un menu.

Temps de chargement de la page

Pour commencer, j'ai utilisé le plugin PageSpeed sur firefox. En objectif, j'avais de lentement mais surement augmenter ma note.

Je regardais aussi régulièrement le temps de chargement du réseau sur firebug. Je partais d'environ 4 secondes et visait 2 secondes grand max. Au delà, les internautes se lassent. D'autre part, un mauvais temps de chargement pénalise le référencement des sites.

En best effort, j'aurais aimé avoir moins de 20 Ko de fichier pour une expérience mobile sympa.

GTMetrix enregistre les performances de façon journalière en se basant sur PageSpeed et YSlow. Vous pouvez même programmer des alertes : c'est pratique et gratuit.

Compression des ressources

Les fichiers .htaccess étant bridés sur www.free.fr, je n'ai pas pu utiliser Apache pour compresser les ressources. J'ai du ciblé les fichiers à compresser comme le style.css avec du PHP.

Concrètement, au lieu d'inclure un fichier style.css, c'est style.php qui est désormais inclus. Son entête fait appel à gzip-css.php

<?php
	ob_start ("ob_gzhandler");
	header("Content-type: text/css; charset: UTF-8");
	//header("Cache-Control: must-revalidate");
	$offset = 50 * 24 * 60 * 60 ;
	$ExpStr = "Expires: " .
	gmdate("D, d M Y H:i:s", time() + $offset) . " GMT";
	header($ExpStr);
?>

Je n'ai pas utilisé la 3e solution, effectivement plus élégante mais qui compressait un peu moins bien lors de mes tests.

Par ailleurs, le style print.php prenait autant de temps à se charger alors qu'il était 20 fois plus petit. L'entrée/sortie coutait visiblement cher. Je l'ai donc minifié et mis en inline.

<style type="text/css" media="screen">
@import url({{tpl:BlogThemeURL}}/style.php);
</style>
<style type="text/css" media="print">
<!-- version inline et minifié pour éviter IO  -->
body{font:10pt serif;margin:0;color:#000;background:#fff}#prelude,#sidebar,.pagination,#comment-form,object{display:none}p{margin:.2em 0 .8em 0;line-height:1.3em}h1,h2,h3,h4,h5,h6{margin:1em 0 .2em 0;font-weight:bold}h1{font-size:160%}h2{font-size:140%}h3{font-size:120%}h4{font-size:100%}h5{font-size:90%}h6{font-size:80%}a{color:#00f;text-decoration:none;border-bottom:1px solid #999}.post-content a[href^="http"]:after,#comments a[href^="http"]:after,#trackbacks a[href^="http"]:after{content:" ("attr(href)") ";color:#333}
</style>

Sprites

Toujours pour limiter notamment les coûts de résolution DNS et lecture de fichier, les images ont été regroupées en sprite.

Stitches permet de créer dynamiquement des sprites en glissant-déplaçant des images sur l'interface. Pour gratter encore plus de performance, le CSS généré peut intégrer l'image en binaire. Le coût à impacter sur la maintenance m'a retenu d'utiliser cette option.

Je n'ai pas eu de souci pour les "boutons" comme l'image RSS par exemple. Par contre, quand j'ai eu besoin d'avoir une image de fond et un texte par dessus ou à côté, cela a été une autre paire de manche. Heureusement, Arnaud m'a tout expliqué (merci!) :

Pour pouvoir utiliser une sprite tu as 3 solutions :

1. Tu utilises 1 seul élément (une div par exemple) et tu lui applique en background une sprite, l’inconvénient de cette méthode c’est que tu doit prévoir à l’avance la place que prendra ton contenu au moment de la création de ta sprite en mettant un espace vide, correspondant à l’espace pris par ton contenu, autour de l’élément pour que l’on ne voit pas les autre motifs de ta sprite. Cette méthode n’est pas très élégante et difficile à maintenir, par contre c’est la plus compatible.

2. Utiliser 2 éléments imbriqués ( ex : <div class=’texte’><span class=’icon’></span> Ici mon contenu</div> ) l’élément imbriqué doit être vide, c’est lui qui recevra la sprite en background. Cette méthode est plus maintenable que la première, compatible IE6, mais manque d’élégance.

3. Utiliser les pseudos éléments CSS :after ou :before associé à la propriété content (compatible à partir de IE8) mais c’est la solution la plus maintenable et la plus élégante.

Concrètement, j'ai ajouté le style before :

.post ul li {
	display: block;
/* remplacé par un sprite ci-dessous 
	padding-left: 14px;
	background: transparent url(images/ul.gif) no-repeat 0 4px; */
}
 
/* sprite au lieu de "background: transparent url(images/ul.gif) no-repeat 0 4px;" */
.post ul li:before {
	content:"";
	display: inline-block;
	background: transparent url("images/sprite3.png") no-repeat -19px -41px;
	padding-right: 2px;
	width: 11px;
    height: 6px;
}

Utilisation de CSS plutôt que d'images

Les citations avaient une image de fond avec une quote à l'envers. Le caractère équivalent existe :

/* pour ne plus faire appel à quote.gif  > http://css-tricks.com/snippets/html/glyphs/ */
blockquote > p:first-child:before {
	content: "\201C"; 
	font-size: 5em; 
	color:#C0C0C0;  
}

Compression d'images

Le sprite généré par Stritches n'est pas optimisé. J'ai été tenté d'intercepter les dépôts d'images dans dotclear pour compresser toutes les images avec PHP GD. Le souci est que le résultat perd un peu de qualité alors que je voulais un affichage iso. D'autre part, il m'aurait obligé à entrer encore plus dans le moteur dotclear.

Finalement, j'appelle pngcrush et jpegoptim dans un script shell, que je lance localement de temps en temps :

IMAGES_DIR="../../themes/giacomo/images ../../public/Billets"
 
for imgDir in $IMAGES_DIR
do
	#compress PNG
	for image in `ls -a $imgDir/ | grep -i png`
	do
		lib/pngcrush -ow $imgDir/$image
	done
 
	#compress JPG
 	for image in `ls -a $imgDir/ | grep -i jpg`
	do
		lib/jpegoptim --strip-all $imgDir/$image
	done
done

Cache des images

L'hébergement sur Free ne permet pas de spécifier des paramètres de cache sur le htaccess. Au niveau du chargement des pages, j'ai dû effectuer un hack en passant par un fichier intermédiaire cacheImage.php pour appeler les images récurrentes.

<?php
header('Expires: '.gmdate('D, d M Y H:i:s \G\M\T', time() + 864000));
 
header("Content-type: image/".pathinfo($_GET['file'],PATHINFO_EXTENSION));
readfile($_GET['file']);
?>

Un exemple d'appel :

background-image: url('cacheImage.php?file=images/sprite3.png') ;

Autres ajustements divers

  • Google analytics propose désormais une version asynchrone pour la webanalyse
  • Les ressources javascript ont été fusionnées en un seul fichier. Ce dernier est compressé à la volée, comme pour les CSS. Elles sont appelées en fin de page pour pas pénaliser l'affichage.
  • Quelques erreurs de validation HTML ont été corrigées conformément au validateur W3C .
  • J'ai supprimé l'appel à certaines API externes comme le surligneur de l'institut agile, qui faisait appel à une image absente, ou même twitter et google plus.
  • Certaines images n'avaient pas de dimensions spécifiées.

Conclusion

Par rapport à un développement back, procéder par petites étapes est plus accessible et nous pouvons rapidement voir des résultats. L'avantage d'un site perso est aussi de pouvoir tout cassé et effectuer les tests ergonomiques que je veux. Je peux tester un bouton pendant un mois, puis un autre pour voir lequel est le mieux accueilli.

A partir du moment où j'ai supprimé les appels javascript twitter, la note PageSpeed a dégringolé d'un 81% à 69% sur mon rapport gtmetrix. J'ai perdu 2 grades. En parallèle, YSlow a grimpé à 91%. La performance se fait au détriment d'autre chose, mais quand même.

GTMetrix scores

Pourtant, sur l'extension Firefox et sur le site PageSpeed de google, la note s'est amélioré à 88%. J'en déduis que c'est un bug :D. J'assume mon choix malgré tout d'héberger plutôt que de dépendre de l'extérieur. Dans le même registre, le tag XITI fourni n'est pas valide du point de vue du W3C : il constitue 100% des erreurs qui restent sur la home.

La home se charge la plupart du temps en moins de 2 secondes et n'est pas trop trop loin des 20 K selon GTMetrix

GTMetrix page size

Le champ d'actions sur un site est infini : je pourrais par exemple appeler tous les javascripts en asynchrone, viser la note A de page speed, poursuivre l'optimisation sur d'autres pages, me pencher plus sur le SEO en mettant des microformats...

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.

Ajouter un rétrolien

URL de rétrolien : http://www.devsnotebook.fr/trackback/137

Haut de page