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.