Clean Grunt

Grunt is the tool of choice for many client side web projects. But, often the
gruntfiles look like a mess. I believe the reason for this is that many people
don't care about keeping it clean. On top of that, the file is often generated
by a tool, such as Yeoman, and not cleaned up after. I happen to think that
the gruntfile should be clean and here is a how to do it.

Here is how the project structure looks in development mode. I keep all my
client side code in an app directory and I use Bower to install external
components into app/components

I will use less, watch, concat, uglify, filerev and usemin to
optimize it and turn it into this.

The above structure is good because it serves one CSS file, one Javascript
file, and everything apart from index.html is named with an MD5 checksum that
allow me to cache everything infinitely!

Loading External Tasks

Loading tasks in Grunt is done with grunt.loadNpmTasks but since all
dependenciies is already declared in package.json there is no need to name
them again. So instead we use matchdep to load all Grunt dependencies
automatically.

The relevant section in package.json contains these files. All Grunt plugins
follow the grunt- naming convention.

JsHint

I think it is a good idea to run JsHint for all my files including the
Gruntfile and here is how I configure it.

Newer versions of JsHint can pick up configuration from package.json and I
take advantage of this so I don't have a duplicate configuration in a .jshint
file that is normally added when using a generated project.

The relevant section in package.json is defined like this:

I truncated the section for brevity but I kept my favorite configuration
options that deal with complexity and forces me to keep my code simple.

Less

As I wrote in CSS Good Practices,
I think using a CSS preprocessor is a really good idea and I use Less in this
project. Since Less is a superset of CSS all I have to do to use less is to
change the extension from .css to .less and configure Grunt to convert Less
files into CSS. In development mode I like to have the CSS files in the same
place I would have put them if I wasn't using Less. To avoid accidentally
checking the generated files into source control I add the following line to
.gitignore

Here is the configuration for generating a CSS file. I add two targets, one for
development and one for release which is compressed.

As you can see I only name one less file. I think it is a good idea to include
all less files via import statements.

Watch

In development mode I also like to have a file watcher that generates the CSS
files automatically when I change a less file. Here is the configuration.

Clean

It is also a good idea to be able to remove generated files with one command
clean will do that for me.

Concat, Uglify and Usemin Prepare

To concatenate and minify the Javascript files, I use concat and uglify.
But I don't want the files used in index.html to be automatically included.
To do this I need to use useminPrepare. It is one of two tasks included in
grunt-usemin, the other is unsuprisingly called usemin and I will describe
it later.

useminPrepare parses HTML files, looking for tags that follow a distinct
pattern, <!-- build:js outputfile.js --> and extracts the filenames from
script tags. These files are then injected into the concat and uglify
tasks. So, there is no need to provide a configuration for those tasks.

There are a few things that are noteworthy above. useminPrepare.options.dest
works in conjunction with the value defined in the build:js comment in the
html file. I always designate the root directory of the generated code in the
Gruntfile and I keep the relative path to the file in the HTML file. I do this
because this configuration is reused by the usemin task later and configuring
it this way in useminPrepare keeps it simpler later.

Also note that concat and uglify needs to have an empty dist property.
Otherwise, useminPrepare cannot inject configuration into it.

Running grunt useminPrepare shows the generated configuration.

Alright, now we have minified both CSS and Javascript, it is time to move the
files that don't need minification, images and html files.

Copy

Here I use a different configuration for the files. The expand option is what
is important. I tells grunt to copy the files preserving the structure.

OK, now all the files have been moved into their proper place and all that is
left is to checksum them and rename all the references.

Filerev, checksumming

filerev is my task of choice for adding the checksum of a file to its name. I
use MD5 to checksum all assets, javascript, css and images with this
configuration.

Usemin

The final task is to change all the references in the HTML and CSS files to use
the checksummed filenames and to change the script tags to reference the
minified file. usemin is the task for this job.

The only difficult thing about this is that usemin uses the paths from the
files it parses when it searches for assets to replace references to. This
means that options.assetsDirs must designate the directories where the
parsed files are located. In my case the CSS files are in dist/app/styles and
the HTML files are in dist/app. Hoohaah! Only one more thing before were
done. Calling all the tasks in order.

Release

I register the release task and tell it to invoke all the other files in
the correct order.

Example Code

This example comes from a workshop I give. If you are interested in one send me
a note. If you would like to give one yourself you are welcome to use
my example code. I also give a Grunt presentation

That's all folks!

5 Comments

  1. Hi there Anders,

    Thanks a lot for your guide! I used it as a way to integrate grunt-usemin and grunt-filerev into my workflow. This came at the right time too as I was just about to change over my image placeholder links to final ones. Plus, my colleagues were having cache issues when going through the test site, so this will help me cachebust right away!

    *One important note*, until I included ‘usemin’ at the bottom of the ‘grunt release’ task, it didn’t seem to replace the files, which I assume is the normal behaviour. Hence, I’d suggest you update the code to reflect the same – unless of course I’m wrong and didn’t configure something right :)

    Cheers!

  2. @AaronK, thanks for the feedback. I fixed the mistake.

  3. Jamie

    Thanks for your article, you’ve helped me sort out my Gruntfile.js problems! :)

  4. Kishore

    Hi Anders,
    thank you for your article. It helped to complete my task

Leave a Reply