One of the most commonly used JavaScript library today is RequireJS. In every project that I’m involved lately, we use RequireJS or I suggest to add RequireJS. In this post I’m going to describe what is RequireJS and some of its basic scenarios.
Async Module Definitions (AMD) First
You can’t start talking about RequireJS without mentioning what are JavaScript modules and what is AMD.
JavaScript modules are just pieces of code that follow the SRP (Single Responsibility Principle) and expose public API. In today’s JavaScript development, you encapsulate a lot of functionality inside modules and in most projects each module exists in its own file. That makes the life of JavaScript developers a little harder since they need to constantly watch for dependencies between modules and load the modules in a specific order or else have errors during runtime.
When you want to load JavaScript modules you use script tags. In order to load module dependencies, you need to load the dependency first and then the dependent. When using script tags, you need to arrange their loading in that specific order and the scripts will be loaded synchronously. You can use the async and defer keywords to make the load asynchronous but you might lose the order of loading in the process. Another option is to bundle all the scripts but still you will need to order them in the right order during the bundling.
AMD is all about defining modules in a way that the module and its dependencies can be asynchronously loaded and in the right order.
CommonJS, which is an attempt to standardize common JavaScript patterns, includes an AMD definition that I encourage you to read before you proceed in this post. In ECMAScript 6, the JavaScript vNext specifications, there are specifications for exports, imports and modules which are going to be a part of the JavaScript language but only in the near future. This is where RequireJS is entering our story.
RequireJS?
RequireJS is a JavaScript file and module framework that can be downloaded from http://requirejs.org/ or by using Nuget, if you work in Visual Studio environment. It is supported both in the browsers and in server environments like node.js. Using RequireJS, you will load only the relevant module dependencies in their right order.
What RequireJS is doing when you use it is to create script tags for each dependency you defined and load those dependencies on-the-fly using the head.appendChild() function. Then, after the dependencies are loaded, RequireJS will figure the right order to define the modules and will call each module definition in that right order. That means that you only need one root to load the entire functionality that you need and RequireJS will do the rest. In order to use that functionality appropriate, you will have to define each of your modules using RequireJS API or else nothing will work as expected.
RequireJS API exists inside the requirejs namespace which is loaded when you load the RequireJS script. RequireJS includes three main API functions:
- define — the function is used to define a module. Each module is defined with a unique module ID which will be used by RequireJS runtime functionality. The define function is a global function and you don’t need to use it with the requirejs namespace.
- require — the function is used to load required dependencies. It is a global function and you don’t need to use it with the requirejs namespace.
- config — the function is used to configure the requirejs runtime functionality.
Later on we will examine how to use those functions, but first lets understand how to start the RequireJS loading process.
The data-main Attribute
Once you downloaded RequireJS, the first thing to do after you put its script in your solution is to understand how RequireJS starts working. Once RequireJS is loaded, it search for a script with data-main attribute (it should be the same script with the src attribute set to load RequireJS). The data-main should be set to the base Url for all the scripts. From the base Url, RequireJS will start loading all the relevant modules. Here is an example of a script tag with the data-main attribute:
<script data-main="scripts/app.js" src="scripts/require.js"></script>
Another way to define the base Url is using the config function which we will see later on. RequireJS assumes that all the dependencies are scripts so when you declare a dependency you don’t need to use the .js suffix.
The config Function
If you want to change the default RequireJS configuration values with your own configurations, you can do that using the requirejs.config function. The config function receives an options object that can include a lot of configurations options. Here are some of the configurations that you can use:
- baseUrl — the root path to start the loading of modules.
- paths — path mapping for modules that don’t exists in under the baseUrl
- shims — configuration for dependencies, exports and initialization function to wrap scripts/modules that don’t use the RequireJS define function. For example, if underscore library doesn’t use the RequireJS define function and you still want to use it with RequireJS, you will have to define it as a shim in the config function.
- deps — array of dependencies to load.
Here is an example of using the config function:
require.config({
//By default load any module IDs from scripts/app
baseUrl: 'scripts/app',
//except, if the module ID starts with "lib"
paths: {
lib: '../lib'
},
// load backbone as a shim
shim: {
'backbone': {
//The underscore script dependency should be loaded before loading backbone.js
deps: ['underscore'],
// use the global 'Backbone' as the module name.
exports: 'Backbone'
}
}
});
The base Url in the example is set to scripts/app, every module that starts with lib is configured to be used from the scripts/lib folder and backbone is loaded as a shim with dependencies.
Defining Modules Using RequireJS
Modules are just well-scoped objects that expose an API and encapsulate their internals. In order to define a module, RequireJS exposes the define function. There should be only one call for define in each JavaScript file by convention. The define function receives an array of dependencies and a function which is going to hold all the module definitions. By conventions the module definition function receives as parameters all the previous dependencies and in the order they were supplied in the array. For example, here is a simple module definition:
define(["logger"], function(logger) {
return {
firstName: “John",
lastName: “Black“,
sayHello: function () {
logger.log(‘hello’);
}
}
}
);
As you can see, an array is passed to the define function with a logger dependency which is later used in the module. Also, you can see that in the module definition function there is a parameter called logger which will be set to the loaded logger module. Every module should return it’s API which in this case is two properties (firstName and lastName) and a function (sayHello). Later on, if you will load this module as another module dependency with a module id, you will be able to use the exposed API.
Using The require Function
Another useful function in RequireJS is the require function. The require function is used to load dependencies without the creation of a module. For example, here is a usage of the require function which defines a function that require jQuery to work:
require(['jquery'], function ($) {
//jQuery was loaded and can be used now
});
Summary
In the post I introduced RequireJS which is one of the libraries that I’m using in every JavaScript app project. Other then just loading module dependencies and in the relevant order, RequireJS helps to write modular JavaScript code which is much more maintainable and reusable.
Add comment
Originally published at blogs.microsoft.co.il on July 23, 2013.