DEV Community

Brian Neville-O'Neill
Brian Neville-O'Neill

Posted on • Originally published at blog.logrocket.com on

Using Google Closure Compiler to deliver better JavaScript

Code review is the process of analyzing code through a (theoretically) rigorous process of reading and critically peer reviewing its content. Before submitting code for review, programmers usually clean it up with one of a number of automated tools, depending on which language and environment they are using.

In the JavaScript world, simply because of the language’s nature, many developers, especially beginners, can’t see the pitfalls and errors they make when programming. These could be anything from the use of undeclared variables, to null pointer exceptions due to inconsistent null checks, to the misuse — or no use at all — of what a function returns. That’s why something else, automated, can help us before submitting code for review.

Google Closure Compiler does exactly that: it compiles from JavaScript to better JavaScript by analyzing, parsing, minifying, and rewriting it. And, of course, it also warns us of the same pitfalls we mentioned above. It removes what’s unnecessary, it checks syntax — in short, it does a lot.

In this article, we’ll present some common problems that front-end developers face and better understand how Closure Compiler can help us to rapidly double-check what we’re writing to ensure we deliver the best code possible.

A simple quickstart

You can execute Closure Compiler from the command line into your application (e.g., Node.js) or via freely available web service.

It basically exposes a web page where you can compile your code via either a linked JavaScript file or inline code pasting. The tool then displays the results on the right-hand side of the screen.

Those results, in turn, show the size difference between the original source code and the compiled version (both gzipped and uncompressed) and an autogenerated link for you to download the JavaScript file.

Most importantly, you’ll see a table with four tabs, displaying:

  • The final compiled code
  • A list of warnings and errors, stating when they happen, the line, the error/warning type, and a description of what was wrong
  • The POST data that was used to make the REST request to the Closure Compiler web service

Regarding the optimizations, you can select from Simple and Advanced options (we won’t consider Whitespace only since it doesn’t do much).

Simple will transpile and minify your JS code, as well as warn about syntax and the most dangerous (yet obvious) errors we usually commit. Simple mode is, as the name implies, simple — and, most of the time, safe.

Advanced, on the other hand, is far more aggressive when it comes to removing code, reorganizing the whole structure of your original implementation.

Take the previous image of the default “hello, world” example on the Closure web service page: it shrunk the code and made it simpler, but it lost the hello() function, which means external references to it would break. But don’t worry; we’ll explore how to fix this.

Let’s take another, slightly more complex example, this one extracted from theofficial Google tutorials:

https://medium.com/media/bf70f915a0c3b2e75f48f5ce11b242dd/href

Here, we basically create a data structure of notes, each with string attributes of a title and content. The rest is made of utility functions for iterating the list of notes and placing them all into the document via each respective create function. The same code will look like this after being compiled by Closure Compiler:

for (var a = [{title:"Note 1", content:"Content of Note 1"}, {title:"Note 2", content:"Content of Note 2"}], b = document.getElementById("notes"), c = 0; c < a.length; c++) { var d = a[c].content, e = b, f = document.createElement("div"); f.appendChild(document.createTextNode(a[c].title)); var g = document.createElement("div"); g.appendChild(document.createTextNode(d)); var h = document.createElement("div"); h.appendChild(f); h.appendChild(g); e.appendChild(h); } ;
Enter fullscreen mode Exit fullscreen mode

Note that the whole noteData variable list was changed for an inline object declaration, which comes inside the loop. The variables were renamed from their originals to alphabet chars. And you can’t reuse the previous functions in other places; Closure Compiler would probably have pasted the list twice if it was being called from somewhere else.

Yet the readability and understanding of the code are not good — which, of course, couldn’t be used in a development environment.

Unused variables

There are plenty of scenarios in which Closure Compiler could actuate — that is, problems that are common to our daily lives as JavaScript developers. Let’s take a single example of JavaScript code:

'use strict';
const helperModule = require('./helper.js');
var notUsed;
Enter fullscreen mode Exit fullscreen mode

What would happen to the generated output code when we use 'use strict' mode? Or an unused variable, even though you set a value for it later?

