Building Modular JavaScript Projects with RequireJS


By

With so much emphasis being laid upon providing a smoothly flowing User Experience in any web project, the client-side codebase is now a very considerable chunk of the overall codebase. However, this opens up a host of problems with managing dependencies in the JavaScript code base.

In this article, we will look at the usage of RequireJS, which makes these dependencies easier to manage, and as a result, less soupy code. Mmmm… soup!

Require.js

Dependencies Shependencies

JavaScript dependencies are, quite simply put, pieces of external JavaScript code which are required for a particular script to work on a web page. These may be classified into known libraries like jQuery or Backbone, or developer file(s), which might be created for a number of reasons. Let’s take a small example.

Let us consider a small web app with a folder structure like this:

  • myApp
    • js
    • lib
      • jQuery.js
    • app
      • Customer.js
      • Bill.js
    • css
    • php
    • images
    • index.php

Let us also see what is inside the JavaScript files.

Customer.js:

function getCustomer() {
        return "Ashwin";
    }

Bill.js:

function generateBill() {
        var customer = getCustomer();
        console.log("The bill for customer " + customer + " is 100.00");
    }

As you can see, Bill.js refers to Customer.js for its functionality. Which means, that when including these script files into our HTML/PHP file(s), it is absolutely necessary for Customer.js to be included BEFORE Bill.js

Let us take a look at how our script tags would work.

Script tags:

<!--load jQuery-->
<script src= "/js/lib/jQuery.js"></script>

<!--load the customer-->
<script src= "/js/app/Customer.js"></script>

<!--load the bill-->
<script src= "/js/app/Bill.js"></script>

Now, when generateBill() is called from anywhere inside this file, it will work.

Oh God, There’s Blood Everywhere!

So, what happens if we misplace the order of the script files?

After the files are loaded, with Bill.js loading before Customer.js, generateBill() will fail with this error [This error is from Google Chrome. Other browsers may not display the same text]

ReferenceError: getCustomer is not defined

It may be easy to keep track of small projects comprising of a few files only, but managing the same for large projects, with tens of developer files as well as JS libraries, it becomes a real pain in the cojones. Team leads are known to pull out their hair when previously working code breaks due to dependency issues. This is where RequireJS comes in extremely handy.

Cleaning Up

RequireJS is a dependency loader which works on the Asynchronous Module Definition (AMD) API. Every module is loaded independently and asynchronously, and then connected to its dependencies before being made available for usage.

To start with RequireJS, we will download require.js from https://requirejs.org and place it inside our lib folder. We will also create another file called main.js and place it in the app folder. The structure should look like this:

  • myApp
    • js
    • lib
      • jQuery.js
      • require.js
    • app
      • Customer.js
      • Bill.js
      • main.js
    • css
    • php
    • images
    • index.php

Next, we will remove all the existing script tags from our page, and only use this one

<script data-bind= "js/app/main.js" src= "js/lib/require.js"></script>

Before delving into what main.js contains, let us take a look at what is happening in this particular tag.RequireJS makes use of the HTML5 data attribute, in this case, coming into play as data-bind. What is passed to this attribute is the path to the file where the dependency conditions and configuration is done. In essence, main.js is the entry point to our scripts.

Now, we can take a look at main.js.

Main.js:

requirejs.config({
    baseUrl : 'js/lib',
    paths : {
        'App' : '../app'
    }
});

requirejs(['App/Bill'], function(bill) {
    bill.generateBill();
});

First off, we configure RequireJS, by telling it to consider all files inside the js/lib folder to be included by default. This is very good for loading libraries, is done by configuring the baseUrl.

Next, we set the paths of our own modules, contained inside the js/app folder. However, the paths parameter contains paths relative to the baseUrl, so care must be taken in that aspect.

After the configuration is done, we simply invoke the constructor of RequireJS with the modules we want to use, and the variables corresponding to those modules. These, work in the following way.

For developer modules:

requirejs(['PathName/CaseSensitiveFileNameWithoutExtension'], 
    function(SomeVar) {});{/javascript]

For libraries, once baseUrl is included:

requirejs(['CaseSensitiveFileNameWithoutExtension'], 
    function(SomeVar) {});

These may be mixed and matched to whatever the developer wants to use inside main.js

For example, the following code is perfectly valid:

requirejs(['App/Bill', 'jQuery'], function(bill, $) {});

The Great Migration

Just writing a main.js and adding require.js is not enough to convert this simple app to a format recognized by RequireJS. So, we are going to change a few things.

Customer.js:

define(function() {
    return {
        getCustomer : function() { return "Ashwin" }
    }
});

Bill.js:

define(['./Customer'], function(customer) {
    return {
        generateBill : function() {
            var c = customer.getCustomer();
            console.log("The bill for customer " + c + " is 100.00");
        }
    }
});

What we are essentially doing in these files are just encasing them inside a define function, which will notify RequireJS that these are independent modules and are defined as such. Any dependent module(s) will also be called in the same way as with the RequireJS constructor.

However, there is a big difference between the constructor and the define methods. The define method just makes a module available on demand, while the constructor immediately loads the needed modules. This difference goes a long way to determine how every application’s dependencies are defined.

Conclusion

By creating a supply of independent modules which can be loaded on demand, we have completely removed the mess caused earlier. With a very simple usage, and the utility it brings to the table, RequireJS is becoming a must have for any medium to large JavaScript project. Our code is now more modular, meaning it can now be easily reused and clean enough to put Dexter at shame.

I hope this article initiated you nicely into RequireJS and will help budding as well as experienced developers.

For more information on RequireJS and advanced usage, please refer to https://requirejs.org/docs.api.html

Related Topics


Top
This page may contain affiliate links. At no extra cost to you, we may earn a commission from any purchase via the links on our site. You can read our Disclosure Policy at any time.