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.
Now let's see how this will be optimized by the closure compiler.
After running the closure compiler the above code snippet is reduced to
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.
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.
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.
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.
I do a detailed comparison of this in one of my other articles.
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.
Now if I run this through the Closure compiler I get the following output:
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.
Integrating jQuery was relatively easy once I defined a few Externs.
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:
Notice the 'firstName and 'lastName' references.
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.
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.
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.
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:
As you can tell I have decorated the properties I want to protect with the @export annotation.
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.
Some might prefer the @export technique over string indexed property names.
In order to make it work we have to include an additional library called google-closure-library. This library defines the mechanism used to export via @export annotations.
As part of this sample I also want to show how to use the Java version of the Closure Compiler. This assumes you have Java installed on your computer.
I have configured a build.sh file below that sets up everything we need to do the compilation.
Some important settings are node_modules/google-closure-library/closure/goog/base.js and –generate_exports. These settings allow the @export annotations to be considered by the compiler.
I have also moved the knockout Externs to a separate file called externs.js.
The name shortening makes debugging a challenge, but by specifying variable_renaming_report and property_renaming_report, we at least get a mapping between shortened and original names.
You may also be interested in my article about how to do do lazy loading and code splitting using the Closure compiler here.