Asynchronous Module Definition (AMD) in JavaScript

Hero image for Asynchronous Module Definition (AMD) in JavaScript. Image by DeepMind.
Hero image for 'Asynchronous Module Definition (AMD) in JavaScript.' Image by DeepMind.

This Article is Over Ten Years Old...

Things can and do move very quickly in tech, which means that tech-related articles go out of date almost as soon as they have been written and published. If you are looking for up-to-date technical advice or opinion, it is unlikely that you will find it on this page.

You may find that my recent articles are more relevant, and you are always welcome to drop me a line if you have a specific technical problem you are trying to solve.

As a web developer, you're probably familiar with the concept of modular programming; breaking your code into smaller, more manageable modules can improve performance and make it easier to maintain.

Asynchronous module definition, (commonly abbreviated as AMD), is a popular JavaScript design pattern used to define and load modules asynchronously in web applications. AMD is particularly useful in complex web applications that involve multiple scripts and dependencies.


What is Asynchronous Module Definition?

The primary purpose of AMD is to simplify and modularise the code structure, making it more maintainable and reusable. The AMD pattern allows you to define modules in separate files and load them asynchronously, thereby reducing the loading time and improving the performance of the web application. This is something that will eventually be supported natively inbrowser, but for now, this is one of the more thorough and complete patterns. I use it personally regularly, although often see CommonJS used in existing (Nodebased) codebases too.


A Basic Example

In an AMD design pattern, modules are defined using the define() function. The define() function takes two parameters, the first being an array of dependencies required by the module, and the second being a callback function that returns the module. Here's a basic example:

define(['module1', 'module2'], (module1, module2) => {  // Module definition and logic here  return {    // Module export object  };});

Here, the define() function is used to define a module that depends on two other modules: module1 and module2. The module's logic is defined inside the callback function, and the module export object is returned. To load the defined module, you would use a separate script loader library such as RequireJS, here's an example of how to load the above module using RequireJS:

require(['mymodule'], (mymodule) => {  // Access module methods and properties here});

In the example above, the require() function is used to load the myModule module. The module's methods and properties can then be accessed inside the callback function.


Advantages in Brief

One of the advantages of using AMD is that it enables you to load dependencies only as and when they are needed, improving the loading time of your application. AMD also provides a clear structure for organising and managing modules in large web applications, where codebases can otherwise become unwieldy and difficult to work with.


Disadvantages in Brief

However, there are some potential issues to consider when using AMD. One issue is that managing module dependencies can become complex, particularly in larger web applications with many modules. This can also increase the complexity of the overall codebase, which can make it harder to maintain and debug.

I realise the irony in saying that, given what I've just said in the section above. Anything designed to simplify a codebase also has the potential to make it worse too without strict usage guidelines.

Another common problem when using AMD is managing dependencies between modules. As the number of modules and dependencies grows, it can become difficult to keep track of them all. One solution to this problem is to use a package manager such as npm or Yarn to manage dependencies.

Despite this, AMD remains a popular choice for defining and loading modules in web applications. Its simplicity and ease of use make it an attractive option for many web developers.


A More Advanced Example

Now let's take a look at a more advanced example of how AMD can be used in a realworld scenario. In this example, we will define a module that uses jQuery and Underscore.js as dependencies:

define(['jquery', 'underscore'], ($, _) => {  // Module definition and logic here  const myModule = {    // Module properties and methods    init: function () {      // Initialise module here    },    doSomething: function () {      // Module logic here    },  };  return myModule;});

In the example above, we are defining a module that depends on jQuery and Underscore.js. The module's logic is defined inside an object named myModule, which contains properties and methods that can be accessed by other parts of the application.

To load the module, we can use the require() function from RequireJS like this:

require(['mymodule'], (mymodule) => {  // Initialise module  mymodule.init();  // Call module method  mymodule.doSomething();});

Here we're initialising the module and calling its doSomething() method. The module's methods and properties are accessible via the mymodule object.


Alternatives

There are two main alternatives available to us as developers, although one isn't anywhere near productionready just yet...

CommonJS

CommonJS is another module system used in JavaScript. However, CommonJS modules are synchronous, meaning that they are loaded at runtime (before the application runs). This can lead to slower application startup times, but it can also simplify the codebase and make it easier to manage dependencies.

ES6 Modules

When ES6 is released and widely adopted, we will also have access to ES6 modules. Obviously, specifications can (and probably will) change, but at the moment it looks like modules will provide developers with a builtin module system using modern JavaScript. They will be similar to AMD modules but will be loaded synchronously by default leading to the same performance considerations as CommonJS.


The Wrap‑Up

AMD is a powerful and useful design pattern for defining and loading modules in JavaScript. By using AMD, you can improve the performance and maintainability of your web applications, whilst also simplifying the code structure. As with most things, there are some potential issues to consider but nevertheless, AMD remains a popular choice for many web developers due to its simplicity and ease of use.


Categories:

  1. Front‑End Development
  2. JavaScript