Using the Closure Compiler - ADVANCED_OPTIMIZATIONS

Published: Sat Dec 03 2016

The Closure compiler is an amazing tool for optimizing JavaScript. In this post I will show how to use it in some common scenarios. For the purposes of this post I will be using the closure compiler with ADVANCED_OPTIMIZATIONS.

From what I can tell the closure compiler is one of the most impressive JavaScript optimizers out there. The idea behind the closure compiler is to take your existing JavaScript and compile it to even better JavaScript. It will run a deep analysis of your code and apply a series of advanced optimizations like dead code removal, function flattening and inlining. The output is a generally a highly optimized version of the original code.

Simple Example

Here is an example of how effective the closure compiler can be at optimizing code

In the snippet below I have created a simple function with functionality to get a person and print the person's name.

After running the closure compiler the above code snippet is reduced to

console.log("Joe");

The only thing left is a console.log statement with the string argument “Joe”.

This code reduction is almost too good to be true, but if we analyze the original code, we notice that this matches the behavior of the original code 100%. In all cases the original code will always result in a simple console.log statement.

The original code is just much more verbose since it's optimized for readability over performance. The closure compiler fixes that by reducing the code to only what's needed to produce the same behavior.

All functions and intermediate state have been flattened or removed, but the result of running the code is the same.

It's worth pointing out that the Closure compiler is so aggressive when looking for unused code that if we remove the call to printPerson, Closure compiler will optimize away the entire application. This is because it finds no usage of any of the code.

Using the Closure Compiler in the Wild

So given the amazing results we just saw, why isn't the closure compiler more common in projects?

The Closure compiler supports a few different compilation levels. In the sample above I used the most aggressive level; ADVANCED_OPTIMIZATIONS.

The other levels are SIMPLE_OPTIMIZATIONS and WHITESPACE_ONLY. SIMPLE_OPTIMIZATIONS takes a less aggressive approach, but is a much safer operation.

ADVANCED_OPTIMIZATIONS comes with a few caveats. Because the optimization techniques are so aggressive, the compiler makes certain assumptions about the code.

Depending on your code these assumptions may turn out to break your application, unless you specifically designed your code with the closure compiler in mind.

Unfortunately this means most code won't be compatible with the closure compiler by default. This is the main reason we don't see the more usage of the Closure compiler in the JavaScript ecosystem.

However, there is hope. In the following sections I will try to show a few simple techniques that will make your code Closure compiler compatible.

Integration with External Libraries

It would be ideal if more external libraries were compatible with the Closure compiler. This would mean we would be able to apply closure compilation across framework boundaries. Meaning we would be left with a highly optimized bundle, customized to meet the needs of our application

Only the parts of the framework used by the application would be included. This combined with the aggressive inlining techniques described above would probably leave regular Tree Shaking in the dust.

Tree Shaking is an optimization technique that involves walking the code's dependency tree and only include modules that are actually in use. Tree Shaking generally works at the module (ES2015) level though. This means we won't be able to Tree Shake unused methods within the module since modules can't be partially included. It's all or nothing per module. This can have a compounding effect since unused methods may cause other modules to be included needlessly.

The Closure compiler (ADVANCED_OPTIMIZATIONS) does not suffer from this limitation. It is able to analyze code across modules and create a highly optimized version of the code that is actually in use.

Sadly, most frameworks are not compatible with the Closure compiler, but there are a few tricks we can apply to make it work. The easiest thing is to leave the external library code alone and only focus on your own application code.

In the following code sample I will show how to integrate Closure compiled application code with knockoutJS, jQuery and lodash.

I will start by showing how to integrate jQuery since it's fairly straightforward.

Integration with libraries like jQuery and lodash is limited to global variables references like jQuery, _ and methods called directly on these global variables.
This will still potentially get us in trouble since the closure compiler will likely shorten functions or variable names belonging to the external library. In my sample I am using the jQuery function fadeIn.

