Telerik blogs

If you have been following JavaScript development, you might have seen a lot of posts on modules. It's a hot topic in JavaScript and it's easy to jump on a bandwagon. The truth is that the problem that needs to be solved is very fundamental to the success of your code.

Why Modules

JavaScript has an inherent lack of a module system...

WHOA! WHAT DOES THAT EVEN MEAN!?!

If that's the first place your mind went, then you are in good company.  Derick Bailey explains the concept of a module like this: 

A module is a wrapper or enclosure around a group of related things in code - whether those things are objects, functions, variables, or whatever they might be. Modules create encapsulation and give us the ability package many smaller things in to something larger, presenting it in a unified manner.
Think about it this way: In .NET or Java, you use classes or what have you to build your project. Then you have a compiler that comes along and bundles everything up for you into an assembly based on which classes you have declared you will be needing. Now you have a module that you can use in other projects, and it can easily be referenced or imported for use. This concept of grouping your code together and packaging it is completely missing from JavaScript.

The problem here in JavaScript is really two fold.
  1. JavaScript does not yet have a syntax for creating modules.
  2. JavaScript does not have a way for you to load the modules that it does not have a syntax for defining. You have to waste your time with tedious script tag ordering and rely on global references.

Creating Modules

Lets take a look at how this problem typically plays out.  Assume that we have a very simple application that has color palette widget. Moving the selector in the color palette changes the page color.

For a more interesting example of the new Color Picker widgets that I didn't end up using, go here.

Let's look at the JavaScript that is behind the scenes here.

Color Experiments Code

 // open a document ready function
$(function () {

  // function that just changes the background color
  var setBackground = function (color) {
      $(document.body).css("background-color", color);
  }

  // function to handle the pallete color selection
  var setColor = function (e) {
      // the color object contains all the hex, rgba, and hsl
      // conversions and utilities
      var color = e.sender.color().toBytes();

      // set the color
      setBackground(e.value);
  };

  // select and create the color pallete
  var colors = $("#colors").kendoFlatColorPicker({
      change: setColor,
      value: "#fff"
  }).getKendoFlatColorPicker();   

});

When I look at the above code, It appears to me that it could be broken out into two different objects.

  1. The utility function to change the page color
  2. The color palette and select event

In JavaScript, you have several choices for creating "modules", but the most fundamental way is to use something called an Immediately Invoked Function Expression (IIFE), that will return you an object. Lets take just the utility function that changes the background color.

Creating A Simple Module

// variables declared outside of a function will go on the global
// scope, so create an "APP" namespace
var APP = window.APP || {};

/*************************************************
                  UTILS MODULE
**************************************************/
// use a function that executes right away and assign 
// whatever it returns to the utils variable
APP.utils = (function() {
  // return an object
  return {
    // declare the function to change the background color
    setBackground: function(color) {
      $(document.body).css("background-color", color);
    }
  };
}());

I realize that you would most likely never have a function to perform a single operation on a single object, but it helps to simplify the overarching idea here, so just bear with me.

Now we have encapsulated that function, and maybe any other utility functions that we have in an object called utils, and we can call it like so:

Using The Utils Object

  // set the background color to blue
  APP.utils.setBackground("#336699");

Next we can encapsulate the color palette widget as a module. The palette module needs to access the utils object which is available off of the same APP "namespace".

Modularize The Palette

// variables declared outside of a function will go on the global
// scope, so create an "APP" namespace
var APP = window.APP || {};

/*************************************************
                  UTILS MODULE
**************************************************/
// use a function that executes right away and assign 
// whatever it returns to the utils variable
APP.utils = (function() {
    // return an object
    return {
        // declare the function to change the background color
        setBackground: function(color) {
            $(document.body).css("background-color", color);
        }
    }
}());

