Closure Compiler is a JavaScript type checker and optimizer. But just what can these optimizations do to shrink that oh-so-important bundle size?

Disclaimer: This is not a completely fair test. Caveats:

Smaller, simpler programs tend be easier to statically analyze and can be optimized more aggressively

The ES6 polyfils from Babel tend to be quite heavy compared to Closure which are a one time cost and look bad in small examples

Closure and Babel/Uglify do not make the same assumptions. Closure optimizations break code that abuses the dynamic nature of JS. See full restrictions. Simple minifiers are therefore much easier to use.

I am not a Babel/Uglify expert. Please alert me if I am using these tools incorrectly or ineffectively

1. Inline Functions

Closure Compiler will inline functions given that it can determine it will yield smaller code size. Simple minifiers like Babel cannot because they do not assume global analysis: there is no way to know that someone isn’t calling “f” outside this program in another script!

Closure: 17b Babel-minify: 34b

2. Strip Dead Code

This is probably the single most powerfull optimization Closure compiler does. Under the hood, its actually a series of optimizations though. First type checking annotates the AST with type information. Next, a compiler pass called “devirtualize methods” (you can toggle this in the UI) will turn member methods into static methods. Something like A.myMethod(x) => myMethod_devirtualized_(A_instance, x). After inlining and dead code removal we have very little code left!

Closure: 17b Babel-minify: 339b (ES6 polyfils are heavy here)

3. Rename Prototype properties

This one seems pretty straightforward but under the covers this kind of optimization is actually quite difficult to do without proper precautions. Again this is something not possible for simple minifiers to do without breaking the code for external callers and without static type analysis.

Closure: 56b Babel-minify: 216b (ES6 polyfils are heavy here)

4. Inline Variables

Once you’ve done function inlining, expression simplification becomes easy!

Closure 25b Babel-minify: 78b

5. Statically infer call graph

This optimization is only possible given static type analysis. The secret sauce here is that the compiler will first run a compiler pass called “disambiguate methods/properties” which, if it has sufficient type information about the corresponding definition of all calls to “method()”, it will rename them to A.prototype.method_belongs_toA() and B.prototype.method_belongs_toB(). Once this is done method devirtualization and inlining can occur (see 2).

Closure: 34b Babel-minify: 727b

6. Determine function side effects for accurate code pruning

Side effect analysis is complicated enough that it probably requires its own blog post!

Closure: 108b Babel-minify: 311b

7. Collapse object namespaces / static functions

Closure and competitors need lots of polyfills to emulate this ES6 code. Closure is still about 1/3 the size!

Closure: 257b Babel-minify: 798b

8. Remove unused returns

Closure: 86b Babel-minify: 237b