Compilation avancée

Présentation

L'utilisation de Closure Compiler avec un compilation_level de ADVANCED_OPTIMIZATIONS offre de meilleurs taux de compression que la compilation avec SIMPLE_OPTIMIZATIONS ou WHITESPACE_ONLY. La compilation avec ADVANCED_OPTIMIZATIONS accroît la compression en appliquant une méthode plus agressive dans la transformation du code et le changement de nom des symboles. Cependant, cette approche plus agressive signifie que vous devez faire plus attention lorsque vous utilisez ADVANCED_OPTIMIZATIONS pour vous assurer que le code de sortie fonctionne de la même manière que le code d'entrée.

Ce tutoriel illustre le fonctionnement du niveau de compilation ADVANCED_OPTIMIZATIONS et ce que vous pouvez faire pour vous assurer que votre code fonctionne après la compilation avec ADVANCED_OPTIMIZATIONS. Elle introduit également le concept d'extern, un symbole défini dans le code externe au code traité par le compilateur.

Avant de lire ce tutoriel, vous devez vous familiariser avec le processus de compilation de JavaScript avec l'un des outils Closure Compiler (l'interface utilisateur du service de compilation, l'API du service de compilation ou l'application de compilation).

Remarque terminologique: l'option de ligne de commande --compilation_level accepte les abréviations ADVANCED et SIMPLE les plus couramment utilisées, ainsi que les méthodes ADVANCED_OPTIMIZATIONS et SIMPLE_OPTIMIZATIONS plus précises. Ce document est plus long, mais les noms peuvent être utilisés de manière interchangeable sur la ligne de commande.

  1. Une meilleure compression
  2. Activer les ADVANCED_OPTIMIZATIONS
  3. Points à prendre en compte lorsque vous utilisez ADVANCED_OPTIMIZATIONS
    1. Suppression du code à conserver
    2. Noms de propriété incohérents
    3. Compiler deux parties de code séparément
    4. Références rompues entre le code compilé et non compilé

Compression encore meilleure

Avec le niveau de compilation par défaut SIMPLE_OPTIMIZATIONS, Closure Compiler réduit le code JavaScript en renommant les variables locales. Toutefois, certains symboles autres que les variables locales peuvent être raccourcis, et il existe des moyens de réduire le code autre que de renommer des symboles. La compilation avec ADVANCED_OPTIMIZATIONS exploite la gamme complète de possibilités de réduction du code.

Comparez les sorties de SIMPLE_OPTIMIZATIONS et ADVANCED_OPTIMIZATIONS pour le code suivant:

function unusedFunction(note) {
  alert(note['text']);
}

function displayNoteTitle(note) {
  alert(note['title']);
}

var flowerNote = {};
flowerNote['title'] = "Flowers";
displayNoteTitle(flowerNote);

La compilation avec SIMPLE_OPTIMIZATIONS raccourcit le code en:

function unusedFunction(a){alert(a.text)}function displayNoteTitle(a){alert(a.title)}var flowerNote={};flowerNote.title="Flowers";displayNoteTitle(flowerNote);

La compilation avec ADVANCED_OPTIMIZATIONS raccourcit entièrement le code en:

alert("Flowers");

Ces deux scripts génèrent une alerte lisant "Flowers", mais le second est beaucoup plus petit.

Le niveau ADVANCED_OPTIMIZATIONS va au-delà du simple raccourcissement des noms de variables de plusieurs manières, y compris:

  • renommage plus agressif :

    La compilation avec SIMPLE_OPTIMIZATIONS ne renomme que les paramètres note des fonctions displayNoteTitle() et unusedFunction(), car il s'agit des seules variables du script qui sont locales pour une fonction. ADVANCED_OPTIMIZATIONS renomme également la variable globale flowerNote.

  • suppression du code mort:

    La compilation avec ADVANCED_OPTIMIZATIONS supprime entièrement la fonction unusedFunction(), car elle n'est jamais appelée dans le code.

  • fonction intégrée :

    La compilation avec ADVANCED_OPTIMIZATIONS remplace l'appel de displayNoteTitle() par l'unique alert() qui compose le corps de la fonction. Le remplacement d'un appel de fonction par le corps de la fonction est appelé "intégration". Si la fonction était plus longue ou plus compliquée, l'intégrer pourrait modifier le comportement du code, mais le compilateur Compiler détermine que, dans ce cas, l'intégration est sûre et économise de l'espace. La compilation avec ADVANCED_OPTIMIZATIONS intègre également les constantes et certaines variables lorsqu'elle détermine qu'elle peut le faire en toute sécurité.

Cette liste n'est qu'un échantillon des transformations permettant de réduire la taille que la compilation ADVANCED_OPTIMIZATIONS peut effectuer.

Activer ADVANCED_OPTIMIZATIONS

L'UI du service Closure Compiler, l'API de service et l'application utilisent des méthodes différentes pour définir compilation_level sur ADVANCED_OPTIMIZATIONS.

Activer ADVANCED_OPTIMIZATIONS dans l'UI du service Closure Compiler

Pour activer ADVANCED_OPTIMIZATIONS pour l'interface utilisateur du service Closure Compiler, cliquez sur la case d'option "Advanced" (Avancé).

Activer ADVANCED_OPTIMIZATIONS dans l'API du service Closure Compiler

Pour activer ADVANCED_OPTIMIZATIONS pour l'API de service Closure Compiler, incluez un paramètre de requête nommé compilation_level avec la valeur ADVANCED_OPTIMIZATIONS, comme dans le programme Python suivant:

#!/usr/bin/python2.4

import httplib, urllib, sys

params = urllib.urlencode([
    ('code_url', sys.argv[1]),
    ('compilation_level', 'ADVANCED_OPTIMIZATIONS'),
    ('output_format', 'text'),
    ('output_info', 'compiled_code'),
  ])

headers = { "Content-type": "application/x-www-form-urlencoded" }
conn = httplib.HTTPSConnection('closure-compiler.appspot.com')
conn.request('POST', '/compile', params, headers)
response = conn.getresponse()
data = response.read()
print data
conn.close()

Activer ADVANCED_OPTIMIZATIONS dans l'application Closure Compiler

Pour activer ADVANCED_OPTIMIZATIONS pour l'application Closure Compiler, incluez l'option de ligne de commande --compilation_level ADVANCED_OPTIMIZATIONS, comme dans la commande suivante:

java -jar compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS --js hello.js

Éléments à prendre en compte lorsque vous utilisez ADVANCED_OPTIMIZATIONS

Vous trouverez ci-dessous quelques-uns des effets indésirables courants de l'ADVANCED_OPTIMIZATIONS, ainsi que les mesures à prendre pour les éviter.

Suppression du code à conserver

Si vous ne compilez que la fonction ci-dessous avec ADVANCED_OPTIMIZATIONS, Closure Compiler produit un résultat vide:

function displayNoteTitle(note) {
  alert(note['myTitle']);
}

Comme la fonction n'est jamais appelée dans le code JavaScript que vous transmettez au compilateur, Closure Compiler suppose que ce code n'est pas nécessaire.

Dans de nombreux cas, ce comportement est exactement ce que vous attendez. Par exemple, si vous compilez votre code avec une grande bibliothèque, Closure Compiler peut déterminer les fonctions de cette bibliothèque que vous utilisez réellement et supprimer celles que vous n'utilisez pas.

Toutefois, si Closure Compiler supprime les fonctions que vous souhaitez conserver, vous pouvez l'éviter de deux manières:

  • Déplacez vos appels de fonction dans le code traité par Closure Compiler.
  • Incluez des externs pour les fonctions que vous souhaitez exposer.

Les sections suivantes présentent chaque option plus en détail.

Solution: Déplacer vos appels de fonctions dans le code traité par le compilateur de fermeture

Vous pouvez rencontrer une suppression de code indésirable si vous ne compilez qu'une partie de votre code avec Closure Compiler. Par exemple, vous pourriez avoir un fichier de bibliothèque ne contenant que des définitions de fonction et un fichier HTML contenant la bibliothèque et le code qui appelle ces fonctions. Dans ce cas, si vous compilez le fichier de bibliothèque avec ADVANCED_OPTIMIZATIONS, Closure Compiler supprime toutes les fonctions de votre bibliothèque.

La solution la plus simple à ce problème consiste à compiler vos fonctions avec la partie de votre programme qui les appelle. Par exemple, Closure Compiler ne supprime pas displayNoteTitle() lorsqu'il compile le programme suivant:

function displayNoteTitle(note) {
  alert(note['myTitle']);
}
displayNoteTitle({'myTitle': 'Flowers'});