/***************************************************
                Color Palette Module 
****************************************************/
// create the color palette module off the app namespace
// all that we need to return out of this function is an instance
// of the color palette widget
APP.palette = (function() {

  // this function is private and not available to utils
  // function to handle the pallete color selection
  var setColor = function (e) {
      // the color object contains all the hex, rgba, and hsl
      // conversions and utilities
      var color = e.sender.color().toBytes();

      // set the color
      APP.utils.setBackground(e.value);
  };

  // select and create the color pallete
  var colors = $("#colors").kendoFlatColorPicker({
      change: setColor,
      value: "#fff"
  }).getKendoFlatColorPicker();   

  // just return the entire widget instance
  return colors;

}());

Now the code is fully modular. It's quite a bit more verbose, but we can extract each one of these modules into it's own file. That gives us two files:

  1. utils.js
  2. palette.js

!<-- include jquery, kendo ui and the app scripts -->
<script src="/Scripts/jquery-1.9.1.min.js"></script>
<script src="/Scripts/kendo/2013.1.319/kendo.web.min.js"></script>
<script src="/Scripts/app/utils.js"></script>
<script src="/Scripts/app/palette.js"></script>

We don't need ANY inline script since the functions we wrapped our modules in execute as soon as they are included in the page and return the necessary references that other modules depend on. There are still some fairly major drawbacks here.

  1. The utils.js file ALWAYS has to be referenced first because it creates the APP variable that all of the modules live on.

  2. There are 2 files already, and this is a dead simple page. Imagine how many files you might have in a full on enterprise application.

  3. You could use a build tool like ASP.NET MVC Bundling, but as your application grows you are still going to have modules that depend on other modules. You can try to keep that all straight in your head, but as your app grows, you are going to find that it starts to feel like you got Gizmo wet and took him to a pizza buffet after midnight.

In Which RequireJS FINALLY Makes An Appearance

The core idea behind RequireJS is to allow you to specify in a JavaScript file which files it depends on so that you can be sure that when your code executes, the dependencies are satisfied.

Installation

RequireJS is just a JavaScript file. It's a prime example of solving deficiencies in JavaScript by writing JavaScript.

You can download it from the RequireJS site, or you can use your package manager of choice. I'm working in ASP.NET today, so I'll grab it off of NuGet.

PM> Install-Package RequireJS

It's going to drop 2 files in the Scripts directory. We're only concerned with require.js at the moment. The other file, r.js, is a utility which concatenates and minifies your files. We'll talk about it shortly.

Usually you will see RequireJS projects structured so that your application lives in a folder called "app". Your JavaScript files will go into a project called "mylibs", and your third party scripts will go into a folder called "libs". That's not gospel, it's just a suggestion. Structure your code the way that you feel the most comfortable with.

Super Simple Configuration

Before I actually reference the RequireJS script, I'm going to need to add a "main" file that acts as an entry point. I'll just call it "main.js" and drop it in the "app" folder at the same level as the "mylibs" directory. That main.js file will call the "require" function, which is a function that is specified by the require.js file. In it, I'm going to declare that I want to load in my two files. It then takes those file paths and injects them into a function as variables and executes that function. RequireJS assumes that you are working with JavaScript files so you don't include .js in your names.

RequireJS Main File

require([
    "mylibs/utils",
    "mylibs/palette"
], function(utils, palette) {

    // the app is loaded...

});

Now I can include RequireJS in my HTML page and specify that I want it to use the main.js file as it's main entry point by specifying the data-main attribute on the script tag.

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

The application now runs and RequireJS loads in the two files that I specified and the app works. If you open your browser tools, you can see the two JavaScript files being loaded in.

Specifying Dependencies

Here is where things start to get very interesting. The palette.js file depends on the utils.js file. Without it, the palette.js code is broken. I'm going to modify these files just slightly to let RequreJS know that. I do that with a define function at the top of those files. The define function is a function created by RequireJS, and it takes two parameters:

  1. An array of paths to JavaScript files needed as dependencies
  2. The function to execute after RequireJS ensures those dependencies have been loaded.

A RequireJS Palette module then looks like this:

Palette Module Depends On Utils

