Aller au contenu

Utilisateur:Francoisrioux/Brouillon

Une page de Wikipédia, l'encyclopédie libre.
Plutôt que de charger les fichiers un à la fois, l'AMD peut les charger séparément même s'il sont dépendant un de l'autre.

La définition asynchrone de modules (de l’anglais Asynchronous Module Definition, ou AMD) est une spécification d’API en JavaScript. Cette interface propose un mécanisme pour la création de modules afin que ces-derniers ainsi que leurs dépendances puissent être chargés de manière asynchrone. Elle est adaptée à l’environnement des navigateurs Web où le chargement synchrone amène des problèmes au niveau de la performance, du déverminage et de l’accès inter-domaine.[1]

Origines[modifier | modifier le code]

La définition asynchrone de modules est dérivée d’un format de module qui fût créé dans le cadre du projet CommonJS. Il s’agit d’un environnement permettant d’exécuter du JavaScript sans navigateur Web. Leur format pouvait être utilisé par les navigateurs en utilisant des mesures de contournement, comme se servir d’un serveur afin de traduire un module CommonJS en un objet utilisable par le fureteur, ou encore charger le texte du module à l’aide d’un XMLHttpRequest et effectuer des transformations et des analyses textuelles pour finalement en obtenir le contenu. Leur objectif était de démontrer l’utilité de leur format et d’influencer les fabricants de navigateurs Web à créer des solutions natives qui permettraient aux développeurs d’utiliser leurs modules de façon plus efficace. Cependant, le groupe CommonJS n’a pas pris en compte certains invariants de l'environnement des navigateurs qui ont un impact sur le design des modules, tel que le chargement par le réseau ou encore l'asynchronisme inhérent du développement Web. De plus, le format proposé par CommonJS ne permet la présence que d’un seul module par fichier. Il impose alors l’utilisation d’un format de transport pour regrouper plusieurs modules ensemble à des fins d’optimisation. En raison de ces facteurs combinés, il était plus ardu pour les développeurs Web d’implémenter le format de module et le déverminage en devenait plus complexe par la faute des mesures de contournements. Ces faiblesses font en sorte que l’utilisation des modules CommonJS n’est pas optimal dans un contexte Web.[2]

L’AMD est apparu avec la proposition « Modules Transport/C », un format de transport pour CommonJS qui apportait les solutions nécessaires pour rendre viable l’utilisation de ce format de modules dans les fureteurs. Il a évolué pour inclure son propre API de définition de modules. Cependant, le groupe CommonJS ne parvint pas à un consensus et ne l’ont pas retenu dans leur spécification. Le développement de cet API s’est donc déplacé vers un groupe indépendant qui a mené à terme le projet. Aujourd’hui, l’AMD peut être utilisé comme format de transport pour CommonJS si les modules respectent certaines conditions, c’est à dire qu’ils ne font pas de chargements synchrones ou conditionnels de modules.[3]

Les modules[modifier | modifier le code]

Design de base[modifier | modifier le code]

En JavaScript, un module diffère d’un fichier de script standard puisque celui-ci définit un objet possédant sa propre portée, évitant ainsi d’ajouter des symboles superflus dans l’espace de nom global.[4] Ceci permet d’encapsuler les états internes du module et leurs traitements à l’intérieur de l’objet et de restreindre leur accès de l’extérieur à travers une interface. La façon usuelle de créer un module en JavaScript est d’utiliser une fonction anonyme qui retourne un objet ou une fonction. Les variables et fonctions déclarées localement dans la fonction anonyme n’existeront qu’à l’intérieur de sa fermeture et ne pourront ainsi être utilisées que par l’interface du module, constituée des propriétés de l’objet retourné. Si un module dépend d’un autre objet ou module pour son fonctionnement, on dit alors qu’il s’agit d’une dépendance de ce module. Ces dépendances sont envoyées comme paramètre à la fonction anonyme malgré qu’elles lui sont généralement déjà accessibles, puisqu’elles existent dans la même portée. Cette approche offre deux avantages. Premièrement, la liste de dépendances est exprimée clairement au début de la déclaration du module, rendant le code plus facile à lire et à entretenir. Deuxièmement, ceci rend l'accès aux dépendances plus rapide, puisque l’interpréteur ne devra remonter la chaîne des portées qu’une seule fois, à l'exécution de la fonction anonyme, pour résoudre ces symboles et non à chaque fois qu’elles sont utilisées par le module.[5]

CommonJS et AMD[modifier | modifier le code]

Les modules utilisés par l'AMD sont fortement inspiré du format mis au point par le groupe CommonJS. Pour définir un module dans ce format, on a recours à trois variables libres, require, exports et module. Le module comme tel est défini en ajoutant des propriétés à l'objet exports, la valeur exportée du module représentant son interface. Les dépendances du module sont résolues à l'aide de require, une fonction qui retourne la valeur exportée d'un module dont l'identificateur est passé en paramètre. Finalement, module est l'objet qui contiendra l'identificateur du module avec lequel il sera possible de le retrouver.[6]

