Chapter 16. Build Tools, Testing, and Production Considerations
After all your hard work developing with Dojo, there comes a point when your application is ready for prime time. Util provides terrific build tools and a testing framework that can get you ready for production before you know it. The build tools provided by Util are the same ones that are used to produce each official Dojo release, and the Dojo Objective Harness (DOH) is a unit-testing framework that facilitates achieving some automated quality assurance before your app ever gets out the door.
Building
For any production setting, minimizing the overall footprint of
your JavaScript files and the number of synchronous requests to the
server is absolutely essential. The difference in downloading scores
of individual resource files via synchronous requests incurred by
dojo.require versus one or two
calls back to the server makes all the difference in the world in
terms of a snappy page load.
Dojo's build tools makes accomplishing what may initially seem like such an arduous task quite easy. In a nutshell, the build tools automate the following tasks:
Consolidates multiple modules into a single JavaScript file called a layer
Interns template strings into JavaScript files, including layers, so that a standalone template is no longer needed
Applies ShrinkSafe, a JavaScript compressor based on Rhino, to minify the size of the layers by removing whitespace, linebreaks, comments, and shortening variable names
Copies all of the "built" files into a standalone directory that can be copied and deployed to a web server
One reason you may not have been aware of the build tools is
that they aren't included in the util directory of an official release. To
get them, you have to download a source release (a source release will
have the -src suffix on the file base part of the
filename) or just grab the source from the Subversion trunk. Chapter 1, Toolkit Overview provides an overview of getting the Dojo
from Subversion, but basically, all that is necessary is to point your
client at the Dojo repository and wait for it to download everything,
whether it is the trunk or a specific tag.
In either case, you'll find that the util directory now holds some additional
directories; one of these directories is buildscripts, which contains the goods
we're looking for.
Tip
http://svnbook.red-bean.com/ contains the unofficial Subversion book, which is available in a variety of formats. Taking a moment to bookmark this valuable resource now will save you time later.
To run the build tools, you'll have to have Java 1.4.2 or later installed, available from http://java.sun.com (because ShrinkSafe is based on Rhino, which is written in Java). But don't worry about having to be a Java programmer to use ShrinkSafe; ShrinkSafe comes packaged as a single jar file (an executable Java archive), so you can treat it like any other executable.
Running a Build
The primary entry point for kicking off a build is via the
buildscripts/build.sh (or build.bat for Windows users), and is
really just a call through to the custom Rhino
jar that does all of the work based on a custom
profile that is provided (more on that in just a moment). As an
ordinary executable, however, build tools such as
Make or ant can easily
include the jar file as an ordinary part of the
production build process. This ability is especially convenient when
server-side components are based on languages that must be
compiled.
Executing the corresponding build script or executing the jar without any command-line options provides an impressive list of options. Table 16.1, “Build script parameters” is adapted directly from the standard option list that is displayed.
Table 16.1. Build script parameters
|
Option |
Description |
|---|---|
|
|
If the |
|
|
Specifies how to
optimize CSS files. If |
|
|
The name of the
release. A directory inside |
|
|
The set of locales to
use when flattening i18n bundles. By default this value is
|
|
|
The top-level release
directory where builds end up. The |
|
|
Turn on or off
copying of test files. This value is |
|
|
Inserts function
symbols as global references so that anonymous functions
will show up in all debuggers (especially in IE, which does
not attempt to infer function names from the context of
their definition). Valid values are |
|
|
The build action(s)
to run. Can be a comma-separated list, like |
|
|
Turn on or off for
widget template file interning. This value is |
|
| Change the default
dojo, dijit, and
dojox scope names to something else.
Useful if you want to use Dojo as part of a JS library, but
want to make a self-contained library with no external
dojo/dijit/dojox references. Format is
a string that contains no spaces, and is similar to the
scopeMap: [[\"dojo\",\"mydojo\"],[\"dijit\",\"mydijit\"], [\"dojox\",\"mydojox\"]] |
|
|
Specifies how to
optimize module files. If |
|
|
The type of dojo
loader to use. |
|
|
Sets the logging
verbosity. See
util/buildtools/jslib/logger.js for
possible integer values. The default value is |
|
|
A file path to the
profile file. Use this if your profile is outside of the
profiles directory. Do not specify the |
|
|
If the |
|
|
The build will be
stamped with this version string. The default value is
|
|
|
The name of the
profile to use for the build. It must be the first part of
the profile file name in the profiles/
directory. For instance, to use
base.profile.js, specify |
|
|
Specifies how to
optimize the layer files. If |
|
|
If the |
|
|
You can use |
|
|
A comma-separated
list of layer names to build. Using this option means that
only those layers will be built. This helps if you are doing
quick development and test cycles with layers. If you have
problems with this option, try removing it and doing a full
build with |
|
|
Inserts function
symbols as global references so that anonymous functions
will show up in all debuggers (especially IE, which does not
attempt to infer function names from the context of their
definition). Valid values are |
|
| Burns a scopeDjConfig={isDebug:true,scopeMap:[[\"dojo\",\"mydojo\"],
[\"dijit\",\"mydijit\"], [\"dojox\",\"mydojox\"]]}
Note that the backslashes are required to avoid shell escaping if you type this on the command line. |
While all of those options may seem like a lot to manage, the routine builds are really quite simple and involve only a handful of options. But first, we need a profile.
Build Profiles
A profile is the configuration for your
build as provided via the profile
or profileFile option. The most
basic function of a profile is to specify the exact Dojo resources
that should consolidated into a standalone JavaScript file, also
known as a layer; a typical rule of thumb is
that each page of your application should have its own layer. The
beauty of a layer is that it is an ordinary JavaScript file, and can
be included directly into the head of a page, loading everything
you've crammed into it via a single synchronous request to the
server—well, sort of. By convention, Base is so heavily used that it
generally stays in its own individual dojo.js
file, so you normally have two synchronous calls, one for Base, and
one for your own layer.
Setting up a build profile
Assuming your application has three distinct pages, you might have three layer files and one copy of Base.
Tip
If you really want to bundle up your own modules inside of the dojo.js file that normally only contains Base, you can name your layer dojo.js. However, it's often a good idea to keep Base separated because it would be used in every page of you application and is cacheable by your web browser.
Physically speaking, a profile is simply a file containing a
JSON object. Example 16.1, “A simple build profile” shows a
profile that consolidates several of the form dijits that are
explicitly dojo.required into a
page. All internal dependencies are tracked down automatically.
Just like with dojo.require,
you state what you need to use directly, and dependency tracking
is automated behind the scenes for you.
Example 16.1. A simple build profile
dependencies ={
layers: [
{
name: "form.js",
dependencies: [
"dijit.form.Button",
"dijit.form.Form",
"dijit.form.ValidationTextBox"
]
}
],
prefixes: [
[ "dijit", "../dijit" ]
]
};Assuming the previous profile is located at util/buildscripts/profiles/form.profile.js and you're working in a Bash shell, the following command from within the util/buildscripts directory would kick off a build. Note that the profile option expects profiles to be of the form <profile name>.profile.js and only expects the <profile name> as an option:
bash build.sh profile=form action=release
Tip
If you don't want to save the file in
util/buildscripts/profiles/form.profile.js,
you can use the profileFile
option instead of the profile
option.
After executing the command, you should see a bunch of output indicating that the build is taking place and that of strings are being interned from template files into JavaScript files. The artifact of the build is a release directory containing dojo, dijit, and util. Inside of the dojo directory, you'll find the usual suspects, but there are four especially important artifacts to note:
The compressed and uncompressed version of Base, dojo.js and dojo.js.uncompressed.js
The compressed and uncompressed version of your form layer in form.js and form.js.uncompressed.js (go ahead and take a peek inside to see for yourself)
But what if you need resources that are not included in your
custom layer file? No problem—if resources aren't included in a
profile, they are fetched from the server whenever the dojo.require statement that specifies
them is encountered. Assuming you take the entire release
directory and drop it somewhere out on your server, the dojo.require statements requesting
nonlayered resources will behave normally, though you will incur a
small roundtrip cost for the request to the server.
Requests for Base functions and resources in your layer do
not incur server-side requests when they are encountered in a
dojo.require statement because
they're already available locally. Resources not in your layer,
however, incur the routine overhead of synchronous HTTP requests
(Figure 16.1, “Conceptual server request illustrating various JavaScript
files loading”).
While you may generally want to include every possible
resource that is needed in a build, there may be some situations
where you want to lazy load. The tradeoff is always between a
"small enough" initial payload size over the wire versus the cost
of synchronous loading via dojo.require later.
Warning
If you accidentally misspell or otherwise provide a
dependency that does not exist, ShrinkSafe may still complete
your build even though it could not find all of the
dependencies. For example, if you accidentally specify dijit.Button (instead of dijit.form.Button), you'll most likely
still get a successful build, and you may not ever notice that
dijit.form.Button wasn't
bundled because a call to dojo.require("dijit.form.Button")
would fetch it from the server and your application would behave
as normal.
It's always a good idea to double-check your build by taking a look at the Net tab in Firebug to ensure that everything you expect to be bundled up is indeed bundled up.
Setting up a (more clever) build profile
A slightly more clever way to set up the build profile just discussed is to create a custom module that does nothing more than require in all of the resources that were previously placed in the layer via the profile file. Then, in the profile file, simply include the custom module as your sole dependency for the layer.
First, Example 16.2, “A custom module for a more clever build profile”
shows how your custom module would look. Let's assume the module
is dtdg.page1 and is located at
called dtdg/page1.js.
Example 16.2. A custom module for a more clever build profile
dojo.provide("dtdg.page1");
dojo.require("dijit.form.Form");
dojo.require("dijit.form.Button");
dojo.require("dijit.form.ValidationTextBox");Now, your profile need only point to the custom module, as
the other dependencies are specified inside of it and will be
tracked down automatically. Example 16.3, “Updated build profile” demonstrates an updated
profile, which assumes your custom module directory is a sibling
directory of util.
Example 16.3. Updated build profile
dependencies ={
layers: [
{
name: "form.js",
dependencies: [
"custom.page1"
]
}
],
prefixes: [
[ "custom", "../custom" ]
]
};Finally, your page might contain the following SCRIPT tag to pull in the module along
with Base:
<script type="text/javascript"
djConfig="baseUrl: './',modulePaths: {custom:'path/to/custom/page1.js'},
require: ['custom.page1']"
src="scripts/dojo.js"></script>Standard build profile
Notice that the util/buildscripts/profiles directory contains a number of example build profiles as well as the standard.profile.js file that contains the layers for a standard build of Dojo. The standard profile builds Base as well as a baseline Dijit layer that contains common machinery that is used in virtually any circumstance involving dijits, as well as a couple of other useful layers. Note that any profile in the standard.profile.js file should be available over AOL's CDN. For example, to retrieve the baseline Dijit profile, you could simply execute the following statement:
dojo.require("dijit.dijit");Remember, however, that the first SCRIPT tag should always be the one for
Base (dojo.xd.js), so you'd include any
additional SCRIPT tags for
layers after the one for Base.
ShrinkSafe optimization and other common options
In virtually any production setting, you'll want to apply ShrinkSafe to minify all of your code. While the previous build example build did optimize the build in the sense that it minified dojo.js and form.js as well as interned template strings, ShrinkSafe can minify every file in the release.
Recall that the size "over the wire" is what really matters
when you're talking about performance from a payload perspective.
While files may be a set size as they exist on the server, most
servers are able to apply gzip compression to
them if the web browser is capable of handling it. While
ShrinkSafe minifies JavaScript files by removing artifacts like
whitespace, comments, and so on, the further compression is
possible because the repetitive use of public symbols such as
dojo, dijit, and your own custom tokens allows
for actual compression to occur.
Tip
Minification is the reduction of a file's size by removing artifacts such as commas, whitespace, linebreaks, etc. Compression is an algorithmic manipulation that reduces a file's size by using by finding multiple instances of the same tokens and encoding an equivalent file by using shorter placeholders for the repetitive tokens. To learn more, see http://en.wikipedia.org/wiki/Gzip for an overview of gzip compression.
An especially notable feature of ShrinkSafe is that it never mangles a public API; this is a direct contrast to some JavaScript tools that attempt to encrypt JavaScript by applying regular expressions or convoluted logic to "protect" the script. In general, attempting to protect your JavaScript is mostly pointless. As an interpreted language that runs in the browser, the user of your application will almost certainly have access to your source code, and it's not terribly difficult to use a debugger to unroll the protected script into something that's fairly intelligible.
Tip
ShrinkSafe itself is not a Dojo-specific tool; you can apply it to any JavaScript file to gain the benefits of compression using the online demonstration at http://shrinksafe.dojotoolkit.org/. OS X users can download a version at http://dojotoolkit.org/downloads, and users of other platforms can grab the standalone custom Rhino jar from http://svn.dojotoolkit.org/dojo/trunk/buildscripts/lib/custom_rhino.jar.
In other words, ShrinkSafe shrinks your files without changing public symbol names. In fact, if you look at the form.js file that is an artifact of the previous build examples, you can see for yourself that ShrinkSafe strips comments, collapses and/or eliminates frivolous whitespace, including newline characters, and replaces nonpublic symbols with shorter names. Note that replacing all symbols with shorter, meaningless names qualifies as a lame attempt at encryption—not particularly useful for debugging purposes either.
Let's update our existing profile:
Minify all files in the release with the
optimize="shrinksafe"optionDesignate a custom notice that should appear at the top of every minified JavaScript file in an additional (mythical)
foomodule provided by CUSTOM_FILE_NOTICE.txtDesignate a custom notice that should appear at the top of the final form.js provided by the same CUSTOM_LAYER_NOTICE.txt
Provide a custom name for the release directory via the
releaseName="form"optionProvide a custom version number for the build via the
version="0.1.0."option
Here's the modified form.profile.js file from Example 16.1, “A simple build profile”. Note that the information in the custom notices must be wrapped in JavaScript comments; the path for the custom notices should be relative to the util/buildscripts directory or an absolute path:
dependencies ={
layers: [
{
copyrightFile : "CUSTOM_LAYER_NOTICE.txt",
name: "form.js",
dependencies: [
"dijit.form.Button",
"dijit.form.Form",
"dijit.form.ValidationTextBox"
]
}
],
prefixes: [
[ "dijit", "../dijit" ],
[ "foo", "../foo", "CUSTOM_FILE_NOTICE.txt" ]
]
};The augmented command to kick off this build is straightforward enough, and creates the artifacts in the release/form directory that exist alongside the dojo source directories:
bash build.sh profile=form action=release optimize=shrinksafe releaseName=form version=0.1.0
To actually use your custom release, simply include the paths to the compressed dojo.js and form.js files in script tags in the head of your page, like so. The dojo.js layer must be included first, because form.js depends on it:
<html>
<head><title>Fun With Forms!</title>
<!-- include stylesheets, etc. -->
<script type="text/javascript" path="relative/path/to/form/dojo.js"></script>
<script type="text/javascript" path="relative/path/to/form/form.js"></script>
</head>
<!-- rest of your page -->And that's it. It takes only two synchronous requests to
load the JavaScript (which now have interned templates) into the
page; other resources included in your build via the prefixes list are at your disposal via
the standard dojo.require
statements.
If you are completely sure you'll never need any additional
JavaScript resources beyond dojo.js and your
layer files, it is possible to pluck out just the individual
resources you need from the release directory structure. However,
you'll have to go through a little extra work to track down
dependencies with built-in CSS themes such as
tundra because some of the stylesheets may
use relative paths and relative URLs in import statements.
Dojo Objective Harness (DOH)
Automated testing practices for web applications are becoming increasingly common because of the sheer amount of coding and complexity involved in many of today's rich Internet applications. DOH uses Dojo internally but is not a Dojo-specific tool; like ShrinkSafe, you could use it to create unit tests for any JavaScript scripts, although no DOM manipulation or browser-specific functions will be available.
DOH provides three simple assertion constructs that go a long
way toward automating your tests. Each of these assertions is provided
via the global object, doh, exposed
by the framework:
doh.assertEqual(expected, actual)doh.assertTrue(condition)doh.assertFalse(condition)
Before diving into some of the more complex things that you can do with DOH, take a look at trivial test harness that you can run from the command line via Rhino to get a better idea of exactly the kinds of things you could be doing with DOH. The harness below demonstrates the ability for DOH to run standalone tests via regular Function objects as well as via test fixtures. Test fixtures are little more than a way of surrounding a test with initialization and clean up.
Rhino Test Harness Without Dojo
Without further ado, here's that test harness. Note that the
harness doesn't involve any Dojo specifics; it merely uses the
doh object. In particular, the
doh.register function is used in
this example, where the first parameter specifies a module name (a
JavaScript file located as a sibling of the util directory), and the second parameter
provides a list of test functions and fixtures:
doh.register("testMe", [
//test fixture that passes
{
name : "fooTest",
setUp : function( ) {},
runTest : function(t) { t.assertTrue(1); },
tearDown : function( ) {}
},
//test fixture that fails
{
name : "barTest",
setUp : function( ) { this.bar="bar"},
runTest : function(t) { t.assertEqual(this.bar, "b"+"a"+"rr"
); },
tearDown : function( ) {delete this.bar;}
},
//standalone function that passes
function baz( ) {doh.assertFalse(0)}
]);Assuming this test harness were saved in a
testMe.js file and placed alongside the
util directory, you could run
it by executing the following command from within
util/doh. (Note that although the custom Rhino
jar included with the build tools is used, any
recent Rhino jar should work just fine):
java -jar ../shrinksafe/custom_rhino.jar runner.js dojoUrl="../../dojo/dojo.js" testModule=testMe
The command simply tells the Rhino jar to
run the testMe module via the
runner.js JavaScript file (the substance of
DOH) using the copy of Base specified. Although no Dojo was involved
in the test harness itself, DOH does use Base internally, so you do
have to provide a path to it.
Now that you've seen DOH in action, you're ready for Table 16.2, “doh module functions”, which summarizes the additional
functions exposed by the doh
object.
Table 16.2. doh module functions
Additionally, note that the runner.js file accepts any of the options shown in Table 16.3, “Options for runner.js”.
Table 16.3. Options for runner.js
|
Function |
Comment |
|---|---|
|
|
The path to dojo.js. |
|
|
The path to a test file. |
|
|
A comma-separated
list of test modules that should be executed, such as
|
Rhino Test Harness with Dojo
Although it is possible to use DOH without Dojo, chances are that you will want to use Dojo with Rhino. Core contains some great examples that you can run by executing runner.js without any additional arguments. The default values will point to the tests located in dojo/tests and use the version of Base located at dojo/dojo.js.
If you peek inside any of Core's test files, you'll see the
usage is straightforward enough. Each file begins with a dojo.provide that specifies the name of
the test module, requires the resources that are being tested, and
then uses a series of register
functions to create fixtures for the tests.
Assume you have a custom foo.bar module located at
/tmp/foo/bar.js and that you have a
testBar.js test harness located at
/tmp/testBar.js. The contents of each
JavaScript file follows.
First, there's testBar.js:
/* dojo.provide the test module just like any other module */
dojo.provide("testBar");
/* You may need to register your module paths when using
custom modules outside of the dojo root directory */
dojo.registerModulePath("foo.bar", "/tmp/foo/bar");
/* dojo.require anything you might need */
dojo.require("foo.bar");
/* register the module */
doh.register("testBar", [
function( ) { doh.t(alwaysReturnsTrue( )); },
function( ) { doh.f(alwaysReturnsFalse( )); },
function( ) { doh.is(alwaysReturnsOdd( )%2, 1); },
function( ) { doh.is(alwaysReturnsOdd( )%2, 1); },
function( ) { doh.is(alwaysReturnsOdd( )%2, 1); },
{
name : "BazFixture",
setUp : function( ) {this.baz = new Baz;},
runTest : function( ) {doh.is(this.baz.talk( ), "hello");},
tearDown : function( ) {delete this.baz;}
}
]);And now, for your foo.bar
module residing in foo/bar.js:
/* A collection of not-so-useful functions */
dojo.provide("foo.bar");
function alwaysReturnsTrue( ) {
return true;
}
function alwaysReturnsFalse( ) {
return false;
}
function alwaysReturnsOdd( ) {
return Math.floor(Math.random( )*10)*2-1;
}
// Look, there's even a "class"
dojo.declare("Baz", null, {
talk : function( ) {
return "hello";
}
});The following command from within util/buildscripts kicks off the tests:
java -jar ../shrinksafe/custom_rhino.jar runner.js dojoUrl=../../dojo/dojo.js testUrl=/tmp/testBar.js
Warning
Especially note that the test harness explicitly registered
the module path for foo.bar
before requiring it. For resources outside of the dojo root
directory, this extra step is necessary for locating your custom
module.
If all goes as planned, you'd see a test summary message
indicating that all tests passed or failed. Registering a group of
tests sharing some common setup and tear down criteria entails the
very same approach, except you would use the doh.registerGroup function instead of the
doh.register function (or a more
specific variation thereof).
If you want more finely grained control over the execution of your tests so you can pause and restart them programmatically, you apply the following updates to testBar.js:
/* load up dojo.js and runner.js */ load("/usr/local/dojo/dojo.js"); load("/usr/local/dojo/util/doh/runner.js"); /* dojo.provide the test module just like any other module */ dojo.provide("testBar"); /* You may need to register your module paths when using custom modules outside of the dojo root directory */ dojo.registerModulePath("foo.bar", "/tmp/foo/bar"); /* dojo.require anything you might need */ dojo.require("foo.bar"); /* register the module */ doh.register("testBar", [ function( ) { doh.t(alwaysReturnsTrue( )); }, function( ) { doh.f(alwaysReturnsFalse( )); }, function( ) { doh.is(alwaysReturnsOdd( )%2, 1); }, function( ) { doh.is(alwaysReturnsOdd( )%2, 1); }, function( ) { doh.is(alwaysReturnsOdd( )%2, 1); }, { name : "BazFixture", setUp : function( ) {this.baz = new Baz;}, runTest : function( ) {doh.is(this.baz.talk( ), "hello");}, tearDown : function( ) {delete this.baz;} } ]); doh.run( ); /* pause and restart at will... */
Although we didn't make use of the fact that testBar is a module that dojo.provides itself, you can very easily
aggregate collections of tests together via dojo.require, just like you would for any
module that provides itself.
Although you could run asynchronous tests using Rhino as well, the next section introduces asynchronous tests because they are particularly useful for browser-based tests involving network input/output and events such as animations.
Browser-Based Test Harness
Although running tests from Rhino is tremendously useful, DOH
also provides a harness that allows you to automate running tests from
within a browser window. Basically, you just define a test as an
ordinary HTML page and then load the test page into the DOH test
runner using query string parameters in the test runner's URL;
internally, JavaScript in the test runner examines the query string,
pulls out configuration values such as testUrl and uses them to inject your test
page into a frame.
Of course, you can still run your browser-based test without the DOH test runner, but you won't get a nice visual display with optional Homer Simpson sound effects if you're willing to read the test results as console output.
Browser Test Example
The following is an example test defined as an ordinary HTML page. Notice that the example uses a local installation of Dojo because as of version 1.1, DOH is not delivered via AOL's CDN:
<html>
<head><title>Fun with DOH!</title>
<script
type="text/javascript"
src="local/path/to/dojo/dojo.js">
</script>
<script type="text/javascript">
dojo.require("doh.runner");
dojo.addOnLoad(function( ) {
doh.register("fooTest", [
function foo( ) {
var bar = [];
bar.push(1);
bar.push(2);
bar.push(3);
doh.is(bar.indexOf(1), 0); //not portable!
}
]);
doh.run( );
});
</script>
</head>
<body></body>
</html>Asynchronous Browser Test Example
Almost any web application test suite worth its salt is going
to involve a significant number of tests that depend upon
asynchronous conditions such as waiting for an animation to happen,
a server side callback to occur, and so on. Example 16.4, “Skeleton for an asynchronous test” introduces how you
can create asynchronous test with DOH. The key concept is that a
doh.Deferred (pretty much an
ordinary dojo.Deferred with some
tweaks) except that it is internal to DOH and, as such, doesn't have
external dependencies. Chapter 4, AJAX and Server Communication included an extensive
discussion of Deferreds if you need a quick refresher.
Before the relevant code sample, here's the basic pattern at play for asynchronous testing with DOH:
Create a
doh.Deferredthat will be used to verify the results from asynchronous function (that returns back adojo.Deferred)Call whatever asynchronous function returns back the
dojo.Deferredand save a reference to itAdd callbacks and errbacks to the
dojo.Deferredthat will simply pass the asynchronous function's results through to thedoh.Deferred's own callbacks and errbacks
Example 16.4. Skeleton for an asynchronous test
doh.register("foo", [
function( ) {
var dohDfd = new doh.Deferred();
var expectedResult = "baz";
var dojoDfd = asynchronousBarFunction();
dojoDfd.addBoth(function(response, io) {
//reference the dohDfd as needed...
if (response == expectedResult) {
dohDfd.callback(true);
}
else {
dohDfd.errback(new Error( /* ... */));
}
});
//...and return back the dohDfd
return dohDfd;
}
]);Depending on your specific test constraints, you might provide
explicit timeout values to ensure
that the asynchronous operations involved timeout according to your
specific testing criteria. At any rate, the key takeaway is that
asynchronous testing doesn't need to be terribly complicated; the
Deferred abstraction simplifies most of that complexity, so you're
left to focus on the task at hand.
Performance Considerations
Tip
This section touches on some of the low-hanging fruit that you can strive to achieve in your frontend engineering. For a fabulous reference on ways to improve performance, be sure to check out High Performance Web Sites: Essential Knowledge for Front-End Engineers by Steve Souders (O'Reilly). It's a quick read and really does live up to the "essential" part of the title. Much of the content is available at http://developer.yahoo.com/performance/rules.html.
While writing good JavaScript goes a long way toward having a snappy web application, there are a few considerations to be particularly cognizant of when it comes time for production. The topic of optimizing a web application's performance could be the subject of an entire book on its own, but the following list captures some of the most obvious low-hanging fruit that you can go after:
- Dojo's build tools
The build tools accomplish a number of essential tasks for you and the effort required on your behalf is trivial. The build process minifies your source, reducing the overall size of the payload, and significantly reduces the HTTP latency by consolidating multiple JavaScript files into layers and interning template strings where applicable.
- Lazy loading
While much has been said in this chapter on the virtues of using the build tools to create a minimal number of layer files for your application, there will certainly be times when it just makes more sense to do some lazy loading. For example, if you determine that users very infrequently make use of a particular feature that adds a nontrivial amount of script to your layer, you may just opt to
dojo.requireit on the fly instead of packaging it up.Another consideration with respect to lazy loading is to intelligently use the layout widgets to load content on the fly. For example, you may choose to only initially load the visible tab of a
TabContainer, and either load the other content when it is requested, or wait long enough that you are certain the rest of the page has been loaded before fetching the other tabs. TheContentPanedijit is a common vehicle for lazy-loading content.- Web server configuration
Explore options to have web browsers aggressively cache JavaScript files and other static content by configuring your server to issue a far future
Expiresheader; configure your server to take full advantage of common configuration options such as gzip compression.- Maximize static content
Because static content can be served so quickly, the more of it you can serve, the less time your web server will spend per request. Maximize the use of static HTML files that are nearly identical by filling in the user-specific portions via cookies or XHR requests where possible. For example, if the only difference on a login page is a few hundred bytes of text containing some user-specific information, serve the page statically, and use script to asynchronously fetch the small bits that need to get filled in instead of dynamically generating the entire page.
- Profiling
If a page seems particularly slow or performance is choppy once it has loaded, use the built-in Firebug profiler to get a better idea of where time is being spent in your JavaScript logic and consider optimizing the execution of the culprit functions.
Benefits of XDomain builds
Although it may not be initially obvious, if you opt to create and use an XDomain build for your application, you potentially gain a number of benefits:
You'll be able to host Dojo on a dedicated machine and share it amongst various applications—whether or not they are on the same domain in your network.
The
dojo.requirestatements that happen when the page loads are satisfied asynchronously instead of synchronously (the case for a default build), which can improve page load times since the requests are nonblocking.Some browsers, such as IE, limit you to two open connections per subdomain by default, so using an XDomain build essentially doubles the number of potential connections for your application—two for Dojo and two for everything else in the local domain.
If you serve multiple applications that all use the XDomain build, the overall HTTP latency your clients endure is likely decreased, as the overall amount of content that their browsers can cache locally is increased.
Don't optimize prematurely
As a final word of caution, don't prematurely optimize your application; when you do optimize it, never do so blindly based on guessing games. Always use demonstrable information such as profiling information or server logs to your advantage. Particularly with respect to optimization, our instincts can often be deceived. And remember: Firebug is your friend.
Summary
After reading this chapter, you should:
Be able to use Dojo's build tools to create consolidated, compressed layers for your web application
Be familiar with some of the most common options for creating a custom build
Be aware that dojo.js generally remains in its own separate JavaScript file; it is not rolled up into a custom layer
Be able to use DOH to write unit tests for JavaScript functions
Be more familiar with Rhino and understand the role it plays in the build tools and with DOH
Be aware that while ShrinkSafe and DOH are important parts of the toolkit, they aren't Dojo-specific, and you may be able to use them in other venues
Be aware of some of the low-hanging fruit you can go after when it comes time to maximize performance for your web application






Add a comment



Add a comment