Optimize Your Frontend Workflow With Grunt

As a developer, the web is currently the most challenging and awesome it has ever been. Finding the time to research all of the tools, techniques, browser APIs, and frameworks, all the while doing your daily work, is well, hard. Grunt helps you organize, refine, and optimize your frontend workflow.

Yes, any task automation stategy will increase productivity, and if you do not have a standard way of doing things, you really should. However, Grunt is a notably robust and flexible framework. If you want to setup a reduced test case for some css, it can be served up in a multi-browser livereload enabled test bed in minutes. If you are developing an application, you can setup your directory structure as you or your framework sees fit, and at your disposal you have JS/CSS preprocessing, JS/CSS concatenation/minification, image optimization, and more. Grunt also supports project templates, so you can be up and running on new projects with your familiar conventions immediately.

As a primer, this tutorial shows you how to setup the Grunt livereload plugin. This was one of the reasons I wanted to checkout Grunt and it serves as great example of the benefit that comes in factoring out repetitive tasks.

Preliminary Steps

Installing node is easy, you can get it multiple ways, which are neatly listed on the node download page. The following have all just worked:

Windows

cinst node.install

OS X

brew install node

Ubuntu

apt-get nodejs

You also need to install the grunt command line interface:

npm install -g grunt-cli

Hello Grunt

Create a package.json file which stores meta data about the project as well as which Grunt plugins are required. NPM's init command will creates this file interactively,

npm init

Install the Grunt package to the project and track the dependency in package.json

npm install grunt --save-dev

Next create the Gruntfile.js Grunt configuration file, with the following contents.

module.exports = function(grunt){

	grunt.registerTask('hello', function(){
			console.log("Hello from the Gruntfile!");
		});
};

Back in the command line we can now run the Grunt task:

grunt hello

Grunt found the registered hello command and executed it. Now let's do what we came to do.

Serve up some web

Create the following directory tree and files in the project root:

.
|__src

In the src directory create the following 2 files: index.html and site.css

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Grunt Project</title>
		<link rel="stylesheet" href="reset.css" />
		<link rel="stylesheet" href="site.css" />
</head>
<body>
	<h1>Grunt Project</h2>
</body>
</html>
/* site.css */
body {
	background-color: #F5B800;
	font-family: Tahoma, Geneva, sans-serif;
}

h1 {
	background-color: #FFDB70;
	margin: 1em;
	padding: .5em;
	x-shadow: 3px 3px 5px 6px #c29100;
	-webkit-box-shadow: 3px 3px 5px 6px #c2910;
	box-shadow:         3px 3px 5px 6px #c29100;
	border-radius: .5em;
}

Let's serve up the site via connect and livereload, with a watch on the files.

npm install grunt-regarde --save-dev
npm install grunt-contrib-connect --save-dev
npm install grunt-contrib-livereload --save-dev

Open up the Gruntfile.js and make the following changes to the boiler plate code.

'use strict';
var path = require('path');
var lrSnippet = require('grunt-contrib-livereload/lib/utils').livereloadSnippet;

var folderMount = function folderMount(connect, point) {
  return connect.static(path.resolve(point));
};

module.exports = function (grunt) {
  // Project configuration.
  grunt.initConfig({
    livereload: {
      port: 35729 // Default livereload listening port.
    },
    connect: {
      livereload: {
        options: {
          port: 9001,
          middleware: function(connect, options) {
            return [lrSnippet, folderMount(connect, options.base)]
          }
        }
      }
    },
    // Configuration to be run (and then tested)
    regarde: {
      html: {
        files: '**/*.html',
        tasks: ['livereload']
      },
			css: {
				files: '**/*.css',
				tasks: ['livereload']
			}
    }

  });

  grunt.loadNpmTasks('grunt-regarde');
  grunt.loadNpmTasks('grunt-contrib-connect');
  grunt.loadNpmTasks('grunt-contrib-livereload');

  grunt.registerTask('default', ['livereload-start', 'connect', 'regarde']);
};

Fire up the web server by issuing the grunt command

grunt

Your web is now being served up at localhost:9001/src with some extra convenience:

  • Livereload enabled: All changes to html/css/js assets are automatically refreshed in the browser.
  • CSS Injection: If you change css, it will be reflected in the browser automatically without doing a full page refresh, useful for styling specic states of the UI.

Go modular stay nimble

This is one example of how Grunt enables workflow optimization. Additional savings comes with setting up deploy time post processing tasks. In general, modular tools like Grunt will be better at responding to the constant change in technique and workflow. This provides a distinct advantage over relying soley on any all-in-one class of tool.