Derek Slager

  • Wednesday, June 30, 2010

    Introducing Closure Snippets

    I just posted a first version of Closure Snippets, an unofficial collection of snippets for Google's Closure Library. As demonstrated in my recent overview of Closure Tools, Closure Library has a tremendous amount of value, but it comes at the cost of brevity.

    Fortunately, we have plenty of tools for making things easier and faster to write. One of my favorites is YASnippet, an Emacs extension which offers TextMate-inspired support for snippets. This forms the basis for Closure Snippets. The snippets in the package are designed to run when js2-mode is active.

    I put together a quick screencast to show Closure Snippets in action. As in my previous Emacs screencast, I've used a slightly modified but mostly standard Emacs configuration:

    Click to Play

    Want to try it for yourself? Grab the source, and check out the README for configuration instructions.

    2 CommentsPosted at 7:53 AM to Categories: Emacs Google Closure JavaScript
  • 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 script element 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 hello function, 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 hello function 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 6

    With 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:

    Comparison Chart

    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.getElement is 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

    jQuery
    var title = $("#title").get(0);
    // - or -
    var title = $("#title")[0];
    Prototype
    var title = $("title");
    Closure Library
    var title = goog.dom.getElement("title");

    Comparison #2: Get an array of elements by ID

    jQuery
    var elements = $("#title,#description,#footer");
    Prototype
    var elements = $("title", "description", "footer");
    Closure Library
    var elements =
        goog.array.map([ "title", "description", "footer" ],
                       goog.dom.getElement);

    Comparison #3: Find an element's closest ancestor by element name

    jQuery
    var list = $("#list\\.item").closest("ul")[0];
    Prototype
    var list = $("list.item").up("ul");
    Closure Library
    var 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 Library
    goog.array.forEach(goog.dom.query("div > p", null),
                       function(e) { goog.style.showElement(e, false); });

    Comparison #5: Get an element's text content

    jQuery
    var text = $("#decorateme-1").text();
    Prototype
    var text = $("decorateme-1").innerHTML.stripTags();
    Closure Library
    var text =
        goog.dom.getTextContent(goog.dom.getElement("decorateme-1"));

    Comparison #6: Retrieve the cumulative offset of an element

    jQuery
    var offset = $("#positioning").offset();
    console.log("left: " + offset.left + "px top: " + offset.top + "px");
    Prototype
    var offset = $("positioning").positionedOffset();
    console.log("left: " + offset.left + "px top: " + offset.top + "px");
    Closure Library
    var 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 Library
    goog.dom.appendChild(
        document.body,
        goog.dom.createDom("div", { className: "created", id: "createdDiv" },
                           "content"));

    Comparison #8: Add, remove and toggle CSS classes

    jQuery
    var d1 = $("#decorateme-1");
    var d2 = $("#decorateme-2");
    d1.toggleClass("aaa").removeClass("bbb");
    d2.addClass("bbb");
    Prototype
    var d1 = $("decorateme-1");
    var d2 = $("decorateme-2");
    d1.toggleClassName("aaa").removeClassName("bbb");
    d2.addClassName("bbb");
    Closure Library
    var 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", ""); });
    Prototype
    function 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 Library
    function 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); });
    Prototype
    new Ajax.Updater("ajaxResponse", "hello.txt", { method: "get" });
    Closure Library
    goog.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.

    9 CommentsPosted at 2:25 AM to Categories: Google Closure JavaScript

About

VP of Development at Appature, a Seattle-area company building a healthcare-focused relationship marketing platform.

Email Me

@derekslager

I'm hiring!

Recent

  • Introducing Closure Snippets
  • An Introduction to Google Closure
  • DVCS Myths
  • A DVCS Story
  • BCrypt.net - Strong Password Hashing for .NET and Mono

Categories

  • .NET
  • Baseball
  • C#
  • DVCS
  • Emacs
  • Google Closure
  • JavaScript
  • LINQ
  • Security
  • Windows
© Copyright 2013 Derek Slager