Understanding dojo.require

Dojo provides a feature-rich system for including JavaScript modules. Before we begin this journey to explore this concept in depth, you should know that absolutely no knowledge of the Dojo module, packaging, and build system are required to use Dojo.

You can easily get started using Dojo by using a script element referring to a copy of Dojo on the AOL or Google CDNs. If you want to host your own version of Dojo, you can easily download dojo.js, include it in a web page using a script element, and be off and running with Dojo Base.

For those new to Dojo, the following resources give a quick overview of Dojo Base:

In general, dojo.js is a lot like jquery.js or prototype js: you get a competitive set of features founds in most JavaScript libraries that are essential for building great web applications. Those features include:

JavaScript Language Helpers

Object utilities

Array utilities

DOM Manipulation

A normalized event system

Ajax & Cross domain requests

JSON utilities

Simple effects

Browser sniffing

However, Dojo is much more than dojo.js, including tools that are not common among most JavaScript libraries. One of these tools is a module system (aka dojo.require()). JavaScript and web browsers do not offer the module loading conveniences found in other programming environments, and Dojo helps solve this problem.

Module System

In a nutshell, the module system leverages dojo.require('my.module') to include JavaScript files into a web page. Conceptually, this is really no different than placing a script element in an HTML page that includes an external JavaScript file.

Let’s assume that you are running a local development environment on your computer at http://localhost:8888. At the root of this web directory, let’s say you have a copy of the Dojo directory (download Dojo 1.4.3 if you’d like to follow along) and an HTML page, index.html.

We now have Dojo Base included in the HTML page and have access to all the functionality that it provides us. Life is good, until we require additional functionality. As an example, I would like to use some of the functionality that is contained in Dojo Core, anything inside of the dojo directory that is not directly provided when including dojo.js.

Suppose we are building an application that pulls data from the Flickr API. If we use this API, we will need to go cross-domain to get data from the API. Since the ability to do a cross-domain request for JSON data is not built into Dojo Base, we need to include code from Dojo Core. So, let’s get back to our index.html page. To load cross-domain functionality, we could simply use a script element and include the needed Dojo file:

Using a script element gets the job done, but it does not leverage the module system included with Dojo Base. From the viewpoint of Dojo’ers the /dojo/io/script.js file is a module, and as such can be loaded into a web page using dojo.require(). Below we load dojo.io.script using dojo.require().

But what have we gained? Two ways of doing the same exact thing? Well, using the dojo.require() method actually provides us with some functionality that using a simple script element does not. By using the dojo.require() method we gain a module system, which provides a few very important features that can help facilitate the building of complex web applications. We are going to examine these features throughout the rest of this article:

Let’s start with cache management, as this feature is relatively simple and does not require significant explanation. By using dojo.require(), Dojo will prevent the same script from loading twice. If a script is cached in the browser, it uses the cached resource and thus optimizes our code by removing unnecessary HTTP requests. Essentially you can use as many dojo.require()‘s as you’d like to include the same modules, and Dojo is intelligent enough to only request it once!

The next feature we will investigate requires us to create our own modules. Before we begin, let me remind you that when dealing with browsers and JavaScript, namespaces are critical so as not to risk polluting the global window scope. If you are curious about modules and namespaces, just review the Dojo source code. All of Dojo is organized into modules/namespaces. And, remember, we have already used dojo.require() to include the predefined dojo.io.script.js module.

Let’s continue with our Flickr API example. Since we intend to build a very large web application that uses Flickr photo data, we want to organize the code so that it is easy to manage. Essentially, we want to create a flickrApp namespace to store all of the application programming logic. To do this, we update the directory structure to include the file flickrApp.js.

If you look at JavaScript development in the Dojo way, the flickrApp.js file is actually a module. However, in order for Dojo to truly consider it a module, we have to tell Dojo. To do that, we use the dojo.provide() method to initialize the file as a Dojo module. We do that by adding the following to the flickrApp.js file:

dojo.provide(“flickrApp“); //similar to doing flickrApp = {};

Basically, dojo.provide() creates an object structure (namespace) based on the string that you pass it. In our situation, it creates an object called flickrApp. With that object created, we can now define aspects of the Flickr application as properties of this object. Here’s an example of what the flickrApp.js file could look like:

Do not be fooled, there is some magic here. How is it possible for Dojo to know where in the file system (or web directory) the flickrApp.js module is located? This is where path management comes into to play. Dojo assumes by default that the string you pass dojo.provide() mimics the directory structure located one directory up from the dojo.js file. What is one directory up from the dojo.js file? The directory that contains the dojo folder.

In other words, dojo.js is located at http://localhost:8888/dojo/dojo.js, Dojo will look for modules at http://localhost:8888/, one directory up from dojo.js. To reinforce this concept, let’s change the file structure so that it is more organized. Let’s now consider the changes required if our directory structure looked like this:

With this change all of the application code is inside a directory (namespace) called flickrApp. Within this directory, we can further separate the application logic into modules. The first module we will need is the data.js module containing the logic for getting Flickr photo data, cross-domain, and returning that data to the application. With this change, we need to place inside of data.js a dojo.provide() statement passing it the new directory structure. The contents of the data.js file could look like this:

//below is similar to doing var flickrApp = {}; flickrApp.data = {};
dojo.provide(“flickrApp.data“);
// Note: do not include the .js
flickrApp.data.getData = function(){};

Remember, Dojo assumes by default that the string passed to dojo.provide() mimics the directory structure one directory up from the dojo.js file. In other words, dojo.js is located at http://localhost:8888/dojo/dojo.js, so Dojo will look for our new data.js module at http://localhost:8888/flickrApp/data.js.

Now, our HTML markup will be changed to reflect the new organization of the code.

OK, a quick sanity check: is all this really necessary? Why not just include a single JavaScript file that contains all of the application logic and forget this module system? You could, and the application could run with or without the module system. However, Dojo uses the module system because separating web applications into organized modules makes building and maintaining applications easier, and it also helps to optimize the code using the build tools. This is essentially the difference between storing paperwork in a stack or in a file cabinet. The file cabinet requires more work to initially setup and to keep organized, but it saves time as the number of papers increases. Unless, of course, you only need to manage a handful of papers.

I hope you are starting to see the purpose of the module system. But we’re not done yet. Next up is the most important part: dependency management.

Modules can contain references to other modules. Or, stated another way, you can require() modules that can require() other modules. And Dojo can help you manage these dependencies! Let’s examine our HTML markup again.

Remember we are including the dojo.io.script.js module from Dojo Core. We did this because the Flickr application will need this module. Leveraging the module system and dependency management we can actually remove this require() statement and place it inside of the data.js module. Essentially we are saying that the data.js module is dependent upon the dojo.io.script.js module. Dojo will manage this dependency, so data.js could look like this:

dojo.provide(“flickrApp.data”);dojo.require(“dojo.io.script”);
// Note: dojo.require() should be used after dojo.provide()
flickrApp.data.getData = function(){};

The HTML will now contain only a single require() statement to include data.js module.

Dojo will now handle the dependency and make sure data.js is in fact dependent on dojo.io.script.js.

But wait, there is more. The dependency management provided by the module system needs to be notified when all dojo.require()statements (and their recursive dependencies) have loaded. This is done by leveraging the dojo.ready() method which will register a function to be invoked once the DOM is ready and all modules and their dependencies have been loaded and parsed by the JavaScript engine. So, when using the module system it is a best practice to use the dojo.ready() before making use of any of the functionality provided by predefined or custom modules.

Before we move on, it’s worth noting that dojo.ready() can be used anytime, even from within the callback function of a dojo.ready(). This means that you can embed a dojo.ready() inside of an dojo.ready(). This allows us to have a tree structure of dependencies and loading phases. For example:

dojo.require(“some.module”);
dojo.ready(function(){
//run code from some.module.js and all its dependencies safely
dojo.require(‘some.other.module’);dojo.ready(function(){
//run code from some.other.module.js and all its dependencies safely});
});

We have come a long way. Now, let’s go a bit futher. As previously mentioned, the module system does some path management for us. And, by default, this path is set to the parent directory of dojo.js. This means that when you require() anything, Dojo by default will look for it in the parent directory of (relative to) dojo.js. As an example, if we require('some.other.module') by default it will look for some/other/module.js starting one directory up from dojo.js:

We can now begin to look at how to override the default path used by Dojo to include custom modules, allowing us to customize the location Dojo will look for modules. Let’s update our directory to something more typical of a Dojo application:

Based on this new directory structure, we have taken our some.other.module.js out of the Dojo parent directory where Dojo will search for modules. Our some.other.module.js is actually another directory up, inside of the js directory. Because of this we have to notify Dojo of this change. To understand how this is done, let’s open up the index.html. Before we dojo.require() the some.other.module.js file, let’s tell Dojo the location of the some directory. This is done by using dojo.registerModulePath():

Based on our directory structure, the dojo.registerModulePath("some", "../../some/")statement is necessary so that Dojo knows where to find the custom module. We are telling Dojo that the some namespace/module can be found two directories up (../../) from dojo.js. Now that Dojo knows where to look, it can resolve the entire namespace some.other.module and include the some.other.module.js file. From this point on, Dojo will find any module contained within the some namespace two directories up from dojo.js. This is all required because, for security reasons, JavaScript applications in the browser do not have access to the file system structure on your web server.

If you are familiar with using djConfig then this configuration object can also be used to register module paths. All paths set using djConfig automatically call dojo.registerModulePath():

You might think I have covered everything related to the module system, however, there are many other facets. One thing I will mention is that everything we have done in this article assumes you are working with a local version of Dojo. Since Dojo can be included cross-domain from the AOL or Google CDN, the module system has to adjust to support this flexibility. Behind the scenes, the module system is based on making XHR requests for modules. This changes if you are using a CDN version of Dojo, as the module system will then switch over to cross-domain mode and include modules using on-demand script elements. This is a topic for another article, but if you are including Dojo from a CDN, you’ll have to register all modules paths and set the baseURL in Dojo.

Beyond Dojo

Finally, these ideas from Dojo can also be used without Dojo at all. YUI 3.0 has taken a similar approach, and there are stand-alone systems that can be used with any library or toolkit. One such system is RequireJS, which is based on the Dojo module system. If you’d like to isolate the concept of a module loading system in order to understand its capabilities, check out RequireJS for more information.

Comments

I’m using an xdomain build and I’m trying to use dojo.require to load some non-dojo scripts. I’m doing what’s described above but dojo.require inserts “.xd” before appending “.js” and attempting to load the script.

Do I have to duplicate all my scripts adding “.xd” to the name or is there a way to disable this on a module by module basis?

Michael

I have the same issue as Charlie above – if I include a Dojo(XD) from a CDN all local modules are loaded using XD as well. This was working fine in Dojo 1.4 and below, but now starting with 1.6 I have to compile my local code as well, even for development and testing :(
Any idea how to fix this?