Le compilateur Closure s'attend à ce que son entrée JavaScript respecte quelques restrictions. Plus le niveau d'optimisation que vous demandez au compilateur est élevé, plus il impose de restrictions sur le code JavaScript d'entrée.
Ce document décrit les principales restrictions pour chaque niveau d'optimisation. Consultez également cette page wiki pour connaître les autres hypothèses faites par le compilateur.
Restrictions pour tous les niveaux d'optimisation
Le compilateur impose les deux restrictions suivantes à tous les codes JavaScript qu'il traite, pour tous les niveaux d'optimisation :
Le compilateur ne reconnaît qu'ECMAScript.
ECMAScript 5 est la version de JavaScript compatible presque partout. Toutefois, le compilateur est également compatible avec de nombreuses fonctionnalités d'ECMAScript 6. Le compilateur n'accepte que les fonctionnalités linguistiques officielles.
Les fonctionnalités spécifiques aux navigateurs qui sont conformes à la spécification du langage ECMAScript appropriée fonctionneront correctement avec le compilateur. Par exemple, les objets ActiveX sont créés avec une syntaxe JavaScript légale. Le code qui crée des objets ActiveX fonctionne donc avec le compilateur.
Les responsables du compilateur s'efforcent activement de prendre en charge les nouvelles versions du langage et leurs fonctionnalités. Les projets peuvent spécifier la version du langage ECMAScript qu'ils souhaitent utiliser à l'aide de l'option
--language_in
.Le compilateur ne conserve pas les commentaires.
Tous les niveaux d'optimisation du compilateur suppriment les commentaires. Par conséquent, le code qui s'appuie sur des commentaires mis en forme de manière spéciale ne fonctionne pas avec le compilateur.
Par exemple, étant donné que le compilateur ne conserve pas les commentaires, vous ne pouvez pas utiliser directement les "commentaires conditionnels" de JScript. Toutefois, vous pouvez contourner cette restriction en encapsulant les commentaires conditionnels dans des expressions
eval()
. Le compilateur peut traiter le code suivant sans générer d'erreur :x = eval("/*@cc_on 2+@*/ 0");
Remarque : Vous pouvez inclure des licences Open Source et d'autres textes importants en haut de la sortie du compilateur à l'aide de l'annotation @preserve.
Restrictions pour SIMPLE_OPTIMIZATIONS
Le niveau d'optimisation "Simple" renomme les paramètres de fonction, les variables locales et les fonctions définies localement pour réduire la taille du code. Toutefois, certaines constructions JavaScript peuvent perturber ce processus de renommage.
Évitez les constructions et pratiques suivantes lorsque vous utilisez SIMPLE_OPTIMIZATIONS
:
with
:Lorsque vous utilisez
with
, le compilateur ne peut pas faire la distinction entre une variable locale et une propriété d'objet du même nom. Il renomme donc toutes les instances du nom.De plus, l'instruction
with
rend votre code plus difficile à lire pour les humains. L'instructionwith
modifie les règles normales de résolution des noms et peut rendre difficile l'identification de ce à quoi un nom fait référence, même pour le programmeur qui a écrit le code.eval()
:Le compilateur n'analyse pas l'argument de chaîne de
eval()
. Il ne renommera donc aucun symbole dans cet argument.Représentations sous forme de chaîne des noms de fonctions ou de paramètres :
Le compilateur renomme les fonctions et les paramètres de fonction, mais ne modifie aucune chaîne de votre code qui fait référence à des fonctions ou à des paramètres par leur nom. Vous devez donc éviter de représenter les noms de fonctions ou de paramètres sous forme de chaînes dans votre code. Par exemple, la fonction de bibliothèque Prototype
argumentNames()
utiliseFunction.toString()
pour récupérer les noms des paramètres d'une fonction. Toutefois, même siargumentNames()
peut vous inciter à utiliser les noms des arguments dans votre code, la compilation en mode Simple interrompt ce type de référence.
Restrictions pour ADVANCED_OPTIMIZATIONS
Le niveau de compilation ADVANCED_OPTIMIZATIONS
effectue les mêmes transformations que SIMPLE_OPTIMIZATIONS
, et ajoute également le renommage global des propriétés, des variables et des fonctions, l'élimination du code mort et l'aplatissement des propriétés. Ces nouveaux passes imposent des restrictions supplémentaires au code JavaScript d'entrée. En général, l'utilisation des fonctionnalités dynamiques de JavaScript empêche une analyse statique correcte de votre code.
Implications du changement de nom des variables, fonctions et propriétés globales :
Le changement de nom global de ADVANCED_OPTIMIZATIONS
rend les pratiques suivantes dangereuses :
Références externes non déclarées :
Pour renommer correctement les variables, fonctions et propriétés globales, le compilateur doit connaître toutes les références à ces éléments globaux. Vous devez informer le compilateur des symboles définis en dehors du code compilé. Compilation avancée et externs explique comment déclarer des symboles externes.
Utiliser des noms internes non exportés dans du code externe :
Le code compilé doit exporter tous les symboles auxquels le code non compilé fait référence. Compilation avancée et fichiers externes explique comment exporter des symboles.
Utiliser des noms de chaînes pour faire référence aux propriétés des objets :
Le compilateur renomme les propriétés en mode avancé, mais jamais les chaînes.
var x = { renamed_property: 1 }; var y = x.renamed_property; // This is OK. // 'renamed_property' below doesn't exist on x after renaming, so the // following evaluates to false. if ( 'renamed_property' in x ) {}; // BAD // The following also fails: x['renamed_property']; // BAD
Si vous devez faire référence à une propriété avec une chaîne entre guillemets, utilisez toujours une chaîne entre guillemets :
var x = { 'unrenamed_property': 1 }; x['unrenamed_property']; // This is OK. if ( 'unrenamed_property' in x ) {}; // This is OK
Faire référence aux variables en tant que propriétés de l'objet global :
Le compilateur renomme les propriétés et les variables de manière indépendante. Par exemple, le compilateur traite différemment les deux références suivantes à
foo
, même si elles sont équivalentes :var foo = {}; window.foo; // BAD
Ce code peut être compilé comme suit :
var a = {}; window.b;
Si vous devez faire référence à une variable en tant que propriété de l'objet global, faites-le toujours de cette manière :
window.foo = {} window.foo;
Implications of dead code elimination
The ADVANCED_OPTIMIZATIONS
compilation level removes code
that is never executed. This elimination of dead code makes the
following practices dangerous:
Calling functions from outside of compiled code:
When you compile functions without compiling the code that calls those functions, the Compiler assumes that the functions are never called and removes them. To avoid unwanted code removal, either:
- compile all the JavaScript for your application together, or
- export compiled functions.
Advanced Compilation and Externs describes both of these approaches in greater detail.
Retrieving functions through iteration over constructor or prototype properties:
To determine whether a function is dead code, the Compiler has to find all the calls to that function. By iterating over the properties of a constructor or its prototype you can find and call methods, but the Compiler can't identify the specific functions called in this manner.
For example, the following code causes unintended code removal:
function Coordinate() { } Coordinate.prototype.initX = function() { this.x = 0; } Coordinate.prototype.initY = function() { this.y = 0; } var coord = new Coordinate(); for (method in Coordinate.prototype) { Coordinate.prototype[method].call(coord); // BAD }
Le compilateur ne comprend pas que
initX()
etinitY()
sont appelés dans la bouclefor
. Il supprime donc ces deux méthodes.Notez que si vous transmettez une fonction en tant que paramètre, le compilateur peut trouver les appels à ce paramètre. Par exemple, le compilateur ne supprime pas la fonction
getHello()
lorsqu'il compile le code suivant en mode avancé.function alertF(f) { alert(f()); } function getHello() { return 'hello'; } // The Compiler figures out that this call to alertF also calls getHello(). alertF(getHello); // This is OK.
Implications de l'aplatissement des propriétés d'objet
En mode avancé, le compilateur réduit les propriétés de l'objet pour préparer le raccourcissement des noms. Par exemple, le compilateur transforme ce qui suit :
var foo = {}; foo.bar = function (a) { alert(a) }; foo.bar("hello");
comme ceci :
var foo$bar = function (a) { alert(a) }; foo$bar("hello");
L'aplatissement de cette propriété permet à la passe de renommage ultérieure de renommer plus efficacement. Le compilateur peut remplacer foo$bar
par un seul caractère, par exemple.
Toutefois, l'aplatissement des propriétés rend également dangereuses les pratiques suivantes :
Utilisation de
this
en dehors des constructeurs et des méthodes de prototype :L'aplatissement des propriétés peut modifier la signification du mot clé
this
dans une fonction. Exemple :var foo = {}; foo.bar = function (a) { this.bad = a; }; // BAD foo.bar("hello");
devient :
var foo$bar = function (a) { this.bad = a; }; foo$bar("hello");
Avant la transformation, le
this
dansfoo.bar
fait référence àfoo
. Après la transformation,this
fait référence àthis
global. Dans ce cas, le compilateur génère l'avertissement suivant :"WARNING - dangerous use of this in static method foo.bar"
Pour éviter que l'aplatissement des propriétés ne rompe vos références à
this
, n'utilisezthis
que dans les constructeurs et les méthodes de prototype. La signification dethis
est sans ambiguïté lorsque vous appelez un constructeur avec le mot clénew
ou dans une fonction qui est une propriété d'unprototype
.Utiliser des méthodes statiques sans savoir sur quelle classe elles sont appelées :
Par exemple :
le compilateur réduira les deux méthodesclass A { static create() { return new A(); }}; class B { static create() { return new B(); }}; let cls = someCondition ? A : B; cls.create();
create
(après la transpilation d'ES6 vers ES5). L'appelcls.create()
échouera donc. Vous pouvez éviter cela avec l'annotation@nocollapse
:class A { /** @nocollapse */ static create() { return new A(); } } class B { /** @nocollapse */ static create() { return new A(); } }
Utiliser "super" dans une méthode statique sans connaître la superclasse :
Le code suivant est sécurisé, car le compilateur sait que
super.sayHi()
fait référence àParent.sayHi()
:class Parent { static sayHi() { alert('Parent says hi'); } } class Child extends Parent { static sayHi() { super.sayHi(); } } Child.sayHi();
Toutefois, l'aplatissement des propriétés interrompra le code suivant, même si
myMixin(Parent).sayHi
est égal àParent.sayHi
non compilé :class Parent { static sayHi() { alert('Parent says hi'); } } class Child extends myMixin(Parent) { static sayHi() { super.sayHi(); } } Child.sayHi();
Évitez cette rupture avec l'annotation
/** @nocollapse */
.Utiliser Object.defineProperties ou les accesseurs ES6 :
Le compilateur ne comprend pas bien ces constructions. Les getters et setters ES6 sont transformés en Object.defineProperties(...) lors de la transpilation. Actuellement, le compilateur n'est pas en mesure d'analyser statiquement cette construction et suppose que l'accès aux propriétés et la définition de celles-ci sont sans effet secondaire. Cela peut avoir des répercussions dangereuses. Exemple :
class C { static get someProperty() { console.log("hello getters!"); } } var unused = C.someProperty;
Est compilé en :
C = function() {}; Object.defineProperties(C, {a: // Note someProperty is also renamed to 'a'. {configurable:!0, enumerable:!0, get:function() { console.log("hello world"); return 1; }}});
La propriété C.someProperty n'ayant aucun effet secondaire, elle a été supprimée.