Compilation avancée

Présentation

L'utilisation du compilateur Closure 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 permet d'obtenir une compression supplémentaire en transformant le code et en renommant les symboles de manière plus agressive. Toutefois, cette approche plus agressive signifie que vous devez être plus prudent 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 explique ce que fait le 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. Il présente également le concept d'extern, un symbole défini dans un code externe à celui traité par le compilateur.

Avant de lire ce tutoriel, vous devez connaître le processus de compilation de JavaScript avec l'un des outils Closure Compiler, comme l'application de compilation basée sur Java.

Remarque sur la terminologie : l'indicateur de ligne de commande --compilation_level accepte les abréviations les plus courantes ADVANCED et SIMPLE, ainsi que les abréviations plus précises ADVANCED_OPTIMIZATIONS et SIMPLE_OPTIMIZATIONS. Ce document utilise la forme longue, mais les noms peuvent être utilisés de manière interchangeable sur la ligne de commande.

  1. Compression encore plus efficace
  2. Activer ADVANCED_OPTIMIZATIONS
  3. Points à surveiller lorsque vous utilisez ADVANCED_OPTIMIZATIONS
    1. Suppression du code que vous souhaitez conserver
    2. Noms d'établissement incohérents
    3. Compiler deux portions de code séparément
    4. Références non fonctionnelles entre le code compilé et non compilé

Compression encore plus efficace

Avec le niveau de compilation par défaut SIMPLE_OPTIMIZATIONS, Closure Compiler réduit la taille du code JavaScript en renommant les variables locales. Il existe d'autres symboles que les variables locales qui peuvent être raccourcis. Il existe également d'autres moyens de réduire le code que de renommer les symboles. La compilation avec ADVANCED_OPTIMIZATIONS exploite toute la gamme des possibilités de minification de code.

Comparez les sorties pour 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 comme suit :

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 complètement le code comme suit :

alert("Flowers");

Ces deux scripts génèrent une alerte indiquant "Flowers", mais le deuxième script est beaucoup plus petit.

Le niveau ADVANCED_OPTIMIZATIONS va au-delà du simple raccourcissement des noms de variables de plusieurs façons :

  • Renommage plus agressif :

    La compilation avec SIMPLE_OPTIMIZATIONS renomme uniquement les paramètres note des fonctions displayNoteTitle() et unusedFunction(), car ce sont les seules variables du script qui sont locales à une fonction. ADVANCED_OPTIMIZATIONS renomme également la variable globale flowerNote.

  • Suppression du code mort :

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

  • Inlining de fonction :

    La compilation avec ADVANCED_OPTIMIZATIONS remplace l'appel à displayNoteTitle() par le seul alert() qui compose le corps de la fonction. Ce remplacement d'un appel de fonction par le corps de la fonction est appelé "intégration". Si la fonction était plus longue ou plus complexe, l'intégration pourrait modifier le comportement du code, mais le compilateur Closure détermine que, dans ce cas, l'intégration est sûre et permet de gagner de l'espace. La compilation avec ADVANCED_OPTIMIZATIONS inclut également des constantes et certaines variables lorsqu'elle détermine qu'elle peut le faire de manière sécurisée.

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

Activer ADVANCED_OPTIMIZATIONS

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

Points à surveiller lorsque vous utilisez ADVANCED_OPTIMIZATIONS

Vous trouverez ci-dessous quelques effets indésirables courants de ADVANCED_OPTIMIZATIONS et les mesures à prendre pour les éviter.

Suppression du code que vous souhaitez conserver

Si vous compilez uniquement la fonction ci-dessous avec ADVANCED_OPTIMIZATIONS, Closure Compiler génère 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 celui que vous souhaitez. 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 vous constatez que Closure Compiler supprime des fonctions que vous souhaitez conserver, vous pouvez l'empêcher 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 décrivent chaque option plus en détail.

Solution : Déplacer vos appels de fonction dans le code traité par Closure Compiler

Vous risquez de rencontrer une suppression de code indésirable si vous ne compilez qu'une partie de votre code avec Closure Compiler. Par exemple, vous pouvez avoir un fichier de bibliothèque qui ne contient que des définitions de fonctions, et un fichier HTML qui inclut la bibliothèque et qui contient 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 supprimera pas displayNoteTitle() lorsqu'il compilera 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 de code indésirable 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 du code où le programme commence à s'exécuter. 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 à ce point d'entrée et trace le flux de contrôle du programme à partir de là.

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

Pour en savoir plus sur cette solution, consultez les sections ci-dessous et la page sur les externs et les exports.

Noms de propriétés incohérents

