News

Get faster, smarter code with RequireJS

Improve your JavaScript prowess by understanding how to code in a modular fashion with the RequireJS library

requirejs800

Building websites can often lead to head tags stuffed with tens of script tags, some of which may depend on each other, others of which may be standalone. Managing these files and the order in which they’re loaded can be time consuming, and browsers stop rendering when parsing these files. This can, at worst, cause an unresponsive UI.

Like a knight in shining armour, RequireJS comes to the rescue. Unlike other programming languages, JavaScript doesn’t have a package management system but Asynchronous Module Definitions (AMD) are part of a solution to this. RequireJS is a module loader, which, as the name might suggests, loads in modules that you specify asynchronously (ie without blocking the page from rendering).
The RequireJS project also has a few helpful plug-ins and tools: domReady, text, a CoffeeScript adapter, i18n, and r.js. In particular, i18n makes converting international strings a doddle and r.js is a separate tool to help minify files or projects, concatenating dependencies into a single file to reduce HTTP requests. We will be using domReady, text, and r.js. In this tutorial you’ll learn how to use RequireJS to manage library dependencies by building a simple application that’ll pull in photos from Flickr and display them in a slideshow.

DOWNLOAD TUTORIAL FILES

Data-main

We’ll start with our HTML page. The most interesting part here is the script tag. We load Require.js as we would any other JavaScript file – but this one has a data-main attribute. This sets the base URL to the scripts folder and runs the code found in scripts/main.js – with most RequireJS definitions you can omit the file extension.

001 <!doctype html>
 002 <html>
 003    <head>
 004        <meta charset=”utf-8”>
 005        <title>Flickr Slideshow</title>
 006        <link rel=”stylesheet” href=”styles/main.css”>
 007    </head>
 008    <body>
 009        <div class=”slideshow”></div>
 010     <script data-main=”scripts/main” src=”scripts/components/    require.js”></script>
 011     </body>
 012 </html>

Directory structure

Our directory structure will look like the following code, as RequireJS encourages a shallow structure. However, it can be customised when it is being configured to match your preferences. Instead of thinking of your scripts by filename, think of them like IDs. For example, if we require jQuery as an ID it’ll request scripts/components/jQuery.js.

001 .
 002  scripts
 003     app.build.js
 004     components
 005     app.js
 006      slides.html
 007      jquery.js
 008    main.js
 009  styles
 010    main.css
 011  index.html

Require configuration

The first thing we’ll do in our main.js file is set up some custom configuration options. The baseUrl defaults to the folder that data-main points to in our HTML, but in this case most libraries are housed under components. We can also set up paths for each dependency separately Now RequireJS will look for domReady at scripts/components/requirejs/domReady.js.

001 require.config({
 002    baseUrl: ‘scripts/components’,
 003    paths: {
 004        domReady: ‘requirejs/domReady’
 005    }
 006 });

Shim configuration

Many scripts will need to be ‘shimmed’ so that RequireJS can work with them. Scripts that declare themselves in the global scope are ‘legacy’ and RequireJS needs to be told what they call themselves (eg Underscore.js is _). We can also set dependencies so if we define a module which requires Backbone, then it’ll automatically grab jQuery and Underscore too.

001 shim: {
 002     handlebar: {
 003         exports: ‘Handlebars’
 004     },
 005     ‘jquery.slideshow’: [‘jquery’],
 006     backbone: {
 007        deps: [‘underscore’, ‘jquery’],
 008        exports: ‘Backbone’
 009    },
 010    underscore: {
 011        exports: ‘_’
 012    }
 013 }

Define a module

Defining a module is pretty simple but there is actually a lot going on here. First we call define and pass it three things: a module ID (‘main’), its dependencies (if any) and a function that’ll contain the rest of the code. The dependencies get passed to the function in the order that you specify them so you could write function(require, app).

001 define(‘main’, [‘require’, ‘app’], function(require) {});       

Requiring dependencies

