« Next 3 Posts Planned for Dojomino | Main| Craig's List Demo »

Dojo Domino Optimization

Category   
Bookmark : del.icio.us  Technorati  Digg This  Add To Furl  Add To YahooMyWeb  Add To Reddit  Add To NewsVine 

Time to visit optimization for Dojo. I like the comment I got in the blog the other day from Captain Oblivious. He says,
I really wish IBM had chosen something other and Dojo. 148Kb download?!? If they cut it to 14, I'll pay attention to them again. That's absurd...
And he's totally right. To be fair, the story is far worse. The actual penalty is 248Kb. The 148Kb refers to the default dojo.js file only. For the widget example, it downloads a host of other dojo package files as well that bring the total to a whopping 248Kb. Dojo does not cut it in the real world if that's the penalty to pay for using it. However, it doesn't have to be the penalty, and in this article, we'll cut that size to just under 59K. Still too large for the simple example shown, but how valid is Dojo for this example anyway?

So let's address that premise. In the original example shown here, the email widget example is a simple page with one field on it connected to Dojo for email address validation. Is Dojo right for this example? If that's the entire page, and there's not going to be any subsequent pages (like an application with a series of screens), then no, Dojo's inappropriate to the task at hand. In most cases though, if you're building a Domino application, there will be many pages, and lots of different things on those pages where Dojo will also be put to use. Now the 148K penalty (paid once and then cached for the default dojo.js file) may not seem like such a big deal. For a longer read on this topic, see the Coach Wei: Direct from Web 2.0 blog for some interesting insights. With Dojo 1.0 and the incorporation of Dijit plus eventual work on a JS Linker subproject which will strip out unnecessary functions from the dojo files, we should see a much more streamlined default Dojo in the future.

Back on topic though, for this article, I'll focus on optimizing this specific example, and how we can take that 148K download and turn it into slightly under 59K...some of which isn't even served up from our own server. More ...

First, let's take a look at the files that the page downloaded in our original example. (I used fiddler on the Email Widget Example url to retrieve this)

File Size (bytes)
dojo.js 150,856
widget/Textbox.js 2,004
widget/__package__.js 605
xml/Parse.js 4,549
widget/Widget/js 9,766
lang/declare.js 3,242
ns.js 2,921
widget/Manager.js 8,029
a11y.js 1,607
uri/__package__.js 362
widget/Parse.js 6,896
widget/DomWidget.js 16,494
widget/HtmlWidget.js 2,812
html/util.js 9,838
lfx/toggle.js 1,497
i18n/common.js 1,232
nls/validate.js 416
namespaces/dojo.js 6,895
widget/InternetTextbox.js 3,096
widget/ValidationTextbox.js 4,737
validate/web.js 1,255
validate/common.js 2,933
regexp.js 11,956
Total 253,998
248Kb
Wow, that's a lot of files. How did they all get there?

The culprit is the dojo.require('dojo.widget.Textbox'); call. After dojo.js loads, which is a compressed file, the dojo.require statement tells it that to load the Textbox widget. Looking into the widget/Textbox.js file, we see the following:

dojo.require("dojo.widget.*");
dojo.require("dojo.widget.HtmlWidget");
dojo.require("dojo.widget.Manager");
dojo.require("dojo.widget.Parse");
dojo.require("dojo.xml.Parse");
dojo.require("dojo.lang.array");
dojo.require("dojo.lang.common");
dojo.require("dojo.i18n.common");
dojo.requireLocalization("dojo.widget", "validate", null, "fr,ja,zh-cn,ROOT");

By the time all the requires are worked through, you end up with the list of 21 files above. To make matters worse, while dojo.js is a compressed file, the other 21 files are pulling directly from the src directory where they are uncompressed. This leads to the worst of scenarios:

  • A large number of http requests for files (22 in all)
  • individual files are uncompressed
This is by far the worst case scenario. How to improve?

Optimizing the Email Widget Example

The way to go about this is to create a custom dojo build that packs only the necessary files into dojo.js and then compresses the output. The earlier reference to a JS Linker (when that work is complete) would further optimize this by looking at the 21 dojo.require statements to see if all the functions within those files are needed. If not, it would further strip those files from the output. For now, we don't have that.

How do we create the custom build?

  1. Download a java 1.5+ jdk, ant, and create your own build
  2. If you're using Eclipse, download the DojoBuilder plugin which gives a friendlier interface to the same tasks
  3. Using your browser, go to the Dojo site's custom build tool
Of the 3, you'll find method 3 (the website tool) the easiest by far. Because I also tried out method 2 (DojoBuilder plugin), here are the steps I took for trying that out.

DojoBuilder is a free Eclipse plugin that performs code synchronization from the dojotoolkit svn repository, and provides a UI for custom builds. Ant and Subversion are built-in to the package and it generally work. To get DojoBuilder up and running, I did the following:

  1. Blew away my existing Eclipse 3.2 environment which had too many configuration errors from various past project trial and errors
  2. Reinstalled the core Eclipse 3.2 distro
  3. DojoBuilder requires the Mylar and Subclipse projects, so setup remote update sites for each and installed them
  4. Setup a remote update site for DojoBuilder, then installed it
  5. Let DojoBuilder do a svn sync for 0.4.2 version (which is the latest with full fidelity)
  6. Created a custom build profile called EmailWidget that specified only 1 file, dojo.widget.Textbox
  7. Set the build to do compression on all files, and give it a destination directory
  8. Go
If you do a lot of work in Eclipse anyway, this might take you much time to get up and running and would feel familiar. Otherwise, it's a fair amount of time and effort to get up and running (1 hour?)

Now, let's take a look at method 3, going to build.dojotoolkit.org.
After reading through the options, you'll end up at this screen

default.jpg
Click for full screen image

Note the XDomain dojo path, we'll come back to that later.

We'll modify this with the following settings:

  • Empty profile
  • Choose the dojo.widget.InternetTextbox entry from the tree box
The screen now looks like this:
custom_profile.jpg
Click for full screen image

Scrolling further down the screen, we get to the build options where I've selected to minify rather than compress. Feel free to go with the compress option.
build_options.jpg
Click for full screen image

After clicking the build button, you'll see a list of the files it will include in the build which at first appear to be a BIGGER list than the one we detailed earlier. The reason for this is in the original, we took the default dojo.js build file (the 148K one remember?) and it came pre-packaged with a lot of files. In this case, we're not taking any defaults and building only what's needed. This produces the file list shown here:

dojoGuardStart.js
../src/bootstrap1.js
../src/loader.js
../src/loader_xd.js
dojoGuardEnd.js
../src/hostenv_browser.js
../src/dom.js
../src/xml/Parse.js
../src/lang/common.js
../src/lang/func.js
../src/lang/array.js
../src/lang/extras.js
../src/lang/declare.js
../src/ns.js
../src/event/common.js
../src/event/topic.js
../src/event/browser.js
../src/event/__package__.js
../src/widget/Manager.js
../src/uri/Uri.js
../src/uri/__package__.js
../src/html/common.js
../src/a11y.js
../src/widget/Widget.js
../src/widget/Parse.js
../src/html/style.js
../src/widget/DomWidget.js
../src/html/display.js
../src/html/layout.js
../src/html/util.js
../src/gfx/color.js
../src/lfx/Animation.js
../src/html/color.js
../src/lfx/html.js
../src/lfx/__package__.js
../src/lfx/toggle.js
../src/widget/HtmlWidget.js
../src/widget/__package__.js
../src/i18n/common.js
../src/widget/Textbox.js
../src/widget/ValidationTextbox.js
../src/regexp.js
../src/validate/common.js
../src/validate/web.js
../src/widget/InternetTextbox.js
The page will then give you a download prompt to save the dojo.js file that includes all these modules. Save the file and guess what? The downloaded file is approximately 249Kb !!! Which is roughly equivalent to the combined size of everything we tracked with Fiddler from the original. So have we made any progress? Yes, here's why?
  1. We have the same download size but in 1 request rather than 22.
  2. For the widget to render, we need all of the content so it doesn't do us any good to take 1 smaller download and then get the rest later.
  3. The nature of the dojo.require functionality is sychronous, so with nested requires, we have to wait until a file is brought down, parsed, additional requires are found, and then those files requested and loaded.
So we've made progress, but we haven't really reduced our download size requirement. Previously, in the build options we could have chosen to compress rather than minify. That would have resulted in a 198Kb file, a little over 50Kb of savings right there. But our next real winner for download savings comes from gzip.

Using gzip compression with Domino

Since Domino version 6, the ability to output gzip encoded web content has been there. DWA uses this by default. However, it's not available to the rest of Domino applications by default. In other words, it won't take our http://myserver.com/mydb.nsf/myform?OpenForm and gzip encode that on the fly. But, you can gzip your own content and tell Domino to add HTTP response headers that tell the browser it's receiving gzip encoded data.

