The Closure Compiler expects its JavaScript input to conform to a few restrictions. The higher the level of optimization you ask the Compiler to perform, the more restrictions the Compiler places on the input JavaScript.
This document describes the main restrictions for each level of optimization. See also this wiki page for additional assumptions made by the compiler.
Restrictions for all optimization levels
The Compiler places the following two restrictions on all JavaScript that it processes, for all optimization levels:
The Compiler only recognizes ECMAScript.
ECMAScript 5 is the version of JavaScript supported almost everywhere. However the compiler also supports many of the features in ECMAScript 6. The compiler only supports official language features.
Browser-specific features that conform to the appropriate ECMAScript language specification will work fine with the compiler. For example, ActiveX objects are created with legal JavaScript syntax, so code that creates ActiveX objects works with the compiler.
The compiler maintainers actively work to support new language versions and their features. Projects can specify which ECMAScript language version they intend by using the
--language_in
flag.The Compiler does not preserve comments.
All Compiler optimization levels remove comments, so code that relies on specially formatted comments does not work with the Compiler.
For example, because the Compiler does not preserve comments, you cannot use JScript's "conditional comments" directly. You can, however, work around this restriction by wrapping conditional comments in
eval()
expressions. The Compiler can process the following code without generating an error:x = eval("/*@cc_on 2+@*/ 0");
Note: You can include open source licenses and other important text at the top of the Compiler output using the @preserve annotation.
Restrictions for SIMPLE_OPTIMIZATIONS
The Simple optimization level renames function parameters, local variables, and locally defined functions to reduce code size. However, some JavaScript constructs can break this renaming process.
Avoid the
following constructs and practices when
using SIMPLE_OPTIMIZATIONS
:
with
:When you use
with
the Compiler can't distinguish between a local variable and an object property of the same name, and so it renames all instances of the name.Furthermore, the
with
statement makes your code harder for humans to read. Thewith
statement changes the normal rules for name resolution, and can make it difficult even for the programmer who wrote the code to identify what a name refers to.eval()
:The Compiler doesn't parse the string argument of
eval()
, and so it won't rename any symbols within this argument.String representations of function or parameter names:
The Compiler renames functions and function parameters but does not change any strings in your code that refer to functions or parameters by name. You should thus avoid representing function or parameter names as strings in your code. For example, the Prototype library function
argumentNames()
usesFunction.toString()
to retrieve the names of a function's parameters. But whileargumentNames()
might tempt you to use the names of arguments in your code, Simple mode compilation breaks this kind of reference.
Restrictions for ADVANCED_OPTIMIZATIONS
The ADVANCED_OPTIMIZATIONS
compilation level performs
the same transformations as SIMPLE_OPTIMIZATIONS
, and
also adds global renaming of properties, variables, and functions,
dead code elimination, and property flattening. These new passes place
additional restrictions on the input JavaScript. In general, using the
dynamic features of JavaScript will prevent correct static analysis on
your code.
Implications of global variable, function, and property renaming:
The global renaming of ADVANCED_OPTIMIZATIONS
makes the
following practices dangerous:
Undeclared external references:
In order to rename global variables, functions, and properties correctly, the Compiler must know about all references to those globals. You must tell the Compiler about symbols that are defined outside of the code being compiled. Advanced Compilation and Externs describes how to declare external symbols.
Using unexported internal names in external code:
Compiled code must export any symbols that uncompiled code refers to. Advanced Compilation and Externs describes how to export symbols.
Using string names to refer to object properties:
The Compiler renames properties in Advanced mode, but it never renames 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
If you need to refer to a property with a quoted string, always use a quoted string:
var x = { 'unrenamed_property': 1 }; x['unrenamed_property']; // This is OK. if ( 'unrenamed_property' in x ) {}; // This is OK
Referring to variables as properties of the global object:
The Compiler renames properties and variables independently. For example, the Compiler treats the following two references to
foo
differently, even though they are equivalent:var foo = {}; window.foo; // BAD
This code might compile to:
var a = {}; window.b;
If you need to refer to a variable as a property of the global object, always refer to it that way:
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 }
The Compiler does not understand that
initX()
andinitY()
are called infor
loop, and so it removes both of these methods.Note that if you pass a function as a parameter, the Compiler can find calls to that parameter. For example, the Compiler does not remove the
getHello()
function when it compiles the following code in Advanced mode.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 of object property flattening
In Advanced mode the Compiler collapses object properties to prepare for name shortening. For example, the Compiler transforms this:
var foo = {}; foo.bar = function (a) { alert(a) }; foo.bar("hello");
into this:
var foo$bar = function (a) { alert(a) }; foo$bar("hello");
This property flattening allows the later renaming pass to rename more
efficiently. The Compiler can replace foo$bar
with a
single character, for example.
But property flattening also makes the following practices dangerous:
Using
this
outside of constructors and prototype methods:Property flattening can change meaning of the keyword
this
within a function. For example:var foo = {}; foo.bar = function (a) { this.bad = a; }; // BAD foo.bar("hello");
becomes:
var foo$bar = function (a) { this.bad = a; }; foo$bar("hello");
Before the transformation, the
this
withinfoo.bar
refers tofoo
. After the transformation,this
refers to the globalthis
. In cases like this one the Compiler produces this warning:"WARNING - dangerous use of this in static method foo.bar"
To prevent property flattening from breaking your references to
this
, only usethis
within constructors and prototype methods. The meaning ofthis
is unambiguous when you call a constructor with thenew
keyword, or within a function that is a property of aprototype
.Using static methods without knowing which class they're called on:
For example, if you have:
class A { static create() { return new A(); }}; class B { static create() { return new B(); }}; let cls = someCondition ? A : B; cls.create();
the compiler will collapse bothcreate
methods (after transpiling from ES6 to ES5) so thecls.create()
call will fail. You can avoid this with the@nocollapse
annotation:class A { /** @nocollapse */ static create() { return new A(); } } class B { /** @nocollapse */ static create() { return new A(); } }
Using super in a static method without knowing the superclass:
The following code is safe, because the compiler knows that
super.sayHi()
refers toParent.sayHi()
:class Parent { static sayHi() { alert('Parent says hi'); } } class Child extends Parent { static sayHi() { super.sayHi(); } } Child.sayHi();
However, property flattening will break the following code, even if
myMixin(Parent).sayHi
is equal toParent.sayHi
uncompiled:class Parent { static sayHi() { alert('Parent says hi'); } } class Child extends myMixin(Parent) { static sayHi() { super.sayHi(); } } Child.sayHi();
Avoid this breakage with the
/** @nocollapse */
annotation.Using Object.defineProperties or ES6 getter/setters:
The compiler does not understand these constructs well. ES6 getter and setters are transformed to Object.defineProperties(...) through transpilation. Currently, the compiler is unable to statically analyze this construct and assumes properties access and set are side effect free. This can have dangerous repercussions. For example:
class C { static get someProperty() { console.log("hello getters!"); } } var unused = C.someProperty;
Is compiled to:
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; }}});
C.someProperty was determined to have no side effects so it was removed.