Load external JavaScript libraries on demand with Dojo 1.5

And reduce your web application's load time

Dojo is an excellent library for creating Rich Internet Applications. However, complex Web 2.0 applications often require more than just one JavaScript library, and loading numerous libraries on page load can introduce performance overhead. In this article, learn how to use Dojo's dojo.io.script method for asynchronous, on-demand loading of JavaScript libraries. Example code helps you concisely "wrap" loading of external JavaScript libraries.

Share:

Nick Maynard, Web 2.0 Consultant, IBM

Author photoNick Maynard works for the Business Solutions Team at the IBM Software Lab in Hursley, United Kingdom. He specializes in Dojo, Ajax, web programming, web services, and Linux. You can contact Nick at nick.maynard@uk.ibm.com.


developerWorks Contributing author
        level

27 March 2012

Also available in Russian Japanese Spanish

Overview

The JavaScript Dojo toolkit is an excellent library that fulfills most requirements for creating Rich Internet Applications. However, in some circumstances, external JavaScript libraries might be needed for niche, or advanced, requirements for an application. In such cases, you might choose to add the library to your application using the standard HTML method of adding an extra <script> tag, which can introduce performance overhead. Happily, Dojo provides an alternative to this method: dojo.io.script.

In this article, learn about the advantages and disadvantages of the dojo.io.script approach. Example code shows how to avoid some of the bigger pitfalls. You can download the source code used in this article.

Two methods for loading external JavaScript libraries

Table 1 compares two methods for loading external JavaScript libraries.

Table 1. <script> tag and dojo.io.script
<script> tagdojo.io.script
Impact on page load speedPotentially high; loading of the library will block page startup.None; library is loaded on demand.
Impact on code speedNone.Initial usage will incur a delay while the library loads.
ComplexitySimple. All code can use the library immediately.Complex. Code must intelligently request and wait for the library to be loaded.

The dojo.io.script method provides clear advantages to applications that only use an external library in a specific code branch. And, lazy loading is particularly advantageous. The <script> tag method has the compelling advantage of simplicity when the library is used throughout the application. You might consider using dojo.io.script because it provides page load time advantages in all circumstances.


Using dojo.io.script

Listing 1 shows a simple example that uses dojo.io.script to asynchronously load a single library.

Listing 1. Single invocation of dojo.io.script
dojo.require("dojo.io.script");

dojo.addOnLoad(function() {
    // Load the library.
    // dojo.io.script.get is asynchronous, so we get back a dojo.Deferred object
    var deferred = dojo.io.script.get({url : "url_of_library.js"});

    // Handle the deferred callback paths
    deferred.then(function() {
        // This function is called when the library is successfully loaded
        // You should place your application code that depends on the library here
    }, function() {
        // This function is called if an error occurs
    });
});

Advanced usage

For libraries that have multiple modules with inter-module dependencies, it might be necessary to chain invocations, similar to those outlined above, to achieve the desired loading behavior. Listing 2 shows a more advanced example with chained invocations.

Listing 2. Chained invocations of dojo.io.script
dojo.require("dojo.io.script");

dojo.addOnLoad(function() {
    // Load a JavaScript library consisting of two modules
    // We construct a partial function to load the second module; 
    // This simplifies the code path
    var deferred = dojo.io.script.get({url : "url_of_first_module.js"})
        .then(dojo.hitch(dojo.io.script, 'get', {url : "url_of_second_module.js"}));
    
    // Deferred handling goes here 
});

Managing complexity

When loading multi-module libraries using dojo.io.script, or when using libraries in multiple locations in the code, managing dojo.io.script and the resulting deferred objects can become complex. Ensuring that a module is not loaded twice is of particular concern with multiple usage points.

If you are loading multi-module libraries or using libraries in multiple locations, consider using intermediary methods.


libproxy

The libproxy method manages inter-module dependencies and ensures that modules are loaded only once. The sample code in Listing 3 shows how to use libproxy.

Listing 3. Using libproxy (libproxy_example.html)
dojo.require("proxy.jsv");

dojo.addOnLoad(function() {
    var jsvProxy = new proxy.jsv();
    
    jsvProxy.load('json-schema-draft-01').then(function() {
        // At this point, the JSV module for validating against draft 1 has been loaded.
        alert("loaded JSV draft 1");
    });
});

Listing 4 shows how to use libproxy to wrap loading of Gary Court's JSON Schema Validator (JSV) library.

Listing 4. Wraps loading of the JSV library (proxy/jsv.js)
dojo.provide("proxy.jsv");

dojo.require("com.ibm.developerworks.libproxy");

/**
 * 
 */
dojo.declare("proxy.jsv", [com.ibm.developerworks.libproxy], {

    /**
     * In this file, we define the structure of our library,
     * and its inter-module dependencies
     */
    constructor : function() {
        var jsvRoot = "https://raw.github.com/garycourt/JSV/master/lib";
      
        this.modules = {
            '_uri' : { 
                sources : [{url : jsvRoot + "/uri/uri.js"}]
            },
            'base' : { 
                sources : [{url : jsvRoot + "/jsv.js"}], 
                deps : ['_uri']
            },
            'json-schema-draft-01' : { 
                sources : [{url : jsvRoot + "/json-schema-draft-01.js"}], 
                deps: ['base']
            },
            'json-schema-draft-02' : { 
                sources : [{url : jsvRoot + "/json-schema-draft-02.js"}], 
                deps: ['base'] 
            },
            'json-schema-draft-03' : { 
                sources : [{url : jsvRoot + "/json-schema-draft-03.js"}], 
                deps: ['base'] 
            }
        };
    }
    
});

Listing 5 shows the libproxy code itself.

Listing 5. Basic implementation for library wrapping (com/ibm/developerworks/libproxy.js)
dojo.provide("com.ibm.developerworks.libproxy");

dojo.require("dojo.io.script");
dojo.require("dojo.DeferredList");

/**
 * Do not directly use this class; instead, extend it and redefine the
 * modules field in the constructor
 */
dojo.declare("com.ibm.developerworks.libproxy", [], {

    constructor : function() {
        this._moduleDeferreds = [];
    },

    /**
     * Holds the modules of the library (and optional references to their
     * dependencies).
     * 
     * NB: 'sources' and 'deps' arrays will be loaded in parallel. If you need
     * to serialise this, add another module layer. ie. uri.js MUST be loaded
     * before jsv.js below
     * 
     * The 'sources' array holds objects that are passed in their entirety to
     * dojo.io.script.get()
     * 
     * Example, { 'module1Ref': { sources: [{ url: "module1.js" }] },
     * 'module2Ref': { sources: [{ url: "module2.js" }], deps: ['module1'] } };
     */
    modules : null,

    /**
     * Returns a dojo.Deferred object whose callback/errback fires when the
     * module is ready for use. Callback chain will contain the module
     * reference.
     * 
     * Example usage: load('module2').then(function() {});
     */
    load : function(/* String */moduleRef) {
        var F = this.declaredClass + ".";
        console.debug(F + "load()", arguments);

        // Check cache - have we loaded this library module before?
        if (this._moduleDeferreds[moduleRef]) {
            return this._moduleDeferreds[moduleRef];
        }

        // Create a new deferred and cache it
        var deferred = this._moduleDeferreds[moduleRef] = new dojo.Deferred();

        var module = this.modules[moduleRef];
        if (module) {
            deferred.callback(moduleRef);
            if (module.deps) {
                // Load dependencies in parallel
                deferred = deferred.then(dojo.hitch(this,
                        function(/* Array */dependencies) {
                            var defs = dojo.map(dependencies, dojo.hitch(this,
                                    'load'));
                            return new dojo.DeferredList(defs, false, true);
                        }, module.deps));
            }
            // Load sources in parallel
            deferred = deferred.then(dojo.hitch(this,
                    function(/* Array */sources) {
                        var defs = dojo.map(sources, dojo.hitch(dojo.io.script,
                                'get'));
                        return new dojo.DeferredList(defs, false, true);
                    }, module.sources));
        } else {
            deferred.errback("Unknown module reference.");
        }

        return deferred;
    }
});

Summary

Managing page load time when using several JavaScript toolkits and libraries can quickly become problematic. In this article, you learned about an alternative method to <script> tags. You can use Dojo's dojo.io.script facility to accelerate page load times by loading libraries in a "lazy" fashion. To manage the complexity introduced by this approach, you learned to use libproxy as an intermediary to manage dependencies and loading.


Download

DescriptionNameSize
Sample, wrapping JSV library using libproxysample.zip4KB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Web development on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development
ArticleID=806607
ArticleTitle=Load external JavaScript libraries on demand with Dojo 1.5
publish-date=03272012