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ónwith
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 usaFunction.toString()
para recuperar los nombres de los parámetros de una función. Sin embargo, aunqueargumentNames()
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()
yinitY()
en el buclefor
, 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 defoo.bar
hace referencia afoo
. Después de la transformación,this
se refiere althis
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 usathis
dentro de los constructores y métodos de prototipo. El significado dethis
no es ambiguo cuando llamas a un constructor con la palabra clavenew
o dentro de una función que es propiedad de unprototype
.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étodoscreate
(después de la transpilación de ES6 a ES5), por lo que la llamadacls.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 aParent.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 aParent.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ó.