Accueil > Web > AMD, ou l’art de concilier bonnes pratiques et performance en JavaScript

AMD, ou l’art de concilier bonnes pratiques et performance en JavaScript

Dans le monde du web, s’il est un langage qui a réussi à percer au point de devenir aujourd’hui incontournable, c’est bien le langage JavaScript. Au fil des années, celui-ci s’est vu enrichir de nouvelles (et ô combien nombreuses) librairies, au point que l’on serait désormais tenté de dire que pour tous nos développements, « il y a une librairie pour ça ! ».

Certes, mais JavaScript c’est aussi et malheureusement un langage qui n’a que peu évolué depuis sa création. Si bien que sa syntaxe n’est désormais plus adaptée pour l’utilisation massive que nous en faisons. Maintenance difficile, temps de chargement des pages accrus, isolation et réutilisabilité du code sont autant de problématiques auxquelles nous sommes désormais confrontés et pour lesquelles le langage ne propose à l’heure actuelle aucune solution native.

C’est dans ce contexte qu’est récemment sorti AMD (pour Asynchronous Module Definition) dont l’initiative, plutôt originale, est de s’attaquer au chargement des scripts de nos pages web. Comme nous allons le voir, l’approche adoptée par AMD va non seulement nous permettre de résoudre de manière élégante cette problématique, mais va de manière plus globale nous amener à repenser notre façon de développer avec le langage.

Le « Script Loading », là où tout commença

Pour comprendre la raison d’être d’AMD, reprenons depuis le début : dans un contexte web, le chargement des scripts (ou « script loading ») est par défaut géré par le navigateur lorsque celui-ci rencontre les balises <script> d’une page. Rien de révolutionnaire jusque là, et pourtant, à bien y réfléchir ce type de chargement n’est pas exempt de tout défaut :

Des problématiques de performance

Sur les anciens navigateurs comme IE6/7, Safari 3, Firefox 3.5 ou encore Chrome 2, le chargement de ces scripts se fait de manière synchrone. Pourtant si plusieurs scripts n’ont pas de lien entre eux on serait tenté de vouloir les charger en parallèle afin de réduire le temps de chargement global de la page.

Une syntaxe HTML difficile à maintenir

La syntaxe d’import des fichiers JavaScript en HTML n’a (malheureusement) que très peu évoluée depuis sa création. Elle consiste en une liste déclarative de balises <script> très verbeuse et nécessitant d’avoir un ordonnancement cohérent, les scripts étant exécutés par le navigateur dans leur ordre de déclaration.

AMD - Exemple d'imports JavaScript au sein d'une page HTML

Une syntaxe qui devient de plus en plus chaotique à mesure que le nombre d’imports augmente et qui incite au final les développeurs à répliquer ces portions de code, voire à les factoriser pour mieux les injecter sur l’ensemble des pages du site, ralentissant inutilement leur temps de chargement étant donné que seule une partie de ces scripts sont réellement utilisés.

Des problématiques d’isolation

Souvenez-vous de vos premiers cours de JavaScript : tous les objets sont créés dans un unique espace de nommage, celui de la page dans laquelle les scripts sont chargés. Par conséquent, lorsqu’on importe deux fichiers contenant chacun des objets portant les mêmes noms, les objets du deuxième fichier écrasent les objets initialisés au chargement du premier fichier. Cette problématique, mieux connue sous le terme « d’isolation » du code,  se résout aisément en encapsulant le code dans des objets (à la manière d’une classe en Java), encore faut-il à la fois le savoir et l’appliquer puisque rien ne contraint les développeurs à respecter cette bonne pratique.

AMD (Asynchronous Module Definition)

C’est pour palier à ces problématiques qu’est née en 2011 l’API AMD (Asynchronous Module Definition). Celle-ci se contente uniquement de définir les notions de « module » et de « dépendance ». L’idée générale est d’encapsuler le code JavaScript dans des modules (un module correspondant à un fichier) puis de décrire en entête ses dépendances de manière à les charger de manière asynchrone, lorsque cela est possible. Et oui, AMD ce n’est pas plus compliqué que ça.

Vous avez dit API ?

Qui dit API dit méthodes, et pour le coup les concepteurs d’AMD se sont attelés à fournir une API minimaliste puisque celle-ci ne contient en tout et pour tout qu’une seule méthode, dont voici la définition :

define (id?, dependencies?, factory);

Celle-ci prend en paramètres :

un id (optionnel) : correspond à l’identifiant unique du module.

un tableau de chaînes de caractères (optionnel) : chacune de ces chaînes correspondant à l’identifiant d’un module. Ce paramètre permet de décrire l’ensemble des dépendances du module en question.

une fonction factory : seul paramètre obligatoire, cette fonction sera exécutée une fois l’ensemble des dépendances chargées. La factory peut également être un objet, auquel cas celui-ci sera rendu accessible dans le reste du code par le nom du module.

Exemple de déclaration d’un module :

define([<span style="color: #333399">'lib/jquery'</span>, <span style="color: #333399">'util/math'</span>], <span style="color: #800080">function</span>($, math) {
  <span style="color: #2e8b57">// Les modules 'lib/jquery' et 'util/math' sont chargés</span>
  $(document).ready(<span style="color: #800080">function</span>() {
    <span style="color: #2e8b57">// Dom tree ready&lt;/span&gt;</span>
    $(body).html(<span style="color: #333399">"La valeur absolue de -2 est : "</span> + math.abs(-2));
  });
});

