Dans le dernier épisode de mes aventures en Javascript, on m'a plusieurs fois fait remarquer qu'on pourrait réduire les appels à un seul programme javascript en copiant collant toutes les bibliothèques utilisées en un seul document.

Un constat qui part d'un calcul logique : Chaque appel d'url HTTP se fait en envoyant plusieurs paquets d'instantiation IP. Juste histoire de dire « youhou ! je suis làààààà ! ». Et c'est ce qui fait que 4 images de 200 octets seront plus longs à charger qu'un de 1200 [NB1].

Oui mais ...

Oui mais ce genre de comptes d'apothicaire prête à sourire quand on a un style avec une dizaine d'images minimum à charger. Et pour ce qui est des lags pour cause de cascade de bibliothèques JS, la meilleure méthode semble être celle du chargement désynchronisé. De n'avoir qu'une petite bibliothèque d'initialisation avec le minimum [NB2] qui en appelle de largement plus grosses.

Lesquelles peuvent à loisir être très lourdes. À titre d'exemple (en complément du précédent billet) et rien que pour rire, Apple envoie un gargantuesque 500 Ko pour les galeries photos de iWeb. Ça laggue un peu dans le tas :


À noter que dans ce tableau, les valeurs de temps indiquées correspondent pour un seul fichier. Le graph représente le cumul...

Notons qu'Apple utilise une méthode de chargement JS asynchrone, mais que ses bibliothèques sont complètes, le source est brut avec commentaires, indentation, etc... Vont-ils penser à réduire leur bande-passante ? Ou laisser cet excellent exercice pratique pour débuggage ?

L'autre inconvénient du programme unique tient dans les bugs à l'interprétation. Si jamais une seule erreur arrive, toute la suite du programme est ignorée. Et vu que le Javascript comporte une multitude de dialectes, ça augmente les chances de plantages.

Réfléchir à réduire...

