The Insignificant Pebble

Grunt and clean Builds

Tagged with: grunt, development, windgazer, bitbucket2phonegap

Creating a clean build

Continuing to build on our previous article that dealt with linking Phonegap to Bitbucket, this post is going to walk you through creating a clean release directory. It's nice to have a place where you can have automated tools mess with your code, but at the same time not messing with your actual codebase.

I'll be building a reference implementation on grunt-build while working on this article, to validate my commands and hopefully to have a nice reference build to clone from when I start new projects.

Create a build directory

First part of my build process is making use of a build directory. In order to do this, I let grunt create a 'target' directory, this stems from my earlier Java years where maven is used as a build tool. Anything generated goes into folders under the target directory and the directory itself is going into the ignore list. Right, so, how do we do all of that?

I'm assuming you have Grunt installed, if not, go ahead and do so now :) Also, while you're at it, check out grunt-init-gruntfile, it's really convenient...

So, for starters we're going to add the grunt-mkdir dependency:

npm install grunt-mkdir --save-dev

Then add a simple setup to your Gruntfile.js to make you a build dir:

mkdir: {
    target: {
        options: {
            create: [ "target" ]
        }
    }
}

This way, if any plugin that you're going to use doesn't automagically create the target directory, at least it'll be there before you start using it. Now go ahead, see if it works:

grunt mkdir

One last tweak is to add target to your .gitignore file. That way, whatever you throw into the directory gets ignored by git, just the way we like it...

Copy files

The need for copying files is pretty obvious. Once you want to start minifying, concatenation and the works, it's nice if you can do so on source-files that don't matter. Quite unsurprisingly, copying files isn't very complicated.

Just add the grunt-contrib-copy dependency:

npm install grunt-contrib-copy --save-dev

And here's an example of what I'd copy to the release folder:

copy: {
    release: {
        files: [
            {
                expand: true, flatten: false,
                src: [
                    "*.html",
                    "fav*.*",
                    "assets/**/*",
                    "js/**/*",
                    "libs/**/*",
                    "css/**/*"
                ],
                dest: "target/release/"
            }
        ]
    }
}

Give it a whirl:

grunt copy

And you can find the relevant commit on our reference repository.

Concat, minify, and so forth

Concatenation, minification and potentially obfuscation are rather important steps in adding a few percentages worth of performance. Coincedentaly, it also makes it harder for third parties to run of with your hard work. Of course there's the offset of effort versus reward and that's where build tools can make or break your efforts.

Personally I think grunt-usemin is pretty much the holy grail on this particular part of the build. So, go ahead, give it a go!

npm install grunt-usemin grunt-contrib-concat grunt-contrib-uglify grunt-contrib-cssmin grunt-filerev --save-dev

Now, since we've copied our code to a nice safe playground, we can mess with it all we want. Of course, that also means we have to point usemin to our release directory. That way only files that are used for release will get altered. Also of note is that you must set the proper dest option towards your release dir and finally, it's convenient to also point the staging option to something in your target dir.

useminPrepare: {
    options: {
        dest: "target/release/",
        staging: "target/staging/"
    },
    html: ["target/release/index.html"]
},
usemin: {
    html: ["target/release/index.html"]
}

Now usemin works a little different than the usual stuff. At first useminPrepare will actually create settings for the rest of the tasks, them being:

  • concat concatenates files (usually JS or CSS).
  • uglify minifies JS files.
  • cssmin minifies CSS files.
  • filerev revisions static assets through a file content hash.

I'm going to strongly advice you to read up on how usemin works on the usemin github pages. That's because the actual requirements of your setup are based on what you're actually going to use. Needless to say, you don't have to use usemin in this step, you can use whatever you want, the takeaway is that you modify the files in the target/release directory, instead of the root of your project...

And again I've added this step to the reference repository for your browser and testing convenience.

Cleaning up

Finally, when you're done with all of this, or actually before you do any of this, it's important to clean the build directory. It is extremely important to clean your build dir before doing a new build, you have to guarantee that the build uses only, and exactly, what you intend, old growth must be cleared with extreme prejudice.

For this last performance we're going to use grunt-contrib-clean. Quite obviously it does what it says on the packaging, clean stuff. By now you should know the drill.

npm install grunt-contrib-clean --save-dep

And now we're adding a config to the Gruntfile.js somewhat like the following, adjust to your tastes:

clean: {
    release: ["target"],
    all: ["libs","node_modules","target"]
}

And let's give this one a whirl:

grunt clean:release

And all our hard work has been undone! Of course, if you've done everything right, this now makes for a repeatable stunt. You can check up on the reference repository to double-check your work if so desired.

The Utopia Cocktail

That was a long read and test-run, if the time I spent creating this article is any indication. But, there's one last nugget left. For those who've been looking at the reference repository this is not a surprise, for the rest of you this should explain how I've mixed all of the above ingredients together.

I personally added the following task to my Gruntfile.js:

grunt.registerTask(
    "install",
    [
        "clean:release",
        "jshint",
        "mkdir",
        "copy:release",
        "useminPrepare",
        "concat:generated",
        "cssmin:generated",
        "uglify:generated",
        "usemin"
    ]
);

As I hope you're now familiar with all these ingredients you can see that I first throw away the old target directory. I then run jshint to verify my code doesn't suck, then make sure we have a new target directory, copy the project files into it and finally run the whole usemin pipeline.

The end result is a smooth 'single command build' that should result in a clean build of my project, with concatenated and minified files for optimal performance! A little bonus, install [http-server][8] globally (npm install http-server -g) and enjoy your new build without configuring some fancy-shmancy big webserver.

grunt install
http-server target/release -o

Clean build TL;DR

Install these dependencies

npm install --save-dev grunt-mkdir grunt-contrib-copy grunt-usemin grunt-contrib-concat
npm install --save-dev grunt-contrib-uglify grunt-contrib-cssmin grunt-filerev
npm install --save-dev grunt-contrib-clean

Add the following to your Gruntfile.js:

clean: {
    release: ["target"],
    all: ["libs","node_modules","target"]
},
copy: {
    release: {
        files: [
            {
                expand: true, flatten: false,
                src: [
                    "*.html",
                    "fav*.*",
                    "assets/**/*",
                    "js/**/*",
                    "libs/**/*",
                    "css/**/*"
                ],
                dest: "target/release/"
            }
        ]
    }
},
mkdir: {
    target: {
        options: {
            create: ["target"]
        }
    }
},
useminPrepare: {
    options: {
        dest: "target/release/",
        staging: "target/staging/"
    },
    html: ["target/release/index.html"]
},
usemin: {
    html: ["target/release/index.html"]
}

grunt.registerTask(
    "install",
    [
        "clean:release",
        "mkdir",
        "copy:release",
        "useminPrepare",
        "concat:generated",
        "cssmin:generated",
        "uglify:generated",
        "usemin"
    ]
);

Run:

grunt install

Next

In the next chapter we'll be discussing how to use Grunt to put the resulting project files into a clean repository.