La compilation Closure Compiler ne modifie jamais les littéraux de chaîne dans 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 ou non. Si vous mélangez des références de chaîne à une propriété avec des références de syntaxe par points, Closure Compiler renomme certaines références à cette propriété, mais pas d'autres. Par conséquent, votre code ne fonctionnera probablement pas correctement.

Prenons l'exemple du code suivant :

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 font exactement la même chose. Toutefois, lorsque vous compressez le code avec ADVANCED_OPTIMIZATIONS, vous obtenez le résultat suivant :

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 entre guillemets à 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 les noms de vos propriétés

Cette solution est assez simple. Pour tout type ou objet donné, utilisez exclusivement la syntaxe à point ou les chaînes entre guillemets. Ne mélangez pas les syntaxes, en particulier pour la même propriété.

De plus, lorsque cela est possible, préférez utiliser la syntaxe à points, car elle permet de meilleures vérifications et optimisations. N'utilisez l'accès aux propriétés de chaîne entre guillemets que lorsque vous ne souhaitez pas que Closure Compiler renomme les propriétés, par exemple lorsque le nom provient d'une source externe, comme un JSON décodé.

Compiler deux portions de code séparément

Si vous divisez votre application en différents blocs de code, vous pouvez compiler les blocs séparément. Toutefois, si deux blocs de code interagissent, cela peut poser problème. Même si vous y parvenez, 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 les affiche.

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 blocs de code séparément, vous rencontrerez plusieurs problèmes. Tout d'abord, le Closure Compiler supprime la fonction getData() pour les raisons décrites dans Suppression du code que vous souhaitez conserver. Deuxièmement, le compilateur Closure 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());

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

Solution : Compiler tout le code d'une page

Pour assurer une compilation correcte, compilez tout le code d'une page en une seule fois. Le compilateur Closure peut accepter plusieurs fichiers JavaScript et chaînes JavaScript en entrée. Vous pouvez donc transmettre le code de la bibliothèque et d'autres codes ensemble dans une seule demande de compilation.

Remarque : Cette approche ne fonctionnera pas si vous devez mélanger du code compilé et non compilé. Consultez 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 renommage des symboles dans ADVANCED_OPTIMIZATIONS interrompra 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 interrompu après la compilation, car il fait toujours référence à l'ancien nom de fonction. De même, les références dans le code compilé à des symboles définis en externe peuvent être modifiées par Closure Compiler.

N'oubliez pas que le "code non compilé" inclut tout code transmis à la fonction eval() sous forme de 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().

Notez qu'il s'agit de problèmes liés, mais distincts : maintenir la communication compilée vers l'externe et maintenir la communication externe vers le compilé. Ces problèmes distincts ont une solution commune, mais il existe des nuances pour chaque côté. Pour exploiter tout le potentiel de Closure Compiler, il est important de comprendre dans quel cas vous vous trouvez.

Avant de continuer, vous pouvez vous familiariser avec les externs et les exports.

Solution pour appeler du code externe à partir de code compilé : compiler avec des externs

Si vous utilisez du code fourni sur 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 indiquera à 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 ceux du fichier externe.

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 pour appeler du code compilé à partir de code externe : implémenter des externs

Si vous avez du code JavaScript que vous réutilisez en tant que bibliothèque, vous pouvez utiliser Closure Compiler pour réduire uniquement la bibliothèque tout en permettant au code non compilé d'appeler des fonctions dans la bibliothèque.

Dans ce cas, 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 fichiers externes. Cela signifie toutes les classes ou fonctions mentionnées par vos externes. Cela peut également signifier que vos classes implémentent des interfaces déclarées dans les fichiers externes.

Ces externes sont utiles à d'autres personnes, pas seulement à vous. Les consommateurs de votre bibliothèque devront les inclure s'ils compilent leur code, car votre bibliothèque représente un script externe de leur point de vue. Considérez les fichiers externes comme le contrat entre vous et vos consommateurs. Vous avez tous les deux besoin d'une copie.

Pour ce faire, assurez-vous d'inclure également les fichiers externes dans la compilation de votre code. Cela peut sembler inhabituel, car nous pensons souvent que les externs "proviennent d'ailleurs", mais il est nécessaire d'indiquer à Closure Compiler les symboles que vous exposez, afin qu'ils ne soient pas renommés.

Un point important à noter ici est que vous pouvez obtenir des diagnostics de "définition en double" concernant le code définissant les symboles externes. Closure Compiler suppose que tout symbole dans les fichiers externes est fourni par une bibliothèque externe et ne peut pas comprendre que vous fournissez intentionnellement une définition. Vous pouvez supprimer ces diagnostics sans risque. La suppression peut être considérée comme une confirmation que vous respectez bien votre API.

De plus, Closure Compiler peut vérifier que vos définitions correspondent aux types des déclarations externes. Vous aurez ainsi une confirmation supplémentaire que vos définitions sont correctes.