
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.
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’,