Optimizing Multiple Javascript Files with grunt and requirejs

Over at CreativEngine, I've been working on integrating gruntjs into our workflow for things like optimizing, finding and concatenating dependencies, minifying javascript and css, and pre-compiling the LESS that our css is based on.

In what turned out to be a sweetly fortuitous and happy accident, I'd been slowly transitioning to requirejs for AMD loading of our javascript.

"Sweetly fortitous?" you ask, "How so?"

Because not only does requirejs come with its own optimizer, but the helpful folks over at gruntjs have provided a grunt plugin: grunt-contrib-requirejs.

Sounds great, but what's the inevitable catch?

The first issue that I ran into was that most of the examples that I saw involved either optimizing a single file, specified via the "name" attribute, or explicitly defined modules.

However, since I'm...

  1. lazy, and
  2. forgetful, and also
  3. lazy

..I wanted a solution where I didn't have to configure anything, and could just run the grunt command and have everything take care of itself.

As it turns out, in the very latest version of our proprietary front-end site system that we've come up over the years, uris are rendered via template files, each of which has its own single point of entry for script injection and whatnot via a (requirejs) index.js file. Each of these index.js scripts resides in a folder with the same name of the template in a "template-scripts" folder in our "scripts" directory.

For example, a hypothetical home page would have a "home" template, and its index.js would reside in "scripts/template-scripts/home/index.js", and might look like so:

require(['file1', 'file2'], function(file1, file2) {  
    'use strict';
    // do stuff
});

So for my purposes, what I needed was for grunt to grab all files named "index.js" in any folder in the "template-scripts" directory, and run 'em through requirejs, resulting in indvidually optimized files.

First steps

As with all things node, the great thing about grunt is that it's frickin' javascript. So given the basic structure of a Gruntfile...

module.exports = function(grunt) {};  

...you can run any arbitary code in the function that you'd like.

My first step was to head on over to the gruntjs api docs, specifically grunt.file. Between the grunt.file.expand method and a quick glance at the "Globbing Patterns" section in the Configuring Tasks doc, I had a quick and easy way to find all the matching index.js files in my template-scripts folder:

module.exports = function(grunt) {  
    var matches = grunt.file.expand('scripts/template-scripts/**/index.js');
};

grunt.file.expand returns an array of all the files that match the globbing pattern, so that seemed like a good starting point.

The next step...

...was to do a bit of searching to see what I could see regarding multiple calls to requirejs within grunt. After a bit of poking about, I found this gist, which seemed promising, but involved requirejs sans grunt, and this issue which seemed like exactly what I needed.

So, forging on, I declared a base object literal and looped through my matches, getting rid of "index.js" for my "name" value and specifying "index.min.js" for my "out" value, and pushing it into an object containing all my other requirejs options:

var requirejsOptions = {};  
if (matches.length > 0) {  
    for (var x = 0; x < matches.length; x++) {
        var path = matches[x].replace(/\/index\.js/, '');
        requirejsOptions['task' + x] = {
            "options": {
                "baseUrl": "./",
                "wrap": true,
                "name": path + "/index",
                "out": path + "/index.min.js",
                "optimize": "uglify2",
                "uglify2": {
                    "mangle": false
                },
                "done": function(done, output) {
                    done();
                }
            }
        };
    }
}

This results in requirejsOptions object with a series of keys ("task0", "task1", etc.), each of which represents a distinct requirejs optimization starting with an index.js, tracing all its dependencies, concatenating and uglifying them, and ending with a single index.min.js output, ready to be called by our template.

Next up I init the grunt config and load the grunt-contrib-requirejs task:

grunt.initConfig({  
    pkg: grunt.file.readJSON('package.json'),
    requirejs: requirejsOptions
});

grunt.loadNpmTasks('grunt-contrib-requirejs');  

So in looking again at this issue, I thought this would be the right way to call the requirejs task...

grunt.registerTask('default', 'requirejs:task0 requirejs:task1 requirejs:taskn');  

...and initially I was building a string in the for loop above with this in mind.

But running grunt resulted in a "Verifying property requirejs.task0 requirejs exists in config...ERROR" warning and ultimately an "Aborted due to warnings" error.

Turns out that grunt-contrib-requirejs handles this automagically, and that this was sufficient:

grunt.registerTask('default', 'requirejs');  

With that in place, I ran the grunt command and was happy to see a series of 'Running "requirejs:taskn" (requirejs) task' messages stream by culminating in the always welcome "Done, without errors" message... yahoo!

Conclusion

I'm fairly new at node, grunt, requirejs, etc., and there's probably a much better way to do all this, but hopefully this will prove helpful for anyone heading down a similar path.

You can find the full content of the Gruntfile.js below, or at this gist.