Within our module we’ll require domReady, a RequireJS plug-in. domReady is a cross-browser solution to waiting for the DOMContentLoaded event without having to wait for jQuery to be loaded. It’s especially important waiting for domReady with asynchronous scripts because they could attempt to change the DOM before it’s finished parsing.

001 ‘use strict’;
 002 require([‘domReady’, ‘http://api.flickr.com/services/        rest/?method=flickr.interestingness.getList&api_key=dfe82aea164aa1    83a555938136493c82&format=json&extras=url_l&jsoncallback=define’,     ‘app’], function (domReady, data, app) {
 003     domReady(function() {
 004         app.setSlides(data);
 005    });
 006 });

External dependencies

RequireJS can also make AJAX calls to different domains, making it very useful for API calls. In this case we’ll get a JSONP feed containing Flickr’s most interesting photos; note that the callback method is called define. This means that we can depend on external services alongside other dependencies, like libraries from content delivery networks.

001 ‘use strict’;
 002 require([‘http://api.flickr.com/services/rest/?method=flickr.    interestingness.getList&api_key=API_KEY&format=json&extras=url_l&jso    ncallback=define’]);

Custom dependencies

Requiring ‘app’ will make a request to scripts/components/app.js where we’ll define our own module. Again, we pass through the arguments in order so: domReady, the Flickr response we’ll call ‘data’, and the ‘app’ will be an object with different methods attached to it. If you get script errors it could be because the file cannot be found.

001 ‘use strict’;
 require([‘app’], function (domReady, data, app) {
 002     console.log(domReady, data, app);
 003 });

Define another module

Now we’ll write app.js. Along with domReady, another RequireJS plug-in is text.js. This is used to load simple text files, we’ll use it in conjunction with Handlebars, a JavaScript templating engine, to load in some HTML. It’s simple to use, just ‘text!’ suffixed with the filename. We’ve already told RequireJS that jquery.slideshow is dependent on jQuery, so it’ll load both.

001 /*global define */
 002 define(‘app’, [‘text!slides.html’, ‘handlebar’, ‘jquery.    slideshow’],     function (slides) {
 003 });

Text.js and Handlebars

slides.html contains this morsel of HTML. The curly brackets are where Handlebars will replace the text with the value of the data that we’ll pass to it. The great thing about Handlebars is that it’s highly readable. It goes through each photo if there’s a length greater than 0, else it displays the message.

001 <ul class=”slides”>
 002     {{#each photos.photo}}
 003         <li><img src=”{{url_l}}” alt=”{{title}}”         title=”{{title}}”></    li>
 004     {{else}}
 005         <li><p>There were no results!</p></li>
 006     {{/each}}
 007 </ul>

setSlides method

Within our app module definition we can set up a method that will take the Flickr data (although because of the implementation abstracting the source away it could be any service). We then return the method so that our service will be able to be used by other parts of the application.

001 var setSlides = function(data){/*  next step */};
 002 return {
 003     setSlides: setSlides
 004 };

Compile Handlebars template

We’ve made a request to get slides.html with the Handlebars brackets and passed it in as a variable called slides. To interpolate our data with the template we need to compile the HTML (Handlebars.compile) and then pass the data to the template, appending it all to our slideshow element.

001 var setSlides = function(data) {
 002    var template = Handlebars.compile(slides);
 003    $(‘.slideshow’).html( template(data) );
 004 };

Responsive slides plug-in

We’ll use Viljami Salminen’s ResponsiveSlides.js to easily convert our list of images into a slideshow that simply fades between each one. Again, because of our implementation, it’s trivial to swap this out with another library if needs change. Because we call this after the Handlebars template has compiled and been appended, we can trust that .slides exists.

001 $(‘.slides’).responsiveSlides();

Use app module

That’s all there is to it! At this point the most interesting Flickr images will populate the slideshow. By arranging individual pieces of functionality into separate files, RequireJS encourages a more modular approach to your front-end software architecture. This is something that other languages have had for some time and comes into its own in large-scale projects.

001 require([ ... ‘app’], function (domReady, data, app) {
 002     console.log(domReady, data, app);
 003     app.setSlides(data);
 004 });

Form HTML

Now we’ll add search functionality to our page. We’ll build a simple form with a search input and a button so that people can type in a search term and we’ll ask Flickr for all of the images that are tagged with that term.

001 <form>
 002     <input type="search" placeholder="Search for     something" required>
 003     <button>Find</button>
 004 </form>

Flexibility of asynchronicity

In our original main.js file we’ll require domReady, app.js and jQuery. Newer versions of jQuery register themselves as a named AMD module called ‘jquery’. Again we’ll wait for domReady but here’s where the asynchronicity of RequireJS comes in to play – because we’re not waiting for a response from Flickr, this function will fire before the one above it.

001 require([‘domReady’, ‘app’, ‘jquery’], function (domReady, app) {
 002     domReady(function() {
 003         // next step
 004     });
 005 });

Listen for submit

We’ll use jQuery to listen for the form to be submitted. When it is submitted we’ll get the text of what they typed in (by reading .value) and pass it through encodeURI, this converts special characters e.g. ‘&’ becomes ‘%26’, so that the search term can be passed to a URL parameter.

001 $(‘.search-flickr’).on(‘submit’, function (e) {
 002     e.preventDefault();
 003     var term = encodeURIComponent(e.target[0].value);
 004     // next step
 005     return false;
 006 });

Get images

This time we’ll use jQuery to make the request using $.getJSON and adding the term in as the tags value. When there’s a response we call the setSlides method exactly as we did before. As the data format is the same we don’t need to change anything else, and you should see the images update.

001 $.getJSON(‘http://api.flickr.com/services/rest/?method=flickr.    photos.search&tags=’ + term + ‘&api_key=API_KEY&format=json&extras=u    rl_l&jsoncallback=?’, function (data) {
 002     app.setSlides(data);
 003 });

RequireJS command line

RequireJS isn’t limited to just handling dependencies, a spinoff project of RequireJS is r.js, which can be loaded either from Node or Java (Node is recommended as it’s much faster). Note that to install it you’ll need to have Node installed and you may have to use sudo if you get permission errors.

001 $ npm install -g requirejs

Minify main.js

r.js traces all of the dependencies in a given file and collates them all into a single output file – it has extensive configuration options but checking out the documentation (requirejs.org/docs/optimization.html) is a very good place to start. It does have some limitations, especially when working with remote sources like CDNs but there are workarounds for this.

001 r.js main.js
 002 // => results in a minified main.js     file

Build configuration

You can pass arguments over the command line each time but it’s easier to maintain if you have a build file with your settings in it. We’ll write a new file called app.build.js in the scripts directory. appDir is the root of the project, baseUrl is where the majority of our required scripts live.

001 ({
 002     appDir: ‘../’,
 003     baseUrl: ‘scripts/components’,
 004     dir: ‘../build’,
 005    paths: {
 006         ‘main’: ‘../main’
 007     },
 008     modules: [{
 009        name: ‘main’
 010    }, {
 011        name: ‘app’
 012    }]
 013 })
 

Use build file

In the configuration we’ve added a custom path for main (because it lives above the other scripts) and told it what our two named modules are called. To build it now we run r.js with the -o[ptimise] flag pointing to our app.build configuration file. Note that command line values supersede those set in the configuration file.

001 $ r.js -o app.build.js

Exclude files

Optimising a project will combine all of the JavaScript files into as little as possible and collate CSS files using @import into one file too. However, this process can be quite complicated and can take up a lot of time when developing. As a result, a ‘shallow exclude’ function is also added in order to exclude the file(s) that you’re currently working on.

001 //app.build.js
 002 baseUrl: ‘scripts/components’,
 003 optimize: ‘none’,
×