Last week Google submitted their own entry into the JavaScript framework race by open sourcing Closure, the library that powers google docs, maps, mail and more.
I couldn’t resist spending some time playing with the closure tools, especially the dojo-like module dependency resolver (a.k.a goog.provide and goog.require) and the closure compiler. It was a bit tricky at first because the example “applications” from the tutorial are pretty contrived and there is no information on using closure with existing JavaScript libraries.
Anyway, I thought I would share how I hooked up Closure to an existing JavaScript library (the one that powers http://gvr.carduner.net).
Step 1 – get the Closure Library.
Closure is actually a set of three tools: a JavaScript Library, a JavaScript compiler, and a templating language. The dependency resolution tools are part of the library, so you should check that out first. At the moment, the closure library can be checked out from the project subversion repository:
svn checkout http://closure-library.googlecode.com/svn/trunk closure
If you are just interested in using the dependency resolution tools and not the entire framework, you just need two files: base.js and calcdeps.py. The full checkout can take a while as they’ve included all the generated api documentation, which you can also read online.
Step 2 – Instrument your JS library code
If you already have your JavaScript code split up into “modules” that live in different files, adding the dependency code is pretty easy. In my case, I wanted to use Closure with gvr.carduner.net, which already has a set of interdependent JavaScript files like so:
gvr.core.js gvr.renderer.js gvr.web.client.js jquery.history.js gvr.js gvr.robot.js gvr.web.tests.js launcher.js gvr.lang.js gvr.runner.js gvr.world.js gvr.lang.parser.js gvr.tests.js gvr.world.parser.js
Open up each JavaScript file, and add declarations to the top about what each file provides and requires. For example, gvr.runner.js depends on gvr.robot.js and gvr.core.js. In turn it provides the gvr.runner namespace. So at the top of gvr.runner.js I added the following:
// gvr.runner.js
goog.require("gvr.core");
goog.require("gvr.robot");
goog.provide("gvr.runner");
Then of course I had to add the provide statements to gvr.core.js and gvr.robot.js, as in:
// gvr.core.js
goog.provide("gvr.core");
// gvr.robot.js
goog.require("gvr.core");
goog.provide("gvr.robot");
The goog.provide statements will actually create the object namespace you pass in, so the following would be valid:
goog.provide("foo.bar.baz");
foo.bar.baz.blah = "blah";
foo.somethingElse = "somethingElse";
In fact, if you try to create the namespaces later, the compiler will throw warnings/errors at you. So if you have any code that looks like the following, you should remove it.
var foo = foo || {}; // DELETE THESE LINES
foo.bar = foo.bar || {}; // AS THEY CONFLICT WITH goog.provide("foo.bar.baz");
foo.bar.baz = foo.bar.baz || {};
Once you have added all the right goog.require and goog.provide statements to your code, you’ll need to generate a dependency graph using the calcdeps.py script.
Step 3 – Build the dependency graph with calcdeps.py
The dependency resolution system uses a pre-generated dependency graph to link namespaces like foo.bar.baz to their corresponding JavaScript files and the files they require. This is all stored in a file called deps.js. The one for the closure library can be found in trunk/closure/goog/deps.js. Here are the first few lines to give you an idea of what this looks like:
// This file has been auto-generated by GenJsDeps, please do not edit.
goog.addDependency('array/array.js', ['goog.array'], []);
goog.addDependency('asserts/asserts.js', ['goog.asserts'], []);
goog.addDependency('async/conditionaldelay.js', ['goog.async.ConditionalDelay'], ['goog.Disposable', 'goog.async.Delay']);
goog.addDependency('async/delay.js', ['goog.Delay', 'goog.async.Delay'], ['goog.Disposable', 'goog.Timer']);
// ... this goes on for quite a while ...
In order for closure’s dependency mechanism to know about your libraries, you need to create a deps.js file of your own. This can be done with the calcdeps.py script you should have downloaded by now. If you checked out the entire closure library source, the calcdeps.py script can be found in trunk/closure/bin/calcdeps.py.
The calcdeps.py script must be run from the directory where your files will be served, as it stores the relative file paths in the generated deps.js file, which is in turn used to build urls to all your JavaScript files. For example, my application’s directory structure looks like this:
gvr-online/
closure/ # this is the trunk checkout of closure
closure/
bin/
calcdeps.py # I'll use this script to generate my own deps.js
goog/ # this is the closure library source, including deps.js and base.js
app/
src/
ui/ # This directory is exposed to the web as http://localhost:8080/ui/
lib/ # this is where my javascript library lives
closure/
goog/ # this is a symlink to the closure/closure/goog/ directory at the top level
The gvr-online/app/src/ui/ directory is what gets exposed through the web, so the calcdeps.py script should be run from that directory. Here is the command I used to run it:
cd app/src/ui/ && python calcdeps.py -p lib -o deps > deps.js
The -p lib option tells calcdeps.py to search in app/src/ui/lib/ for js files with goog.provide and goog.require statements. The -o deps option tells calcdeps.py to generate a dependency graph file, which gets saved to app/src/ui/deps.js. If you are using the rest of the closure library, and not just the dependency stuff, you will need to add an extra -p closure argument.
With that done, we can try this out in a browser.
Step 4 – Instrument your HTML
Next you’ll need to add the closure hook to your html files. In my project, there is just one html file, index.html. If you just include the base.js file like the closure tutorial suggests, you will not be able to goog.require your own library modules. You have to tell base.js where to find your library, and where to find the deps.js file. I added the following to the <head> section of index.html:
<script type="text/javascript">
var CLOSURE_NO_DEPS = true;
var CLOSURE_BASE_PATH = "/ui/"; //this is the directory where I ran calcdeps.py
</script>
<script type="text/javascript" src="/ui/closure/goog/base.js"></script>
<script type="text/javascript" src="/ui/deps.js"></script>
The CLOSURE_NO_DEPS option tells base.js that it shouldn't load closure's deps.js file and that we will handle the dependency graph loading ourselves. The CLOSURE_BASE_PATH setting is a prefix that should be added to the paths specified in the deps.js file. Next we load base.js which defines the goog.require and goog.provide functions. And finally, we load the deps.js file that was generated in the last step.
With these files loaded, you can now goog.require any of your modules. For example, at the bottom of my index.html file, I can have this:
<script type="text/javascript">
goog.require("gvr.web.client");
</script>
<script type="text/javascript">
client = gvr.web.client.newClient();
client.getUser(function(user){ alert("Hi "+user.nickname); });
</script>
Closure - pun intended
So far, I think closure's dependency resolutions tools are my favorite. It's relatively simple (you only need two files really) and doesn't require you to structure your code in any particular directory hierarchy (unlike Dojo last I checked). My only wish at this point is for calcdeps.py and base.js to have a mechanism for registering third party libraries like jQuery without adding goog.provide() to the top of their files. You could add other libraries to the end of the generated deps.js, but that isn't very maintainable and won't work with the closure compiler (I think?). I haven't yet gotten to using the closure compiler with my code, so more on my experience with that later.


