Working with Dojo and AMD in Production

By on August 27, 2012 6:17 am

In our recent post on dgrid and Dojo Nano, we showed a technique of using nested require statements in order to make use of optimized layers using the Dojo build system. As a refresher, a layer is Dojo’s terminology for a file that combines many JavaScript resources into a single file. This improves the performance of your web application by minimizing the number of HTTP requests.

The technique we originally presented was a quick and simple approach:

<script type="text/javascript" src="../../dojo/dojo.js"
	data-dojo-config="async: true"></script>
<script type="text/javascript">
	require(['dgrid/dgrid'], function () {
	    require(["dgrid/List", "dgrid/OnDemandGrid","dgrid/Selection", 
		"dgrid/Keyboard", "dojo/_base/declare", "dgrid/test/data/perf", 
		"dojo/domReady!"],
		function(List, Grid, Selection, Keyboard, declare, testPerfStore){
		//...

While this works, it’s not ideal because you will need to modify your source when switching between development and production environments which is suboptimal. While you could of course fix this with PHP or some other server-side approach for your initial require statement, there are many simple alternative techniques that you can make directly to your markup. Here we explore five other approaches.

Single-layer builds

Ideal for: A quick, simple, single-layer application

In the original blog post, we focused on building simple nano and baseless builds. In this scenario, you may want to just create a custom dojo.js to include all modules that you need. Then we just have the main require() statement with all the dgrid modules. We don’t need to require() calls to the layer in advance, since the dgrid modules would already be loaded from dojo.js in production. The require() call would just immediately callback since everything the application needs has already loaded.

Defining a different require module based on the URL

Ideal for: Quick and simple approach to loading based on the URL format

This approach evaluates the application’s dependencies as a function of the URL. For example, we could test for the word dev in the URL, and only define a dependency on the built dgrid layer when not in a development environment:

require(/dev/.test(location.search) ? [] : ['dgrid/dgrid'], function(){
	require(["dgrid/List", "dgrid/OnDemandGrid","dgrid/Selection", 
		"dgrid/Keyboard", "dojo/_base/declare", "dgrid/test/data/perf", 
		"dojo/domReady!"],
		function(List, Grid, Selection, Keyboard, declare, testPerfStore){
		//...
	});
});

Configuring layers with deps and callback

Ideal for: Handling the differences between development and production based on URL or other settings within the dojoConfig object

Dojo’s configuration settings allow you to use dojoConfig.deps to load the layer and dojoConfig.callback to start an application:

<script type="text/javascript">
var dojoConfig = {
	async: 1,
	deps: ['dgrid/dgrid'],
	callback: function(){
		require(["dgrid/List", "dgrid/OnDemandGrid","dgrid/Selection", 
			"dgrid/Keyboard", "dojo/_base/declare", "dgrid/test/data/perf", 
			"dojo/domReady!"],
			function(List, Grid, Selection, Keyboard, declare, testPerfStore){
			//...
		});
	}
};
</script>
</script type="text/javascript" src="../../dojo/dojo.js"></script>

When the defined dependencies have loaded, a callback function is executed, all of which is defined within the configuration object. We can now evaluate the application’s dependencies as a function of the URL, similar to the previous example, and then handle the check in the dojoConfig object rather than within a require statement :

<script>
var dojoConfig = {
	async: 1,
	deps: /dev/.test(location.search) ? [] : ['dgrid/dgrid'],
	callback: function(){
		require(["dgrid/List", "dgrid/OnDemandGrid","dgrid/Selection", 
			"dgrid/Keyboard", "dojo/_base/declare", "dgrid/test/data/perf", 
			"dojo/domReady!"],
			function(List, Grid, Selection, Keyboard, declare, testPerfStore){
			//...
		});
	}
};
</script>
</script type="text/javascript" src="../../dojo/dojo.js"></script>

This allows us to vary dojoConfig.deps on a flexible switch. It could be the URL, or a single variable defined at the top of the application’s markup. We can also define dependencies as a function, with more elaborate criteria for defining the dependencies to load for the application.

The flexibility of this technique makes moving between development, test, and production environments simple and efficient.

Top level modules

Idea for: Larger applications with many dependencies

Another common approach for a smooth transition between development and production is to create a single top level module per layer that loads all the other modules each application uses. Then the Dojo build tool can be given a single module per layer to create a layer of the same name, so that development and production both load the entry module, as a single dependency in the HTML. This has the added benefit of keeping JavaScript out of your HTML, particularly your dependencies, so you can have a JavaScript-free HTML file.

For example, you might have a file, app/app.js:

define(["dgrid/List", "dgrid/OnDemandGrid","dgrid/Selection", "dgrid/Keyboard",
    "dojo/_base/declare", "dgrid/test/data/perf", "dojo/domReady!"],
    function(List, Grid, Selection, Keyboard, declare, testPerfStore){
        //...
});

And then, your HTML file would simply contain the following fragment:

<script>
var dojoConfig = {
	async: true,
	deps: ["app/app"]
};
</script>
<script src="dojo/dojo.js"></script>

generator-dojo

Ideal for: Mostly automated approach to following many Dojo best practices for building apps

The generator-dojo project will handle this scenario for you automatically. It identifies a few Dojo application architecture conventions to follow, and using Grunt grabs packages and sets you up with a build script that assists when moving from development to production.

Conclusion

As you can see, there are many approaches to simplify your approach for working with Dojo layers in production when using AMD. Let us know in the comments if you have another technique you prefer, or if you have any questions.

References

Help for your application

Contact us for more information on how we can help you migrate your application to fully leverage AMD. Or, check out our excellent Dojo and JavaScript support services, which can be utilized for working on any AMD-based development project.

Comments

  • Matthew Pilgrim

    Hi, Sorry for asking questions on an old post but here goes!

    The Boilerplate appears to use the ‘Top level module’ approach to load a single top level layer. But this does not appear to address the issue of how to handle layers
    within your application – a key issue for me. The nested require approach seemed sensible but I can’t see how I can define modules/widgets using this syntax.

    Can you provide an example of defining a widget which includes a layer for some of its resources? Is there a nice way of having this code fallback onto individual includes in the development environment (as the layer won’t exist yet)? I had an
    idea of using dojo/has to check a debug flag and conditionally include either the layer or list of resources.

    Thanks for the great work, Matt

  • Alan

    I think the answer is in this article, but I don’t understand it well enough to make it work, yet. I’ve got some trial and error ahead of me, still.

    The text goes like this:

    “Another common approach for a smooth transition between development
    and production is to create a single top level module per layer that
    loads all the other modules each application uses. Then the Dojo build
    tool can be given a single module per layer to create a layer of the
    same name, so that development and production both load the entry
    module, as a single dependency in the HTML.”

    So you’d do your require( require( thing, but in development, the outer require refers to module, and in production, it’s a layer.

    I think.

    I feel like this is the mechanism that I’m looking for, too. We’ll see.