My proposal for the contest http://webperf-contest.com/
JavaScript
Switch branches/tags
Nothing to show
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
css
iframe-footer
img
js
sources
unused
.gitignore
.htaccess
README
RULES-EN.html
RULES-FR.html
content.mini.html
content.mini.ll.html
extra.mini.ll.html
favicon.ico
footer.mini.html
footer.mini.ll.html
index.html
index.st.html
marketplace.js
menu_full.html
menu_level1.mini.html
nav-sec.mini.html
top.mini.html

README

= Parti-pris de mon optimisation.

Le rendu est identique à l'origine dans les navigateurs modernes hormis :
- les coins ronds ne sont pas rendus dans les navigateurs ne supportant pas border-radius,
et se degradent en coins carrés
- les header des boites de la colonne de droite qui sont homogénéisées
avec les boites en colonne centrale

Pour tous les header de boite, j'ai remplacé le système
d'images de background par un jeu d'aplat en pur CSS, ce qui ne nécessite aucune ressource externe.

J'ai nettoyé le layout de la page initialement en table, puis remplacé par le layout tiré de
http://blog.html.it/layoutgala/ qui permet d'envoyer le contenu principal avant les colonnes
latérales (les id de blocs de la page ont été conservés).

== Header de la page
L'auto-complétion sur le champ de recherche n'est activée qu'au focus sur le champ, ce qui permet
d'économiser le chargement de ce script la plupart du temps.

Le menu déroulant à onglets n'étant navigable qu'en présence de javascript, j'ai prix le parti de
n'insérer dans le HTML du document statique les onglets visibles. L'ensemble des parties déroulantes
est chargé de manière asynchrone par une requête ajax, et n'est injecté dans le DOM par morceau
que lorsque cela est nécessaire.

Pour l'affichage des onglets, j'ai gardé des images car c'est la seule solution industrielle
pour respecter la police et être robuste vis a vis de l'agrandissement du texte dans le navigateur.
En revanche j'ai mutualisé l'affichage survol/actif qui utilisent tous deux du texte noir (le rendu n'est
ici pas parfait car il aurait fallu régénérer les textes sans background, ce que je n'ai pu faire en me contentant
de découper les textes dans les images disponibles).

Les images d'illustration dans le megamenu dépliant sont rassemblées en un sprite unique qui n'est chargé
qu'au premier affichage, par positionnement de la directive css par le javascript.

== Contenu central
Le diaporama central était constitué d'images sans alternatives. J'ai remplacé cette image par un
texte+une image de background en superposition. Seule la première image est référencée au chargement
de la page, les autres images ne le sont que lors de leur affichage (via js), ce qui permet un
chargement différé. Pour accélérer le rendu initial, la première image affichée est initialement un jpg
de basse qualité remplacé ensuite par l'image de qualité initiale (png dans le cas présent) lorsque le
reste de la page est chargé.

Les images de la page ne véhiculant pas d'information et utilisées comme illustration sont toutes
referencées en image de background et regroupées en sprintes par ensemble logique cohérent
(automatisable en production):
- illustrations dans le menu déroulant, enrichies d'une alternative texte lorsqu'aucune image
n'est chargée
- illustrations des héros préférés, avec suppression du lien double identique qui complique
la navigation avec revue d'écran
- illustrations des univers en colonne de droite

En revanche, les images des produits constituent une information essentielle de la page
et nécessaire à la vente (au même titre que le nom du produit ou son prix).
Il m'a donc semblé essentiel que ces images soient présentes aussi bien en l'absence de styles
que de javascript. J'ai donc choisi de les conserver en image inline, mais j'ai utilisé du
lazy-loading dessus (cf plus bas).

== Colonne de droite
La colonne de droite est chargée par une requête asynchrone. Au chargement de la page, elle est visuellement
remplacée par un cadre bloc légèrement grisé visible en cas de rendu lent (IE7 ou connexion lente)
Le gif animé utilisé en bannière publicitaire interne est lourd à charger.
Pour ne pas retarder le rendu complet de la page, il est remplacé par un png statique qui ne reprend
que la première image. Il est ensuite remplacé dynamiquement en js par le gif animé pour lancer
l'animation lorsque la page est achevée de charger.

Les illustrations de chaque cadre sont en sprite. Comme ces boites sont en general sous la ligne de
flottaison et non visible au chargement de la page, la directive background-image est inserree en js
asynchrone pour ne pas retarder le onload, uniquement lorsque le premier bloc est au dessus de la ligne
de flottaison (utilisation de js/sources/jquery.lazyload.js)

== Pied de page
Le pied de page non initialement visible en général est chargé par une requête asynchrone.

Liframe est conservée, conformement au règlement, mais son src est initialement vide.
L'iframe est chargée de manière aysnchrone par injection du src en js lorsque le pied de page devient visible (lazy loading)


== Techniques utilisées

=== Chargement asynchrone de blocs de page
J'utilise une technique inspirée des BigPipe de Facebook
http://www.facebook.com/notes/facebook-engineering/bigpipe-pipelining-web-pages-for-high-performance/389414033919
et déjà automatisée dans le cadre de SPIP
http://www.yterium.net/Ajax-Parallel-Loading-accelerer-un

Certains blocs de la page sont assemblés en js par le client au lieu d'être assemblé côté serveur.
Le chargement asynchrone se fait par une requête xhr lancée en js aussitôt que possible.
Le html est injecté dans la page lorsqu'il arrive. Ici le html ne contient pas de script inline, et on fait tout cela
en js natif sans jQuery

Ici, IE7 étant une cible a prendre en compte, j'ai limité au nombre de 2 les blocs chargés en asynchrone
car ceux ci doivent être chargés sur le même domaine que index.html, et IE7 ne peut faire que deux connexions
par domaine.

L'accessibilité au contenu en l'absence de js se fait par une directive noscript avec un meta refresh qui redirige
le visiteur sans js vers une version de la page assemblée côté serveur. Dans un contexte industriel, on
pose un cookie au visiteur la première fois que cela arrive, et on ne lui sert plus que des pages complètes.
De même, les robots connus sont repérés côté serveur par leur user-agent et sont directement servis
par la version complète. Les robots non connus suivent en principe le meta refresh. Au pire, il leur manque ici
les deux blocs (colonne de droite et pied de page) qui ne sont pas cruciaux pour le référencement.

Dans cette approche, seul l'ossature de la page change de forme entre les deux versions (techniquement c'est
le mécanisme d'inclusion qui est traduit de deux façons différentes possibles)

Ici, la redirection se fait sur index.st.html, qui est une version complète sans js (c'est une version moins avancée
de l'optimisation, et elle est également fonctionnelle avec js)

=== Lazy Loading
A partir du moment ou les visiteurs sans js étaient redirigés vers une page complète, j'ai considéré
acceptable de faire reposer l'affichage de certains contenus sur javascript dans la version optimisée.

J'utilise donc du lazy-loading sur 3 familles d'éléments :

* sur les images de produit du contenu principal de la page.
les img de la page sont dépourvues d'attribut mais disposent à la place d'un attribut original.
le script js/sources/jquery.lazyload.js est utilisé pour charger dynamiquement les images qui
apparaissent dans la fenêtre du visiteur lorsque celui-ci scrolle. Ceci se fait par recopie de
l'attribut original sur l'attribut src.

Dans la version alternative pour les utilisateurs sans js, les images sont référencées normalement
dans la page et sont toutes chargées dès le début.

* sur les background de décoration des boites en colonne de droite
ces décorations ont été passées en sprite et du coup le lazy load est géré manuellement.
L'utilisation d'une sprite n'a du coup ici que peu d'intérêt en perfo dans ce cas, sauf à
permettre de charger toutes les 4 images d'un coup.

* sur l'iframe : celle-ci étant en pied de page, je n'injecte son attribut src que lorsqu'elle est
susceptible de devenir visible (toujours sur la base du script lazyload)

=== CSS
la feuille de style principale unique initiale a été découpée en unités logiques, chacune dans une feuille,
toutes rangées dans le dossier css/sources/
Ensuite, je créé une feuille SSI css/sources/style.combined.css qui n'inclue que les feuilles nécessaires
à la page.
J'ai donc procédé par sélection de module fonctionnel CSS comme on pourrait le faire
en contexte de production, mais j'ai exclu de nettoyer les css de tous les sélecteurs inutiles à *la* page
du concours.
Par exemple, j'inclue la feuille entière qui style toutes les boites du site (css/sources/box.css) même
si ici on n'en utilise que quelques variantes.

J'ai testé la séparation des css en deux lots core+complements dont le premier serait inséré inline
mais cela n'améliore pas le début de rendu dans IE7 (cela améliore le 'start render' apparent mesuré,
mais en pratique cela n'améliore pas le début réel du rendu tel qu'on peut le voir avec capture vidéo).