Donc, revenons à l'idée d'une seule et très grosse bibliothèque unique, réunissant un voire plusieurs toolkits et ses extensions [NB3] (Pourquoi pas, puisque c'est ce mode de distribution qui est proposé par mootools)... plus des routines de pub AJAX ou de liens sociétaux...
Néanmoins, il faut correctement maîtriser l'ordre d'appels, éviter les conflits (dans les espaces de nommages de variables, de fonction, les ordres d'appels d'évènements, voire carrément les méthodes addevent).
L'autre souci, c'est que ce copier-coller rend difficile la gestion des versions (si une des bibliothèques vient de changer de version, il faut réinsérer manuellement dans le source la mise-à-jour). La meilleure méthode dans ce cas-là est de créer un script côté serveur (pourquoi pas en évènement de répertoire, via bash, pour éviter d'éventuel trou de sécurité si le script est en accès public), qui aura pour instruction de construire le programme à livrer aux visiteurs.

Purger les caractères inutiles.

On peut aussi enlever tout commentaires, les lignes vides inutiles... Cela n'accélérera pas de façon visible l'interprétation du script, mais pourrait fortement alléger le poids, donc le temps de chargement.
Évidemment, il faut garder une version originale, voire carrément mieux, utiliser un système de versionning, mais à ce stade, autant l'appliquer à l'ensemble de votre serveur web !

Et puis...
et puis on peut supprimer les espaces et tabulations inutiles, celles qui courbent et déhanchent si élégamment votre code source, ou peuplent de leur non-dit vos fins de lignes... quelle utilité sur un site web grand public ? Zou ! À la trappe...

Et puis... ces retours chariots, eux aussi ont peut les virer...
Et hop ! regex-ons comme à la Légion : s/[ ]/g
Sauf que contrairement au C, au Pascal, au Java, les retours chariot ont parfois signification : celle de séparer les instructions. Les “ ; ” en fin d'instruction ne sont pas obligatoires, et implicitement placées en fin de ligne après une instruction complète.

Et si on songeait à exterminer toutes ces fonctions inutiles ? Par là, je parle de toutes les instructions de débuggage (comme breakpoint) , voire de fonctions propres à votre dev. L'excellent compresseur de Dean Edwards, qui m'a été bien utile pour tous mes tests, supprime les lignes commençant par “ ;;; ”. Une méthode efficace, 100% compatible et pas prise de tête pour différencier dans le source le comportement de développement et celui d'exploitation.
Une bonne habitude à prendre.

Nom d'une lettre !

Les fonctions, les variables, les objets régulièrement appelés, plutôt que leur donner des noms explicites, certains se lancent dans une réduction de taille de nommage à un voire deux octets ! Bien du plaisir pour mettre en place, mais surtout pour redécoder si le logiciel de substitution fait son travail de manière trop automatisé...

J'ai aussi vu l'usage de caractères de jeu étendu (mais cette fois-ci dans une CSS). Or là, on nage en eaux troubles : la confusion du jeu de caractère selon les navigateurs. Car si le jeu par défaut du HTML a tendance à être de plus en plus souvent fixé en UTF-8 par les concepteurs de sites, les Javascripts (par défaut dans la norme) et les CSS (surtout avec Safari) ont tendance à revenir spontanément en jeu ISO-Latin.
Foutoir garanti.

Si vous vous amusez à ce petit jeu, la meilleure solution est d'à la fois indiquer au navigateur dans la balise HTML le jeu de caractères du script/style appelé dans la balise via l'attribut charset="", et de le repréciser à nouveau dans le header. En priant pour qu'un logiciel client (ai-je dit “MSIE ” ?) ne fasse par un charset-sniffing foireux sur vos appels JSON.

Jusqu'à quel point peut-on “compresser” un Javascript ?

Tout en revenant à l'excellent outil de compression en ligne de Dean Edwards, celui-ci propose une option “packer”. Une fonction qui permet de faire une compression sommaire par règles de substitution, qui peut aussi faire office d'obfuscation. Un cryptage sommaire qui rendra difficile pour le premier script-kiddie ou webdesigner débutant venu de repomper votre source. Promis, je ne vise personne...
Je ne crois pas me tromper en écrivant que l'outil est très populaire probablement pour cette dernière raison.

En fait, il est idiot de croire qu'en cryptant de la sorte son Javascript, on va accélérer le chargement. C'est faux : le navigateur perd du temps à décompresser le source originel puis à le ré-interpréter. Et ceci à chaque changement de page puisqu'il ne le garde pas en cache (un peu comme MSIE avec des Images appelés dynamiquement par JS ou CSS). Car bien évidemment, dans une navigation client, un programme JS n'est pas lié à un site, mais à un document HTML. Votre JS est donc susceptible de changer à chaque navigation, donc le navigateur va fort logiquement ne pas garder le runtime de votre bibliothèque, et en initier une nouvelle à chaque page.

Autre souci, ces règles de substitution peuvent être sujettes à des bugs ésotériques, comme celui concernant les moteurs regex. Mais rien que ça, y'a de quoi en faire un billet...

Conclusion

On peut :

  • stripper les commentaires
  • supprimer les indentations
  • supprimer les retours chariots
  • substituer les noms de variables, de fonction, les objets récurrents par des noms très courts
mais on peut éviter :
  • d'utiliser des noms (variable, fonction, objet) avec des caractères non US-ASCII
  • de compresser par substitution
  • de tout coller dans une seule bibliothèque extrêmement lourde

Le meilleur exemple à suivre est celui de Google : Il suit très exactement toutes ces méthodes. N'allez pas dire que j'ai copié sur eux, je m'en suis rendu-compte après coup.
Une tel coïncidence, c'est que mon raisonnement ne doit pas être si foireux...


Nota bene :

  1. En plus du problème de la taille minimale de trame IP, mais ça, c'est un autre problème... élégamment résolu par chiptune.com, un incroyable émulateur d'Amiga tout en Javascript et DOM !
    Pour afficher les textes dans les polices du Workbench, il ne charge pas une image par lettre, mais tout un alphabet en une seule image, laquelle est dupliquée pour chaque lettre avec une fenêtre de visibilité soigneusement calculée. Seul inconvénient, cette méthode ne marche que si les images sont en positionnement CSS fixé, voire absolu.
  2. fonctions minimales : Dans ma boîte à outil basique, j'y mettrais le chargement par insertion DOM, la gestion des cookies, timeout, addevent et domready. Ces fonctions primitives sont très faciles à écrire.
  3. Parmi les pages intéressantes que j'ai trouvé, Alister Cameron propose le pack prototype.js + scriptaculous.js (avec quelles extensions ?) packé en un seul fichier. Notez sa solution sur la gestion de charset appelé.