Asynchronous JavaScript Loader

Introduction

Asynchronous JavaScript loading provides a mechanism to load JavaScript files asynchronously. It helps in improving the performance of the web applications / browser. In addition to this, it also provides a way to encapsulate JavaScript files in many different files, similar to Java where keywords like import, package and class are used for this purpose. Using the traditional synchronous method of loading the files may affect the performance and usability of the applications that have more dependencies (load multiple JavaScript files at runtime).

There are multiple libraries available for this purpose. These libraries are complaint with the Asynchronous Module Definition (AMD) specification for defining modules. Let us discuss about the popular libraries that facilitates asynchronous loading.

Synchronous vs Asynchronous

The traditional way of loading JavaScript modules is using <script> element and css files using <link> element in a HTML page. This procedure enforces the browser to execute them sequentially not knowing the dependencies among them assuming all the files are placed in order. There is no point in executing them sequentially if there is no dependency between these files. Instead they can be executed in parallel. Here comes the part of asynchronous loading, Asynchronous loader facilitates loading of JavaScript files in parallel each time possible. Thus enhancing the performance while reducing the time taken for loading of multiple files on page load.

AMD

AMD – Asynchronous Module Definition is a standard commonly practiced by developers to write JavaScript modules that can be loaded by any AMD-complaint loader. Commonly used AMD API’s include require(), define(), exports and module. The function require() specifies a list of dependent modules/resources to be loaded before executing a set of code. This code is placed in a callback method which will be executed as a separate thread only after all the dependencies are loaded. The function define() communicates the loader about the list of modules loaded by a script.

Curl.js

Curl.js is a compact and a fast AMD-complaint asynchronous loader. Most async loaders cannot be loaded on local filesystem, i.e., you cannot use them with ‘file:’ protocol, instead you should use them from a web server. In the same lines, you must use curl from a page loaded from a web server using ‘http:’ or ‘https:’ protocols.

Defining a module

define(['dp1', 'dp2', 'dp3' /* etc */], factory);
define(['dp1', 'dp2', 'dp3' /* etc */], module);
define(module);
define(name, ['dp1', 'dp2', 'dp3' /* etc */], factory);
define(name, ['dp1', 'dp2', 'dp3' /* etc */], module);
define(name, module);
  • [‘dp1’, ‘dp2’, ..]: Module names. Dependencies may be named ‘require’, ‘exports’ or ‘module’
  • Factory: Defines the module. Dependencies can be passed as parameters to factory.
  • Module: A javascript object, function, constructor, or primitive
  • Name: String used to name module (not commonly used/recommended)

A typical AMD-wrapped-CommonJS format module definition. This format is assumed when a factory function is defined with parameters without a list of dependencies. The parameters ‘exports’ and ‘module’ are optional.

define(function (require, exports, module) {
    var dep1 = require('app/foo');
    exports.foo2 = function () { return foo() + 2; };
});

A different way of definition with ‘pseudo-modules’

define(['require', 'exports', 'module'], function (require, exports, module) {
    var dep1 = require('app/foo');
    exports.foo2 = function () { return foo() + 2; };
});

Script.js

Script.js is an asynchronous javascript loader with an amazingly remarkable lightweight footprint. Like other javascript loaders, it facilitates loading scripts on-demand from any url without blocking other resources from loading. Traditional way of loading javascript files

<script src="jquery.js"></script>
<script src="some-jquery-plugin.js"></script>
<script src="script-that-uses-plugin.js"></script>

Using script.js

// load both jquery and plugin at the same time and give it a name 'bundle'

$script(['jquery.js', 'some-jquery-plugin.js'], 'bundle')
$script('script-that-uses-plugin.js')
// in some-jquery-plugin.js
$script.ready('bundle', function() {
  // both jquery & the current plugin are ready
  // plugin code...
})
// in script-that-uses-plugin.js 
$script.ready('bundle', function() {
  // use your plugin
})

Other way of using script.js

$script(['jquery.js', 'some-jquery-plugin.js'], function() {
  // both jquery & the current plugin are ready
})

While working on larger paths, it is recommended to define a path variable to set a base. You can achieve this using

$script.path('/js/modules/')
$script(['dom', 'event'], function () {
  // use dom & event
})

$script.get('http://somedomain.com/base.js', function () {
})

Require.js

Require.js is another popular javascript file and module loader. It can be used in javascript environments like jQuery, Rhino and Node. It encourages modular code by using module Ids instead of script tags. Require.js uses a special attribute data-main to start script loading.

// This sets the baseUrl to the "scripts" directory, and
    // loads a script that will have a module ID of 'main'
<script data-main="scripts/main.js" src="scripts/require.js"></script>

In general, it is recommended to use baseURLs + paths config while setting the paths for module IDs. Require.js will read the modules with a .js extension, adding an extension is not required.

requirejs.config({
    //By default load any module IDs from js/lib
    baseUrl: 'js/lib',
    //except, if the module ID starts with "app",
    //load it from the js/app directory. paths
    //config is relative to the baseUrl, and
    //never includes a ".js" extension since
    //the paths config could be for a directory.
    paths: {
        app: '../app'
    }
});

// Start the main app logic.
requirejs(['jquery', 'canvas', 'app/sub'],
function   ($,        canvas,   sub) {
    //jQuery, canvas and the app/sub module are all
    //loaded and can be used here now.
});

Modules and Functions

Definition of a module with simple name/value pairs and no dependencies.

define({
  color: "black",
  size: "unisize"
});

Definition of a function with dependencies

//module in the same directory as current file
define(["./cart", "./list "], function(cart, inventory) {
  //return an object to define the current module.
  return {
    color: "cyan",
    size: "medium",
    addToCart: function() {
      list.decrement(this);
      cart.add(this);
       }
     }
   }
);

Definition of a module with Simplified CommonJS Wrapper

define(function(require, exports, module) {
  var a = require('a'),
  b = require('b');
  //Return the module value
  return function () {};
}
);

Definition of a module with a Name

// module name "foo/title"
  define("foo/title",
    ["my/cart", "my/list"],
      function(cart, inventory) {
        //Definition of foo/title object.
       }
);

References: