Using Automator and Closure Compiler to painlessly minify, optimize JavaScript

As many people know, one of my hobbies is web development. But I am not really a huge fan of designing and deploying plain websites (thus, ironically, the church website tends to languish). Rather, I enjoy pushing the envelope of what I can do – and sometimes, what is even possible on the web. To that end, I write a great deal of JavaScript. And as the web has advanced and what can be done with JavaScript has exploded over the years, the amount of JavaScript code which is incorporated into any project has grown exponentially. It is no longer uncommon for even relatively simple web applications to have a couple thousand lines of JavaScript under the hood. In the olden days (i.e., before broadband), this would have been unthinkable. With the advent and proliferation of cable, DSL, and other high speed connections, though, applications like GMail and Facebook became common. And as it was assumed that the bandwidth bottleneck would be perpetually expanded, a lot of web applications became bloated with tens, hundreds, even thousands of lines of bad code because developers assumed that the connection was their greatest obstacle, and it was largely mitigated.

In recent years, however, a number of factors have come together to rekindle an interest in streamlined code. First, the advent of mobile applications, which often, even today, rely on creaky 3G cell connections, has necessitated a re-examination of what really needs to be sent to the browser. Second, data caps, which started in the mobile sector but have since migrated even to landline broadband connections, should be compelling developers of even desktop web applications to reconsider bloated code. And third, perhaps most significantly, with the broadened pipeline through which data can reach the user, the biggest obstacle to web application performance has moved into the browser itself.

Thus, companies like Google, Microsoft, Yahoo! and more have, in recent years, developed a near obsession with high performance JavaScript which is loaded fast and executed even faster. In fact, it has been widely publicized that Google even wrote site performance into its search algorithms, thus favoring websites which loaded and rendered faster than the competition.

So it behooves the web developer to utilize every trick available to eek out every last bit of performance from a chunk of JavaScript.

Enter Closure Compiler. Developed originally by Google for use on internal projects (e.g., Google Maps, AJAX APIs), Closure (not to be confused with the Google-developed JavaScript library of the same name), Closure has been deliberately designed to do two things really, really well: (1) minify JavaScript code so that it eats up as little bandwidth as possible traveling from server to client and (2) optimize JavaScript so that it runs as efficiently as possible in the browser.

Now, Closure is not the first JavaScript compiler. Nor will it be the last. But for the moment, when it comes to doing these two things, it is just about the best. And the really cool thing is that Google makes it available to hacks like me for free and in a variety of flavors, all of which led me to start using Closure Compiler extensively in my projects about two years ago.

Until a couple of months ago, though, it was a huge hassle. See, Google makes Closure available as a web app, via an API, as a standalone compiler, or even as part of Chrome’s developer tools. But it was always a hassle to copy-paste my code into a web app and save the result; switch to the terminal and remember all the CLI options; or even just set up the dev tools to utilize the compiler. I wanted something simpler, and a couple of months ago, when I shut down for a day to wipe my MacBook and re-install everything from scratch, I had an opportunity to realize that something.

Mac OS X has a phenomenal resource called Automator which allows users to build custom workflows, tools, and much more. I wanted to build a Finder service so that, when I was ready to compile a batch of JS, I had only to right-click on the file in the Finder and select “Compile JS” in the context menu. So using a little bit of Perl (you can use just about whatever language you want; I chose Perl because it’s still the best programming language since sliced bread) and the Closure Compiler REST API (documentation: https://developers.google.com/closure/compiler/docs/gettingstarted_api), I did exactly that. And here is what I came up with (notes after the code):

require LWP::UserAgent;
use JSON;
use Data::Dumper;
my %config = (
'SERVICE_URL' => 'http://closure-compiler.appspot.com/compile',
'COMPILATION_LEVEL' => 'ADVANCED_OPTIMIZATIONS',
'OUTPUT_INFO' => 'compiled_code',
'OUTPUT_FORMAT' => 'json',
);
# print "Starting...\n";
my $ua = new LWP::UserAgent;
foreach(@ARGV){
# check the filename to make sure it ends with .js
if(($_ !~ /\.js$/i) || (!-e $_)){
next;
}

# print "Source: $_\n";
# open and read the source file
open JS, '<', $_;
my @js_code = <JS>;
my $js_code = join("\n", @js_code);
#print $js_code . "\n";

# send for the compilation
my $response = $ua->post( $config{'SERVICE_URL'}, {
'compilation_level' => $config{'COMPILATION_LEVEL'},
'output_info' => $config{'OUTPUT_INFO'},
'output_format' => $config{'OUTPUT_FORMAT'},
'js_code' => $js_code,
});


# deal with the response
if($response->is_success){ # if request was successful
my $json;
eval{ # attempt to parse the JSON response
$json = decode_json($response->decoded_content);
};
if($@ ne ''){ #if the json parsing threw an error
print "JSON error: $@\n";
} elsif(defined($json->{'compiledCode'})) { # check if we have good compiled code
# generate the destination filename
my $minified_filename = $_;
$minified_filename =~ s/(\.js)$/\.min$1/i;

# write the compiled file
open COMPILED, '>', $minified_filename;
print COMPILED $json->{'compiledCode'};
close COMPILED;
} else { # fall back to dumping the json to stdout so we can see what went wrong
my $json_filename = $_;
$json_filename =~ s/(\.js)$/\.json$1/i;
#write the json
open JSON, '>', $json_filename;
print JSON Dumper($json);
close JSON;
}
} else { # if the request failed, print the status to stdout
my $error_filename = $_;
$error_filename =~ s/(\.js)$/\.error$1/i;
#write the error
open ERR, '>', $error_filename;
print ERR $response->status_line;
close ERR;
}
}
#print "Done.\n";
exit(0);

Now, if you know much about programming, most of that should be fairly self-explanatory. But there are a couple of notes that I should make. First, to utilize this code, you have to open Automator, create a new Service, add a “Run Shell Script” block to the workflow, and select usr/bin/perl for the shell. Then just copy-paste the code into that shell script block, save the workflow, and you’re of to the races. And second, it’s not quite perfect yet. For now, the script will simply work through the file(s) selected, compiling each in turn. Eventually, I want to improve it to allow me to compile an entire list of files into a single JS file. I just haven’t gotten that far.

But for now, this is quite adequate for what I want to do. And my theory is that it might help someone else, too. So have at it. And happy coding!

Advertisements

1 Response to “Using Automator and Closure Compiler to painlessly minify, optimize JavaScript”


  1. 1 Rita March 9, 2014 at 3:03 pm

    Just wanted to thank you on your post of the 31 conquered kings in Joshua . What a wonderous God we serve. Your passion for insight is encouraging and more importantly anointed. May you continue to follow HIS leading as you reach many for the kingdom! May 2014 be filled with HIS continued Glory on your life.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s





%d bloggers like this: