Medryx Observations

May 30, 2009

A Professional JavaScript Build System

Filed under: dojo — maulin @ 4:05 pm

Its been a long time since my last post. That’s not to say I have run out of things to say. But the list of topics has gotten long enough, that I think its high time I start writing again.

In this post, I will discuss a build system for JavaScript development based on the Dojo build system. Having done plenty of server side development, “building” a project was not a foreign concept to me. And the Dojo build system is quite nice. It has a rich feature set, and does an amazing job of optimizing scripts. If you look at your set of scripts after they have been build and optimized, they resemble binary code more than they resemble any meaningful text. And as long as you adhere to the basic design principles encouraged by Dojo (namespacing, using dojo.moduleUrl when loading resources, using templatePath in widgets, etc), you get most of this for free.

But when we build server software, we frequently think of various “build environments” – dev, QA, and live, for example. That is not a concept that I have seen applied in the Dojo community in any of the many example projects that exist. In a previous post, I had described a “MockRestService” that allowed you to test all your JsonRestStore functionality without an app server. The MockRestService simply called static json files and “served them” to your store.

When I develop complicated applications, and when I try to apply Test Driven Development principles, I am constantly striving to simplify my test. I want to remove as many complicating factors as possible. Having an app server running, even if its just a simple PHP script, is just another layer of complexity that can be weeded out when developing.

As another example, I frequently have other xhr calls in my applications, hitting multiple internal and external resources. I would love to be able to hit one version of the service for local development and testing, another for QA, and yet another for live systems. To this end, I have developed my own work pattern, that I think makes my development much more productive. This breaks down into a few main concepts:

  1. Using a bootstrap JavaScript to load the library
  2. Abstracting any calls to resources that can change into a service layer
  3. Modifying the build system to allow easy swapping of resources.
  4. Creating multiple build environments to facilitate development – static resources (statically served files without the need of an app server), dynamic resources (using a local app server, and using a foreign app server if you will need to test cross-domain access), qa, and live.

In some ways, you can think of this whole system as a “dependency injection” framework for JavaScript. The bootstrap is your “container”, and controls which services get sent to your scripts. Your scripts only use “interfaces” to access resources – never making “low-level” calls to xhr, but instead letting a service layer handle that. A store is one example of a service – abstracting your calls to the actual resource. If you always use a data store, then you simply pass your script a different store depending on which resource you want to access.

So lets look at how to do this. I recently build an application that displays a set of people, their picture and their title. Clicking on their picture sends them a message. Not rocket science. This application contained three widgets:

  1. The individual “headshot” for each person
  2. The “grid” of headshots that uses a data store to access the information about each person, and creates headshot for each person and places them in a table (not a dojox.grid, just a simple table).
  3. The messaging system that presents a dialog to the user when they click on a headshot to send a message to that person.

(This application actually had quite a bit more complexity which warranted all this scaffold, but I’m trying to simplify.)

The headshot widget is given a JS object with a “image” path and a person’s name and uses them to create something like this:

[Need to insert image here]

But the path to the image is a relative path, and depending on our environment, that absolute path may be different. So this is the first “service” we are going to require. A very simple service that is given a relative path to an image and returns the absolute path.

dojo.provide("medryx.services.static.PhotoPathService");
 
medryx.services.static.PhotoPathService = function(relativePath) {
    return "http://blog.medryx.org/pics/" + relativePath;
}

So that is NOT a lot of code by any stretch. But if you were to  dojo.require(“medryx.services.PhotoPathService”) in your widget, you would be defeating the purpose. You would still have to actually change the service to get a different build environment. And remember, the goal is to have to update exactly one file to get a completely different environment behavior. So we need to build one more script – a service accessor. This is very similar to dojo.require (you could even argue that you could simply extend dojo.require – but I just now thought of that, so I haven’t tried it).

dojo.provide("medryx.environment.Static");
dojo.require("medryx.services.static.PhotoPathService");
 
medryx.service = function(service) {
    switch(service) {
        case "medryx.services.PhotoPathService":
            return medryx.services.static.PhotoPathService;
        default:
            throw new Error("Could not find the requested service: " + service);
    }
}

So this to, is not a complicated class. It just takes a string name of the desired interface, and returns an instance that “implements” it. So now if my bootstrapper changes its “require” from “medryx.environment.Static” to “medryx.environment.DynamicLocal” (for example), I could change what services I return.

<pre lang="javascript">dojo.provide("medryx.environment.DynamicLocal");
dojo.require("medryx.services.dynamic.PhotoPathService");
 
medryx.service = function(service) {
    switch(service) {
        case "medryx.services.PhotoPathService":
            return medryx.services.dynamic.PhotoPathService;
        default:
            throw new Error("Could not find the requested service: " + service);
    }
}

As long as the “environment” scripts define a “medrxy.service” method, then all my other scripts know that as long as an environment has been required, then they can call medryx.service() with the name of the service they need. So my headhsot widget does:

dojo.provide("medryx.widgets.Headshot");
dojo.require("dijit._Widget");
dojo.require("dijit._Templated");
 
dojo.declare("medryx.widgets.Headshot", [dijit._Widget, dijit._Templated], {
    templatePath: dojo.moduleUrl("medryx.widgets", "templates/Headshot.html"),
    person: null,
    postMixInProperties: function() {
      var pathService = medryx.service("medryx.services.PhotoPathService");
      this.imagePath = pathService(this.person.imagePath);
    }
});

So already, even without the bootstrapping and building described above we can change the behavior of a page with the change of one line:

<html>
 <head>
 <script type="text/javascript" src="/path/to/dojo.js"></script>
 <script type="text/javascript">
    dojo.require("medryx.environment.Static");
    dojo.require("medryx.widgets.Headshot");
</script> 
</head>
<body>
<div person="p" dojotype="medryx.widgets.Headshot"></div>
</body>
</html>

And this behaves differently than:

<html>
  <head>
  <script type="text/javascript" src="/path/to/dojo.js"></script>
  <script type="text/javascript">
    dojo.require("medryx.environment.DynamicLocal");
    dojo.require("medryx.widgets.Headshot");
  </script>
</head> 
<body>
<div person="p" dojotype="medryx.widgets.Headshot"></div>
</body>
</html>

So this is a very simple way to abstract one part of my application that has a dependency on what environment the application is running in. Clearly, this is a bit of overkill for this simple example. In fact, in this example, I still do use a service, but I have a generic “ConstantsService” that gets me information like which server I should hit, or which absolute path to use. But, for more complicated services – likely stores, or services that have many methods that use various XHR calls, this is the pattern I use.

So what about the bootstrapper? What does that look like? Because hard-coding the dojo.require to which environment I am using might be okay for a test page, but for a live page, I don’t want to have to write a script (or manually) change all those require statements to include the right environment. That is what I will write about in my next post.

Let me know if you have any comments or suggestions!

1 Comment »

  1. [...] my last post I described the parts that can make up a robust build system for a complex JavaScript library that [...]

    Pingback by Medryx Observations » A Professional JavaScript Build System: Part II – Bootstrapping — May 31, 2009 @ 6:31 am

RSS feed for comments on this post. TrackBack URL

Leave a comment

Powered by WordPress