Dans ce cas, la fonction displayNoteTitle() n'est pas supprimée, car Closure Compiler voit qu'elle est appelée.

En d'autres termes, vous pouvez empêcher la suppression indésirable du code en incluant le point d'entrée de votre programme dans le code que vous transmettez à Closure Compiler. Le point d'entrée d'un programme est l'endroit où il commence à s'exécuter dans le code. Par exemple, dans le programme de notes de fleurs de la section précédente, les trois dernières lignes sont exécutées dès que le code JavaScript est chargé dans le navigateur. Il s'agit du point d'entrée de ce programme. Pour déterminer le code à conserver, Closure Compiler commence à cet emplacement d'entrée et suit le flux de contrôle du programme.

Solution: incluez des externs pour les fonctions que vous souhaitez exposer

Vous trouverez plus d'informations sur cette solution ci-dessous et sur la page Externes et exportations.

Noms de propriété incohérents

La compilation Closure Compiler ne modifie jamais les littéraux de chaîne de votre code, quel que soit le niveau de compilation utilisé. Cela signifie que la compilation avec ADVANCED_OPTIMIZATIONS traite les propriétés différemment selon que votre code y accède avec une chaîne. Si vous mélangez des références de chaîne à une propriété avec des références à syntaxe à points, Closure Compiler renomme certaines des références à cette propriété, mais pas à d'autres. Par conséquent, votre code ne s'exécutera probablement pas correctement.

Par exemple:

function displayNoteTitle(note) {
  alert(note['myTitle']);
}
var flowerNote = {};
flowerNote.myTitle = 'Flowers';

alert(flowerNote.myTitle);
displayNoteTitle(flowerNote);

Les deux dernières instructions de ce code source ont exactement la même fonction. Toutefois, lorsque vous compressez le code avec ADVANCED_OPTIMIZATIONS, vous obtenez ce qui suit:

var a={};a.a="Flowers";alert(a.a);alert(a.myTitle);

La dernière instruction du code compressé génère une erreur. La référence directe à la propriété myTitle a été renommée a, mais la référence à myTitle dans la fonction displayNoteTitle n'a pas été renommée. Par conséquent, la dernière instruction fait référence à une propriété myTitle qui n'existe plus.

Solution: soyez cohérent dans le nom de vos propriétés

Cette solution est assez simple. Pour tout type ou objet, utilisez exclusivement une syntaxe à points ou des chaînes entre guillemets. Ne mélangez pas les syntaxes, en particulier si vous faites référence à la même propriété.

Dans la mesure du possible, privilégiez également la syntaxe à points, car elle offre de meilleurs contrôles et optimisations. N'utilisez l'accès aux propriétés de chaîne entre guillemets que si vous ne souhaitez pas que Closure Compiler renomme le fichier, par exemple lorsque le nom provient d'une source externe, telle que le code JSON décodé.

Compiler deux parties de code séparément

Si vous divisez votre application en différents segments de code, vous pouvez compiler les fragments séparément. Toutefois, si deux fragments de code interagissent, cela peut poser problème. Même si vous réussissez, la sortie des deux exécutions de Closure Compiler ne sera pas compatible.

Par exemple, supposons qu'une application soit divisée en deux parties: une partie qui récupère les données et une partie qui affiche les données.

Voici le code permettant de récupérer les données:

function getData() {
  // In an actual project, this data would be retrieved from the server.
  return {title: 'Flower Care', text: 'Flowers need water.'};
}

Voici le code permettant d'afficher les données:

var displayElement = document.getElementById('display');
function displayData(parent, data) {
  var textElement = document.createTextNode(data.text);
  parent.appendChild(textElement);
}
displayData(displayElement, getData());

Si vous essayez de compiler ces deux fragments de code séparément, vous rencontrerez plusieurs problèmes. Tout d'abord, Closure Compiler supprime la fonction getData(), pour les raisons décrites dans la section Suppression du code que vous souhaitez conserver. Ensuite, Closure Compiler génère une erreur fatale lors du traitement du code qui affiche les données.

input:6: ERROR - variable getData is undefined
displayData(displayElement, getData());

Comme le compilateur n'a pas accès à la fonction getData() lorsqu'il compile le code qui affiche les données, il considère getData comme non défini.

Solution: compiler tous les codes d'une page

