Creating a Web-app with Grunt – Part 1

This is the first of a two-part tutorial, in which we’ll be creating a web-app – Breeze – that will load and display temperature information for various cities using data from OpenWeatherMap. In this part of the tutorial, we’ll be setting up a simple build workflow using Grunt and npm.

If you’re short on time and just want to see the complete code for this part of the tutorial then you can fork the repo on Github and switch to the ‘tutorial-part-1’ branch.

A Note for Windows Users

Throughout this tutorial I will be referring to the Mac command line application – Terminal, so if you are on Windows then you’ll simply need to use Command Prompt instead.

Development Environment Setup

Firstly we need to have our dev environment setup so that we can use npm and Grunt. To do this we just need to ensure Node.js is installed, as npm comes bundled with it by default. So if you haven’t already got this up and running then installing one of the pre-built packages from the Node.js website is probably the simplest method.

Next we need to install Sass. If you are using Windows then you’ll first need to install Ruby, whereas if you are using a Mac then Ruby comes pre-installed. To install Sass, open Terminal and run the following command:

gem install sass

This can take a few minutes to complete, so be patient, but if you are using a Mac and the above command is failing then you could use sudo gem install sass instead.

Project Setup

You will need to download a small library called ‘atomic’, which our project will use for making AJAX requests to the OpenWeatherMap API.

Now we need to setup the project files and folders – create the following structure:


breeze
│
├──• Gruntfile.js
├──• index.html
├──• package.json
│
└───src
     │
     ├───js
     │    ├───app
     │    │    │
     │    │    └──• app.js
     │    │
     │    └───libs
     │         │
     │         └──• atomic.js
     └───scss
          │
          └──• app.scss


The breeze directory will serve as the document root of our application.

Now that we have the basic structure, let’s add some content to the files we just created. The contents of index.html should be as follows:

<!DOCTYPE html>

<html>
    <head>
        <title>Breeze</title>
        <link rel="stylesheet" type="text/css" href=“dist/css/app.min.css">
    </head>

    <body>
        <div class="main-container"> </div>

        <script src=“dist/js/app.min.js"></script>
    </body>
</html>

Astute readers may have noticed that – within the HTML markup – we are referencing directories and files that don’t exist – dist/css/app.min.css and dist/js/app.min.js. These directories and files will be created automatically by Grunt once we set it up and run a build task.

For the contents of app.js, we will temporarily just add an alert message:

alert("Breeze");

And finally, the contents of app.scss will currently just contain the following styles:

html, body, div {
    margin: 0;
    padding: 0;
}

Installing Development Dependencies

We are going to use npm for installing development dependencies – those used by Grunt for the build step of our application.

The first thing we need to install is the CLI (command line interface) for Grunt. This will be a global install; it isn’t specific to our project and will be available system-wide, so you’ll only need to do this once per-system:

Mac users
You will need to run this command with sudo.

sudo npm install -g grunt-cli

Windows users

npm install -g grunt-cli

Next up, within the breeze directory, open the package.json file that we created earlier and add the following:

{
  "name": "breeze",
  "version": "0.0.1"
}

This simply defines some basic information about our application which is required by npm. Save and close the file.

Although the package.json file has many uses, within the scope of this tutorial – and other than the required name and version properties – it will simply be used to define the development dependencies of our application.

Note that the command npm init could instead be used to automatically generate a package.json file, but doing so adds many properties that we won’t need to use in this tutorial, so creating it manually is a simpler option in this case.

Now that we have our package.json file, using Terminal, change to the breeze directory. We will need to run all remaining commands from this directory. Install the development dependencies by running the following command:

npm install grunt grunt-contrib-connect grunt-contrib-sass grunt-contrib-uglify grunt-contrib-watch grunt-devtools --save-dev

This installs all of our development dependencies into a node-modules folder within the breeze directory. A ‘devDependencies’ property is also added to package.json, listing the names and current version numbers of our application’s development dependencies:

{
  "name": "breeze",
  "version": "0.0.1",
  "devDependencies": {
    "grunt": "^0.4.5",
    "grunt-contrib-connect": "^0.8.0",
    "grunt-contrib-sass": "^0.8.1",
    "grunt-contrib-uglify": "^0.6.0",
    "grunt-contrib-watch": "^0.6.1",
    "grunt-devtools": "^0.2.1"
  }
}

Doing the Grunt Work

We will be using Grunt – a JavaScript task runner – to carry out the build step of our application. Grunt works by running various user-defined tasks when instructed to do so. We are going to set it up in such a way that our tasks run when a change to JavaScript or SCSS files is detected.

To use Grunt, we first need to open Gruntfile.js that we created earlier within the breeze directory and paste in the following code, which we will discuss in more detail below:

module.exports = function(grunt) {

    "use strict";

    grunt.initConfig({

    });
};

When we use the Grunt CLI (more on this below), the version of the Grunt library specified within the devDependencies property inside package.json is loaded and the configuration within the Gruntfile is automatically applied. Currently our Gruntfile doesn’t contain any configuration options, so we’ll start adding these now.

Sass

First, we’ll add a config for compiling our SCSS code using the sass task (grunt-contrib-sass) – a wrapper for the version of Sass installed on our system i.e. the Sass Ruby gem we installed earlier:

module.exports = function(grunt) {

    "use strict";

    grunt.loadNpmTasks("grunt-contrib-sass");

    grunt.initConfig({
        sass: {

            dev : {
                options: {
                    style: "compressed",
                    sourcemap : true
                },

                files: {
                    "dist/css/app.min.css": "src/scss/app.scss"
                }
            }
        }
    });
};

Here, we first tell Grunt to load the sass task and then we define the config for this task within the object that gets passed into Grunt’s initConfig method. Within the sass task we define a ‘dev’ configuration object. We can define however many configurations we like and call them whatever we like, but within these configuration objects we need to follow the rules of the task we are using, which in this case is sass. The sass task allows us to define an ‘options’ object containing various properties that affect how our SCSS is converted into CSS. In this case we are simply stating that we want the CSS to be compressed into one line with any empty space removed. The ‘files’ object contains two file path strings – the one on the left is the file path to which we want Grunt to output our compiled CSS, and the one on the right is the main SCSS file we want to use to start the compilation process.

To test that everything is working as expected, run the following command in Terminal:

grunt sass:dev

Doing that should compile the SCSS into an app.min.css file within the dist/css/ directory.

Uglify

Next up, we want to use the uglify (grunt-contrib-uglify) task to concatenate all of our JavaScript into a single file and then minify this file when complete so that it loads quickly in web browsers. This is also a good way to reduce the number of HTTP requests an application makes when developing with more than one JavaScript file; rather than using multiple script tags within the HTML markup we will only need to use a single one that points to the compiled file containing all of our JavaScript code – app.min.js. To carry out the concatenation and minification process we will add a configuration object for the uglify task:

module.exports = function(grunt) {

    "use strict";

    grunt.loadNpmTasks("grunt-contrib-sass");
    grunt.loadNpmTasks("grunt-contrib-uglify");

    grunt.initConfig({

        sass: {

            dev : {
                options: {
                    style: "compressed",
                    sourcemap : true
                },

                files: {
                    "dist/css/app.min.css": "src/scss/app.scss"
                }
            }
        },

        uglify : {

            dev : {
                options : {
                    compress : true,
                    mangle : true,
                    preserveComments : false
                },

                files: {
                    "dist/js/app.min.js" : ["src/js/libs/atomic.js", "src/js/app/app.js"]
                }
            }
        }
    });
};

We have now also loaded the uglify task and defined a dev config for it within the object passed into Grunt’s initConfig method.

The ‘files’ object for uglify works in exactly the same way as the one within the sass config, with the exception that we are passing in an array of JavaScript file paths on the right. All of these files will be concatenated, minified and output to a single JavaScript file. When set to ‘true’, the compress option shortens variable and function names, and removes empty space in an attempt to reduce the overall file size. Similarly, When set to ‘true’, the mangle option performs various optimisations such as removing unreferenced functions and variables. We have set the preserveComments option to ‘false’ so that all comments will be removed from the minified file.

To test that everything is working as expected, run the following command in Terminal:

grunt uglify:dev

Doing that should compile the JavaScript into an app.min.js file within the dist/js/ directory.

