- Sunday, June 27, 2010
An Introduction to Google Closure
Late last year, Google released Closure Tools, a trio of open source technologies aimed at developers writing large scale JavaScript applications. Included in the initial release were Closure Compiler, a sophisticated JavaScript compiler, Closure Library, a massive library of JavaScript code designed for use with the Compiler, and Closure Templates, a templating system implemented in both JavaScript and Java.
The sales pitch was certainly compelling. Closure had been used internally at Google since 2005, with contributions from over 400 engineers. This was the technology that powered Gmail, and several other high profile Google applications (Docs, Reader, Calendar, Maps and Wave amongst them). Google pitched it as their "standard library" for JavaScript-based applications. It would have be reasonable to expect that the hype meter would have been off the charts.
Despite this, the response was rather tepid. The announcement spread broadly, but the standard response seemed to be "so what?". Not much has changed in the six-plus months since its release, either. In the past 30 days, there have been a grand total of three questions tagged with google-closure on Stack Overflow. During the same time period, there have been 3,161 questions about jQuery.
So, what's the problem? Is it the confusing name (a closure is a JavaScript language feature)? "Anti-hype" due to the fact that it was released by Google? Exhaustion from the rapid pace of new JavaScript library releases? A lack of conceptual documentation? Or, is it just too complex to actually use?
I think all of these may be factors to some degree, but the real reason is much simpler: it's simply not the right tool for a lot of applications. That's not intended as a criticism of Closure Tools, it's just that it was designed with far different goals than other popular libraries like, say, jQuery and Prototype.
For small applications, blogs, design galleries, or static content sites which just need some simple form validation, Closure is probably overkill. It is a perfectly suitable solution, as the Closure Compiler enables an efficient footprint for applications of any size. However, the learning curve is quite steep, there are far fewer examples, and you need to find a way to integrate the compiler into your workflow. With a library like jQuery, you just add a script reference (if using a CDN, you don't even need to upload it to your server) and start coding away.
So aside from large Web applications from Google, who is Closure for? Having studied Closure exhaustively for the past few weeks, I would suggest that it is an excellent option to consider for any "medium-plus" sized application, regardless of the back-end "technology stack". These applications will almost always involve multiple developers, and are likely to contain at least 100 kilobytes of non-library source code. If there is no build system in place to combine scripts, an average page in an application of this size probably references 5 to 10 external scripts, if not more.
If your application has reached this level of complexity (or you project it to), this is a good starting point where the benefits of Closure start to become significant. The impact of Closure Compiler on your code's execution speed and size (and, if your scripts aren't being combined, HTTP request overhead) will be significant, and you're likely to be in position to benefit from a large portion of Closure Library as well. As your application grows, so do the benefits. Let's take a closer look at the two primary components of Closure Tools (we'll not be covering Closure Templates).
Closure Compiler
The Closure Compiler is the arguably the flagship component of Closure Tools. The Compiler supports two primary optimization modes: "minification" (whitespace-only or simple) and "advanced compilation".
A JavaScript minifier is nothing new. There have been high quality minifiers available for several years. A JavaScript compiler isn't even necessarily new, as compilers from other languages to JavaScript have also been floating around for some time. What is unique about Closure Compiler is that it is a JavaScript-to-JavaScript compiler, capable of performing optimizations to JavaScript code that were previously unheard of.
Closure Compiler can be used as a standalone component against an existing JavaScript codebase, or in concert with Closure Library, which has been explicitly designed for maximum optimization when used with the compiler. It's easier to understand the power of these advanced compilation features by way of a simple example. Consider the following script:
function unused() { alert('i am only ever called by a function that is uncalled'); } function unused2() { unused(); alert('i am never called'); } function hello(name) { alert('Hello, ' + name + '!'); } var hi = hello; hi('friend');After running this through the compiler in advanced mode (which you can easily try for yourself), we're left with the following:
alert("Hello, friend!");The result speaks for itself. The compiler eliminated dead code, inlined functions, optimized string concatenation, and left us with a significantly smaller but functionally identical result.
Hey, where'd my functions go?
It might seem strange or abitrary that in the examples above, the unused functions simply disappeared in the output. The compiler considers this removal perfectly safe and reasonable because it assumes that you have provided it with the entire source code for your application. If you want one of your functions to be available outside of the context of your core JavaScript code (say, from a
scriptelement in an HTML page), you need to explicitly tell the compiler to export it. See the compiler documentation for details.This isn't to say that there won't be any function calls in your final compiled output if you don't export anything. Closure Compiler will only inline code when it considers it appropriate. When not inlining, functions will typically be renamed. If the compiler chose not to inline our
hellofunction, for example, the output would look something like the following:function a(b){alert("Hello, "+b+"!")}a("friend");I'm supposed to debug that?
If your reaction to the above code is one of terror, you are probably not alone. Anyone who has written large systems in JavaScript knows that debugging code is an inevitable part of the process. The significant optimizations provided by Closure Compiler might make your code smaller, but they also make it extremely difficult to map back to the original code. If you experience a runtime failure that only happens in the compiled code, what do you do?
Fortunately, Closure Compiler is capable of creating a source map, which enables areas of the compiled code to be definitively traced back to the original source from which it was compiled. Even better, you don't have to work with the source map file itself, as Google provides Closure Inspector, a Firebug extension (yes, Firefox only for now) which integrates into your standard debugging experience.
Is that it?
While Closure Compiler is an excellent tool for optimizing code size and execution speed, that's not the only value it provides. It also supports a huge number of JSDoc annotations which enable it to help you find bugs in your code (these also allow the compiler to even better optimize your code). For example, we could redefine our
hellofunction from above with a type annotation as follows:/** @param {string} name */ function hello(name) { alert('Hello, ' + name + '!'); }Here, we have told the compiler that we expect a single parameter of type
string. Now, let's add some problematic calls to the function:hello(); hello(3.14);
Now, when we compile this code, the compiler issues the following warnings.
JSC_WRONG_ARGUMENT_COUNT: Function hello: called with 0 argument(s). Function requires at least 1 argument(s) and no more than 1 argument(s). at line 5 character 5 hello(); ^ JSC_TYPE_MISMATCH: actual parameter 1 of hello does not match formal parameter found : number required: string at line 6 character 6With large programs in particular, these warnings can help you quickly isolate hard to find bugs that would otherwise only be visible at runtime. The only downsides are the need to add the JSDoc tags to your code comments (though these are also quite useful as documentation when reading the code), and the need to "cast" from time to time, as below:
// Explicitly "cast" a {Node} to an {Element} var element = /** @type {Element} */ (node); goog.dom.setTextContent(element, 'success!');Closure Library
Closure Library is a massive library of JavaScript code optimized for use with Closure Compiler. In theory, pairing it with the Compiler is optional. And in fact, this is an incredibly important feature in development environments, as it makes the compilation step optional and dramatically speeds some stages of development. In production, however, use of the compiler is not optional, as the uncompiled library code is very large, and the strategy used to include the files is not designed to be efficient.
So just how big is Closure Library? The following chart provides some indication. This is the uncompressed, non-minified JavaScript on-disk file size of a few popular libraries (excluding test code), as reported by du. The file sizes include comments and indentation, so it is not meant to be indicative of the actual number of "lines of code". All libraries shown are the current stable versions at the time of this writing:
So yes, the library is indeed massive. But is it any good? For starters, there are no "versions" of Closure Library. The code is simply updated periodically in a public Subversion repository, which is fed from Google's internal source control system. That doesn't mean that it's "prerelease" (or "beta") quality, it simply means that Google is confident enough in the quality of the code that the latest release is always considered to be (in their own words) "very stable". Many parts of Closure Library are actively in production at Google, and it comes with a massive suite of unit tests. At the end of the day, it's up to you and or your organization to determine if it is "stable" enough for your needs. Having spent a large amount of time in various modules of the Closure Library code recently, my personal opinion is that it is uniformly well designed, well documented, and very well written.
So what's in there, taking up all of that space? A surprising amount is in comments, believe it or not. Closure Library is exhaustively documented, and most of the documentation is inline. This makes browsing the code a great strategy for learning more about Closure. As far as the code itself is concerned, some portion of the library is dedicated to things that would be broadly useful in any JavaScript project, including those that do not target Web browsers (array, crypt, date, math, string, and more). A bigger portion of the code is exclusively designed for JavaScript applications targeting browsers (dom, editor, fx, history, style, ui, useragent, and more). A good starting point for understanding what's available in Closure Library is the demos folder (index), where you'll find examples for things as basic as event handling, and as exotic as a popup "emoji" picker. Lastly, Closure Library also includes selected third party code. For example, Dojo's Query implementation is exposed as
goog.dom.query.Closure Library code is modular, with related code organized into "namespaces". This organization is well suited for large scale projects with large teams. Another advantage of this namespacing strategy is that Closure Library is extremely unlikely to interfere with existing libraries, as all of the library code is organized under a single global symbol (
goog). Note that this does not necessarily mean that other libraries will not impact the behavior of Closure Library. Existing libraries that pollute the global namespace unpredictably, or modify the prototypes of built-in JavaScript types could cause Closure Library code to behave undesirably (as it would with any other third party library). To put it more succinctly, Closure Library plays well with others, provided that they too play well with others.The downside to the namespacing strategy is that your code ends up being more verbose. There are many ways to control for this. You might decide that your application won't need to interoperate with other libraries, and can safely add aliases for commonly used functions in the global namespace. For example, if
goog.dom.getElementis too much typing for your taste, you can selectively trade off namespacing by simply adding a global alias as$:goog.exportSymbol('$', goog.dom.getElement);There are, of course, risks to modifying the global namespace in this fashion, so it's undoubtedly a good thing that the Closure Library designers left the decision to us.
Closure Library vs. jQuery, Prototype
So if we don't modify the global namespace, what does Closure Library code actually look like? This is probably easier to understand by example. Following are ten simple code examples comparing the syntax of Closure Library to two other popular libraries: jQuery and Prototype. I created these examples by looking through a few codebases built on top of the libraries, choosing the most popular examples which corresponded to functionality common to all three. My intent is not to make one library look better than the other, but rather to compare the style and syntax. The examples are functionally equivalent.
Comparison #1: Get a single element by ID
jQueryvar title = $("#title").get(0); // - or - var title = $("#title")[0];Prototypevar title = $("title");Closure Libraryvar title = goog.dom.getElement("title");Comparison #2: Get an array of elements by ID
jQueryvar elements = $("#title,#description,#footer");Prototypevar elements = $("title", "description", "footer");Closure Libraryvar elements = goog.array.map([ "title", "description", "footer" ], goog.dom.getElement);Comparison #3: Find an element's closest ancestor by element name
jQueryvar list = $("#list\\.item").closest("ul")[0];Prototypevar list = $("list.item").up("ul");Closure Libraryvar list = goog.dom.getAncestorByTagNameAndClass( goog.dom.getElement("list.item"), goog.dom.TagName.UL);Comparison #4: Hide all paragraphs which are immediate descendants of div elements
jQuery$("div > p").hide();Prototype$$("div > p").invoke("hide");Closure Librarygoog.array.map(goog.dom.query("div > p", null), function(e) { goog.style.showElement(e, false); });Comparison #5: Get an element's text content
jQueryvar text = $("#decorateme-1").text();Prototypevar text = $("decorateme-1").innerHTML.stripTags();Closure Libraryvar text = goog.dom.getTextContent(goog.dom.getElement("decorateme-1"));Comparison #6: Retrieve the cumulative offset of an element
jQueryvar offset = $("#positioning").offset(); console.log("left: " + offset.left + "px top: " + offset.top + "px");Prototypevar offset = $("positioning").positionedOffset(); console.log("left: " + offset.left + "px top: " + offset.top + "px");Closure Libraryvar rect = goog.style.getPageOffset(goog.dom.getElement("positioning")); console.log("left: " + rect.x + "px top: " + rect.y + "px");Comparison #7: Create a DOM element and append to the current document body
jQuery$("<div class=\"created\" id=\"createdDiv\">content</div>").appendTo("body");Prototype$(document.body) .insert(new Element("div", { "class": "created", id: "createdDiv" }) .update("content"));Closure Librarygoog.dom.appendChild( document.body, goog.dom.createDom("div", { className: "created", id: "createdDiv" }, "content"));Comparison #8: Add, remove and toggle CSS classes
jQueryvar d1 = $("#decorateme-1"); var d2 = $("#decorateme-2"); d1.toggleClass("aaa").removeClass("bbb"); d2.addClass("bbb");Prototypevar d1 = $("decorateme-1"); var d2 = $("decorateme-2"); d1.toggleClassName("aaa").removeClassName("bbb"); d2.addClassName("bbb");Closure Libraryvar d1 = goog.dom.getElement("decorateme-1"); var d2 = goog.dom.getElement("decorateme-2"); goog.dom.classes.toggle(d1, "aaa"); goog.dom.classes.remove(d1, "bbb"); goog.dom.classes.add(d2, "bbb");Comparison #9: Create a manual hover effect using mouse events
jQuery$("h1:first") .mouseover(function() { $(this).css("color", "red"); }) .mouseout(function() { $(this).css("color", ""); });Prototypefunction setColor(e, color) { e.element().setStyle({ color: color }); } var h1 = $$("h1")[0]; h1.observe("mouseover", setColor.bindAsEventListener(this, "red")); h1.observe("mouseout", setColor.bindAsEventListener(this, ""));Closure Libraryfunction setColor(color, e) { e.target.style.color = color; } var h1 = goog.dom.getElementsByTagNameAndClass("h1")[0]; goog.events.listen(h1, goog.events.EventType.MOUSEOVER, goog.partial(setColor, "red")); goog.events.listen(h1, goog.events.EventType.MOUSEOUT, goog.partial(setColor, ""));Comparison #10: Update the contents of a DOM element using XmlHTTPRequest response content
jQuery$.get("hello.txt", null, function(data) { $("#ajaxResponse").html(data); });Prototypenew Ajax.Updater("ajaxResponse", "hello.txt", { method: "get" });Closure Librarygoog.net.XhrIo.send("hello.txt", function(e) { goog.dom.getElement('ajaxResponse').innerHTML = e.target.getResponseText(); });In general, the Closure Library examples are more verbose than the equivalents written using jQuery and Prototype. After being run through the compiler, they will be more compact at runtime, but there is some extra visual overhead to deal with when reading and writing Closure Library code. Of course, as discussed above, Closure Compiler helps a great deal when writing this code, as it can find bugs before you run them in the browser.
All of these libraries are quite easy to work with for such trivial examples as those above. The real benefits of Closure are found in larger codebases. If you're working on such a codebase, or are about to, I strongly suggest giving Closure Tools a look.
Comments
- Monday, June 28, 2010 10:32:01 PM by Martin S.
- Tuesday, June 29, 2010 7:42:37 AM by markvgtiThanks for taking the time to write this. There is too little material related to Google's Closure tools available online — every little bit helps (and this is a good introduction).
I use a lot of Google libraries/APIs, and even I didn't know about Closure Tools till I noticed a presentation on it at the recent Google I/O 2010 conference. - Friday, September 03, 2010 8:18:05 AM by mbt shoes
- Friday, September 03, 2010 8:41:33 AM by ecco shoes
- Saturday, September 04, 2010 8:38:04 AM by cheap nike dunkswe offer ghd hair straighteners online,
happy shopping mbt shoes
and cheap nfl jerseys, nike dunks and air max 95 are hot sell recenltly,
welcome to come to our website, mlb jerseys for world cup
and ghd iv styler enjoy much discount wholesale nfl jerseys here
now join us, cuold enjoy 75% discount coupon code, please dont hestitate, take actions. - Monday, September 06, 2010 9:20:41 AM by cheap nike dunksthank younike shox
We use GWT instead of Closure. When we started developing Closure was freshly released and since it's used in Google Docs, Gmail, etc. we were strongly considering it.
In the end there were a few features in GWT that made it worth to write Java instead of Javascript:
* UiBinder (http://code.google.com/p/google-web-toolkit/wiki/UiBinder) Which is Templating + CSS Sprites + much more
* Deferred Binding (http://code.google.com/webtoolkit/doc/1.6/FAQ_Client.html#What_is_Deferred_Binding?) GWT automatically compiles multiple .js files, each for a different browser/locale combination. It's extremly easy to add another parameter to it, like - in our case - iPad support. If you visit letsannotate.com on your iPad we switch out the View Classes to provide a touch optimized experience.
AFAIK Closure has no equivalent to deferred binding
* MVP architecture: When we were evaluating GWT vs. Closure it became clear that Patterns and Best Practices for developing large applications are much more readily available for GWT than for Closure. (e.g. http://code.google.com/events/io/2009/sessions/GoogleWebToolkitBestPractices.html)
While Closure was used to write GMail, there seems to be no agreed upon best practice for navigation, MVP separation and so on. This is where GWT shines the most. It's incredibly easy to architect an application that's capable of growth, simply because Google tells you how to do it. While the API reference for Closure Library is comprehensive, I didn't find any such documentation.
All in all I'd currently recommed GWT for Web App development over Closure, but that might change in a year or two if Google (or someone else) releases similiar best practice code for Closure.