Información sobre las restricciones impuestas por Closure Compiler

El Closure Compiler espera que su entrada de JavaScript cumpla con algunas restricciones. Cuanto más alto sea el nivel de optimización que le pidas al compilador, más restricciones aplicará el compilador al código de entrada de JavaScript.

En este documento, se describen las principales restricciones para cada nivel de optimización. Consulta también esta página wiki para conocer las suposiciones adicionales que realizó el compilador.

Restricciones para todos los niveles de optimización

El compilador establece las siguientes dos restricciones en todo JavaScript que procese, para todos los niveles de optimización:

  • El compilador solo reconoce ECMAScript.

    ECMAScript 5 es la versión de JavaScript compatible en casi todas partes. Sin embargo, el compilador también es compatible con muchas de las funciones de ECMAScript 6. El compilador solo admite funciones de lenguaje oficiales.

    Las funciones específicas del navegador que se ajusten a la especificación de lenguaje ECMAScript correspondiente funcionarán sin problemas con el compilador. Por ejemplo, los objetos de ActiveX se crean con la sintaxis legal de JavaScript, por lo que el código que crea objetos ActiveX funciona con el compilador.

    Los encargados de mantener el compilador trabajan activamente para admitir nuevas versiones de lenguaje y sus funciones. Los proyectos pueden especificar qué versión de lenguaje ECMAScript quieren usar mediante la marca --language_in.

  • El compilador no conserva comentarios.

    Todos los niveles de optimización del compilador quitan comentarios, por lo que el código que se basa en comentarios con formato especial no funciona con el compilador.

    Por ejemplo, debido a que el compilador no conserva comentarios, no puedes usar los "comentarios condicionales" de JScript directamente. Sin embargo, puedes evitar esta restricción si unes los comentarios condicionales en las expresiones eval(). El compilador puede procesar el siguiente código sin generar un error:

     x = eval("/*@cc_on 2+@*/ 0");
    

    Nota: Puedes incluir licencias de código abierto y otro texto importante en la parte superior del resultado del compilador mediante la anotación @preserve.

Restricciones para SIMPLE_OPTIMIZATIONS

El nivel de optimización simple cambia el nombre de los parámetros de función, las variables locales y las funciones definidas de forma local para reducir el tamaño del código. Sin embargo, algunas construcciones de JavaScript pueden interrumpir este proceso de cambio de nombre.

Evita las siguientes construcciones y prácticas cuando uses SIMPLE_OPTIMIZATIONS:

  • with:

    Cuando usas with, el compilador no puede distinguir entre una variable local y una propiedad de objeto con el mismo nombre, por lo que cambia el nombre de todas las instancias del nombre.

    Además, la declaración with hace que tu código sea más difícil de leer para las personas. La declaración with cambia las reglas normales de resolución de nombres y puede dificultar incluso la tarea de que el programador que escribió el código identifique a qué hace referencia un nombre.

  • eval():

    El compilador no analiza el argumento de string de eval(), por lo que no renombrará ningún símbolo dentro de este argumento.

  • Representaciones de strings de nombres de funciones o parámetros:

    El compilador renombra funciones y parámetros de funciones, pero no cambia ninguna string en el código que haga referencia a funciones o parámetros por nombre. Por lo tanto, debes evitar representar nombres de funciones o parámetros como strings en el código. Por ejemplo, la función argumentNames() de la biblioteca Prototype usa Function.toString() para recuperar los nombres de los parámetros de una función. Sin embargo, aunque argumentNames() puede tentar a usar los nombres de los argumentos de tu código, la compilación en modo simple interrumpe este tipo de referencia.

Restricciones para ADVANCED_OPTIMIZATIONS

El nivel de compilación ADVANCED_OPTIMIZATIONS realiza las mismas transformaciones que SIMPLE_OPTIMIZATIONS y también agrega un cambio de nombre global de las propiedades, variables y funciones, eliminación de código muerto y compactación de propiedades. Estos pases nuevos aplican restricciones adicionales al JavaScript de entrada. En general, el uso de las funciones dinámicas de JavaScript evitará el análisis estático correcto de tu código.

Implicaciones de las variables globales, la función y el cambio de nombre de la propiedad:

El cambio de nombre global de ADVANCED_OPTIMIZATIONS hace que las siguientes prácticas sean peligrosas:

  • Referencias externas no declaradas:

    Para renombrar correctamente las variables, funciones y propiedades globales, el compilador debe conocer todas las referencias a esos globales. Debes informar al compilador sobre los símbolos que se definen fuera del código que se está compilando. En Compilación y elementos avanzados avanzados, se describe cómo declarar símbolos externos.

  • Usa nombres internos no exportados en código externo:

    El código compilado debe exportar cualquier símbolo al que haga referencia el código sin compilar. En Compilación y elementos avanzados avanzados, se describe cómo exportar símbolos.

  • Usa nombres de string para referirse a propiedades de objetos:

    El compilador cambia el nombre de las propiedades en el modo avanzado, pero nunca cambia el nombre de las strings.

      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 necesitas hacer referencia a una propiedad con una string entre comillas, siempre usa una string entre comillas:

      var x = { 'unrenamed_property': 1 };
      x['unrenamed_property'];  // This is OK.
      if ( 'unrenamed_property' in x ) {};   // This is OK
    
  • Haz referencia a las variables como propiedades del objeto global:

    El compilador cambia el nombre de las propiedades y variables de forma independiente. Por ejemplo, el compilador trata las siguientes dos referencias a foo de manera diferente, aunque son equivalentes:

      var foo = {};
      window.foo; // BAD
    

    Este código puede compilarse en:

      var a = {};
      window.b;
    

    Si necesitas referirte a una variable como una propiedad del objeto global, siempre haz referencia a ella de esa manera:

    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
    }
        

    El compilador no comprende que se llame a initX() y initY() en el bucle for, por lo que quita ambos métodos.

    Ten en cuenta que, si pasas una función como parámetro, el compilador puede encontrar llamadas a ese parámetro. Por ejemplo, el compilador no quita la función getHello() cuando compila el siguiente código en modo avanzado.

    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.
        

Implicaciones de la compactación de propiedades de objetos

En el modo Avanzado, el compilador contrae las propiedades del objeto a fin de prepararse para la reducción de nombres. Por ejemplo, el compilador transforma esto:

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

en esto:

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

Esta separación de propiedades permite que el cambio de nombre posterior pase a llamarse de manera más eficiente. El compilador puede reemplazar foo$bar por un solo carácter, por ejemplo.

Sin embargo, compactar las propiedades también hace que las siguientes prácticas sean peligrosas:

  • Usa this fuera de los constructores y los métodos de prototipo:

    La compactación de propiedades puede cambiar el significado de la palabra clave this dentro de una función. Por ejemplo:

       var foo = {};
       foo.bar = function (a) { this.bad = a; }; // BAD
       foo.bar("hello");
    

    se convierte en:

       var foo$bar = function (a) { this.bad = a; };
       foo$bar("hello");
    

    Antes de la transformación, this dentro de foo.bar hace referencia a foo. Después de la transformación, this se refiere al this global. En casos como este, el compilador genera esta advertencia:

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

    Para evitar que la compactación de propiedades rompa tus referencias a this, solo usa this dentro de los constructores y métodos de prototipo. El significado de this no es ambiguo cuando llamas a un constructor con la palabra clave new o dentro de una función que es propiedad de un prototype.

  • Cómo usar métodos estáticos sin saber en qué clase se llaman:

    Por ejemplo, si tienes lo siguiente:

    class A { static create() { return new A(); }};
    class B { static create() { return new B(); }};
    let cls = someCondition ? A : B;
    cls.create();
    
    El compilador contraerá ambos métodos create (después de la transpilación de ES6 a ES5), por lo que la llamada cls.create() fallará. Puedes evitar esto con la anotación @nocollapse:
    class A {
      /** @nocollapse */
      static create() {
        return new A();
      }
    }
    class B {
      /** @nocollapse */
      static create() {
        return new A();
      }
    }
    

  • Usar super en un método estático sin conocer la superclase:

    El siguiente código es seguro, ya que el compilador sabe que super.sayHi() hace referencia a Parent.sayHi():

    class Parent {
      static sayHi() {
        alert('Parent says hi');
      }
    }
    class Child extends Parent {
      static sayHi() {
        super.sayHi();
      }
    }
    Child.sayHi();
    

    Sin embargo, la compactación de propiedades romperá el siguiente código, incluso si myMixin(Parent).sayHi es igual a Parent.sayHi sin compilar:

    class Parent {
      static sayHi() {
        alert('Parent says hi');
      }
    }
    class Child extends myMixin(Parent) {
      static sayHi() {
        super.sayHi();
      }
    }
    Child.sayHi();
    

    Evita esta falla con la anotación /** @nocollapse */.

  • Usa métodos get y set de Object.defineProperties o ES6:

    El compilador no comprende bien estas construcciones. Los métodos get y set de ES6 se transforman en Object.defineProperties(...) a través de la transpilación. Actualmente, el compilador no puede analizar estáticamente esta construcción y supone que el acceso y la configuración de las propiedades no tienen efectos secundarios. Esto puede tener repercusiones peligrosas. Por ejemplo:

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

    Se compila para:

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

    Se determinó que C.someProperty no tenía efectos secundarios, por lo que se quitó.