Gérer ses dépendances JavaScript avec Google Closure Compiler / Library (1 / 2)

JavaScript ne propose pas (encore) de moyen d’organiser son code et notamment de déclarer les dépendances entre les différents fichiers JavaScript de votre projet. Dans un langage compilé comme ActionScript ou Java, vous avez les directives « import pack.sub.MaClasse » qui vous permettent d’indiquer au compilateur qu’une classe requiert une autre classe pour fonctionner.

Si vous avez quelques dizaines de lignes de code JavaScript dans votre projet, vous pouvez toutes les mettre dans un seul fichier ou directement dans le fichier HTML mais quand vous commencez à avoir plusieurs centaines / milliers de lignes de code, vous allez rapidement découper votre code en différent fichiers.

Comme il n’y a encore rien dans le langage pour vous aider, plusieurs librairies / systèmes ont été créés, notamment:

Exemple simple avec RequireJS

Pour en apprendre plus sur RequireJS, une des plus populaires, je vous conseille de lire ce très long et très bon article @mklabs :

RequireJS ➤ mo-du-la-ri-té !

Même si RequireJS est devenu assez « standard » (il y a quelques trolls sur le net pour déterminer le champion des champions), vous pouvez toujours décider de ce que vous allez utiliser.

Vous trouverez plein d’exemples dans l’article cité plus haut mais voici comment on déclare un module requireJS:

//my/shirt.js now has some dependencies, a cart and inventory
//module in the same directory as shirt.js
define(["./cart", "./inventory"], function(cart, inventory) {
        //return an object to define the "my/shirt" module.
        return {
            color: "blue",
            size: "large",
            addToCart: function() {
                inventory.decrement(this);
                cart.add(this);
            }
        }
    }
);

On utilise la méthode globale define (amené par require.js), on lui passe (si nécessaire) en premier argument la liste des dépendances puis une fonction (callback) qui sera appelée lorsque les dépendances auront été chargées. A cette callback, on passe autant d’arguments que de dépendances (dans l’ordre). Ces arguments sont des références vers les dépendances chargées.

Il y a plusieurs manières plus ou moins élégantes de le faire mais il faut que le module renvoie son « interface publique », c’est-à-dire les méthodes qu’il va exposer. Le reste sera « privé » grâce à l’utilisation d’une closure, ici la callback principale.

Ensuite, pour aller chercher la dépendance, vous allez utiliser la méthode globale « require() » qui prend en argument l’identifiant du module JavaScript que vous voulez utiliser:

require("module/name").callSomeFunction()

Voilà, cela fonctionne très bien mais, et c’est très personnel, plusieurs points me chagrinent:

  • La syntaxe qui peut rapidement devenir verbeuse
  • Le fait d’enfermer son module dans une callback permet de ne pas polluer le namespace globale mais pour une application (pas une librairie), je ne trouve pas cela « trop » grave d’arriver avec son namespace
  • Code plus difficile à lire (pour moi) qu’avec une approche simple par prototype « à plat »

Bref, j’ai décidé d’aller voir ailleurs pour enfin tester la méthode de Google Closure

Google Closure en deux mots

Google Closure est une famille de projets soutenus par Google et utilisés par Google dont:

Voici une vidéo du Google IO 2010 qui explique un peu tout:

Ici on va principalement s’intéresser au Compiler pour notre gestion de dépendances. Le compilateur en lui-même se compose d’un JAR mais Google propose aussi d’autres outils en Python pour vous aider à automatiser votre travail.

La Google Closure Library est un ensemble de « classes » JavaScript et de composants que l’on présente souvent comme une alternative à jQuery (alors que ce n’est pas vraiment le cas). Pour notre exemple, on va utiliser seulement un fichier, « base.js » qui contient certaines méthodes qui nous seront utiles pour déclarer nos dépendances.

Un exemple

Le plus simple pour trouver des exemples et d’aller voir dans le code de la librairie Google Closure directement:

http://code.google.com/p/closure-library/source/browse/#svn%2Ftrunk%2Fclosure%2Fgoog

Vous verrez que les fichiers sont très verbeux, avec énormément de commentaires JSDoc qui sont en fait utilisés par le compilateur pour transformer JavaScript en un langage presque typé.

Prenons par exemple le fichier ClickToEditWrapper.js:

http://code.google.com/p/closure-library/source/browse/trunk/closure/goog/editor/clicktoeditwrapper.js

Voici ce que vous allez trouver dans les premières lignes du fichier:

goog.provide('goog.editor.ClickToEditWrapper');

goog.require('goog.Disposable');
goog.require('goog.asserts');
goog.require('goog.debug.Logger');
goog.require('goog.dom');
goog.require('goog.dom.Range');
goog.require('goog.dom.TagName');
goog.require('goog.editor.BrowserFeature');
goog.require('goog.editor.Command');
goog.require('goog.editor.Field.EventType');
goog.require('goog.editor.range');
goog.require('goog.events.BrowserEvent.MouseButton');
goog.require('goog.events.EventHandler');
goog.require('goog.events.EventType');

et ensuite, le protoype est enrichi de manière dynamique:

/** @return {goog.editor.Field} The field. */
goog.editor.ClickToEditWrapper.prototype.getFieldObject = function() {
  return this.fieldObj_;
};

/** @return {goog.dom.DomHelper} The dom helper of the uneditable element. */
goog.editor.ClickToEditWrapper.prototype.getOriginalDomHelper = function() {
  return this.originalDomHelper_;
}

Ici, on utilise 2 méthodes qui sont en fait présentes dans le fichier base.js de la Google Closure Library: goog.provide(…) et goog.require(…).

goog.provide(…)

L’instruction goog.provide(…) prend en seul paramètre, un namespace comme « goog.editor.ClickToEditWrapper ». Avec cette instruction, vous indiquez que le fichier qui contient cette directive est celui qui va pouvoir fournir le namespace « goog.editor.ClickToEditWrapper ». C’est un peu comme si vous déclariez le package et le nom de la classe en même temps (le fully-qualified classname pour les intimes).

Notez qu’un fichier peut contenir plusieurs instruction goog.provide(…), en effet, un fichier peut définir plusieurs « classes » JavaScript.

goog.require(…)

Comme son nom l’indique, goog.require(…) indique que la classe en question a une dépendance forte vers un autre namespace. Si vous faîtes un peu de Java / ActionScript 3, cela ressemble aux instructions « import » ou aux fichiers .h en C. Bien entendu, vous pourrez avoir autant de goog.require(…) qu’il en faudra.

Ici, plusieurs points que j’apprécie:

  • La syntaxe est moins intrusive et plus élégante à mon goût (ressemble *très* grossièrement à de l’AS3)
  • Pas de callback à laquelle on passe des références aux dépendances, on utilisera simplement nos objets définis dans nos namespaces.
  • Plus facile à lire car assez linéaire et peut être séparé par sub-package, comme ce que font les formatters de code Java / AS3 (séparer bg.model.xxx de bg.application.xxx)
  • Dépendance au fichier base.js (8Ko minifié, 2.5Ko gzippé + min) qui contient en plus pas mal de « petits trucs » bien pratiques (mixins, typeOf, inherits, …)

La suite dans la seconde partie, où j’expliquerai comment compiler en ligne de commande, puis comment utiliser les scripts Python fournis pour construire son arbre de dépendances.

2 réflexions au sujet de « Gérer ses dépendances JavaScript avec Google Closure Compiler / Library (1 / 2) »

  1. Ping : Gérer ses dépendances JavaScript avec Google Closure Compiler / Library (2 / 2) | HTML5 Tutorial

  2. Ping : JavaScript – Améliorer la qualité de son code (Linter, IDE, compilateur, tests et build) | HTML5 Tutorial

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *