This section will walk through a simple example to demonstrate how all of the Closure Tools can be used together. Both Java 6 (the JDK) and Python 2.6.5 (or later) must be installed and available from the command line. A simple web search should yield appropriate instructions for installing Java and Python on your computer if you do not have them already.
Closure Library
The first step will exercise the Closure Library by creating a web page that loads the kernel of the Library and then some of its DOM utilities to insert the text
Hello World! into the page. Assuming you have all of the tools checked out as described in the previous section, first create a subdirectory of your closure directory named hello-world. Then create the following file named hello.js in your hello-world directory with the following Javascript code:goog.provide('example');
goog.require('goog.dom');
example.sayHello = function(message) {
goog.dom.getElement('hello').innerHTML = message;
};In the same directory, also create a file named
hello.html with the following HTML:<!doctype html>
<html>
<head>
<title>Example: Hello World</title>
</head>
<body>
<div id="hello"></div>
<script src="../closure-library/closure/goog/base.js"></script>
<script src="hello.js"></script>
<script>
example.sayHello('Hello World!');
</script>
</body>
</html>Open
hello.html in a web browser and you should see a page that says Hello World!. In this example goog.provide() and goog.require() are responsible for managing dependencies in Closure. If you examine this page in Firefox using Firebug (which you should have installed along with the Closure Inspector) and expand the <body> element, you can see that 12 additional Javascript files from the Closure Library have been loaded behind the scenes (Figure 1-3).These
<script> elements are used to load goog.dom and all of its dependencies. This may seem like a lot of code to load in order to do the equivalent of document.getElementById(), but remember that the focus is on minifying the size of the compiled code, not the source code.Closure Templates
Although “Hello World!” is a classic example, it is also fairly boring, so Closure Templates can be used to spice things up by making it easier to insert some HTML into the page. In the
hello-world directory, create a new file named hello.soy that will define a Closure Template:{namespace example.templates}
/**
* @param greeting
* @param year
*/
{template .welcome}
<h1 id="greeting">{$greeting}</h1>
The year is {$year}.
{/template}Assuming that
SoyToJsSrcCompiler.jar is in closure-templates/build/, run the following command from your hello-world directory in the Command Prompt on Windows or the Terminal on Mac or Linux:java -jar ../closure-templates/build/SoyToJsSrcCompiler.jar \
--outputPathFormat hello.soy.js \
--shouldGenerateJsdoc \
--shouldProvideRequireSoyNamespaces hello.soyThis should generate a file named
hello.soy.js with the following content:// This file was automatically generated from hello.soy.
// Please don't edit this file by hand.
goog.provide('example.templates');
goog.require('soy');
goog.require('soy.StringBuilder');
/**
* @param {Object.<string, *>=} opt_data
* @param {soy.StringBuilder=} opt_sb
* @return {string|undefined}
* @notypecheck
*/
example.templates.welcome = function(opt_data, opt_sb) {
var output = opt_sb || new soy.StringBuilder();
output.append('<h1 id="greeting">', soy.$$escapeHtml(opt_data.greeting),
'</h1>The year is ', soy.$$escapeHtml(opt_data.year), '.');
if (!opt_sb) return output.toString();
};Now update
hello.js so it uses the function available in hello.soy.js and includes another goog.require() call to reflect the dependency on example.templates:goog.provide('example');
goog.require('example.templates');
goog.require('goog.dom');
example.sayHello = function(message) {
var data = {greeting: message, year: new Date().getFullYear()};
var html = example.templates.welcome(data);
goog.dom.getElement('hello').innerHTML = html;
};In order to use
hello.soy.js, both it and its dependencies must be loaded via <script> tags in the hello.html file:<!doctype html>
<html>
<head>
<title>Example: Hello World</title>
</head>
<body>
<div id="hello"></div>
<script src="../closure-library/closure/goog/base.js"></script>
<script>goog.require('goog.string.StringBuffer');</script>
<script src="../closure-templates/javascript/soyutils_usegoog.js"></script>
<script src="hello.soy.js"></script>
<script src="hello.js"></script>
<script>
example.sayHello('Hello World!');
</script>
</body>
</html>Now loading
hello.html should look like Figure 1-4.
Although everything is working, maintaining these dependencies manually is very tedious. Fortunately, the Closure Library contains a Python script named
calcdeps.py that can be used to write dependency information into a file of Javascript code that Closure can use to dynamically load dependencies:python ../closure-library/closure/bin/calcdeps.py \
--dep ../closure-library \
--path ../closure-templates/javascript \
--path hello.soy.js \
--path hello.js \
--output_mode deps > hello-deps.jsThe
hello-deps.js file must be loaded to instruct Closure where to find the source code to support its dependencies. Now several of the <script> tags from the previous example can be replaced with a single <script> tag that loads hello-deps.js:<!doctype html>
<html>
<head>
<title>Example: Hello World</title>
</head>
<body>
<div id="hello"></div>
<script src="../closure-library/closure/goog/base.js"></script>
<script src="hello-deps.js"></script>
<script>
goog.require('example');
</script>
<script>
example.sayHello('Hello World!');
</script>
</body>
</html>To make sure that your dependencies are loading correctly, verify that loading
hello.html still looks like Figure 1-4 after replacing the explicit template dependencies with hello-deps.js.To change the content of the template, edit
hello.soy and run the java command used to generate hello.soy.js again. If hello.soy.js is not regenerated, then changes to hello.soy will not be reflected in hello.html.Closure Compiler
Now that we have created a giant heap of Javascript, it is time to shrink it down using the Closure Compiler. Even though
calcdeps.py is a utility from the Closure Library, it uses the Closure Compiler via its --compiler_jar argument. (Make sure that the Closure Compiler jar is available at ../closure-compiler/build/compiler.jar before running the script.) This command compiles hello.js and all of its dependencies into a single file named hello-compiled.js:python ../closure-library/closure/bin/calcdeps.py \
--path ../closure-library \
--path ../closure-templates/javascript/soyutils_usegoog.js \
--path hello.soy.js \
--input hello.js \
--compiler_jar ../closure-compiler/build/compiler.jar \
--output_mode compiled \
--compiler_flags="--compilation_level=ADVANCED_OPTIMIZATIONS" \
> hello-compiled.jsNow that
hello-compiled.js is available, create a new file named hello-compiled.html that uses it:<!doctype html>
<html>
<head>
<title>Example: Hello World</title>
</head>
<body>
<div id="hello"></div>
<script src="hello-compiled.js"></script>
<script>
example.sayHello('Hello World!');
</script>
</body>
</html>Unfortunately, loading
hello-compiled.html fails to display “Hello World!”. Instead, it yields a Javascript error: example is not defined. This is because example has been renamed by the Compiler in order to save bytes, but the final <script> tag still refers to example.sayHello(). The simplest solution is to make sure that example.sayHello() still refers to the original function after compilation by adding the following line to the bottom of hello.js:goog.exportSymbol('example.sayHello', example.sayHello);The Compiler must be run again on the updated version of
hello.js that includes the call to goog.exportSymbol(). Once hello-compiled.js has been regenerated, loading hello-compiled.html should appear as hello.html did in Figure 1-4 because hello-compiled.js should behave the same as hello.js does when it loads the Closure Library. However, now that hello-compiled.js is used, it is the only Javascript file that needs to be loaded to run hello-compiled.html.Looking at
hello-compiled.js, it may come as a surprise that it is a little over 2K when all it does is insert some HTML into a <div>, but that is because it contains a bit of bootstrapping code that will be necessary for all applications built with Closure.Most of that logic deals with browser and platform detection that can be eliminated by specifying the target environment at compile time. In the following code, additional flags are used to specify a Gecko-based browser running on Windows, which removes almost a kilobyte from the compiled code:
python ../closure-library/closure/bin/calcdeps.py \ --path ../closure-library \ --path ../closure-templates/javascript/soyutils_usegoog.js \ --path hello.soy.js \ --input hello.js \ --compiler_jar ../closure-compiler/build/compiler.jar \ --output_mode compiled \ --compiler_flags="--compilation_level=ADVANCED_OPTIMIZATIONS" \ --compiler_flags="--define=goog.userAgent.ASSUME_GECKO=true" \ --compiler_flags "--define=goog.userAgent.ASSUME_WINDOWS=true" \ --compiler_flags="--define=goog.userAgent.jscript.ASSUME_NO_JSCRIPT=true" \ > hello-compiled-for-firefox-on-windows.js
Although many of the compiler flags in this example will be discussed later in this book, one that is worth highlighting now is the one that specifies the use of
ADVANCED_OPTIMIZATIONS. This runs the Compiler in Advanced mode. For now, the only thing you have to know about Advanced mode is that it is able to dramatically optimize Javascript code written in a particular style, so many of the upcoming chapters on the Closure Library will cite Advanced mode as the reason why code is written in a certain way. After reading all of the chapters on the Closure Library, you will be able to fully appreciate all of the optimizations that the Compiler can perform in Advanced mode.Closure Testing Framework
Although the code appears to work fine when run in the browser, it is a good idea to create a unit test to ensure the correct behavior is preserved. In this case,
example.templates.welcome should be tested to ensure that its input is escaped properly. The first step is to create a web page named hello_test.html that will host the test:<!doctype html> <html> <head> <title>Unit Test for hello.js</title> <script src="../closure-library/closure/goog/base.js"></script> <script src="hello-deps.js"></script> <script src="hello_test.js"></script> </head> <body> <div id="hello"></div> </body> </html>
The next step is to create
hello_test.js, which contains the test code itself. This test verifies that the string '<b>greeting</b>' is properly escaped by the template (HTML escaping is a feature of Closure Templates that can be configured, but is enabled for all input, by default):goog.require('goog.testing.jsunit');
goog.require('example');
goog.require('goog.dom');
goog.require('goog.dom.NodeType');
var testHtmlEscaping = function() {
example.sayHello('<b>greeting</b>');
var greetingEl = goog.dom.getElement('greeting');
assertEquals('The <h1 id="greeting"> element should only have one child node',
1, greetingEl.childNodes.length);
assertEquals('The <h1 id="greeting"> element should only contain text',
goog.dom.NodeType.TEXT, greetingEl.firstChild.nodeType);
};Loading
hello_test.html in the browser will run the test and display the results as shown in Figure 1-5.Note how information is also logged to the Firebug console while the test is running to help with debugging.
Closure Inspector
Because the Closure Inspector is used to help with debugging compiled Javascript,
hello.js must be recompiled with a bug in it to demonstrate how the Inspector can be used. Though rather than create an actual bug, simply insert a debugger statement in hello.js as follows:goog.provide('example');
goog.require('example.templates');
goog.require('goog.dom');
example.sayHello = function(message) {
var data = {greeting: message, year: new Date().getFullYear()};
var html = example.templates.welcome(data);
debugger;
goog.dom.getElement('hello').innerHTML = html;
};
goog.exportSymbol('example.sayHello', example.sayHello);Because
hello.js has changed, hello-compiled.js must also be regenerated, but an additional flag, --create_source_map, must be supplied to the Compiler to generate the metadata used by the Inspector:python ../closure-library/closure/bin/calcdeps.py \
--path ../closure-library \
--path ../closure-templates/javascript/soyutils_usegoog.js \
--path hello.soy.js \
--input hello.js \
--compiler_jar ../closure-compiler/build/compiler.jar \
--output_mode compiled \
--compiler_flags="--compilation_level=ADVANCED_OPTIMIZATIONS" \
--compiler_flags="--create_source_map=./hello-map" \
> hello-compiled.jsIn addition to
hello-compiled.js, this will also create a file named hello-map. Although the data in hello-map may look like Javascript, only its individual lines are valid JSON, not the file as a whole.When a web page hits a
debugger statement while a Javascript debugger is enabled, such as Firebug, the program will suspend so that the program state can be inspected using the debugger. Reloading hello-compiled.html with the newly compiled version of hello-compiled.js in Firefox with the Script tab enabled in Firebug should suspend execution and look something like Figure 1-6.When the Closure Inspector is installed, there will be an extra tab in the Firebug debugger named “Source Mapping” where the path to the source map can be entered. Click “Open Local File” to select
hello-map from your computer. When you use the Closure Inspector for the first time, it will also ask you to choose a file where your settings for the Inspector can be saved. Something like inspector-settings.js is a reasonable default to use, as the contents of the file are JSON.Once you have set the source map in the Inspector, refresh
hello-compiled.html with the Firebug panel open and you should see the program stopped on the line that contains the debugger statement, as shown in Figure 1-7.With the “Stack” tab selected in Firebug, pushing the “Copy Stack” button adds the following contents to the clipboard, which identify the current stacktrace:
In file: file:///c:/closure/hello-world/hello-compiled.js
A | Line 2 | Original Name: goog.string.StringBuffer
In file: file:///c:/closure/hello-world/hello-compiled.html/event/seq/1
onload | Line 2 | Original Name: goog.string.StringBufferBy using the deobfuscated stack trace provided by the Closure Inspector, it is possible to look at the program and determine where the current point of execution is.

None of the Javascript libraries today has a more impressive track record than Google Closure, the tool suite used for Gmail, Google Docs, and Google Maps. Closure: The Definitive Guide has precisely what you need to get started with these tools, including valuable information not available publicly anywhere else.




Help