This is not closure compiler safe since it will likely be renamed from fadeIn to a new one character name. This is great for byte savings, but not so great for runtime since the app will break.

Luckily there is a feature in the closure compiler that allows us to specify which function names we want to protect from optimization. The way you protect external library code is by specifying the variables and functions belonging to the library as “Externs”. Anything listed as an Extern will be left alone by the Closure compiler.

The following sample code shows some code where I load a greeting from an external service before inserting it into the DOM using jQuery.

Like before the code is much more compact after aggressive function inlining, but notice the jQuery references are left intact (jQuery and fadeIn). This is because I have listed jQuery and jQuery.fadeIn as Externs in the Closure compiler configuration.

There are several choices for how to integrate the closure compiler. In my case I am doing it using Rollup and a Closure compiler plugin.
The Externs are passed directly into the plugin configuration like so:

I am also specifying the compilation level as ADVANCED and telling the Closure compiler to convert my ES6 code to regular ES5.

Otherwise this is a standard Rollup configuration file.

I am only specifying the Externs I need for my sample, but you will have to define more of the jQuery api as needed. There are however more complete Externs available that people have published for this reason.

Notice I am also defining externs for knockoutJS (ko). This will set the scene for using knockoutJS in the next section.

KnockoutJS

Integrating jQuery was relatively easy once I defined a few Externs.

Next I want to go beyond this and integrate it with the popular template binding framework knockoutJS. This is slightly more challenging since the application logic is spread across JavaScript files and html templates.

Specifically the template bindings refer to JavaScript properties by name. The Closure compiler does not know about the html templates, so when property names are shortened the template bindings break.

Like before the solution is to give the Closure compiler cues to tell it not to shorten the names of bound properties. We could solve this by using Externs, but the Closure compiler team discourages the use of Externs for your own application code. The reason they give is that it might make the optimizations less effective if you overuse Externs.

Instead they recommend an alternative technique. It turns out that property references using dynamic string based lookup will not be renamed by the Closure compiler. By referring to a property as myObj['someName'] instead of myObj.someName, the someName name is not shortened and available to external code referring to your Closure compiled code.

To reason exporting properties via dynamic string lookup is better than Externs is that Externs are never modified by the compiler. This could potentially lead to a lot of repeated long property names if the property is used in several places.

If you use the dynamic lookups, Closure will retain a single reference with the long version of the name and use a shortened alias everywhere else.

In my knockoutJS sample I bind to firstName and lastName properties in my templates. Here is how I define the properties in my view model to ensure compatibility with the Closure compiler:

It may seem a bit awkward to refer to properties dynamically here, but I think there is an opportunity for transpilers to make this less painful.

It might be possible for something like a TypeScript transpiler to output JavaScript with string indexing based on annotations in the original TypeScript.

After I protect the property names from renaming my knockoutJS app works perfectly. I have a second example using the VueJS framework here in case you are interested.

The main challenge with these types of frameworks is that we have parts of the application (html templates) that the Closure compiler doesn't have visibility into. This will unfortunately be true for any library with a separation between html and JavaScript.

There are however frameworks that are more compatible. One example is the Svelte framework. I have an example here where I show how to use Svelte with the Closure compiler.

Closure Compiler Annotations

In the previous section I showed how to use string indexing of properties to tell the Closure Compiler to not shorten property names.

Some people might not like that they have to use string indexing just to satisfy the Closure Compiler. Luckily there is an alternative in JSDoc comment tags.

By annotating properties with special comment tags we can tell the Closure Compiler to leave certain properties alone. It will still use shorten versions of the names internally in the code, but the original property names are exported to the outside world. This makes it easier to integrate the code with external code.

To show how to use annotations I have redone the knockoutJS sample using annotations:

We can then refer to the properties in the knockoutJS template without issue. Without @exports or string indexed property names there would be a mismatch between the binding expressions and the view model.