Spécification[modifier | modifier le code]

define[modifier | modifier le code]

La spécification de l'API de définition asynchrone de modules requiert au minimum l'implémentation de la fonction define, disponible en tant que variable globale[7]. La signature de cette fonction est :

define(id?, dependencies?, factory);

L'identificateur[modifier | modifier le code]

Le premier paramètre, id, est une chaîne de caractères littérale. Elle spécifie l'identificateur du module en cours de définition. C'est à l'aide de cet identificateur que le module pourra être chargé par la suite. Ce paramètre est optionnel. S'il n'est pas présent, l'identificateur du module devrait avoir comme valeur par défaut celle du module dans lequel il a été requis. Les identificateurs doivent respecter le format suivant :

  • Une chaîne de caractères composée de termes séparés par des barres obliques.
  • Un terme doit être un identifiant en CamelCase ou bien un ou deux points («.» ou «..»).
  • Ne doit pas contenir une extension de fichier comme «.js»

Un identificateur peut être relatif ou absolu. Un identificateur relatif débute par «.» ou «..» et le module qu'il représente sera résolu de manière relative au module duquel il est requis, de manière similaire aux chemins relatifs pour les fichiers des principaux systèmes d'exploitation. Les identificateurs absolus, quand à eux, seront résolus à partir de la racine de l'espace de nom de l'application.[8]

Dépendances[modifier | modifier le code]

Le deuxième paramètre, dependencies, est un tableau littéral contenant les identificateurs des modules qui sont les dépendances requises par le module en cours de définition. Les dépendances doivent être résolues avant l'exécution de la fonction de fabrication. Les valeurs résolues devraient être passées en paramètre à la fonction de fabrication dans le même ordre qu'on les retrouve dans le tableau des dépendances. Les identificateurs des dépendances peuvent être relatives et, si c'est le cas, devraient être résolus relativement au module en cours de définition. La résolution se fait à partir de l’identificateur du module et non le chemin utilisé pour trouver le module. La spécification définie trois noms de dépendances spéciaux qui ont une résolution distincte, soit require, exports et module. Si ces valeurs apparaissent dans la liste de dépendances, ils devraient être résolus de façon à correspondre aux variables libres du même nom définies par la spécification de module de CommonJS. Le paramètre dependencies est optionnel. S'il est omis, il devrait prendre ["require", "exports", "module"] comme valeur par défaut. Cependant, si le nombre de paramètres envoyés à la fonction de fabrication est inférieur à trois, le chargeur de modules peut choisir de l'appeler avec son nombre de paramètre et d'ignorer les autres.[9]

Fabrique[modifier | modifier le code]

Le troisième paramètre, factory, est soit une fonction qui est exécutée pour instancier le module, soit un objet. S'il s'agit d'une fonction, elle ne devrait être exécutée qu'une seule fois. S'il s'agit d'un objet, il devrait être utilisé comme valeur exportée par le module. Si la fonction de fabrication retourne une valeur dont la coercition de type donne la valeur true, elle devrait être utilisée comme valeur exportée.[10]

require[modifier | modifier le code]

La fonction require permet de charger un module pour l'utilisation dans le code client. Elle se sert d'une chaîne de caractère contenant l'identificateur d'un module pour en rapporter la valeur exportée, qui contient ses fonctionnalités.[11]

La spécification de l'AMD prévoie deux version de cette fonction. La principale, de la forme require(Array, Function), effectue le chargement asynchrone de modules. Array est un tableau de chaînes de caractères représentant les identificateurs des modules qui doivent être chargés. Lorsque tous les modules requis sont disponibles, la fonction de rappel Function est appelée en recevant les modules en paramètre dans l'ordre qu'ils apparaissent dans le tableau.[12] La deuxième, de la forme require(String), retourne de manière synchrone la valeur exportée par le module dont l'identificateur compose la chaîne de caractères envoyée à la fonction. Cette version ne fonctionne uniquement qu'avec les modules qui ont déjà été chargés et évalués. Dans le cas contraire, la fonction doit soulever une erreur et ne doit en aucun cas tenter de rapporter le module de façon dynamique.[13]

Il existe aussi une différence entre le require global et le local, qui est passé à la fonction de fabrication de define. La version globale a un fonctionnement similaire à la locale, à la différence où:

  • Les identificateurs sont traités comme absolu plutôt que d'être résolu relativement à l’identificateur d'un autre module.
  • Seule la version asynchrone devrait fonctionner puisqu'il n'est peut-être pas possible de faire des chargement synchrone à partir du niveau supérieur de l'application. [14]

Si define dispose d'un version locale, sa fonction de fabrication peut être analysée pour trouver des dépendances chargées via l'appel synchrone de require. De cette façon, elles peuvent être chargées et exécutées avant l'appel de la fonction de rappel. Dans ce cas, on utilise la valeur exportée du module retournée par require[13]

Références[modifier | modifier le code]