Advanced Compilation and Externs

Overview

Using the Closure Compiler with a compilation_level
of ADVANCED_OPTIMIZATIONS offers better compression rates
than compilation with SIMPLE_OPTIMIZATIONS
or WHITESPACE_ONLY. Compilation
with ADVANCED_OPTIMIZATIONS achieves extra compression by
being more aggressive in the ways that it transforms code and renames
symbols. However, this more aggressive approach means that you must
take greater care when you use ADVANCED_OPTIMIZATIONS
to ensure that the output code works the same way as the
input code.

This tutorial illustrates what the ADVANCED_OPTIMIZATIONS
compilation level does and what you can do to make sure your code
works after compilation with ADVANCED_OPTIMIZATIONS. It
also introduces the concept of the extern: a symbol that is
defined in code external to the code processed by the compiler.

Even Better Compression

With the default compilation level
of SIMPLE_OPTIMIZATIONS, the Closure Compiler makes
JavaScript smaller by renaming local variables. There are symbols
other than local variables that can be shortened, however, and there
are ways to shrink code other than renaming symbols. Compilation
with ADVANCED_OPTIMIZATIONS exploits the full range of
code-shrinking possibilities.

Compare the outputs for SIMPLE_OPTIMIZATIONS
and ADVANCED_OPTIMIZATIONS for the following
code:

Compilation with SIMPLE_OPTIMIZATIONS only
renames the note parameters of
the displayNoteTitle()
and unusedFunction() functions, because these are
the only variables in the script that are local to a
function. ADVANCED_OPTIMIZATIONS also renames the
global variable flowerNote.

dead code removal:

Compilation with ADVANCED_OPTIMIZATIONS removes
the function unusedFunction() entirely, because it
is never called in the code.

function inlining:

Compilation with ADVANCED_OPTIMIZATIONS replaces
the call to displayNoteTitle() with the
single alert() that composes the function's
body. This replacement of a function call with the function's
body is known as "inlining". If the function were longer or more
complicated, inlining it might change the behavior of the code,
but the Closure Compiler determines that in this case inlining
is safe and saves space. Compilation
with ADVANCED_OPTIMIZATIONS also inlines constants
and some variables when it determines that it can do so
safely.

This list is just a sample of the size-reducing transformations
that ADVANCED_OPTIMIZATIONS compilation can perform.

How to
Enable ADVANCED_OPTIMIZATIONS

The Closure Compiler service UI, service API, and application all
have different methods for setting
the compilation_level
to ADVANCED_OPTIMIZATIONS.

How to Enable ADVANCED_OPTIMIZATIONS
in the Closure Compiler service UI

To enable ADVANCED_OPTIMIZATIONS for the Closure
Compiler service UI, click the "Advanced" radio button.

How to Enable ADVANCED_OPTIMIZATIONS
in the Closure Compiler service API

To enable ADVANCED_OPTIMIZATIONS for the Closure
Compiler service API, include a request parameter
named compilation_level with a value
of ADVANCED_OPTIMIZATIONS, as in the following python program:

What to Watch Out for When
Using ADVANCED_OPTIMIZATIONS

Below are listed some common unintended effects
of ADVANCED_OPTIMIZATIONS, and steps you can take to avoid them.

Removal of Code You Want to Keep

If you compile just the function below
with ADVANCED_OPTIMIZATIONS, Closure Compiler produces
empty output:

function displayNoteTitle(note) {
alert(note['myTitle']);
}

Because the function is never called in the JavaScript that you pass to
the compiler, Closure Compiler assumes that this code is not needed!

In many cases this behavior is exactly what you want. For example, if
you compile your code together with a large library, Closure Compiler can
determine which functions from that library you actually use and
discard the ones that you don't use.

If, however, you find that Closure Compiler is removing functions you want to
keep, there are two ways to prevent this:

Move your function calls into the code processed by Closure Compiler.

Export the symbols you want to keep.

The next sections discuss each option in more detail.

Solution: Move Your Function Calls into the Code
Processed by the Closure Compiler

