Shaking Hands with RequireJS

Before you know it your site is growing in complexity and your JavaScript files are quickly becoming extremely hard to manage, let alone make performant.

Thankfully these days we have a handful of tools to help with this situation. Let’s examine RequireJS in an attempt to decide if it’s for you… or not.

If you’re familiar with script tags then you understand you need to give them a src attribute containing a path to your desired JS file. The problem is that script tags tend to pollute the global scope. Each script could/will be creating variables along with more objects and that’s where you run the risk of a collision with additional scripts that may have a dependency order requiring the proper respect.

For example, Backbone.js requires that Underscore.js is loaded pre Backbone’s call. So, in your doc you’ll always need to make sure you’re loading Underscore.js before Backbone.js. It’s the same situation for jQuery and it’s plethora of plugins where a jQuery plugin will require the jQuery core all the time.

The pain is felt when you have 20 script tags lingering around in your doc making assets and dependency order tough to manage.


Solving The Dilemma

In order to solve our script tag soup dilemma, you’ll need some way to break apart different JavaScript files into different concerns and we don’t want a production site loading in 30 different files. You can think of RequireJS like this;

RequireJS will let you define your “required” modules.

For example, you may have a jQuery plugin that requires jQuery so RequireJS says “Ok, I have this jQuery plugin now let me get jQuery for you first,” but what if one view uses this weird jQuery plugin that needs two other jQuery plugins? Masonry would be a good example of this case.

RequireJS is an “In browser dependency manager” to help break up large applications into smaller blocks of manageable code (much like we approach partials in Sass). Essentially we’re composing a set of highly decoupled, distinct pieces of functionality stored in modules where the primary goal is to encourage modularity and portability.

This approach can also be referred to as “AMD – Asynchronous Module Definition” and is the philosophy for ECMAScript6.


The AMD Module


define(id?, dependencies?, factory);

The first two arguments are optional and the factory argument is a function that returns the exported value of your module.

Note: This does not always need to return an exported value. The factory function also acts as a closure to protect variables and functions from leaking globally.

The id is used as a unique identifier for your module which is really just a path, however RequireJS discourages the use of an id as a path to avoid modifying future updates of paths by hand if and when a module changes location.


define('jquery', function($) {
  // do some jquery jazz.
  $('element').toggleClass('class-name');
});

The dependencies argument can be an array listing out dependencies. RequireJS will automatically inject these dependencies into the module prior to the function call. Remember that dependency order matters!


define(['jquery', 'fitvids'], function($, fitvids) {});

Now we can use jQuery and FitVids within the function body and go nuts! When RequireJS detects the list of dependencies above it will turn into this…


<head>
  <script src="js/main.js"></script>
  <script src="js/jquery.js"></script>
  <script src="js/jquery.fitvids.js"></script>
</head>

Getting Started

Before you jump in make sure to note that all the examples to follow are available on Web Design Weekly’s GitHub account under the WDW RequireJS repo.

The WDW RequireJS repository is setup so you can get the feeling for a RequireJS workflow including the build process. The README contains general instructions to get you started. Make sure to follow all the instructions listed on the README before you begin.


Make a Config

Start by setting the baseURL from the config.js file like so…

js/src/config.js


requirejs.config({
  baseUrl: 'js/src'
});

The baseUrl is normally set to the same directory as the script used in the data-main attribute of your index.html. This url will tell RequireJS where to find your modules.


<!--This sets the baseUrl to the "js/src" directory, and loads a script that will have a module ID of 'main'--> 
<script data-main="js/src/main.js" src="js/lib/require.js"></script>

You can shortcut paths to avoid typing long subfolder names out constantly using the path property. This config that follows will load the CDN version of jQuery first and if the connection or CDN is unresolved we load the local copy much like HTML5 Boilerplate handles it.

js/src/config.js

requirejs.config({
  baseUrl: 'js/src',
  paths : {
    'jquery': [
      '//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min',
      '../lib/jquery-2.1.1.min'
    ]
  }
});

Now anytime we list jquery as a dependency in a module it will use //ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min, but in a shortened way for us to simply write ‘jquery’.


Script Reference

Within the root of your project create an index.html and reference the require.js file and the entry point for your logic inside the data-main attribute.

index.html


<html>
  <head>
    <meta charset="utf-8">
    <title></title>
    <link rel="stylesheet" href="css/styles.css">
    <script src="js/lib/modernizr.min.js"></script>
  </head>
  <body>
    <script data-main="js/src/main" src="js/lib/require.js"></script>
  </body>
