Comprendre les restrictions imposées par le compilateur de la fermeture

Closure Compiler s'attend à ce que ses entrées JavaScript soient conformes à quelques restrictions. Plus le niveau d'optimisation que vous demandez au compilateur est élevé, plus le compilateur applique de restrictions au code JavaScript d'entrée.

Ce document décrit les principales restrictions de chaque niveau d'optimisation. Consultez également cette page wiki pour connaître les autres hypothèses formulées par le compilateur.

Restrictions pour tous les niveaux d'optimisation

Le compilateur applique les deux restrictions suivantes à tous les éléments JavaScript qu'il traite, pour tous les niveaux d'optimisation:

  • Le compilateur ne reconnaît qu'ECMAScript.

    ECMAScript 5 est la version de JavaScript prise en charge presque partout. Toutefois, le compilateur est également compatible avec de nombreuses fonctionnalités d'ECMAScript 6. Le compilateur n'accepte que les fonctionnalités de langue officielle.

    Les caractéristiques propres au navigateur qui sont conformes à la spécification de langage ECMAScript appropriée fonctionneront correctement avec le compilateur. Par exemple, les objets ActiveX étant créés avec une syntaxe JavaScript légale, le code qui crée ces objets fonctionne avec le compilateur.

    Les responsables de la maintenance travaillent activement à la prise en charge des nouvelles versions de langages et de leurs fonctionnalités. Les projets peuvent spécifier la version du langage ECMAScript qu'ils souhaitent en utilisant 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 repose sur des commentaires au format particulier ne fonctionne pas avec le compilateur.

    Par exemple, comme le compilateur ne conserve pas les commentaires, vous ne pouvez pas utiliser directement les "commentaires conditionnels" de JScript. Vous pouvez toutefois contourner cette restriction en encapsulant les commentaires conditionnels dans les 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 des résultats du compilateur à l'aide de l'annotation @preserve.

Restrictions concernant les SIMPLE_OPTIMIZATIONS

Le niveau d'optimisation simple renomme les paramètres de fonction, les variables locales et les fonctions définies localement afin de réduire la taille du code. Cependant, certaines constructions JavaScript peuvent interrompre ce processus de changement de nom.

É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 portant le même nom. Par conséquent, toutes les instances du nom sont renommées.

    De plus, l'instruction with rend votre code plus difficile à lire. L'instruction with modifie les règles normales de résolution de noms et peut rendre difficile l'identification du nom d'un nom par le programmeur qui a écrit le code.

  • eval() :

    Le compilateur n'analyse pas l'argument de chaîne de eval(). Par conséquent, il ne renomme aucun symbole dans cet argument.

  • Représentations de chaîne de noms de fonctions ou de paramètres :

    Le compilateur renomme les fonctions et leurs paramètres, mais ne modifie aucune chaîne de votre nom faisant référence à des fonctions ou à des paramètres par leur nom. Par conséquent, évitez 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 de prototypes argumentNames() utilise Function.toString() pour récupérer les noms des paramètres d'une fonction. Cependant, même si argumentNames() peut vous inciter à utiliser les noms des arguments dans votre code, la compilation en mode simple rompt ce type de référence.

Restrictions relatives à ADVANCED_OPTIMIZATIONS

Le niveau de compilation ADVANCED_OPTIMIZATIONS effectue les mêmes transformations que SIMPLE_OPTIMIZATIONS, et ajoute également un changement de nom global des propriétés, des variables et des fonctions, une élimination du code mort et un aplatissement de la propriété. Ces nouvelles cartes imposent des restrictions supplémentaires sur le code JavaScript d'entrée. En général, l'utilisation des fonctionnalités dynamiques de JavaScript empêche l'analyse statique correcte de votre code.

Implications du changement de nom de la variable, de la fonction et de la propriété globales:

Le changement global de ADVANCED_OPTIMIZATIONS rend les pratiques suivantes dangereuses:

  • Références externes non déclarées :

    Pour renommer correctement les variables, les fonctions et les propriétés globales, le compilateur doit être informé de toutes les références à ces variables globales. Vous devez indiquer au compilateur les symboles définis en dehors du code compilé. La section Compilation avancée et Externs décrit comment déclarer des symboles externes.

  • Utiliser des noms internes non exportés dans le code externe:

    Le code compilé doit exporter tous les symboles auxquels le code non compilé fait référence. La section Compilation et Externations avancées explique comment exporter des symboles.

  • Utiliser des noms de chaîne pour faire référence à des propriétés d'objet:

    Le compilateur renomme les propriétés en mode avancé, mais il ne renomme 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 indépendamment. Par exemple, le compilateur compile les deux références suivantes à foo différemment, 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-la toujours comme suit:

    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() et initY() sont appelés dans la boucle for. 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 compile les propriétés des objets afin de préparer le raccourcissement des noms. Par exemple, le compilateur compile les éléments suivants:

   var foo = {};
   foo.bar = function (a) { alert(a) };
   foo.bar("hello");

en:

   var foo$bar = function (a) { alert(a) };
   foo$bar("hello");

Cette opération d'aplatissement de propriété permet à la carte de renommer ultérieurement plus efficacement. Le compilateur peut remplacer foo$bar par un seul caractère, par exemple.

Toutefois, les pratiques suivantes sont également dangereuses:

  • Utilisation de this en dehors des constructeurs et des méthodes de prototypes :

    L'aplatissement d'une propriété 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, this dans foo.bar fait référence à foo. Après la transformation, this fait référence au this global. Dans ce cas, le compilateur génère cet avertissement:

    "WARNING - dangerous use of this in static method foo.bar"

    Pour empêcher l'aplatissement d'une propriété de casser vos références à this, n'utilisez this que dans les constructeurs et les méthodes de prototypes. La signification de this est claire lorsque vous appelez un constructeur avec le mot clé new ou dans une fonction qui est une propriété de prototype.

  • Utiliser des méthodes statiques sans savoir sur quelle classe elles sont appelées

    Par exemple, si vous avez :

    class A { static create() { return new A(); }};
    class B { static create() { return new B(); }};
    let cls = someCondition ? A : B;
    cls.create();
    
    Le compilateur réduire les deux méthodes create (après la transpilation de ES6 à ES5) afin que l'appel cls.create() échoue. 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 des super dans une méthode statique sans connaître la super-classe:

    Le code suivant est sûr, 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 de la propriété corrompt 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 faille avec l'annotation /** @nocollapse */.

  • Avec Object.defineProperties ou ES6 getter/setters :

    Le compilateur ne comprend pas bien ces constructions. Les "getter" et "setters" d'ES6 sont transformés en objet.defineProperties(...) par transpilation. Actuellement, le compilateur ne peut pas analyser statiquement cette construction et suppose que l'accès et la définition des propriétés sont sans effet secondaire. Cela peut avoir des conséquences dangereuses. Exemple :

    class C {
      static get someProperty() {
        console.log("hello getters!");
      }
    }
    var unused = C.someProperty;
    

    Compilation sur:

    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;
    }}});
    

    L'outil C.someProperty n'a aucun effet secondaire. Il a donc été supprimé.