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.
Before reading this tutorial you should be familiar with the process of compiling JavaScript with one of the Closure Compiler tools (the compiler service UI, the compiler service API, or the compiler application).
A note on terminology: the --compilation_level
command line flag
supports the more commonly used abbreviations ADVANCED
and
SIMPLE
as well as the more precise
ADVANCED_OPTIMIZATIONS
and SIMPLE_OPTIMIZATIONS
.
This document uses the longer form, but the names may be used interchangeably on
the command line.
- Even Better Compression
- How to Enable ADVANCED_OPTIMIZATIONS
- What to Watch Out for When Using ADVANCED_OPTIMIZATIONS
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:
function unusedFunction(note) { alert(note['text']); } function displayNoteTitle(note) { alert(note['title']); } var flowerNote = {}; flowerNote['title'] = "Flowers"; displayNoteTitle(flowerNote);
Compilation with SIMPLE_OPTIMIZATIONS
shortens the
code to this:
function unusedFunction(a){alert(a.text)}function displayNoteTitle(a){alert(a.title)}var flowerNote={};flowerNote.title="Flowers";displayNoteTitle(flowerNote);
Compilation with ADVANCED_OPTIMIZATIONS
fully shortens the
code to this:
alert("Flowers");
Both of these scripts produce an alert reading "Flowers"
,
but the second script is much smaller.
The ADVANCED_OPTIMIZATIONS
level goes beyond simple
shortening of variable names in several ways, including:
- more aggressive renaming:
Compilation with
SIMPLE_OPTIMIZATIONS
only renames thenote
parameters of thedisplayNoteTitle()
andunusedFunction()
functions, because these are the only variables in the script that are local to a function.ADVANCED_OPTIMIZATIONS
also renames the global variableflowerNote
. - dead code removal:
Compilation with
ADVANCED_OPTIMIZATIONS
removes the functionunusedFunction()
entirely, because it is never called in the code. - function inlining:
Compilation with
ADVANCED_OPTIMIZATIONS
replaces the call todisplayNoteTitle()
with the singlealert()
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 withADVANCED_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:
#!/usr/bin/python2.4 import httplib, urllib, sys params = urllib.urlencode([ ('code_url', sys.argv[1]), ('compilation_level', 'ADVANCED_OPTIMIZATIONS'), ('output_format', 'text'), ('output_info', 'compiled_code'), ]) headers = { "Content-type": "application/x-www-form-urlencoded" } conn = httplib.HTTPSConnection('closure-compiler.appspot.com') conn.request('POST', '/compile', params, headers) response = conn.getresponse() data = response.read() print data conn.close()
How to Enable ADVANCED_OPTIMIZATIONS
in the Closure Compiler application
To enable ADVANCED_OPTIMIZATIONS
for the Closure
Compiler application, include the command line
flag --compilation_level ADVANCED_OPTIMIZATIONS
, as in
the following command:
java -jar compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS --js hello.js
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.
- Include externs for the functions you want to expose.
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:
function displayNoteTitle(note) { alert(note['myTitle']); } displayNoteTitle({'myTitle': 'Flowers'});
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: Include Externs for the Functions You Want to Expose
More information on this solution is given below and in the page on externs and exports.
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.
For example, take the following code:
function displayNoteTitle(note) { alert(note['myTitle']); } var flowerNote = {}; flowerNote.myTitle = 'Flowers'; alert(flowerNote.myTitle); displayNoteTitle(flowerNote);
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. For any given type or object, use dot-syntax or quoted strings exclusively. Don't mix the syntaxes, especially in reference to the same property.
Also, when possible, prefer to use dot-syntax, as it supports better checks and optimizations. Use quoted string property access only when you don't want Closure Compiler to do renaming, such as when the name comes from an outside source, like decoded JSON.
Compiling Two Portions of Code Separately
If you split your application into different chunks of code, you might want to compile the chunks separately. However, if two chunks of code interact at all, doing so may cause difficulty. 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.
Here's the code for retrieving the data:
function getData() { // In an actual project, this data would be retrieved from the server. return {title: 'Flower Care', text: 'Flowers need water.'}; }
Here's code for displaying the data:
var displayElement = document.getElementById('display'); function displayData(parent, data) { var textElement = document.createTextNode(data.text); parent.appendChild(textElement); } displayData(displayElement, getData());
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.
input:6: ERROR - variable getData is undefined displayData(displayElement, getData());
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.
Note: This approach won't work if you need to mix compiled and uncompiled code. See Broken References between Compiled and Uncompiled Code for tips on handling this situation.
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 related but distinct problems: maintaining compiled-to-external communication, and maintaining external-to-compiled communication. These separate problems have a common solution, but there are nuances to each side. To get the most out of Closure Compiler it's important to understand which case you have.
Before continuing, you may want to familiarize yourself with externs and exports.
Solution for Calling into External Code from Compiled Code: Compiling with Externs
If you use code supplied into your page by another script, you need to be sure that Closure Compiler doesn't rename your references to the symbols defined in that external library. To do this, include a file containing the externs for the external library into your compilation. That will tell Closure Compiler which names you do not control and therefore cannot be changed. Your code must use the same names that the external file uses.
Common examples of this are APIs like the
OpenSocial API
and the Google Maps API. For instance, if
your code calls the OpenSocial function opensocial.newDataRequest()
,
without the appropriate externs, Closure Compiler will transform
this call into a.b()
.
Solution for Calling into Compiled Code from External Code: Implementing Externs
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 to implement a set of externs defining the public API of your library. Your code will supply definitions for the symbols declared in these externs. This means any classes or functions your externs mention. It may also mean having your classes implement interfaces declared in the externs.
These externs are useful for others as well, not just yourself. Consumers of your library will need to include them if they are compiling their code, since your library represents an external script from their perspective. Think of the externs as the contract between you and your consumers, you both need a copy.
To this end, make sure that when you compile your code, you also include the externs in the compilation. This may seem unusual, since we often think of externs as "coming from somewhere else", but it's necessary to tell Closure Compiler which symbols you're exposing, so they aren't renamed.
One important caveat here is that you may get "duplicate definition" diagnostics about the code defining the extern symbols. Closure Compiler assumes that any symbol in the externs is being supplied by an outside library, and can't currently understand that you are intentionally supplying a definition. These diagnostics are safe to suppress, and you can think of the suppression as a confirmation that you really are fulfilling your API.
Additionally, Closure Compiler may typecheck that your definitions match the types of the extern declarations. This provides additional confirmation that your definitions are correct.