This is the third post in a series on automation in web development. The most common task in web development is running a series of tasks on your JavaScript and CSS source files to optimize them for a high performing website. In general, our optimized JS and CSS files would be small in size, and small in number.
To do this, I first started using Apache Ant with the YUI compressor to create minified versions of my JavaScript and CSS source files. We’ve come a long way since then. We are now using CoffeeScript and LESS source files that compile to JavaScript and CSS. This doesn’t quite fit into the legacy workflow of using Apache Ant. So, Grunt to rescue. To get started, I had to install and configure Grunt for my project. At then end of the tutorial, we we able to use our package.json file to install the required plugin dependencies for my Gruntfile.
This post will finish up the [automation series(/2014/04/18/web-development-automation/) by creating our Gruntfile and running the grunt watch
command to continually run our automation tasks after any changes are saved to our source files.
We can then seamlessly code our CoffeeScript and LESS files, save them, and instantly test in the browser.
Gruntfile Wrapper
The Gruntfile is pretty simple. Here’s the basics.
- Create a file in the same folder as your package.json file in the root of your project
- The file can either be in JavaScript: Gruntfile.js
- Or, it can be in CoffeeScript: Gruntfile.coffee
- Define a function that is called when Grunt runs
- This function will configure Grunt, setup your tasks, and include the plugins that we previously installed
- The function must be defined under
modules.exports
The following snippet is the first part of our Gruntfile.
gruntFunction = (grunt) ->
gruntConfig =
pkg:
grunt.file.readJSON 'package.json'
grunt.initConfig gruntConfig
null
module.exports = gruntFunction
We have defined a function named gruntFunction()
, which is our wrapper function as it is defined as the module.exports
function on the last line of the above snippet.
Within our gruntFunction, we are setting up the configuration options for Grunt.
In this example, I am simply telling Grunt where to find the package file.
The next step is to call the loadNpmTasks()
method to load the plugins that we installed.
grunt.loadNpmTasks 'grunt-contrib-coffee'
grunt.loadNpmTasks 'grunt-coffeelint'
grunt.loadNpmTasks 'grunt-concat-css'
grunt.loadNpmTasks 'grunt-contrib-cssmin'
grunt.loadNpmTasks 'grunt-contrib-concat'
grunt.loadNpmTasks 'grunt-contrib-uglify'
grunt.loadNpmTasks 'grunt-contrib-less'
grunt.loadNpmTasks 'grunt-contrib-watch'
OK, now we are ready to start adding our tasks.
Create Application CSS from LESS Source
gruntConfig =
pkg:
grunt.file.readJSON 'package.json'
less:
app:
files:
"www/webroot/css/app.css": "src/less/styles.less"
grunt.initConfig gruntConfig
grunt.loadNpmTasks 'grunt-contrib-less'
null
module.exports = gruntFunction
In this example, I am using the less plugin to create the app.css file from the styles.less source file.
Concat Library CSS
gruntFunction = (grunt) ->
gruntConfig =
pkg:
grunt.file.readJSON 'package.json'
concat_css:
libs:
src: ["build/css/libs/*.css"]
dest: "www/webroot/css/libs.css"
grunt.initConfig gruntConfig
grunt.loadNpmTasks 'grunt-concat-css'
null
module.exports = gruntFunction
In this example, I am combing (concatenating) all of the CSS library files into a single libs.css file.
Minify Library and Application CSS
gruntFunction = (grunt) ->
gruntConfig =
pkg:
grunt.file.readJSON 'package.json'
cssmin:
app:
files:
"www/webroot/css/app.min.css": ["www/webroot/css/app.css"]
libs:
files:
"www/webroot/css/libs.min.css": ["www/webroot/css/libs.css"]
grunt.initConfig gruntConfig
grunt.loadNpmTasks 'grunt-contrib-cssmin'
null
module.exports = gruntFunction
In this example, I am minifying the app.css and libs.css file into app.min.css and libs.min.css.
Create Application JS from CoffeeScript Source
gruntFunction = (grunt) ->
gruntConfig =
pkg:
grunt.file.readJSON 'package.json'
coffee:
app:
options:
join: true
sourceMap: true,
files:
"build/js/app.js": ["src/coffee/*.coffee"]
grunt.initConfig gruntConfig
grunt.loadNpmTasks 'grunt-contrib-coffee'
null
module.exports = gruntFunction
In this example, I am compiling my CoffeeScript source files into a single app.js JavaScript file.
Concat Library JS
gruntFunction = (grunt) ->
gruntConfig =
pkg:
grunt.file.readJSON 'package.json'
concat:
options:
separator: ';'
libs:
src: [
"build/js/libs/moment-with-langs.min.js",
"build/js/libs/jquery.timepicker.min.js",
"build/js/libs/md5.js"
]
dest: "build/js/libs/libs.js"
grunt.initConfig gruntConfig
grunt.loadNpmTasks 'grunt-contrib-concat'
null
module.exports = gruntFunction
In this example, I am combining all of the JavaScript library files into a single libs.js file.
Uglify Application JS
gruntFunction = (grunt) ->
gruntConfig =
pkg:
grunt.file.readJSON 'package.json'
uglify:
options:
banner: "/img/banners/2014/""/**
* <%= pkg.name %>
* Copyright (c) <%= grunt.template.today("yyyy") %>
* <%= pkg.author.name %> <<%= pkg.author.email %>>
*/"""
mangle: true
compress: true
drop_console: true
dev:
options:
beautify: true
compress: false
mangle: false
drop_console: false
preserveComments: 'all'
files:
"www/webroot/js/libs.js": "build/js/libs/libs.js"
"www/webroot/js/app.js": "build/js/app.js"
dist:
files:
"www/webroot/js/libs.min.js": "build/js/libs/libs.js"
"www/webroot/js/app.min.js": "build/js/app.js"
grunt.initConfig gruntConfig
grunt.loadNpmTasks 'grunt-contrib-uglify'
null
module.exports = gruntFunction
In this example, I am running UglifyJs to minify the JavaScript files for both the libraries (libs.js) and my application (app.js).
Setup Watch Tasks
gruntFunction = (grunt) ->
gruntConfig =
pkg:
grunt.file.readJSON 'package.json'
watch:
less:
files: ["src/less/*.less"]
tasks: ["less", "cssmin"]
coffee:
files: ["src/coffee/*.coffee"]
tasks: ["coffee", "uglify"]
grunt.initConfig gruntConfig
grunt.loadNpmTasks 'grunt-contrib-watch'
null
module.exports = gruntFunction
In this example, I am setting up a watch task. This enables me to run the grunt watch command. It will monitor all of the source files (LESS and CoffeeScript) in my source directories for any changes. When a change has been saved, I run the tasks necessary to compile and minify the application files as necessary. To fire this up, I simply run the command:
$ grunt watch
Register Default Task
gruntFunction = (grunt) ->
gruntConfig =
pkg:
grunt.file.readJSON 'package.json'
grunt.registerTask 'default', ['less', 'concat_css', 'cssmin', 'coffee', 'concat', 'uglify']
grunt.initConfig gruntConfig
null
module.exports = gruntFunction
In this example, I am setting up the default tasks to run grunt is executed. Note that I have specified the order in the array. This is important, as most of the tasks rely on the completion of previous tasks.
Complete Gruntfile.coffee and Package.json Files
Here is the complete Gruntfile. You can also download the full source file if you prefer.
After downloading the file, just remove the .txt file extensions and place the file in your project root folder.
gruntFunction = (grunt) ->
gruntConfig =
pkg:
grunt.file.readJSON 'package.json'
less:
app:
files:
"www/webroot/css/app.css": "src/less/styles.less"
concat_css:
libs:
src: ["build/css/libs/*.css"]
dest: "www/webroot/css/libs.css"
cssmin:
app:
files:
"www/webroot/css/app.min.css": ["www/webroot/css/app.css"]
libs:
files:
"www/webroot/css/libs.min.css": ["www/webroot/css/libs.css"]
coffee:
app:
options:
join: true
sourceMap: true,
files:
"build/js/app.js": ["src/coffee/*.coffee"]
coffeelint:
app:
src: "src/coffee/*.coffee"
no_tabs:
level: "ignore"
indentation:
level: "warn"
no_trailing_whitespace:
level: "error"
no_trailing_semicolons:
level: "error"
no_plusplus:
level: "warn"
no_implicit_parens:
level: "ignore"
max_line_length:
level: "ignore"
concat:
options:
separator: ';'
libs:
src: [
"build/js/libs/moment-with-langs.min.js",
"build/js/libs/jquery.timepicker.min.js",
"build/js/libs/md5.js"
]
dest: "build/js/libs/libs.js"
uglify:
options:
banner: "/img/banners/2014/""/**
* <%= pkg.name %>
* Copyright (c) <%= grunt.template.today("yyyy") %>
* <%= pkg.author.name %> <<%= pkg.author.email %>>
*/"""
mangle: true
compress: true
drop_console: true
dev:
options:
beautify: true
compress: false
mangle: false
drop_console: false
preserveComments: 'all'
files:
"www/webroot/js/libs.js": "build/js/libs/libs.js"
"www/webroot/js/app.js": "build/js/app.js"
dist:
files:
"www/webroot/js/libs.min.js": "build/js/libs/libs.js"
"www/webroot/js/app.min.js": "build/js/app.js"
watch:
less:
files: ["src/less/*.less"]
tasks: ["less", "cssmin"]
coffee:
files: ["src/coffee/*.coffee"]
tasks: ["coffee", "uglify"]
grunt.initConfig gruntConfig
grunt.loadNpmTasks 'grunt-contrib-coffee'
grunt.loadNpmTasks 'grunt-coffeelint'
grunt.loadNpmTasks 'grunt-concat-css'
grunt.loadNpmTasks 'grunt-contrib-cssmin'
grunt.loadNpmTasks 'grunt-contrib-concat'
grunt.loadNpmTasks 'grunt-contrib-uglify'
grunt.loadNpmTasks 'grunt-contrib-less'
grunt.loadNpmTasks 'grunt-contrib-watch'
grunt.registerTask 'default', ['less', 'concat_css', 'cssmin', 'coffee', 'concat', 'uglify']
grunt.registerTask 'lint', 'coffeelint'
null
module.exports = gruntFunction