All Posts

After learning about browserify, we will look into another interesting and popular Javascript package, which is Grunt.

What is it for?

Web project development typically involves certain tasks, such as minification, testing, or optimization of the files. Grunt helps you by automating these tasks, leaving you to do only the most essential parts of the development.

Why use it?

As our project gets more complex, it's better to leave small repetitive tasks for automation. With Grunt, we can easily customize what kind of tasks that you want to automate.

How to use it?

In this example, let's say we want to make a simple web page that has a button that change its color when clicked and a text that displays current time. And then we want to automate certain tasks within the project. The structure of the project is like this:

.
    ├── js
    │   ├── main.js          # main page's script
    │   ├── button.js        # button events' script
    ├── package.json         # 
    └── index.html           # main page

index.html

This is the main HTML page. Here, we include the Javascript files: main.js and button.js.

<!DOCTYPE html>
<html>
    <head>
        <title>Example</title>
    </head>
    <body>
        <p id="time"></p>
        <button class="toggle-color">Toggle color</button>

        <!-- include jQuery -->
        <script src="https://code.jquery.com/jquery-3.3.1.min.js"
            integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
            crossorigin="anonymous"></script>

        <script src="js/main.js"></script>
        <script src="js/button.js"></script>
    </body>
</html>

main.js

This file displays the current time when the page is loaded.

// main page script

$(document).ready(function() {
    // display current time
    $('#time').html(new Date());
});

button.js

This file handles the buttons' click event.

// manage buttons' event

/**
* Toggle the color of the button.
*/
$('button.toggle-color').click(function() {
    $(this).css('background', 'red');
});

package.json

This is the basic package.json file, the one you get when you use npm init.

{
    "name": "try-grunt",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    "author": "Muhammad Farhan Majid <han.majid2004@gmail.com> (https://hanmajid.com)",
    "license": "MIT"
}

You can open index.html and make sure that everything works as intended.

Now, let's say that we want to automate these tasks:

  1. Checking Javascript files' syntax and best practices.
  2. Concat all the Javascript files into 1 file.
  3. Minified the concatted Javascript file.

We can do this with Grunt.

Here are the steps:

  1. Install Grunt's CLI in your computer
    npm install -g grunt-cli​
  2. Install Grunt to your project.
    npm install grunt --save-dev​
  3. Install Grunt's plugins that you need for our tasks. In this example, we want to check syntax, concat files, and minified it. So, we have to install these packages:
    // each plugin corresponds to a task that we need
    
    // this plugin checks for syntax
    npm install grunt-contrib-jshint --save-dev
    // this plugin concat files
    npm install grunt-contrib-concat --save-dev
    // this plugin minified file
    npm install grunt-contrib-uglify --save-dev
    
    // this plugin is an extra, to monitor the changes of your files in real-time
    npm install grunt-contrib-watch --save-dev​
    There are many more other Grunt's plugins that you can use. Check them out here.

  4. Create file named Gruntfile.js in the root folder. This file consists of the configuration of the tasks that we want to use. The basic structure of Gruntfile.js is like this:
    // basic structure of Gruntfile.js
    // here, we only use 1 plugin: grunt-contrib-jshint
    
    module.exports = function (grunt) {
        // 1. initialize Grunt's configuration object
        grunt.initConfig({
            // package information
            pkg: grunt.file.readJSON('package.json'),
            // grunt-contrib-jshint's configuration
            jshint: {
                files: ['Gruntfile.js', 'src/**/*.js']
            }
        });
    
        // 2. load tasks
        grunt.loadNpmTasks('grunt-contrib-jshint');
    
        // 3. register tasks
        grunt.registerTask('test', ['jshint']);
    };​

First, we will initialize Grunt's configuration object. Notice that each plugin will be configured in the property of Grunt's configuration object. For example, grunt-contrib-jshint is configured in the property jshint in the Grunt's configuration object. 

Second, we will load the tasks we used in the Grunt's  configuration object. We can load multiple tasks here. In the example above, we only load 1 plugin.

Lastly, we will register task commands that tells Grunt which tasks to execute. In the example above, if we run command grunt test in terminal, Grunt will execute jshint task.

Beside the task command that you registered in Gruntfile.js, you can also execute each plugin individually, by typing grunt <plugin-name>, for example grunt jshint.

That's the basic structure of Gruntfile.js.

Back to our simple web page example.

Our Gruntfile.js will look like this.

module.exports = function (grunt) {
    // 1. initialize Grunt's configuration object
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        jshint: {
            files: ['Gruntfile.js', 'js/**/*.js'],
            options: {
            // options here to override JSHint defaults
                globals: {
                    jQuery: true,
                    console: true,
                    module: true,
                    document: true,
                }
            }
        },
        concat: {
            option: {
                separator: ';\n'
            },
            dist: {
                src: ['js/**/*.js'],
                dest: 'dist/<%= pkg.name %>.js'
            }
        },
        uglify: {
            options: {
                banner: '/* <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n'
            },
            dist: {
                files: {
                    'dist/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>']
                }
            }
        },
        watch: {
            files: ['<%= jshint.files %>'],
            tasks: ['jshint']
        }
    });

    // 2. load tasks
    grunt.loadNpmTasks('grunt-contrib-jshint');
    grunt.loadNpmTasks('grunt-contrib-concat');
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-contrib-watch');

    // 3. register tasks
    grunt.registerTask('test', ['jshint']);
    grunt.registerTask('default', ['jshint', 'concat', 'uglify']);
};

Here, we have four plugin configurations, namely jshint, concat, uglify, and watch. Each plugin has their own properties that we can tweak as we like. Check out each documentation for more info.

I will explain briefly what each configuration does.

  • jshint will check the syntax of Gruntfile.js and all Javascript files in the js folder.
  • concat will read all Javascript files in the js folder and concat them to a new file (try-grunt.js) in dist folder.
  • uglify will minified the concatted file to a new file (try-grunt.min.js)
  • watch will execute jshint task whenever there's changes in Gruntfile.js and all Javascript files in js folder.

Now, you can execute each task individually by typing the plugin name, like grunt jshint. Or, you can use the task commands that we registered in Gruntfile.js: test and default. We can try these commands:

  1. Run grunt test to execute jshint task. This will check the syntax of all your Javascript files.
  2. Run grunt (this is the default command) to execute jshint, concat, and uglify (in that order). This will check the syntax, concat, and minify the Javascript files. You can test the minified file by including it in the index.html file, and exclude main.js and button.js. It will work the same.
  3. Run grunt watch to monitor the changes in your files in real-time. Try this: edit the Javascript files, delete a semicolon, and saves the file. Grunt watch will immediately notice the mistake and warn you to fix your syntax.

That's it for now! It turned out to be pretty long.

P.S.

You can avoid loading too much plugins by using load-grunt-tasks

// before
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-watch');

// after 
require('load-grunt-tasks')(grunt);

Since we talked about browserify last week, you can incorporate browserify in Grunt with grunt-browserify.

Idiot Note Javascript