Build systems have a long tradition in software development infrastructures. They allow for easy automation of tedious tasks that have to be run often while a software product is in development. And there is a great variety of such tasks: compiling source files, running unit tests, gathering metrics from the code and finally deploying it.
This article will introduce Grunt, a build tool for JavaScript projects.
Grunt is a build tool for JavaScript projects, suited for web projects as well as for NodeJS-based projects. It itself runs on NodeJS and can easily be installed using npm: npm install -g grunt
. As many other build frameworks, it was heavily inspired by ant and make.
base config
As in every other build system, we need a config file for Grunt, where we define settings for our build system. Grunt expects this file to be named grunt.js
and when we run grunt
, it will look in the current directory for this file. So in most cases, it’s good to place this file in the root of your JavaScript projects.
We’ll start with the following template. Note that this is JavaScript, not just plain JSON! This will give us much more flexibility than just a plain JSON file will and we can use all the libraries that NodeJS supplies, too.
module.exports = function(grunt) {
grunt.initConfig({
lint: {
all: ['main.js', 'src/**/*.js']
},
jshint: {
options: {
browser: true
}
}
});
// Default task
grunt.registerTask('default', 'lint');
};
This template already defines some basic settings for jshint, for which support is already built-in into the Grunt core. The line grunt.registerTask('default', 'lint');
defines a default task that we can run using just grunt
in the directory of the grunt.js file. By now, this is pretty much the same as running grunt lint
.
built-in tasks
Grunt already comes with a bunch of useful tasks that just need to be configured some project-specific settings for. Besides jshint, these include:
- concat: concat multiple JavaScript files into one
- lint: lint JavaScript source files
- min: minify JavaScript files
- qunit: run QUnit tests in a headless PhantomJS instance
- server: runs a simple and static webserver
- watch: watches files for changes and runs tasks only on changed files
Plugins
Grunt is easily extensible via plugins and there is already a huge number of components available from npm (search for “gruntplugin“). This is just a small list of plugins that I use for JavaScript/CoffeeScript projects:
- CoffeeScript: grunt-coffee
- RequireJS: grunt-requirejs
- Jasmine unit testing: grunt-jasmine-node
- Stylus: grunt-stylus
grunt.js
I am going through some plugins now and extend the base template grunt.js file with the config for the plugins. My sample project will use CoffeeScript, RequireJS and Stylus and the directory structure of the project is like this:
build
src
views
ListView.coffee
components
jquery
...
backbone
...
css
style.styl
table.styl
main.js
grunt.js
To configure plugins and built-in tasks, we just need to extend the object that gets injected in the grunt.initConfig()
call. Consider this for the following code examples.
RequireJS
Install the plugin using npm install grunt-requirejs
and load the tasks of the plugin in grunt.js in the module function: grunt.loadNpmTasks('grunt-requirejs');
.
requirejs: {
appDir: 'src',
baseUrl: "./", // relative to appDir
dir: 'build',
modules: [
{ name: "main" }
],
paths: {
'jquery': 'components/jquery/jquery',
'backbone': 'components/backbone/backbone',
'underscore': 'components/underscore/underscore',
}
}
This is very similar to the usual RequireJS config. When we run grunt requirejs:js
, RequireJS will copy all the sources from src/ to build/ and concat and minify main.js, which is the name of the module given in the Grunt configuration. Of course you can have multiple modules to be built with Grunt.
CoffeeScript
I like CoffeeScript, but it’s always a hurdle to compile the sources to JavaScript for testing. The Grunt plugin grunt-coffee
simplifies this step a lot:
coffee: {
app: {
src: ['src/**/*.coffee'],
options: {
bare: true,
preserve_dirs: true
}
}
}
This should be pretty self-explanatory to CoffeeScript users. Just like app, you can add multiple objects for different folders and/or settings. As I am using RequireJS here, I like to go with the bare files.
Stylus
Stylus is a meta-language like CoffeeScript, but for CSS. But just like CoffeeScript, you need to compile it to test it in the browser. The grunt-stylus
plugin is perfectly suited to automate this task:
stylus: {
compile: {
options: {},
files: {
'src/css/style.css': 'src/css/style.styl',
'src/css/table.css': 'src/css/table.styl'
}
}
}
This is also a very simple config. We just have to add all target CSS files as keys and all source Stylus files as values in the files object. A run of grunt stylus
will then compile the two files to CSS.
Chaining and aliases
By now, we are able to run several small tasks, like coffee
and stylus
, but we have to run every task in sequence before we can actually test the whole app in the browser. We can now chain these targets and a single Grunt run will build all CoffeeScript files, compile all Stylus files to CSS and create a concatted and minified build with RequireJS.
First of all, I’ll change my default task to run coffee, lint and stylus:
grunt.registerTask('default', 'coffee lint stylus:compile');
Another task named release will then build everything and package it with RequireJS:
grunt.registerTask('release', 'default requirejs:js');
Watch
While developing on the project we have to run the default task whenever a CoffeeScript file was beeing changed. Why not automate this also? Grunt has a watch task, that checks on changes in files and runs other tasks whenever a watched file was changed:
watch: {
files: 'src/**/*.coffee',
tasks: 'coffee lint'
}
So now we only have to run grunt watch
and grunt will run the coffee and lint tasks whenever we change a .coffee source file.
And that’s it! There are plenty of other plugins out there, at least one for every JavaScript-related technology and configuring them is very easy. There’s no need to do any of these tasks manually and creating a complete, clean build of your JavaScript projects is of a minimal effort for every team member now!
Schreibe einen Kommentar