</html>

Main Script

Generally the consensus from the documentation and community regarding when to use define() vs require() is as follows;

The require() function is used to run immediate functionalities, while define() is used to define modules for use in multiple locations of your project. Since we’re using require() in this case we’re asking for immediate functionalities and treating this as our main entry point for script execution logic.

js/src/main.js


// Load common code that includes config and globals
// then load the app logic for this view.
require(['config', 'globals', 'script'], function(config, globals, script) {
  // This function is called when common, globals and script is loaded.
  // If script calls define(), then this function is not fired until
  // script's dependencies have loaded.
  script.speak();
});

Optimization

During development its typical for RequireJS to load all our dependencies and scripts as a plethora of http requests. To remove that pain for production we’ll make sure to trace all dependencies for our site and ultimately minify them in order to minimize the amount of http requests using a build process provided by RequireJS.

command line


$ cd js
$ node ../node_modules/requirejs/bin/r.js -o build.js

Notice I used the locally installed version via npm of r.js which is the file required to optimize RequireJS projects via the command line. Using the r.js file inside node_modules alleviates the need to download the file manually from their site.

Once the optimized file has been built we can reference only one built script that will cut down on multiple http requests to a server and that my friend… is never a bad thing. For the visually curious here’s what a before and after looks like in the DOM during development and production…

Before Optimization

<script type="text/javascript" charset="utf-8" async="" data-requirecontext="_" data-requiremodule="main" src="js/src/main.js"></script>
<script type="text/javascript" charset="utf-8" async="" data-requirecontext="_" data-requiremodule="config" src="js/src/config.js"></script>
<script type="text/javascript" charset="utf-8" async="" data-requirecontext="_" data-requiremodule="globals" src="js/src/globals.js"></script>
<script type="text/javascript" charset="utf-8" async="" data-requirecontext="_" data-requiremodule="script" src="js/src/script.js"></script>
<script type="text/javascript" charset="utf-8" async="" data-requirecontext="_" data-requiremodule="jquery" src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script type="text/javascript" charset="utf-8" async="" data-requirecontext="_" data-requiremodule="jqplugins" src="js/src/jqplugins.js"></script>
<script type="text/javascript" charset="utf-8" async="" data-requirecontext="_" data-requiremodule="disablehover" src="js/src/disablehover.js"></script>

After Optimization

<script type="text/javascript" charset="utf-8" async="" data-requirecontext="_" data-requiremodule="main" src="js/build/main.js"></script>
<script type="text/javascript" charset="utf-8" async="" data-requirecontext="_" data-requiremodule="jquery" src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Is it right for you?

Hopefully based on what we’ve looked at up until this point can help you determine if RequireJS is right for your next project?

In my opinion, AMD makes sense and paves the cow paths regarding our current conversation around the importance of modularity and the benefits of portability.

Feel free to ping me on Twitter or leave a comment below regarding any questions concerning RequireJS.


Resources

10 thoughts on “Shaking Hands with RequireJS”

  1. RequireJs is amazing for a heavily driven Javascript website, but from my experience if you have less than 10 js files, its better for you to arrange them manually rather than using RequireJs.

  2. Context is definitely everything. There’s absolutely no need to over complicate your workflow if there is no warrant for it. The nice thing with RequireJS is the ability to protect the global scope and modularize everything. That part I like 😉 Thanks for reading and the great point. Cheers!

  3. Having tried writing apps using RequireJS, I find that declaring a module is quite cumbersome. It also requires modules we use to be in AMD format. Otherwise, we need to configure shims or write a wrapper.

    In the next project, I am going to experiment with SystemJS, another module loader, created to the specification of ES6 Modules (but works in ES5 browsers too) with a more futuristic approach.

    From what I read so far, it is able to detect load AMD, CommonJS (Node-style modules), ECMAScript 6 Modules (compiling it to ES5 using Traceur), and plain old global modules, so I can write modules in CommonJS style without the ugly AMD wrapper. I can also write modules in ES6, taking advantage of new language features. The greatest benefit is that a module in one format may require a module in another format directly.

    Finally, jspm.io is a package manager for SystemJS, allowing you to install packages from npm and GitHub for client-side use. It can also compile your app into single JavaScript file (but we might not want to do that anymore when HTTP/2.0 comes).

    I haven’t tried it extensively yet, however, but for me it looks very promising.

Comments are closed.