Pour garantir une compilation appropriée, compilez l'ensemble du code d'une page en une seule exécution. Closure Compiler peut accepter plusieurs fichiers JavaScript et chaînes JavaScript en entrée. Vous pouvez ainsi transmettre le code de la bibliothèque et d'autres codes en une seule requête de compilation.

Remarque:Cette approche ne fonctionnera pas si vous devez combiner du code compilé et non compilé. Consultez la section Références rompues entre le code compilé et non compilé pour obtenir des conseils sur la gestion de cette situation.

Références non fonctionnelles entre le code compilé et non compilé

Le changement de nom dans ADVANCED_OPTIMIZATIONS perturbe la communication entre le code traité par Closure Compiler et tout autre code. La compilation renomme les fonctions définies dans votre code source. Tout code externe qui appelle vos fonctions sera corrompu après la compilation, car il fait toujours référence à l'ancien nom de fonction. De même, les références du code compilé à des symboles définis en externe peuvent être modifiées par Closure Compiler.

Gardez à l'esprit que "code non compilé" inclut tout code transmis à la fonction eval() en tant que chaîne. Closure Compiler ne modifie jamais les littéraux de chaîne dans le code. Il ne modifie donc pas les chaînes transmises aux instructions eval().

Sachez qu'il s'agit de problèmes connexes, mais distincts: maintenir la communication compilée vers l'extérieur et maintenir la communication externe vers la compilation. Ces problèmes distincts ont une solution commune, mais il existe des nuances de chaque côté. Pour tirer le meilleur parti de Closure Compiler, il est important de comprendre votre cas.

Avant de continuer, vous pouvez vous familiariser avec les externes et les exportations.

Solution permettant d'appeler du code externe à partir de code compilé: compiler avec des externs

Si vous utilisez le code fourni dans votre page par un autre script, vous devez vous assurer que Closure Compiler ne renomme pas vos références aux symboles définis dans cette bibliothèque externe. Pour ce faire, incluez dans votre compilation un fichier contenant les externs de la bibliothèque externe. Cela indique à Closure Compiler les noms que vous ne contrôlez pas et qui ne peuvent donc pas être modifiés. Votre code doit utiliser les mêmes noms que le fichier externe.

Les API telles que l'API OpenSocial et l'API Google Maps en sont des exemples courants. Par exemple, si votre code appelle la fonction OpenSocial opensocial.newDataRequest(), sans les externs appropriés, Closure Compiler transformera cet appel en a.b().

Solution permettant d'appeler du code compilé à partir d'un code externe: implémenter des externs

Si vous réutilisez du code JavaScript en tant que bibliothèque, vous pouvez utiliser Closure Compiler pour réduire uniquement la bibliothèque tout en autorisant le code non compilé à appeler des fonctions de la bibliothèque.

La solution consiste à implémenter un ensemble d'externs définissant l'API publique de votre bibliothèque. Votre code fournira des définitions pour les symboles déclarés dans ces externs. Cela signifie que toutes les classes ou fonctions mentionnées par vos externs Cela peut également signifier que vos classes mettent en œuvre des interfaces déclarées à l'extérieur.

Ces externs peuvent également être utiles à d'autres personnes, et pas seulement à vous-même. Les utilisateurs de votre bibliothèque doivent l'inclure s'ils compilent leur code, car votre bibliothèque représente un script externe de leur point de vue. Considérez les externs comme le contrat entre vous et les consommateurs : vous avez tous deux besoin d'une copie.

À cette fin, assurez-vous d'inclure également les externs dans la compilation lorsque vous compilez votre code. Cela peut sembler étrange, car nous considérons souvent les externs comme "provenant d'un autre endroit". Cependant, il est nécessaire d'indiquer à Closure Compiler les symboles que vous exposez, afin qu'ils ne soient pas renommés.

Il est important de noter que vous pouvez obtenir des diagnostics en "définition en double" concernant le code définissant les symboles externes. Closure Compiler suppose qu'un symbole présent dans les externs est fourni par une bibliothèque externe et ne peut pas comprendre pour l'instant que vous fournissez intentionnellement une définition. Ces diagnostics peuvent être supprimés en toute sécurité. Vous pouvez considérer la suppression comme une confirmation que vous traitez réellement votre API.

En outre, Closure Compiler peut vérifier que vos définitions correspondent aux types des déclarations externes. Cela permet de confirmer que vos définitions sont correctes.