You may encounter unwanted code removal if you only compile part of
your code with Closure Compiler. For example, you might have a library file that
contains only function definitions, and an HTML file that includes the
library and that contains the code that calls those functions. In this
case, if you compile the library file
with ADVANCED_OPTIMIZATIONS, Closure Compiler removes
all of your library functions.

The simplest solution to this problem is to compile your functions
together with the portion of your program that calls those functions.
For example, Closure Compiler will not remove displayNoteTitle()
when it compiles the following program:

The displayNoteTitle() function isn't removed in this
case because Closure Compiler sees that it is called.

In other words, you can prevent unwanted code removal by including
your program's entry point in the code that you pass to
Closure Compiler. The entry point of a program is the place in the
code where the program begins executing. For example, in
the flower note program from the previous
section, the last three lines are executed as soon as the JavaScript
is loaded in the browser. This is the entry point for this program. To
determine what code you need to keep, Closure Compiler starts at this
entry point and traces the control flow of the program forward from
there.

Solution: Export the Symbols You Want to Keep

What if you want to compress only a set of function definitions to
create a small, reusable library? How do you prevent Closure Compiler from
removing all the functions?

The best solution in this case is to export your functions, as the
following example illustrates:

Note that the compressed code still contains a function with a single
alert statement. This function is the
old displayNoteTitle() function, renamed
to a(). The function can still be called by its old name,
however, because its old name has been added as a property of the
global object. For example, if code external to this compiled code makes the
following call, the call still correctly produces an alert:

displayNoteTitle({'myTitle': 'Flowers'});

This call works because the window.displayNoteTitle
property now has a function as its value, just as the
native window.alert property has the alert()
function as its value.

Furthermore, Closure Compiler does not remove the definition of the
function (now renamed to a()), because Closure Compiler
sees that the function is used in code that gets executed. It's used
in the assignment statement window['displayNoteTitle'] =
displayNoteTitle;

You can export constructors and prototype properties in the same
way. For example:

If putting together these export statements seems too tedious, you
can use a function to do the exporting for you, using the Closure
Library functions goog.exportSymbol()
and goog.exportProperty().

Inconsistent Property Names

Closure Compiler compilation never changes string literals in your code, no
matter what compilation level you use. This means that compilation
with ADVANCED_OPTIMIZATIONS treats properties differently
depending on whether your code accesses them with a string. If you mix
string references to a property with dot-syntax references, Closure Compiler
renames some of the references to that property but not others. As a
result, your code will probably not run correctly.

The last two statements in this source code do exactly the same
thing. However, when you compress the code
with ADVANCED_OPTIMIZATIONS, you get this:

var a={};a.a="Flowers";alert(a.a);alert(a.myTitle);

The last statement in the compressed code produces an error. The
direct reference to the myTitle property has been renamed
to a, but the quoted reference to myTitle
within the displayNoteTitle function has not been
renamed. As a result, the last statement refers to
a myTitle property that is no longer there.

Solution: Be Consistent in Your Property Names

This solution is pretty simple. Whenever possible, use dot-syntax
property names rather than quoted strings. Use quoted string property
names only when you don't want Closure Compiler to rename a property
at all. For example, to export a property you
must use a quoted string. However, for properties used only within your
compiled code, use dot syntax.

Compiling Two Portions of Code Separately

If you split your application into different modules of code, you
might want to compile the modules separately. However, if two chunks
of code interact at all, you may have trouble compiling them
separately. Even if you succeed, the output of the two Closure Compiler runs
will not be compatible.

For example, assume that an application is divided into two parts: a
part that retrieves data, and a part that displays data.

If you try to compile these two chunks of code separately, you will
encounter several problems. First, the Closure Compiler removes
the getData() function, for the reasons described
in Removal of Code You Want to Keep. Second,
the Closure Compiler produces a fatal error when processing the code
that displays the data.

Because the compiler does not have access to the getData()
function when it compiles the code that displays the data, it
treats getData as undefined.

Solution: Compile All Code for a Page Together

To ensure proper compilation, compile all the code for a page together
in a single compilation run. The Closure Compiler can accept
multiple JavaScript files and JavaScript strings as input, so you can
pass library code and other code together in a single compilation
request.