A word of warning here – the files are concatenated in the same order that they are specified in the array of file paths, so you’ll need to ensure that dependencies are ordered correctly.

With all that working as expected, the project directory structure should now look like this:


breeze
│
├──• Gruntfile.js
├──• index.html
├──• package.json
│
├───src
│    │
│    ├───js
│    │    │
│    │    ├───app
│    │    │    │
│    │    │    └──• app.js
│    │    │
│    │    └───libs
│    │         │
│    │         └──• atomic.js
│    └───scss
│         │
│         └──• app.scss
│
└───dist
     │
     ├───css
     │    │
     │    └──• app.min.css
     └───js
          │
          └──• app.min.js


Connect

Now that we’ve setup a build for our SCSS and JavaScript, we are going to create a connect (grunt-contrib-connect) config that allows us to quickly start a basic server in order to run our application, which will be launched in the default browser. This is way quicker than configuring Apache, for example. And like Apache, the connect server runs our application using the http:// protocol – which allows us to use AJAX – rather than the file:// protocol which does not.

We will set this up in a similar way to the other configs:

module.exports = function(grunt) {

    "use strict";

    grunt.loadNpmTasks("grunt-contrib-sass");
    grunt.loadNpmTasks("grunt-contrib-uglify");
    grunt.loadNpmTasks("grunt-contrib-connect");

    grunt.initConfig({

        sass: {

            dev: {
                options: {
                    style: "compressed",
                    sourcemap : true
                },

                files : {
                    "dist/css/app.min.css": "src/scss/app.scss"
                }
            }
        },

        uglify: {

            dev: {
                options: {
                    compress: true,
                    mangle: true,
                    preserveComments: false
                },

                files: {
                    "dist/js/app.min.js" : ["src/js/libs/atomic.js", "src/js/app/app.js"]
                }
            }
        },

        connect: {

            server : {
                options: {
                    open: true,
                    keepalive: true
                }
            }
        }
    });
};

As with the other tasks, we have loaded the connect task and defined a config, which this time we have called ‘server’. The only two options we need to use at this point are ‘open’ and ‘keepalive’ – both of which we have set to ‘true’. The open option simply launches the default web browser on our system and loads the index page within the same directory as the Gruntfile. By default, the connect task only keeps the server active for as long as our other tasks are running, so since we have no continually-running tasks, we use the keepalive option to force the server to stay active until it is manually stopped.

Run the following command to launch our application in the default browser:

grunt connect:server

Notice that, within the Terminal window, we can no longer run any commands now that we have the server running. To stop the server, on the keyboard hold the ctrl key and press c.

If you want to run Grunt commands while the server is active then you can open another Terminal window and once again change to the breeze directory.

Running Multiple Tasks with One Command

So far, we have set things up so that we can run tasks one at a time, but what if we want to run multiple tasks? We could run them one after the other in Terminal, but there is an easier way – we can register our own tasks with Grunt, which is much more straightforward than it sounds. Let’s start by creating a task that will first run sass:dev, followed by uglify:dev, and then finally connect:server. We’ll call this task ‘breeze’:

module.exports = function(grunt) {

    "use strict";

    grunt.loadNpmTasks("grunt-contrib-sass");
    grunt.loadNpmTasks("grunt-contrib-uglify");
    grunt.loadNpmTasks("grunt-contrib-connect");

    grunt.initConfig({

        sass: {

            dev: {
                options: {
                    style: "compressed",
                    sourcemap : true
                },

                files : {
                    "dist/css/app.min.css": "src/scss/app.scss"
                }
            }
        },

        uglify: {

            dev: {
                options: {
                    compress: true,
                    mangle: true,
                    preserveComments: false
                },

                files: {
                    "dist/js/app.min.js" : ["src/js/libs/atomic.js", "src/js/app/app.js"]
                }
            }
        },

        connect: {

            server : {
                options: {
                    open: true,
                    keepalive: true
                }
            }
        }
    });

    grunt.registerTask("breeze", ["sass:dev", "uglify:dev", "connect:server"]);
};

So now instead of sequentially running each task manually, in Terminal, we now simply need to run:

grunt breeze

This will run all the tasks we specified in the array for the second parameter of Grunt’s registerTask method.

Automating Build Tasks

Now everything is set up and ready for us to manually run builds of our SCSS and JavaScript, and view our app within the browser. But, wouldn’t it be nice if we could go one step further and have Grunt run relevant tasks automatically when any of our development files is changed? That’s precisely what we’ll be doing in this section of this tutorial.

Watch

To automate the running of the tasks that we have already defined, we first need to setup a watch (grunt-contrib-watch) task which watches specific files and runs relevant tasks if any of the files changes.

Setting up the watch task is very similar to the way in which we setup all the other tasks (this is now the complete Gruntfile):

module.exports = function(grunt) {

    "use strict";

    grunt.loadNpmTasks("grunt-contrib-sass");
    grunt.loadNpmTasks("grunt-contrib-uglify");
    grunt.loadNpmTasks("grunt-contrib-connect");
    grunt.loadNpmTasks("grunt-contrib-watch");

    grunt.initConfig({

        sass: {

            dev: {
                options: {
                    style: "compressed",
                    sourcemap : true
                },

                files : {
                    "dist/css/app.min.css": "src/scss/app.scss"
                }
            }
        },

        uglify: {

            dev: {
                options: {
                    compress: true,
                    mangle: true,
                    preserveComments: false
                },

                files: {
                    "dist/js/app.min.js" : ["src/js/libs/atomic.js", "src/js/app/app.js"]
                }
            }
        },

        connect: {

            server : {
                options: {
                    open: true
                }
            }
        },

        watch: {

            js: {
                files: ["src/js/**/*.js"],
                tasks: ["uglify:dev"]
            },

            scss: {
                files: ["src/scss/**/*.scss"],
                tasks: ["sass:dev"]
            }
        }
    });

    grunt.registerTask("breeze", ["sass:dev", "uglify:dev", "connect:server", "watch"]);
};

We have loaded the watch task and created two configs – ‘js’ and ‘scss’. These configs could have been called anything else, but the names were chosen so that the nature of what the configs will be used for is clear. Within each config, using the files option, we specify an array of file paths that are to be watched for changes, and then, using the tasks option, we specify a list of tasks to run when any of the listed files is changed. In the js config we are watching all files that have a .js file extension within the src/js directory and its subdirectories. When any of these files is changed, the uglify:dev task is run automatically. Similarly, with the scss config we are watching all files that have a .scss file extension within the src/scss directory and its subdirectories. When any of these files is changed, the sass:dev task is run automatically.

At this point, it is important to note that we have removed the keepalive property from the ‘server’ config of the connect task, since it would have stopped the watch task – or any task, for that matter – from running afterwards. The keepalive property is no longer needed because we are now using the continually-running watch task, which will keep the server alive automatically.

Notice that – within the breeze task we created earlier – ‘watch’ has been added to the array of tasks that Grunt will run for us sequentially.

Now we simply need to once again run our grunt breeze task using Terminal (be sure to cancel any running tasks using ctrl + c first) and our app will launch in the browser, with the watch task waiting for changes on the lists of files we specified. To check this is working change the alert message in app.js to the following:

alert("Grunt is a breeze");

Save the file and Grunt should automatically run the tasks we specified in the watch:js config, so refresh the web browser and you should see the new alert.

Next steps

So far we have covered how to setup Sass and npm, and how to use Grunt to setup a simple workflow for the build step of a web application.

In the next instalment of this two-part tutorial, we will cover the logic of the breeze application – loading and displaying current temperature information for various cities by making AJAX requests to the OpenWeatherMap API. We’ll also briefly cover SCSS variables and mixins.

Feel free to ask questions in the comments below.

Happy coding!

4 Comments

  1. Damian

    Nice work dude! Detailed, yet easily understood instructions. Bring on part 2..

    1. Ivan

      Thanks! I’m glad it was helpful. And part 2 should be complete within the next few weeks, dependent on my day-to-day workload 🙂

  2. Leigh

    Great post! You have saved me days of trial and error.

    1. Ivan

      Glad it helped 🙂

      Checkout part-2 if you want to take it a little bit further.

Leave a Reply

Required fields are marked *