It’s common to create many structures (not only variables, but constants, functions, classes, etc.) to be removed later that are easily forgettable — even more so if you’re dealing with a huge amount of source code files. Depending on the complexity of your models, or how you expose your objects to the external world, this can lead to unwanted situations.

Well, that’s the result:

var a = require(“./helper.js”);
Enter fullscreen mode Exit fullscreen mode

Those structures that were unused were automatically identified and removed by Closure Compiler. Plus, local variables (let) and constants (const) are substituted by var declarations.

Conditional flows

What about a scenario in which one flow depends on another conditional flow? Let’s say you have one function, check(), that relies on another, getRandomInt(), to generate a random number between 0 and 1, which returns true if it is 1.

Based on that flow, we don’t know what’s going to happen because the function is random — that is, only in runtime will we see if the code enters the if or not:

let abc = 1;
if (check()) {
   abc = "abc";
}
console.info(`abc length: ` + abc.length);

function check() {
   return getRandomInt(2) == 1;
}

function getRandomInt(max) {
   return Math.floor(Math.random() \* Math.floor(max));
}
Enter fullscreen mode Exit fullscreen mode

Here’s the compiled code:

var b = 1;

1 == Math.floor(2 \* Math.random()) && (b = "abc");

console.info("abc length: " + b.length);
Enter fullscreen mode Exit fullscreen mode

The conditional flow was analyzed and reprogrammed to one single line. Notice how Closure Compiler checks for the first condition preceded by an && operator. This operator says that only if the first condition is true will the second be executed. Otherwise, if our random number is not equal to 1, then b will never receive "abc" as value.

How about a multi-conditional if?

if(document == null || document == undefined || document == ‘’)
   console.info(`Is not valid`);
Enter fullscreen mode Exit fullscreen mode

Take a look at the result:

null != document && void 0 != document && “” != document || console.info(“Is not valid”);
Enter fullscreen mode Exit fullscreen mode

The conditional ifs were nested. That’s the default behavior of Closure Compiler: it’ll always try to shrink as much as possible, keeping the code small yet executable.

External references and annotations

It can be potentially dangerous and unproductive to always review the code that was compiled. Also, what happens if you want to keep the check() function globally available to other JavaScript files? There are some tricks here, like Google’s suggestion of attaching the function to the global window object:

window.check = c;
Enter fullscreen mode Exit fullscreen mode

Here, we would receive the following output:

var a = require("./helper.js"), b = 1;
c() && (b = "abc");
console.info("abc length: " + b.length);
null != document && void 0 != document && "" != document || console.info("Is not valid");
function c() {
return 1 == Math.floor(2 \* Math.random());
}
window.check = c;
Enter fullscreen mode Exit fullscreen mode

The typing system of what Closure Compiler checks is the heart of the whole thing. You can annotate your code to be more typed, which means Compiler will check for wrong usages based on your annotations.

Let’s take the example of the getRandomInt() function. You need your param to be a number in all cases, so you can ask Closure Compiler to check if a caller is passing anything different than that:

function getRandomInt(/\*\* number \*/ max) {
return Math.floor(Math.random() \* Math.floor(max));
}
window['getRandomInt'] = getRandomInt;

getRandomInt('a');
Enter fullscreen mode Exit fullscreen mode

This would return the following:

Even though the file is always compiled for warnings, you can have a taste of what’s going on with your code, mainly for codes that are updated by many people.

Another interesting feature is the export definitions. If you decide you don’t want something renamed through the Advanced option, you can also annotate your code with:

/\*\* @export \*/
Enter fullscreen mode Exit fullscreen mode

In this way, the output code won’t be renamed.

Conclusion

There are so many different scenarios you can use to test the power of this tool. Go ahead, take your own JavaScript code snippets and try them with Closure Compiler. Take notes on what it generates and the differences between each optimization option. You can also get any external JavaScript plugin/file and import it to the web service for testing purposes.

Remember, you can also run it from the command line or even from your building system like Gulp, Webpack, and other available plugins. Google also provides a samples folder at their official GitHub repository, where you can test more of its features. Great studies!

Plug: LogRocket, a DVR for web apps

https://logrocket.com/signup/

LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

Try it for free.


Top comments (0)