define([
  "mylibs/utils"
], function (utils) {

  // this function is private and not available to sliders or utils
  // function to handle the pallete color selection
  var setColor = function (e) {
      // the color object contains all the hex, rgba, and hsl
      // conversions and utilities
      var color = e.sender.color().toBytes();

      // set the color
      utils.setBackground(e.value);
  };

  // select and create the color palette
  var colors = $("#colors").kendoFlatColorPicker({
      change: setColor,
      value: "#fff"
  }).getKendoFlatColorPicker();

  // just return the entire widget instance
  return colors;

});

And the utils module doesn't depend on anything...

Colors Module With Dependencies

define([], function () {
  return {
      // declare the function to change the background color
      setBackground: function (color) {
          $(document.body).css("background-color", color);
      }
  };
});

Notice that those IIFE's went away?  So did the APP variable/namespace. RequireJS handles your modules and holds them for you. You can get them at anytime by executing require("Your Module Name"). I can also remove utils.js from the main.js file completely and just load palette.js. RequireJS will load in palette.js, see that it depends on utils.js and load that file in first.

require([
    "mylibs/palette"
], function() {

    // the app is running...

});

Third Party Libraries

I still have Kendo UI and jQuery hanging about in my page and not included in my RequireJS configuration. There isn't anything really "wrong" with this, but you can include these third party libraries in RequireJS so you can bundle and minify everything down into one file.

Configuring Paths

You can configure these third party libraries in the main.js file by creating a "path", which is just a named variable that is pointing to the library location. If you are using a CDN for these libraries (which you should if you can), then you can specify a fallback to a local file if the CDN isn't available.

Configuring RequireJS For Third Party Libraries

require.config({
  paths: {
      // specify a path to jquery, the second declaration is the local fallback
      jquery: ["//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery",
              "../Scripts/jquery.1.9.1.min"]
  }
});

require([
    "mylibs/palette"
], function(jquery, palette) {

    // the app is running...

});

You then pass jQuery into your main entry point. Many people will choose to add in an "app.js" file here so that they are not loading a bunch of their own files along with the third party ones in the main.js file. The "app.js" file then loads in all of your libraries.

Use An App File Instead Of Main

require.config({
  paths: {
      // specify a path to jquery, the second declaration is the local fallback
      jquery: [ "//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min",
                 "../Scripts/jquery.1.9.1.min"]
  }
});

require([
   'app'
], function(jquery, app) {

  // this loads jquery and your app file

});

You would of course want to specify jQuery as a dependency in the palette.js file. You ALWAYS want to specify all a file's dependencies. This is the only way that RequireJS will know which files to load and in what order. Just because I specified jQuery before palette.js, it does not mean RequireJS will load them in that order.

Third Parties With Dependencies

Some third party libraries will have dependencies on other third parties. Kendo UI depends on jQuery. While the different Kendo UI modules will specify their internal dependencies for RequireJS, they do not specify a dependency on jQuery since it's easy to do and requires just one line. Here I am adding in a dependency for Kendo UI with a local fallback, and using "shim" to let RequireJS know that I need jQuery loaded before Kendo UI.

Specifying jQuery As A Dependency For Kendo UI

require.config({
  paths: {
      // specify a path to jquery, the second declaration is the local fallback
      jquery: [ "//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min",
                 "../Scripts/jquery.1.9.1.min"],
      kendo: [ "//cdn.kendostatic.com/2013.1.319/js/kendo.web.min",
               "../kendo/2013.1.319/kendo.web.min"]
  },
  // inform requirejs that kendo ui depends on jquery
  shim: {
      "kendo": {
          deps: ["jquery"]
      }
  }
});

require([
  'app'
], function(jquery, kendo, app) {

  // this loads jquery and your app file

});

Now you can remove the script tags for jQuery and Kendo UI from your page and let your modules specify if they need them. For instance, the palette file now looks like this:

Palette Module