J'en suis donc resté à une css unique, minifiée par CSSTidy.

Ce dernier n'est plus maintenu mais la dernière version distribuée contenait quelques bugs. J'ai donc
repris le code sur https://github.com/Cerdic/CSSTidy,
corrigé les tickets ouverts sur sourceforge, complété les tests etc?

La feuille minifiée est dans css/style.minified.css et correspond a css/sources/style.combined.css

Cette répartition permet d'avoir à la fois un rendu effectif qui démarre tôt, avec une apparence qui
fait illusion (le bandeau haut est stylé, les colonnes sont en place), et une mise en cache d'une
grande partie de la CSS.

CSS et JS sont placés dans la page afin d'être chargés le plus tôt possible dans bloquer aucun chargement

=== JS
les js inline ont été déportés dans des js externes, par fonctionnalité principale.
Dans la même approche que pour les css, seuls les scripts
utiles à la page ont été inclus dans la page. Dans les fonctions d'alignement de bloc, seules celles utiles sur
la page ont été conservées.

Les js ont été concaténés et minifiés par le Google Closure Compiler.

J'ai travaillé initialement avec un unique script qui chargeait tout en une fois. Mais la taille de ce js
en a fait le chemin critique pour réduire encore le onload de la page, et je suis donc passé
à deux fichiers :
js/jquery-1.4.4.min.js = js/sources/jquery-1.4.4.js+js/sources/jquery.lazyload.js
js/script.minified.js = js/sources/script.ll.combined.js
autocomplete.minified.js = js/sources/jquery.autocomplete.js + js/sources/jquery.ajaxQueue.js
et l'ensemble des autres scripts

Il a fallu gérer le chargement parallèle et asynchrone de ces deux fichiers, et que cela fonctionne
dans tous les navigateurs. J'ai donc développé un loader jQuery pour cela :

==== jQuery Loader
Dans le cadre du concours j'ai développé un loader jQuery js/sources/jQl.js
et https://github.com/Cerdic/jQl

Ce loader est capable de gérer le chargement asynchrone de jQuery,
le chargement asynchrone parallèle de scripts dépendants de jQuery par xhr
et de capturer les references js inline du type jQuery(function(){?)) ou jQuery(document).ready(function(){?});
pour les relancer quand jQuery est chargé.
Sa version minifiée est inclue inline dans index.html, pour un coût de 700 octets gzip
Le loader est utilisé pour charger les deux scripts js/jquery-1.4.4.min.js et js/script.minified.js en parallèle
(defer pour le premier et xhr pour le second)

=== JSON
La page contenait initialement 16 requêtes JSON pour charger les informations de disponibilité et prix
des articles.
J'ai mutualisé ces requête en une seule, à laquelle on passe en argument les prid recherchés, sous
forme de tableau. Le script qui gère cela de façon réaliste est js/sources/f_market.js
Le serveur d'origine de la fnac n'étant pas en mesure de servir cela, on mime en renvoyant le fichier
maketplace.js qui renvoit en JSON toutes les infos demandées.


=== HTML
le html a été minifié par suppression des espaces redondants et commentaires.

=== Cookies
Par défaut, le marqueur de statistiques pose des cookies sur .webperf-contest.com et contamine les sous domaine s1/s2/s3.
J'ai donc patché le marqueur de statistique pour qu'il pose ses cookies sur entries.webperf-contest.com

=== Paralelisation et Cookie-free domains
J'ai utilisé les sous domaines s1/s2/s3 au maximum pour charger les ressources statiques (images et css)
En revanche les js sont servis sur entries.webperf-contest.com car cela permet une meilleure paralélisation
(les autres domaines sont tous bookés) et un des scripts chargé en xhr ne peut provenir d'un autre domaine.
Les morceaux de page sont chargés aussi en xhr, et donc sur entries. Comme il s'agit de html qui pourrait éventuellement
utiliser des cookie, c'est logique.
Le Json du marketplace est aussi chargé sur entries.

=== htacess
Le fichier htaccess est utilisé pour
- forcer le deflate (gzip) sur tous les contenus pertinents (html, js, css,?) mais pas sur les images.
- forcer des expire lointains sur les fichier statiques (image, css, js). En situation de production cela s'accompagnerait
d'un timestamp sur les urls pour les versionner et forcer la maj en cas de changement
- bloquer les coolie sur les fichiers images (précaution en général inutile, mais je me suis battu sur ce point avec le yottaa
et il s'avère que c'était en définitive un dysfonctionnement de yottaa)