Broken References between Compiled and
Uncompiled Code

Symbol renaming in ADVANCED_OPTIMIZATIONS will break
communication between code processed by the Closure Compiler and any
other code. Compilation renames the functions defined in your source
code. Any external code that calls your functions will break after you
compile, because it still refers to the old function name. Similarly,
references in compiled code to externally defined symbols may be
altered by Closure Compiler.

Keep in mind that "uncompiled code" includes any code passed to
the eval() function as a string. Closure Compiler never alters
string literals in code, so Closure Compiler does not change strings passed
to eval() statements.

Be aware that these are actually very different problems: maintaining
compiled-to-external communication, and maintaining
external-to-compiled communication. These separate problems have
different solutions, and to get the most out of Closure Compiler it's important
to use the right solution for the right situation.

Solutions for these two problems are discussed further in the next
sections.

Solution for Calling in from External Code to
Compiled Code: Exports

If you have JavaScript code that you reuse as a library, you may want
to use Closure Compiler to shrink only the library while still allowing
uncompiled code to call functions in the library.

The solution in this situation is identical to the solution for
unwanted code removal described in Export the
Symbols You Want to Keep. Exporting symbols not only keeps those
symbols from being removed, but also makes them available to external
code.

Solution for Calling out from Compiled Code to
External Code: Externs

If you use third-party JavaScript libraries or APIs
like the
OpenSocial API or
the Google Maps API,
you need to be sure that Closure Compiler doesn't rename the symbols
you use that are defined in those external libraries. For example, if
your code calls the OpenSocial JavaScript
function opensocial.newDataRequest(), you do not want
Closure Compiler to transform this call into a.b(). Your
code must use the same names that the external file uses.

Closure Compiler provides a mechanism for declaring that a name is defined in
external code and so should not be renamed. This mechanism is called
the extern. The compiler assumes that externs will exist in
the environment in which the compiled JavaScript will be interpreted.

Declaring Externs

The following JavaScript file contains code that requires an
externs declaration:

You don't want Closure Compiler to rename textDiv, because it is
defined externally, so you declare an extern.

In both cases, you declare the extern by writing JavaScript that
defines the symbol you want preserved, and then giving that JavaScript
to the Closure Compiler. In this example, we want to prevent renaming
of the textDiv() function, so we write JavaScript that
declares the function, giving it an empty body:

function textDiv(text){};

If we give this JavaScript to Closure Compiler as an extern definition, Closure Compiler
does not include it in the output JavaScript. The sole purpose of this
JavaScript is to tell Closure Compiler, "Our code uses a function
named textDiv() that's defined somewhere you don't know
about, so don't rename any calls to textDiv()."

Sending our JavaScript to Closure Compiler with the extern
declaration function textDiv(text){}; produces the
following output. Note that we have given the Closure Compiler service
a formatting parameter of pretty_print to
make it easier to see how Closure Compiler uses the externs
declaration.

Closure Compiler has changed the original program dramatically, but
the textDiv calls are still there with the original
function name. This code will therefore interoperate with the third
party textops.js file that
defines textDiv().

Both the Closure Compiler application and the Closure Compiler
service API allow extern declarations. The Closure Compiler service
UI does not provide an interface element for specifying externs files.

Do Not Use Externs Instead of Exports!

Externs are such a handy way to protect symbols from renaming that you
might be tempted to use them instead
of exports. If you want to expose an API from a
compiled file so that external code can call functions in the
file, you might think that you can just declare your API functions in
an externs file.

Exporting allows better compression than externs. Closure Compiler never
renames symbols declared in externs, no matter how many times they
appear in your code. This means that a single extern declaration can
create many long names in your compiled file. Exporting, in contrast,
only includes the full name of the exported symbol once. This one
instance of the full name sets up an alias, so that the symbol can be
shortened everywhere else it appears.

Note: Only use externs to declare symbols in external code. Never use them
to protect symbols defined in the compiled code.

How to Declare Externs with the Closure Compiler
Application

Pass the names of externs files to the Closure Compiler application
using a separate --externs command-line flag for each
extern file, as in the following command: