Advanced Compilation

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).

  1. Even Better Compression
  2. How to Enable ADVANCED_OPTIMIZATIONS
  3. What to Watch Out for When Using ADVANCED_OPTIMIZATIONS
    1. Removal of Code You Want to Keep
    2. Inconsistent Property Names
    3. Compiling Two Portions of Code Separately
    4. Broken References between Compiled and Uncompiled Code

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 shortens the code even further to this:

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

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 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:

#!/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.