define([
  "jquery",
  "kendo",
  "mylibs/utils"
], function ($, kendo, utils) {

  // this function is private and not available to sliders or utils
    // function to handle the pallete color selection
    var setColor = function (e) {
        // the color object contains all the hex, rgba, and hsl
        // conversions and utilities
        var color = e.sender.color().toBytes();

        // set the color
        utils.setBackground(e.value);
    };

    // select and create the color pallete
    var colors = $("#colors").kendoFlatColorPicker({
        change: setColor,
        value: "#fff"
    }).getKendoFlatColorPicker();

    // just return the entire widget instance
    return colors;
});

The application is fully configured an ready for deployment. It could be deployed as-is, but that's not very responsible. You should always minify and concatenate your scripts before you go to production. This is where the r.js file comes in.

In Which I Tell You To Install NodeJS

Before we can use the r.js file, you need to have NodeJS installed. If you don't have Node, and it makes you nervous, just think of it as a command line utility with a TON of useful plugins that help you automate your workflow regardless of operating system and platform. In this case, I need NodeJS to run the r.js file which will perform a build on the JavaScript.

After you install NodeJS, you are ready to run the r.js build file, also known as the optimizer.

The Mighty Optimizer

The RequireJS optimizer is good, but it's not a mind reader. You need to give it a file that it can read which will tell it about your project and which files you want it to minify. This file can be named whatever you want and can exist anywhere, but I usually call mine "build.js" and keep it in the top level directory. It doesn't need to be in a publicly accessible location.

RequireJS Optimizer Configuration

({
  baseUrl: "./Scripts/app",
  paths: {
      jquery: "empty:",
      kendo: "empty:"
  },
  name: "main",
  out: "Scripts/main-built.js"
})

You will notice that I provided paths for Kendo UI and jQuery in the form of "empty:", which tells RequireJS that these are CDN scripts and need only to be referenced, not included in the build.

Then you simply call the r.js file from the command line with Node, and pass in the location of the build.js file.

// run from the project directory
node r.js -o build.js

That will spit out a "main-built.js" file which contains all of the project JavaScript concatenated and minified. You can now just modify the data-main file that RequireJS is loading to be the main-built.js file, instead of app.main.js.

Special Considerations For ASP.NET

Since I am working in ASP.NET, there are a few other changes I want to make to streamline my workflow.

Loading The Correct JavaScript For Release

I do not want to have to manually change my script tag whenever I go to release and then back for development. I'm going to sniff out my current build mode in the HomeController and then pass a variable to the Index.cshtml page so I know which script to include.

Determining The Build Configuration

public ActionResult Index()
{
  #if DEBUG
      ViewBag.useBuild = false;
  #else
      ViewBag.useBuild = true;
  #endif

  return View();
}

Conditionally Loading The Build File

 @if (ViewBag.useBuild) {
    <script src="@Url.Content("~/Scripts/require.js")" data-main="@Url.Content("~/Scripts/main-built.js")"></script>
} else {
    <script src="@Url.Content("~/Scripts/require.js")" data-main="@Url.Content("~/Scripts/app/main.js")"></script>
}
Integrating The Optimizer Into The Release Build

I also don't want to have to jump down to the command line every time I change my status to Release so that I can run the optimizer. You can specify a Pre Build event by going to Project / Project Settings / Build Events. I only want the optimizer build to occur when the project is in Release mode.

Build The JavaScript When In Release Mode

if $(ConfigurationName) == Release node $(ProjectDir)/r.js -o $(ProjectDir)/Scripts/build.js

Dress For Success

Modularizing your JavaScript code really sets you up for success. It's really just the foundation on which you can build highly maintainable applications of great size. You can download Kendo UI and start building extremely modular applications today using RequireJS.

Additional Resources

You can download the sample project from today from our ASP.NET MVC Examples repo. I would also highly recommend that you checkout these additional resources.


Burke Holland is the Director of Developer Relations at Telerik
About the Author

Burke Holland

Burke Holland is a web developer living in Nashville, TN and was the Director of Developer Relations at Progress. He enjoys working with and meeting developers who are building mobile apps with jQuery / HTML5 and loves to hack on social API's. Burke worked for Progress as a Developer Advocate focusing on Kendo UI.

Comments

Comments are disabled in preview mode.