Javascript Minification

One of the main techniques to keep websites lean and loading fast is to minify all the javascript using tools like the YUI Compressor, UglifyJS or Google’s Closure Compiler.

These tools take perfectly readable javascript code:

var bestFunctionEver = function(number) {
    return number + 1;
};
bestFunctionEver(1);

…and turn it into something like:

var a=function(b){return b+1};a(1);

So that without changing your code’s functionality, your files are as small as possible.

Then, when a browser fetches your site it doesn’t have to download that many bytes over the wire, which translates to a faster load of your page.

Closure Compiler

Here at Yext, we use the Closure Compiler to minify our Javascript. This compiler was developed at Google when trying to give closure to their Javascript problems at Gmail. And no, I did not make that up; former Yext engineer Michael Bolin said it first!

You can set the compiler to one of three modes: WHITESPACE, SIMPLE and ADVANCED.

  • WHITESPACE just removes comments and unnecessary whitespace.

  • SIMPLE will go further and do renaming of local variables (to shorter names).

Both of these modes will guarantee your javascript will behave as it did before compilation.

  • ADVANCED, however, is kind of a “use at your own risk” type of deal. It achieves the best compression ratio out there by removing unreachable code, renaming global functions and inlining functions.

These techniques in ADVANCED can’t be done correctly unless the compiler analyzes your code. For example, in order for it to know what functions it can delete, it has to check they are not called anywhere.

This means, you will have to do extra work to tell the compiler what your code is doing.

Going from SIMPLE to ADVANCED

Since SIMPLE is the best compression level that doesn’t require additional work, this is the setting most people out there use, and its equivalent to using any old Javascript minifier like UglifyJS.

At Yext, we’ve taken the task of compiling our javascript in ADVANCED and have collected helpful tips while at it. The best advice being to try to do it before you start a project!

Tip 0: Compiling

The first step will probably be to try to get this command not to explode:

java -jar compiler.jar 
	--js input.js
	--js_output_file output.js 
	--compilation_level ADVANCED_OPTIMIZATIONS

This process will be guided by whatever the compiler tells you, and comparing it with this list of errors. The most common one will probably be the global this error.

Now, having your Javascript compile in ADVANCED doesn’t mean that it will work as expected. Sadly, there can be many subtle bugs in the execution of this code that you still need to squash. But compiling under ADVANCED without warnings is already a great milestone; congrats!

Tip 1: Make the Errors Debuggable

After you’ve made your code compile under ADVANCED, there will be a long period of bug squashing as you navigate around your site trying to execute every line of Javascript you have.

To make this bug squashing easier it is imperative to at least have sourcemaps set up, and use the --debug=true and --formatting=PRETTY_PRINT compiler flags.

For source maps, we let the closure compiler generate it and append

//# sourceMappingURL=/url/where/we/will/serve/the/map.js.map

to our output wrapper.

The --debug=true flag will make the compiler rename variables by surrounding them with $ (instead of shortening them!) This is so that you can map renamed values back to original ones. So, as in the example below, you’ll see .$selectbox$ instead of .a or some other random-looking value.

Tip 2: Tackle the Console in Order!

At this stage, when you load your site, the Chrome’s Developers Tool Console might look like:

Errors In Console

Tackle the errors in order! I can’t stress this enough.

I emphasize in order because javascript execution stops when it breaks. This means that if some code depended in some other code that was supposed to be executed right after an offending line, such code will also break. Thus, solving earlier errors might solve later ones!

Also note that fixing errors will let execution go further and so you will most likely just see more errors… Encouraging… :) But believe me there is a light at the end of the tunnel.

Tip 3: Common Bugs

At this point you can keep loading the different parts of your site trying to trigger all your Javascript code and fixing errors.

The 3 most common mistakes are the following:

  • Inconsistent Access

    I call it “looking for something that is not there”. They look like:

    Uncaught TypeError: Cannot read property 'undefined' of undefined

    If you jump around dot notation (foo.bar) and bracket notation (foo['bar']) the code will break. The first access will get renamed by the compiler, the second one is left untouched (the compiler doesn’t touch strings!).

    AJAX calls are particularly prone for this. If your callback tries to: response.yourField, it will fail. The JSON response from your server will be treated as a string, and so you have to response['yourField'] instead.

    Be careful when deciding to use bracket access as opposed to dot access. Overdoing this defeats the purpose of ADVANCED compilation!

  • Failed Exports

    If you include inline Javascript in your HTML via a <script> tag, it will most likely break!

    The compiler never sees your HTML files, so there is no way for it to know that the awesome.function.youWrote(); will be called at runtime. If that is the only call of your function (no other javascript file calls it) the compiler will think it is dead code and guess what? DELETED.

    Make sure you goog.exportSymbol the functions you call in the HTML.

  • Failed Externs

    Similarly, when you <script src="https://code.jquery.com/jquery-2.2.4.min.js">, the compiler doesn’t know about this.

    So if in the list of files you give the compiler to compile, you call say jquery.animate, the compiler will have no idea what jquery is and that it’ll furthermore provide this animate function.

    For this, you need to write extern files that provide these APIs. For common libraries, there are many extern files here, for others we try to use this tool.

    Other libraries, particularly those written as IIFEs, don’t work under the externs tool. Furthermore, these libraries might simply break if we include them in our ADVANCED compilation unit (say they do inconsistent access).

These are harder to deal with so we called them UN-ADVANCED modules…

Tip 4: Dealing with UN-ADVANCED Modules

One option you have is fixing the library to make it work under ADVANCED. If I were you, yeah… I’m wouldn’t be trying to rewrite an old library.

Another option, which is the one we went with here at Yext, is to not include these libraries in your normal compilation unit. Some ideas are (deciding which is best will probably depend on your build setup):

  • Have a 2-step compilation process, where you first compile all your code in ADVANCED, and run this result as an input to a second compilation step, along with the rest of the UN-ADVANCED modules, all in SIMPLE. Effectively appending the modules at the end of your ADVANCED result.

  • Have 2 different compilation units. One for your ADVANCED code and one for all the UN-ADVANCED (this latter again in SIMPLE). Then from your HTML include these two results as different <script> tags.

Tip 5: AJAX GREP-ing

Now, after you’ve compiled in ADVANCED and gone through many of the user flows trying to trigger all the code, how sure can we be that we didn’t miss anything? This is of particular interest in big codebases, where it’s hard to spot-check all the code.

My last tip is to GREP for common patterns in AJAX calls. For example:

  • get
  • post
  • ajax
  • response
  • result
  • res
  • data
  • error
  • err
  • request
  • req

are good patterns to search for.

Summary

Set your project to ADVANCED before you start (if you can), invest in making your Javascript debuggable, and have some patience while tackling errors in order.

Compiling Javascript in ADVANCED can be a daunting task and frustrating at times, but it will achieve the best compression ratio for your Javascript!

We hope this list can come in handy as you move your codebases to ADVANCED!