A noter que pour correctement fonctionner, il faut que l’ordre de déclaration des dépendances soit rigoureusement le même que celui des paramètres de la fonction de callback ! On comprend que cela peut vite s’avérer fastidieux lorsque le nombre de dépendances explose, aussi la librairie propose une seconde variante de la fonction define, qui utilise la fonction require pour charger de manière synchrone une dépendance :

define(<span style="color: #800080">function</span>(require, exports, module) {
  var $ = require(<span style="color: #333399">'lib/jquery'</span>),
  math = require(<span style="color: #333399">'util/math'</span>);
  <span style="color: #2e8b57">// Les modules 'lib/jquery' et 'util/math' sont à présent chargés</span>
  exports.action = <span style="color: #800080">function</span>() {
    $(document).ready(<span style="color: #800080">function</span>() {
      <span style="color: #2e8b57">// Dom tree ready</span>
      $(body).html(<span style="color: #333399">"La valeur absolue de -2 est : "</span> + math.abs(-2));
    });
  };
});

Les deux écritures sont équivalentes, la première tend simplement à produire un code horizontal, idéal lorsque le nombre de dépendances est faible, et la deuxième un code vertical, davantage lisible lorsque le nombre de dépendances augmente.

Le module ainsi créé est partout réutilisable sur notre site, il suffit pour cela de le déclarer comme dépendance d’un autre module et le tour est joué. Une fois notre code modularisé, il ne nous reste plus qu’à déléguer à un Script Loader le chargement et l’exécution de nos fichiers JavaScript. Elle est pas belle la vie ?

That’s all folks !

Contrairement à ce que son nom pourrait laisser penser, le véritable enjeu d’AMD n’est pas simplement l’optimisation du temps de chargement d’une page par le chargement asynchrone de ses fichiers JavaScript, mais bel et bien l’enrichissement du code par l’introduction des modules.

Ce découpage en modules facilement réutilisables valorise le code produit et incite naturellement les développeurs à avoir une approche type librairie/API et non terminale comme c’est souvent le cas lors des phases de conception et de développement.

L’approche modulaire permet ensuite d’identifier en un coup d’œil le contexte d’exécution de notre code : en explicitant les dépendances d’un module et en les passant en argument de la fonction de callback, on adopte ainsi une approche totalement orientée objet tout en collant parfaitement aux bonnes pratiques JavaScript. Notre code devient plus lisible, mieux isolé et les risques de collisions dans l’espace de nommage sont éliminés.

Enfin, la lourde et périlleuse tâche d’ordonnancement des fichiers JavaScript se voit désormais déléguée au Script Loader. Oubliez les listes interminables de balises <script> dans vos pages, avec AMD il n’en restera qu’une permettant simplement de charger le Script Loader. La seule tâche du développeur consiste à définir les dépendances des modules – sans tenir compte de leur ordre de déclaration – ce qui permettra par la suite au Script Loader de charger les modules et leur dépendance de manière optimisée (comprendre : asynchrone), lorsque cela est possible.

Attention toutefois, si le découpage en unités logiques (modules) du code est en soit une excellente pratique, cela engendre par ailleurs une démultiplication du nombre de fichiers importés dans les pages. Aussi trouve-t-on désormais dans certains Scripts Loader un module complémentaire permettant d’agréger en un fichier – à la compilation – les différents modules importés dans chacune des pages du site.

Les Scripts Loaders

AMD n’est en soit qu’une API et nos modules ne serviraient à rien s’ils n’étaient pas interprétés au chargement de la page. C’est pour cela que l’on fait appel à un Script Loader qui, une fois chargé par le navigateur (grâce à la balise <script>), prend la main, détermine en fonction des dépendances des modules l’ordre optimal de chargement des scripts puis exécute la fonction de callback des modules une fois l’ensemble de leur(s) dépendance(s) chargée(s).

Voici une liste des Script Loaders AMD les plus en vogue à l’heure actuelle :

RequireJS : Sans doute le plus populaire à l’heure actuelle, notamment grâce à son Optimizer qui permet d’agréger et de minifier les dépendances en un seul fichier pour les déploiements en production.

– Almond : Version allégée de RequireJS, qui permet dans certains cas de ne pas embarquer la librairie RequireJS une fois les fichiers JavaScript optimisés.

Curl.js : Dans ses dernières versions, Curl.js a largement rattrapé son retard sur RequireJS. Curl.js tend à se démarquer de ce dernier dans sa conception, notamment par l’utilisation de Promises qui permettent une meilleure gestion des erreurs lors des problèmes de chargement.

Dojo (1.7+)

Le mot de la fin

De par sa simplicité d’écriture, son faible coût de mise en place et ses nombreux bénéfices, la norme AMD est certainement promise à un bel avenir et deviendra à n’en pas douter un incontournable dans les prochaines années. Elle ne souffre finalement aujourd’hui que d’un manque de visibilité et de notoriété au sein de la communauté web, même si certaines implémentations telles que RequireJS ou Curl.js commencent enfin à faire parler d’elles. Notons en conclusion que depuis sa version 1.7, la célèbre librairie JQuery est à titre d’exemple passée au format AMD… S’il fallait encore vous convaincre d’y passer !

  1. Pas encore de commentaire
  1. Pas encore de trackbacks


deux × = 14