April 30, 2014

Automating Bower library integration with Grunt

Grunt and Bower are great command line tools for automating tedious front-end workflow tasks, and quickly adding libraries to a project. With Grunt we can define a series of tasks such as concatenating files, compressing images, running scripts, and so on. Bower is self-described as a “package manager for the web”. It provides an easy way to install a wide variety of libraries into a project, from twitter bootstrap, jQuery, font-awesome, and even your own personal repositories.

In this tutorial, we’ll look at one method of further streamlining the installation and integration of new Bower packages to a project with the help of Grunt. While the example is presented in the context of Drupal’s Omega theme, the principles can be applied to any project using Grunt and Bower.

Please note that this tutorial assumes you have a basic knowledge of both Grunt and Bower. I highly recommend taking some time to get familiar with these great tools at http://gruntjs.com/getting-started and http://bower.io

 

Using Grunt-Bower-Concat

Once a library is installed with Bower, it usually sits in a bower_components directory, waiting to be included in the project’s markup via a script tag or the script section in Drupal’s .info file. It would be much simpler for us if these were added automatically. How we can tackle this?

In general, concatenating and minifying JavaScript files is a good idea. Fewer files means fewer resource consuming HTTP requests, and smaller files mean less bandwidth is used as resources are transported. Lets begin by concatenating all our libraries together.

Now, we could simply grab every file ending in .js within the bower_components directory, but that could potentially include library scripts that we don’t really need, as well as possibly including the same script twice in minified and source forms! Not all bower libraries are neatly organized, and many still come with test files and so on. Not something we necessarily need for our project.

This is where a nifty grunt plugin called grunt-bower-concat comes in. With it, we can smartly concatenate all our Bower libraries, include and exclude specific files, and define which files need to be added before and after others.

Start by installing grunt-bower-concat, and saving it as a development dependency:

$ npm install grunt-bower-concat --save-dev

Then add it to the gruntfile so is loaded:

grunt.loadNpmTasks('grunt-bower-concat');
 

 

Loading Grunt Tasks Automatically

 

If you’d rather not have to manually add grunt tasks after installing them, it’s possible to auto-load them using another grunt task called load-grunt-tasks

 

$ npm install load-grunt-tasks --save-dev

 

We can then replace all grunt.loadNpmTasks calls with the following single line:

require('load-grunt-tasks')(grunt);

All tasks will now be loaded automatically, saving us from updating the Gruntfile each time a new plugin is added. Awesome!

 

More info can be found on the project’s repository: https://github.com/sindresorhus/load-grunt-tasks

 

Let’s set up some very basic configuration for our shiny new task. It’s pretty smart about grabbing the right files, so let’s see how it fares with Omega’s included Bower components. We’ll simply state a destination file that we want to add all of our libraries to.

bower_concat: { all: { dest: 'js/bower.js' } }

Once that is done, we can give it a quick test to see what happens.

 

$ grunt bower_concat
Running "bower_concat:all" (bower_concat) task
File js/bower.js created.
Done, without errors.

 

Great! All our libraries are bundled up nicely in the file we specified.

 

 

Bower Concat Options

 

Sometimes this doesn’t happen so smoothly though. If bower-concat cannot find the library’s files, you can tell it which files are required from each library. We can define this using the mainFiles option, providing the library name followed by a list of files relative to the library directory.

bower_concat: { all: { dest: 'js/bower.js', mainFiles: { 'jQuery': ['src/ajax.js'] } } }

Another important option that bower-concat includes is dependency declaration. Usually the task is able to determine this from the bower.json file included in the library’s root. If it isn’t included and concatenation order is important, you can define it like so:

bower_concat: { all: { dest: 'js/bower.js', dependencies: { 'underscore': 'jquery', 'mygallery': ['jquery', 'fotorama'] } } }

In this case, underscore requires jquery to be added first, while mygallery requires both jquery and fotorama to be concatenated before itself.

 

Finally, we can tell bower-concat to ignore or only include specified libraries. This is fairly straightforward, and is done so as follows:

exclude: [ 'matchmedia', 'selectivizr' ] include: [ 'html5shiv', 'respond' ]

More options and documentation can be found at the project repo on github:
https://github.com/sapegin/grunt-bower-concat

 

 

Minification

 

Now that our libraries exist in a single file, we can minify it using the uglify task included in Omega’s Gruntfile. Let’s add a unique task for our Bower library file.

uglify: { bower: { options: { mangle: true, compress: true }, files: { 'js/bower.min.js': 'js/bower.js' } } }

 

 

Task creation

 

Since we’ll often be using our bower_concat and uglify:bower tasks together, let’s create define a single task that runs them both.

grunt.registerTask('buildbower', [ 'bower_concat', 'uglify:bower' ]);

We can now run both tasks with:

 

$ grunt buildbower

 

This command pulls our libraries out and concatenates them into a single file, then minifies it. All that’s left to do is to include this file in the theme’s .info file, so it will automatically be included in the generated pages.

scripts[] = js/bower.min.js

As it stands, our current workflow for installing a new Bower library is the following:

 

$ bower install jquery
$ grunt buildbower
 

 

This one goes to 11

 

What we have so far is a great way to speed up integrating new Bower libraries into our project, but we can push it a bit further still.

 

It would be nice if new libraries were compiled the moment we installed them. We might try to make use of Grunt’s watch task, but that would require us to run it every time we were about to start updating or installing libraries. Instead, we’ll try using grunt-shell. And look! It’s right there waiting in Omega’s Gruntfile waiting for us.

 

We’ll begin by adding some simple tasks to shell:

shell: { // ... bowerinstall: { command: function(libname){ return 'bower install ' + libname + ' -S'; } }, bowerupdate: { command: function(libname){ return 'bower update ' + libname; } } }

Using ‘jquery’ as an example Bower library, these two tasks would be called like so:

 

$ grunt shell:bowerinstall:jquery
$ grunt shell:bowerupdate:jquery

 

Each of them accepts a single parameter, in this case a Bower library name, and either installs the library and saves it as a dependency, or updates the library according to bower.json.

 

We can now combine these shell tasks with our buildbower task.

grunt.registerTask('bowerinstall', function(library) { grunt.task.run('shell:bowerinstall:' + library); grunt.task.run('buildbower'); }); grunt.registerTask('bowerupdate', function(library) { grunt.task.run('shell:bowerupdate:' + library); grunt.task.run('buildbower'); });

Our new method of installing a bower library is as follows:

 

$ grunt bowerinstall:jquery

 

This task will tell Bower to install the library, save it as a dependency, concatenate all libraries, and minify the result. This final javascript file will be automatically included in all theme pages as it is included as a script in the .info file.

 

Closing Thoughts

 

Both Grunt and Bower are great tools for speeding up day-to-day front end tasks. At the end of the day, your tools should suit your needs, and help you in doing your job. The method I've shown here is just one of the many possible ways of configuring them to achieve one specific goal. So give Grunt and Bower a shot, and see how they can help you out.