P.S. Thanks to Dwight Wilbanks for providing a lot of the following detail in an email he sent to me about this article.

First, you need gzip. Then you need to gzip the dojo.js file you downloaded. HINT: make a copy of the dojo.js file first. Gzip doesn't create a zipped copy of the source file, it just zips the source file. You'll want to keep both versions around. Now you should have dojo.js and dojo.js.gz.

Now recall our $HTMLHead field from the original Email Widget example. It looked like this:

"<script type=\"text/javascript\" src=\"dojo.js\"></script>"
We're going to incorporate some browser sniffing gzip capability and some version control now by replacing that code with the following:
DeveloperFlag := @False;
DojoURL := @If(DeveloperFlag; "dojo.js";
   @Contains( @GetHTTPHeader("Accept-Encoding");"gzip");
      "v43/gz/dojo43.js";
      "v43/dojo43.js");
"<script type=\"text/javascript\" src=\"" + DojoURL + "\"></script>"
In effect this code can be interpreted as follows:
  • If the DeveloperFlag is turned on (normally located outside the HTMLHead field), then just serve up the regular dojo.js file
  • If not, then check to see whether the browser accepts gzip encoded content
  • Yes? Then give them the version 0.4.3 dojo.js file that's been gzip encoded
  • No? Then give them the regular 0.4.3 dojo.js file
What's with the versioning? Well, as Dwight points out, this gives you the ability to upgrade your dojo resources whenever you choose by going to one shared $$HTMLHead field and changing the dojo version number.

Now you take the files downloaded earlier and import them into Shared File Resources (or your domino server's html directory) with the file names shown above. The regular dojo.js file that you started with is still there. The minified or compressed dojo.js from the build tool becomes v43/dojo43.js. The gzipped version becomes v43/gz/dojo.js.

The files are now in place. If a gzip-enabled browser were to hit the page right now, you'd get just a regular Domino text field, and a couple of js errors. Why? Because the browser just received a garbled gzip stream when it was expecting to find javascript. Again, why? Because the Domino server doesn't know to serve up v43/gz/dojo.js with a content-encoding of gzip, therefore the browser doesn't know it's got gzip encoded data, therefore it treats it as if the content is regular js which it delivers and then fails to parse.

In order for the browser to decode the data to straight javascript, it has to receive a HTTP header of Content-Encoding = gzip. Enter Domino Web Site Rules.

Hopefully you're already using Intenet Site Documents for your Web server. I spent a painful hour or so taking our legacy-since-version 5 web configurations for our main server and converting them all over to Internet Site-based documents. Shame you can't copy and paste them. Anyway, open your server's internet site doc, and use the Web Site...Create Rule button.

create_rule.jpg

Create a "HTTP Response Header" rule with the following settings

gz_rule.jpg

This does 2 things. One, it tells the browser set the expiration on these type files for 90 days, you don't need to ask me for it again, just use your cache. That's a great savings in itself.
Two, it adds a custom header to the output that is Content-Encoding=gzip, which tells the browser it needs to decode the data first.

Great! After you restart the http task, you're set to go. You've just reduced the 248Kb download to a 54Kb (if using the compress build option) or a 58Kb (if using the minified version) download, that the browser should not even check for again for 90 days. To see the updated Email Widget example that uses this, see Email Widget Optimized.

Ultimately, what does this mean? Do we optimize for every page that we serve with dojo content? No, I don't think so. The individual savings of a completely optimized dojo.js file will be offset in an application where many pages are being served up. If each page has to get a custom dojo.js file, in all probability there is a lot of duplication going on. Rather, it would make more sense to look at the required files for the application as a whole. Is it using AJAX functions? No, then those can be left out of the build. Whatever those common requirements are, make a custom build for them, gzip it and cache it. If a few pages have additional requirements, then the dojo.require statements will handle it and those files will be downloaded separately.

One other thing, I mentioned the versioning of those files (v43/gz/dojo43.js and v43/dojo43.js), you can also setup a Web Site Rule that adds an Expires header for pattern match of */v43/* so that those files also are cached. If there's ever an update, you can change the Web Site Rule accordingly. I'm running out of time today...more on that in a future article.

Post A Comment

:-D:-o:-p:-x:-(:-):-\:angry::cool::cry::emb::grin::huh::laugh::lips::rolleyes:;-)

Documentation

Tutorials

Dojo Blogs

TripIt

RSS