RequireJS

RequireJS Tutorial – How to use RequireJS

This article is about RequireJS, it describes its main features and what specification it follows and it shows some working snippets and configuration examples.

1. What is require JS?

RequireJS is an AMD JavaScript library that supports asynchronous file and module loading. It is optimized for web browser usage but it is also possible to use in Java or Node environments. It is compatible with all main browsers and quite intuitive to use.

The goal of this article is not to explain how RequireJS works but how to work with RequireJS while implementing web applications or libraries. Basically what RequireJS does is to create code on demand and add it to the page header as an script tag (using head.appendChild()) taking care of all its dependencies (and loading them as well in the proper order). For deeper information about how RequireJS works please visit http://requirejs.org/docs/why.html

2. What is AMD?

AMD comes from Asynchronous module definition. Asynchronous module definition (AMD) is a JavaScript specification that defines an API for defining code modules and their dependencies, and loading them asynchronously if desired. (from https://en.wikipedia.org/wiki/Asynchronous_module_definition). It specifies a mechanism for defining modules where the module and its dependencies can be asynchronously loaded.

RequireJS is an implementation of this specification.

A very important actor in AMD are the modules. Modules are registered pieces of code that can be referenced and its value can be exported or its behaviour can be used. So it is possible to declare a module that takes care of performing some calculations, expecting some input values and operations to perform and returning the result of these operations when are applied to the input values passed. This declaration can be used in different places around a web application without the need to declare it globally and polluting the global name space.

For a list of advantages or AMD please visit https://addyosmani.com/writing-modular-js/ or http://requirejs.org/docs/whyamd.html

Important to note that the global variables “define” and “require” are part of the specification, so these literals can not be re declared in your application code (they are reserved). For the AMD specification please visit https://github.com/amdjs/amdjs-api/blob/master/AMD.md

3. Configuration

The first thing needed when using RequireJS is to initialize it by executing its main configuration. In this chapter we are going to show how to do that.

From your HTML template you include the requireJS file passing its main configuration (via the data-main attribute):

emapp.html

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

This will execute the main.js file completely, this file contains all the required requireJS configuration. But developers can not assume that the execution of this script will happen before other scripts are initialized. If other scripts referenced in the application depends on the fully initialization of requireJS and the full execution of the main.js (in this case) then the main.js file should be required inside the application:

app.js

	//calling require.js
	//nothing starts until requireJS is fully configured
	require(['scripts/main.js'], function() {
		//safe now
		require(['app'], function(app) {
			app();
		});
	});	

The configuration options for requireJS are huge, here we are going to list the most important and used ones:

  • baseUrl: base path appended to all modules loaded via requireJS. The exceptions are modules ending with “.js” extension, containing a protocol, starting with “/” (absolute paths) or containing query string arguments. In these baseUrl is not used. Default is the path of the HTML page that loads requireJS, in case the “data-main” attribute is used, the default is the path of this attribute.
  • paths: path mapping for modules, relative to the baseUrl. At the end of this chapter an example is presented.
  • shim: Mechanism to support the configuration and usage of non AMD libraries with requireJS. It defines their dependencies, exports them using their global variables or calls their no conflict functions explicitly. The explanation of this option can take longer, but is out of the scope of this tutorial. For more information please visit http://requirejs.org/docs/api.html#data-main.
  • map: different paths for same modules, this is very useful in case the same application needs to use different versions of the same module for different purposes and needs (or wants) to share their ids in order to use the same code for different situations.
  • config: a way of passing configuration to the modules. This is done by using the special module “module” and its function module.config().
  • urlArgs: query parameters that are added to all resources loaded via requireJS. Very useful for cache busting during development, to be used with care on production.
  • waitSeconds: number of seconds before throwing a timeout, default 7 seconds. 0 means no timeout will be thrown.

An example of the configuration:

main.js

		require.config({
			baseUrl: "/web/app",
			paths: {
				"moduleA": "js/moduleA"
			},
			urlArgs: "version=" +  (new Date()).getTime(),
			waitSeconds: 15
		});

as stated before, the configuration options list is larger that that, more information to be found here:http://requirejs.org/docs/api.html#config

4. Require

This method is used for requiring and executing a given module (by id) or a desired url. Examples:

app.js

		//it is assumed that module a is defined already, if not an error will occur
		require(['a'], function(a){
			console.log(a);//Object {price: "300", currency: "USD"}
		})
		
		var url = "/module/service?q=33"
		//url above will be required (relative to the document)
		require([url], function(content){
			console.log(content);//content is the response of the server when calling the given url
		})

Require calls use the requireJS baseUrl (prepending it) in all cases but:

  • when query strings parameters are present
  • when absolute paths are present (‘/’)
  • a protocol like http or https is contained in the url
  • when the path ends with .js (js extensions)

In these cases, requireJS does not modify the url and does not prepend the baseUrl.

5. Define

Modules are loaded using the define() function. This function is used for module definition. A module can be basically any kind of object (not like an script file) and is not available in the global namespace (so the global namespace is not polluted). The main advantage of defining modules using requireJS is that no global objects are used. Modules are loaded (potentially) in different order as they are requested, but they are evaluated in the same order, so it is possible to load multiple versions of the same module in the same page.

Some examples of modules loading:

example1.js

	//This module contians two properties with fixed values ready to use:
	define('a', {
		price: "300",
		currency: "USD"
	});
	
	require(['a'], function(a){
		console.log(a);//Object {price: "300", currency: "USD"}
	})

example2.js

	//some operations are done before returning the module.
	define('a',function () {
		//Some calculations first
		var price = 3*4*5;
		
		return {
			price: price,
			currency: "USD"
		}
	});
  
	require(['a'], function(a){
		console.log(a); //Object {price: 60, currency: "USD"}
	})

example3.js

	//this module priceCalculation depends on other modules calendar and location
	define(["./calendar", "./location"], function(calendar, location) {
			//returns an object with a function and two properties.
			return {
				calculatePrice: function() {
					if(calendar.inTheWeek(today())){
						if(location.inEurope(here())){
							//in the week, in europe is calculated in Euros
							return{
								price: 300,
								currency: "EUR"
							}
						}else{ //completely non sense
							return{
								price: 300,
								currency: "USD"
							}
						}
					}
				}
			}
		}
	);

Note: It is not recommended to use global variables inside modules, because the usage of multiple versions of the same modules can pollute these variables. At least this should be used with care.

6. Module loading error handling

RequireJS provides a set of mechanisms to detect and handle errors while loading modules. It is important to mention that this applies only to the loading of modules via require (using callbacks) and not to the define() method which is used for defining a module.

The require-specific errbacks (require([]) errbacks) are used as callbacks when the loading of a module fails. For example when trying to load the module “moduleA”:

error.js

		require([moduleA], loadFunction, errorCallback)

if the loading is successful the loadFunction function is called, in case an error occurs, the errorCallback errback is called.

In order to determine what type of error happened, requireJS provides the following variables:

  • requireType: specifies the error type: “timeout”, “nodefine”, “scripterror”,…
  • requireModules: timed out module (as array) ids. It is possible that the modules that timed out are one or more of the modules that the required one depends on and not directly the required one, but the error will contain the required module.

Once we know the cause of the error and the module (or modules) that failed, it is possible to retry to load the same modules from a different source (in case this was the problem). For this purpose, developers can undefine modules using the requirejs.undef function that expects as parameter(s) the module ids that the developer wants to undefine:

error.js

		requirejs.undef(failedId);	

There are many cases where the error callback mechanism is useful. For example if an application depends on 3 modules: moduleA, moduleB and moduleC, but can run in recovery mode with the moduleD only. Meaning moduleD is needed for recovery but not for normal execution, but moduleA, B and C are needed only if we are not running in recovery mode (is just an example). For this type of situation the following code can be useful:

main.js

		//requireJS configuration file
		requirejs.config({
			enforceDefine: true,
			paths: {
				moduleA: 'js/moduleA',
				moduleB: 'js/moduleB',
				moduleC: 'js/moduleC'
			}
		});

		//application code
		require(['moduleA', 'moduleB', 'moduleC'], 
			function (a,b,c) {
				//application code using a, b, c
			}, 
			function (error) {
				//errback, error callback with the information about the causes of the error
				//var id = error.requireModules && error.requireModules[0]; -> useful to pick the exact name (in case only one)
				if (error.requireModules) {
					//undefine the failed modules in order to clean their dependencies in case something has been loaded
					requirejs.undef(moduleA);
					requirejs.undef(moduleB);
					requirejs.undef(moduleC);

					//Try again with moduleD only, reconfigure application
					requirejs.config({
						paths: {
							moduleD: 'js/moduleD'
						}
					});

					//Try in recovery mode
					require(['moduleD'], 
						function (d) {
							//application code using d (recovery mode)
						}, 
						function(error){}
							//error for the recovery mode code, not much to do, call the police
						);
				} else{
					//other error, depending in the situation, something can be done
				}
			}
		);

Other example with a different use case is the usage of public CDNs, in case the loading of the JavaScript file fails from the CDN, it is possible to try to load it from the local repository (probably an older version). An example for that can be found in http://requirejs.org/docs/api.html.

A short cut for this approach is called path fallback and consists in passing a second url or path to a given module in its paths configuration:

main.js

	
	//code copied from http://requirejs.org/docs/api.html
	//configuration
	requirejs.config({
		//To get timely, correct error triggers in IE, force a define/shim exports check.
		enforceDefine: true,
		paths: {
			jquery: [
				'http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min',
				//If the CDN location fails, load from this location
				'lib/jquery'
			]
		}
	});

	//application code
	require(['jquery'], function ($) {
	});

RequireJS also provides a global variable called requirejs.onError which contains the default behaviour in case of error. When developers want to detect errors that can not be catched by error callbacks or errbacks, they can provide a custom implementation for the requirejs.onError() function in the configuration of requireJS.

7. Optimizations

RequireJS provides a mechanism for optimization and combination of the modules containing the application. It combines all modules (or a set of them) together, minifies them and uglifies them (using an UglifyJS or the Closure Compiler when using Java). It is also possible to minify and combine the CSS files that are part of the application, but this in this tutorial we are going to explain the JavaScript part only.

The optimizer is part of r.js, (for both Node.js and Nashorn) and it is supposed to be used in the build process in order to deliver the application to the end users (pre live and live stages) and not to be used during development.

It is important to mention that only modules that are loaded via string literals passed to the top level require can be combined, minified and uglified (not the ones loaded using dynamic variables or methods).

Here we are going to show an example of usage but we are not going to explain how to install the optimizer in the different environments like Node.js, Nashorn or in the Browser. For this information please refer to the documentation that can be found in the link at the end of this chapter.

This is the code (part of it) of the imaginary application currencyCalculator.js:

app.js

	//the applications is composed of 3 modules
	require(["calculator", "translator", "formatter"], function (calculator, translator, formatter) {});

this is the requireJS build configuration called main-build.js (part of it):

main-build.js

	({
    baseUrl: ".",
    paths: {
        jquery: "some/other/jquery",
		//other external dependencies
    },
    name: "currencyCalculator",
    out: "currencyCalculator-built.js"
	})

In your build process you call:

	node r.js -o main-build.js
	node r.js -o name=main-build out=currencyCalculator-built.js baseUrl=.

This will generate a file currencyCalculator-built.js containing all needed modules. In this case, the modules translator, calculator and formatter plus all their dependencies.

By passing the option

main-build.js

	optimize: "uglify2", //"uglify2", "none",

to the main-build.js the code produced will be uglified by the UglifyJS component. If a module that is part of the main-build.js configuration file is declared like:

main-build.js

	modules: [
        // Define your modules here, so that they can be optimized.
        {
            name: "translator",
            exclude: ["underscore", "backbone""]
        },

indicates the optimizer that the translator dependencies underscore and backbone should not be minified, combined or uglified as part of the build process.

As stated before there are also configuration options in order to minifiy, combine and optimize the CSS files that are part of the project.

RequireJS recommends to run the optimizer on Node.js.

8. Plug-ins

RequireJS supports different plug-ins for loading files or dependencies which are not JavaScript code. In the following list you can find a list of all available plug-ins: https://github.com/jrburke/requirejs/wiki/Plugins.

As you can see in the link above there is a large list of them, here we are going to explain some of them that are broadly used and maintained by the RequireJS project team:

8.1 text

This plug-in allows developers to load text based resources asynchronously. It is very useful and clean when embedding HTML content in JavaScript files. It is automatically loaded when using the “text!” prefix in any require or define module call. The file extension needs to be passed to the plug-in (different that normal JavaScript files that are automatically recognized by RequireJS). Other difference to normal module loading in RequireJS is that this plug-in loads modules using XHR and do not append the code to the header as an script tag.

app.js

		/*
		* example of usage, 
		* note that the extensions html and css need to be passed
		* baseUrl from the RequireJS configuration is used
		*/
		require(["calculator", "text!calculator.html", "text!calculator.css"],
			function(calculator, html, css) {
				//the html and css variables will contain the content as text of the calculator.html and calculator.css files respectively
			}
		);

8.2 domReady

In order to interact with the DOM, developers need to wait until it is completely loaded. Some of the modules loaded by RequireJS can be ready (or not) before this happens. In order to prevent problems and assure that the DOM is completely ready before starting any interaction there are several approaches. The modern approach to do this is the DOMContentLoaded event (https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded) but this is not supported by all browsers. RequireJS offers a plug-in that is supported by all browsers and checks when the DOM is ready.

It can be downloaded, installed and used via the “domReady!” literal:

app.js

		require(['domReady!'], function (dom) {
			//This function is called once the DOM is ready, the value for 'domReady!' is the current document
		});

8.3 i18n

This one is useful when working with multiple locales. It uses the prefix “i18n!”. You need to download it and put it in the same directory as the application main JavaScript file. In order to use this plug-in you need to locate your localization files in a directory called “nls”:

app.js

		
		/*
		* example of usage
		* 
		*/
		//root/nls/labels.js
		define({
			"root": {
				"title": "title",
				"header": "header",
				"description": "description"
			},
			"es-es": true
		});
		//root/nsl/es-es/labels.js
		define({
			"title": "título",
			"header": "cabecera",
			"description": "descripción"
		});
		...
		//root/title.js
		define(["i18n!main/nls/labels"], function(labels) {
			return {
				translatedTitle: labels.title
			}
		});

In the example above RequireJS will use the browser properties navigator.languages, navigator.language or navigator.userLanguage to check what locale should be used and if this is one of the defined in the /root/nls/labels.js file then the translations from the correspondent language will be picked.

It is possible to configure the locale in the RequireJS configuration:

main.js

		requirejs.config({
			config: {
				//configuration for i18n plugin
				i18n: {
					locale: 'es-es'
				}
			}
		});
				

8.4 CSS loading using RequireJS

Even if possible, RequireJS do not recommend to load CSS using RequireJS: http://requirejs.org/docs/faq-advanced.html#css.

There are some plug-ins that allow developers to do that, basically these plug-ins just append to the header a link to the CSS file to be loaded.

If you are interested in writing a plug-in with your very specific needs, you need to follow the instructions in here http://requirejs.org/docs/plugins.html.

9. Creating a library using RequireJS

Using AMD and RequireJS for implementing and delivering your web application is a great choice. In case you are developing a JavaScript library that is supposed to be used by other developers in their web applications it is not a good idea to force them to use also AMD and RequireJS. As a library developer you want to deliver one single file JavaScript that can be installed and used as preferred. In this type of situations there is a tool called almond (https://github.com/jrburke/almond) that replaces RequireJS and allows developers to wrap your AMD code into one single file.

Almond has a couple of restrictions that need to be known before using it, the most important one is that dynamic require calls are not allowed because all the code is optimized into one single file.

10. Conclusions

In this article we explained what RequireJS is and how to use it, including a brief explanation and useful resources about the principles of AMD. We listed the main configuration properties while using RequireJS in an application and we described how to use the most important mechanisms like define and require. Working examples that can be used as basis for designing and implementing your project have been shown for configuration, defining modules, requiring modules (internally or externally), error handling, packaging and others. Also a brief list of the most important plugins has been provided included the text, domReady and i18n ones. Finally optimizations and library generation have been also explained.

In the following chapter you can find a list of links where you can get more information about the API that RequireJS exposes, about almond and library generation and also about AMD in general.

11. Download

Download
In the following link you can download a full working example covering the main components explained in this article : requirejs tutorial

12. Links

If you found this tutorial interesting you can continue reading about RequireJS and AMD in the following links:

Dani Buiza

Daniel Gutierrez Diez holds a Master in Computer Science Engineering from the University of Oviedo (Spain) and a Post Grade as Specialist in Foreign Trade from the UNED (Spain). Daniel has been working for different clients and companies in several Java projects as programmer, designer, trainer, consultant and technical lead.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
MoreOnFew
7 years ago

To add on, sometimes you might get timeout error in requirejs. This can be fixed by changing the waitSeconds parameter to 0.

Jorge
Jorge
7 years ago

Hello, nice tutorial first of all. I’m playing with Struts2 and would like to give a go to RequireJS. The thing is that I only need some kind of “import” functionality on my JS files, and RequireJS seems a bit overwhelming. Javascript ES6 actually comes with this modules thing, so in the future I might solve my problem using that, but right now, RequireJS seems the way to go. I’m not developing a SPA, so I find it confusing as I only found one example of multi-page application using requireJS (https://github.com/requirejs/example-multipage). Would it be an option for me? or is… Read more »

Back to top button