Creating Modular Front-End Components with RequireJS (Part 1 of 4)

"The secret to building large apps is never build large apps. Break your applications into small pieces. Then, assemble those testable, bite-sized pieces into your big application." - Justin Meyer, author JavaScriptMVC

Introduction

For various reasons, I have avoided the use of AMD script loaders (e.g. RequireJS) for some time. Let me be the first to tell you - that was a mistake. I have recently returned to the AMD camp and at this point would be hard-pressed to consider starting a new project without such a tool in place.

In this series, I'll touch on RequireJS specifically. We'll take a look at how RequireJS and some of the plugins within its ecosystem can be used to create loosely-coupled, modular front-end components. Perhaps just as importantly, these components will be self-aware in regards to any additional assets they may need to function (i.e. Handlebars templates, stylesheets, images, etc...).

The Goal

The example that will be used throughout this post will be that of a "weather widget." Something along the lines of what you see in this Dribbble submission. The overall goal here is to create a component that can be used by a front-end developer without requiring any knowledge regarding the inner-workings of the component itself. In addition, we should be able to copy this component over into a completely different project and know that it will continue to work in exactly the same way, without requiring any further modification.

The directory structure and code below should convey the gist of what we are working towards.

Directory Structure

/project/modules/weather/main.js
/project/modules/weather/assets/style.css
/project/modules/weather/assets/template.hbs
/project/modules/weather/assets/background.png

Code

require(['Weather'], function(Weather) {        
    $("#container").weather({
        'postal_code': '37204'
    });
    /*
    At this point, our component is displayed
    within the page. World peace ensues.
    */
});

Step One - Get Thee to a Module Loader

Head on over to requirejs.org and download the latest version (2.1.9 at the time of writing).

What is RequireJS and why should I use it?

An in-depth answer to that question would warrant several blog posts on its own. For the purposes of this discussion, I'll keep the answer short and to the point. If you're interested in the finer details, this is a good place to start.

RequireJS implements the AMD module loading API. In short, the AMD API provides the Javascript community with a common pattern for importing smaller Javascript modules into a larger whole.

If you've ever used PHP, the following code probably looks familiar to you:

require_once './Weather.php';

In an object-oriented application with a proper separation of concerns, Weather.php would most likely contain a Weather class. Ideally, this class would be designed in such a way as to provide useful functionality in a number of different situations, regardless of the nature of the larger application that happens to be using it.

The process of creating re-usable "modules" of code is precisely what the AMD API hopes to faciliate within the Javascript community. The ability to do so is surprisingly lacking within the language itself, although that will be fixed in an upcoming release. In the meantime, RequireJS has become the de facto standard in regards to AMD API implementations.

I downloaded it. Now what?

You can download an archive of the following example here.

For the purposes of this post (we'll get more advanced in later steps), we're aiming for a file structure that looks like this:

/index.html
/modules/require/require.js
/modules/require/config.js
/modules/weather/main.js
/modules/start.js

Note: It is assumed that the above file structure lives at the root of whatever domain you happen to be serving this example from.

Our index.html file should contain some boilerplate HTML, as shown below.

<!DOCTYPE html>
<html class="no-js">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>RequireJS Demonstration</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script src="/modules/require/require.js"></script>
    <script src="/modules/require/config.js"></script>
    <script src="/modules/start.js"></script>
</head>
<body>
</body>
</html>

Our modules/require/config.js file will contain some setup instructions needed by RequireJS. Many different (and initially confusing) configuration options exist for RequireJS, but for now, the following is sufficient:

require.config({
    baseUrl: "/modules"
});

Our modules/weather/main.js file will contain the contents of our first AMD module, as shown below. Don't get too caught up in the semantics of what's going on here. We'll get to that in a minute.

define([], function() { 
    var Weather = function() {
        console.log("Looks like rain.");
    };
    return Weather;
});

Finally, our modules/start.js file will contain the Javascript code that initializes our app:

require(['weather/main'], function(Weather) {
    var weather = new Weather();
});

If you were to run this application in your browser, you should see the following line printed out on your developer console:

Looks like rain.

Define and Require

With our example code in place, we can now take a look at two of the most important AMD API calls that are available to us: define() and require().

Webster's Dictionary defines "define" as...

The define method is the one you will call when creating a new module. The format looks like this:

define(['dependency1', 'dependency2', 'etc'], function(Dep1, Dep2, Etc) {
    var MyModule = function() {
        // Use Dep1, Dep2, Etc as needed.
    };
    return MyModule;
});

For now, all you need to know is that define accepts two parameters:

  • An array of modules that our module depends on (if any).
  • A callback function in which our new module is created and returned, the arguments of which are the result of any specified dependencies.
Require

The require method is the one you will call when you want to use a module. The format looks like this:

require(['dependency1', 'dependency2', 'etc'], function(Dep1, Dep2, Etc) {
    var dep1 = new Dep1();
    dep1.doSomething();
});

Our two calls to define and require look pretty similar, don't they? As a matter of fact, they look damn near identical. That's because they are. The only major difference between define and require is that one creates modules to be shared, while the other consumes them and shares nothing (jerk).

That'll do, pig. That'll do.

Let's review what we've done, so far:

  • We've gone over a brief description of what AMD modules are and what purpose they serve.
  • We've downloaded RequireJS (the de facto standard when it comes to module loaders) and setup a test project.
  • We've created a test module using the define function.
  • We've consumed our test module using the require function.

Exciting stuff? I suppose that depends on your definition of "exciting" (don't answer that). I've purposefully kept the first part of this series simple and to the point. Later on, we'll get a little more advanced when it comes to RequireJS. We'll take a look at a few extremely useful plugins and we'll dive into the RequireJS optimizer, which is when things really start to get interesting.

In the meantime, here is a funny cat photo: