Lint + Concat + Minify With GruntJSA guide to automating your deployment process with GruntJS.

We’ll be covering how you can automate the process of linting, concatenating and minifying your projects files for deployment using GruntJS.


Overview

This guide assumes that you have some basic knowledge of GruntJS. If you’ve never encountered GruntJS before, I suggest that you take a few minutes to read Getting Started With GruntJS before proceeding.

In this guide we’re going to automate the following:

  • CSS Concat
  • CSS Lint
  • CSS Minify
  • HTML Lint
  • HTML Minify
  • JavaScript Concat
  • JavaScript Lint
  • JavaScript Minify

Folder Structure

We’re going to create a folder structure along with a number of test files for our project as an illustration.

Create a new folder named lint-concat-minify-with-gruntjs and replicate the following folder structure:

  • lint-concat-minify-with-gruntjs/
  • lint-concat-minify-with-gruntjs/static/
  • lint-concat-minify-with-gruntjs/static/dist/
  • lint-concat-minify-with-gruntjs/static/src/
  • lint-concat-minify-with-gruntjs/script/
  • lint-concat-minify-with-gruntjs/script/grunt/

Configuration Files

The files in this section will serve as our projects configuration files. Gruntfile.js is the configuration for grunt, and package.json is the configuration file for the NPM.

Create the following project configuration files in the lint-concat-minify-with-gruntjs/ directory:

Gruntfile.js

Our GruntJS configuration file.

/*global module, require*/

module.exports = function(grunt) {  
    // URI paths for our tasks to use.
    grunt.uri = './';
    grunt.uriStatic = grunt.uri + 'static/';
    grunt.uriDist = grunt.uriStatic + 'dist/';
    grunt.uriSrc = grunt.uriStatic + 'src/';
    grunt.uriTask = grunt.uri + 'script/grunt/';

    // Our task object where we'll store our configuration.
    var tasks = {};
    tasks.concat = {};

    // Lint Tasks
    // tasks = require(grunt.uriTask + 'css-lint.js')(grunt, tasks);
    // tasks = require(grunt.uriTask + 'html-lint.js')(grunt, tasks);
    // tasks = require(grunt.uriTask + 'js-lint.js')(grunt, tasks);

    // Concatenation Tasks
    // tasks = require(grunt.uriTask + 'css-concat.js')(grunt, tasks);
    // tasks = require(grunt.uriTask + 'js-concat.js')(grunt, tasks);

    // Minify Tasks
    // tasks = require(grunt.uriTask + 'css-minify.js')(grunt, tasks);
    // tasks = require(grunt.uriTask + 'html-minify.js')(grunt, tasks);
    // tasks = require(grunt.uriTask + 'js-minify.js')(grunt, tasks);

    // Register The Tasks
    grunt.registerTask('lint', ['csslint', 'htmllint', 'jshint']);
    grunt.registerTask('minify', ['cssmin', 'htmlmin', 'uglify']);
    grunt.registerTask('default', ['lint', 'concat', 'minify']);

    // Initialize The Grunt Configuration
    grunt.initConfig(tasks);
};

package.json

Our project configuration file which includes the dependencies that we need to carry out our specific automation duties.

{
  "name": "lint-concat-minify-with-gruntjs",
  "description": "Lint + Concat + Minify With GruntJS. https://salvatore.garbesi.com/lint-concat-minify-with-grunt/",
  "dependencies": {
    "csslint": "^0.10.0",
    "grunt": "^0.4.5",
    "grunt-contrib-concat": "^0.4.0",
    "grunt-contrib-csslint": "^0.2.0",
    "grunt-contrib-cssmin": "^0.10.0",
    "grunt-contrib-htmlmin": "^0.3.0",
    "grunt-contrib-jshint": "^0.10.0",
    "grunt-contrib-uglify": "^0.5.0",
    "grunt-html": "^1.4.0"
  },
  "author": "Salvatore Garbesi <sal@garbesi.com> (https://salvatore.garbesi.com/)",
  "license": "MIT"
}

Files To Test Against

The files that we’ll create in this section will be linted, concatenated and minified by GruntJS.

Create the following test files in the lint-concat-minify-with-gruntjs/static/src/ directory:

index.html

A test HTML file that we’ll lint and minify.

<!doctype html>  
<html>  
    <head>
        <title>Test</title>
    </head>

    <body>
        test
    </body>
</html>

test.css

A test CSS file that we’ll lint, concat, and minify.

.test {
    color: white;
}

test2.css

A test CSS file that we’ll lint, concat, and minify.

.test2 {
    color: black;
}

test.js

A test JavaScript file to lint, concat, and minify.

/*jshint browser: true*/

function test() {  
    window.console.log('Hello Test World!');
}

test();

test2.js

Another test JavaScript file to lint, concat, and minify.

/*jshint browser: true*/

function test2() {  
    window.console.log('Hello Test2 World!');
}

test2();

Installing Dependencies

We’re going to install all of the dependencies that we listed in our projects package.json file which GruntJS needs to perform its tasks.

Open up the Terminal application Applications > Utilities > Terminal on your computer.

From the CLI prompt cd to the lint-concat-minify-with-gruntjs/ directory that we previously created, and enter the following command:

npm install;

Keep your terminal open, as we’re going to use it throughout the guide.


CSS Lint + HTML Lint + JavaScript Lint

We’re going to lint check our CSS, HTML and JavaScript files prior to minifying and concatenating them. If our lint check fails, the entire deployment process will halt. If our lint check passes, the process will then proceed to our next step.

Create the following lint task files in the lint-concat-minify-with-gruntjs/script/grunt/ directory:

css-lint.js

This is our CSS Lint task for GruntJS.

/*global module*/

module.exports = function(grunt, tasks) {  
    // Load our node module required for this task.
    grunt.loadNpmTasks('grunt-contrib-csslint');

    // The configuration for a specific task.
    tasks.csslint = {
        // The files that we want to check.
        dist: {
            src: [
                grunt.uriSrc + '*.css', // Include all CSS files in this directory.
                grunt.uriSrc + '!*.min.css' // Exclude any files ending with `.min.css`
            ]
        }
    };

    return tasks;
};

html-lint.js

This is our HTML Lint task for GruntJS.

/*global module*/

module.exports = function(grunt, tasks) {  
    // Load our node module required for this task.
    grunt.loadNpmTasks('grunt-html');

    // The configuration for a specific task.
    tasks.htmllint = {
        // The files that we want to check.
        dist: {
            options: {
                path: false,
                reportpath: false // Turns logging to a file off, output will show in the CLI.
            },

            // The files that we want to check.
            src: [
                grunt.uriSrc + '*.html', // Include all HTML files in this directory.
                '!' + grunt.uriSrc + '*.min.html' // Exclude any files ending with `.min.html`
            ]
        }
    };

    return tasks;
};

js-lint.js

This is our JavaScript Lint task for GruntJS.

/*global module*/

module.exports = function(grunt, tasks) {  
    // Load our node module required for this task.
    grunt.loadNpmTasks('grunt-contrib-jshint');

    // The configuration for a specific task.
    tasks.jshint = {
        // The files that we want to check.
        dist: {
            src: [
                grunt.uriSrc + '*.js', // Include all JS files in this directory.
                '!' + grunt.uriSrc + '*.min.js' // Exclude any files ending with `.min.js`
            ]
        }
    };

    return tasks;
};

Modify the Gruntfile.js file by uncommenting the following lines:

tasks = require(grunt.uriTask + 'csslint.js')(grunt, tasks);  
tasks = require(grunt.uriTask + 'htmllint.js')(grunt, tasks);  
tasks = require(grunt.uriTask + 'jslint.js')(grunt, tasks);

Go back to the CLI prompt and enter the following command:

grunt lint;

You should then see the following output displayed at the CLI prompt:

$ grunt lint;
Running "csslint:dist" (csslint) task  
>> 2 files lint free.

Running "htmllint:dist" (htmllint) task  
>> 1 file lint free.

Running "jshint:dist" (jshint) task  
>> 2 files lint free.

Done, without errors.

That’s all there is to linting! We just successfully preformed a CSS, HTML, and JavaScript Lint on our files.

Each of the dependencies that we used to perform our lint has a variety of different options that you can set. Here are the documentation pages of the dependencies that we used for our linting process:


CSS Concat + JavaScript Concat

We’re going to concat our CSS and JavaScript files prior to minifying them.

Create the following concat task files in the lint-concat-minify-with-gruntjs/script/grunt/ directory:

css-concat.js

/*global module*/

module.exports = function(grunt, tasks) {  
    // Load our node module required for this task.
    grunt.loadNpmTasks('grunt-contrib-concat');

    // The configuration for a specific task.
    // In this case we have more than a single concat task. We need to append our task to our `tasks.concat` object that
    // way we're not overriding any of other previous tasks.
    tasks.concat.css = {
        // Where to output our concatenated file to.
        dest: grunt.uriDist + 'concat.css',

        // The files that we want to concatenate.
        src: [
            grunt.uriSrc + '*.css', // Include all CSS files in this directory.
            '!' + grunt.uriSrc + '*.min.css' // Exclude any files ending with `.min.css`
        ]
    };

    return tasks;
};

js-concat.js

/*global module*/

module.exports = function(grunt, tasks) {  
    // Load our node module required for this task.
    grunt.loadNpmTasks('grunt-contrib-concat');

    // The configuration for a specific task.
    // In this case we have more than a single concat task. We need to append our task to our `tasks.concat` object that
    // way we're not overriding any of other previous tasks.
    tasks.concat.js = {
        // Where to output our concatenated file to.
        dest: grunt.uriDist + 'concat.js',

        // The files that we want to concatenate.
        src: [
            grunt.uriSrc + '*.js', // Include all JS files in this directory.
            '!' + grunt.uriSrc + '*.min.js' // Exclude any files ending with `.min.js`
        ]
    };

    return tasks;
};

Modify the Gruntfile.js file by uncommenting the following lines:

tasks = require(grunt.uriTask + 'css-concat.js')(grunt, tasks);  
tasks = require(grunt.uriTask + 'js-concat.js')(grunt, tasks);

Go back to the CLI prompt and enter the following command:

grunt concat;

You should then see the following output displayed at the CLI prompt:

$ grunt concat;
Running "concat:css" (concat) task  
File ./static/dist/concat.css created.

Running "concat:js" (concat) task  
File ./static/dist/concat.js created.

Done, without errors.

That’s all there is to concatenating! We just successfully performed a CSS, and JavaScript concat on our files.

The dependency that we used to perform our concat has a variety of different options that you can set. Here’s the documentation page of the dependency that we used for our concatenation process:


CSS Minify + HTML Minify + JavaScript Minify

We’re going to minify our original CSS, HTML and JavaScript test files along with our newly generated concatenated CSS and JavaScript files.

css-minify.js

/*global module*/

module.exports = function(grunt, tasks) {  
    // Load our node module required for this task.
    grunt.loadNpmTasks('grunt-contrib-cssmin');

    // The configuration for a specific task.
    tasks.cssmin = {
        dist: {
            cwd: grunt.uriSrc, // The current working directory.
            dest: grunt.uriDist, // The destination directory to store our minified files.
            expand: true,
            ext: '.min.css', // The extension to use for our minified file.
            src: [
                '../dist/concat.css', // Specific rule to minify our `concat.css` file in our dist directory.
                '*.css', // Include all CSS files in this directory.
                '!*.min.css' // Exclude any files ending with `.min.css`
            ]
        }
    };

    return tasks;
};

html-minify.js

/*global module*/

module.exports = function(grunt, tasks) {  
    // Load our node module required for this task.
    grunt.loadNpmTasks('grunt-contrib-htmlmin');

    // The configuration for a specific task.
    tasks.htmlmin = {
        dist: {
            options: {
                collapseWhitespace: true,
                removeComments: true
            },

            files: [{
                dest: grunt.uriDist, // The destination directory to store our minified files.
                expand: true,
                ext: '.min.html', // The extension to use for our minified file.
                flatten: true,
                src: [
                    grunt.uriSrc + '*.html', // Include all HTML files in this directory.
                    '!' + grunt.uriSrc + '*.min.html' // Exclude any files ending with `.min.html`
                ]
            }]
        }
    };

    return tasks;
};

js-minify.js

/*global module*/

module.exports = function(grunt, tasks) {  
    // Load our node module required for this task.
    grunt.loadNpmTasks('grunt-contrib-uglify');

    // The configuration for a specific task.
    tasks.uglify = {
        dist: {
            cwd: grunt.uriSrc, // The current working directory.
            dest: grunt.uriDist, // The destination directory to store our minified files.
            expand: true,
            ext: '.min.js', // The extension to use for our minified file.
            flatten: true,
            src: [
                '../dist/concat.js', // Specific rule to minify our `concat.js` file in our dist directory.
                '*.js', // Include all JS files in this directory.
                '!*.min.js' // Exclude any files ending with `.min.js`
            ]
        }
    };

    return tasks;
};

Modify the Gruntfile.js file by uncommenting the following lines:

tasks = require(grunt.uriTask + 'css-minify.js')(grunt, tasks);  
tasks = require(grunt.uriTask + 'html-minify.js')(grunt, tasks);  
tasks = require(grunt.uriTask + 'js-minify.js')(grunt, tasks);

Go back to the CLI prompt and enter the following command:

grunt minify;

You should then see the following output displayed at the CLI prompt:

$ grunt minify;
Running "cssmin:dist" (cssmin) task  
File static/dist/concat.min.css created: 50 B → 35 B  
File static/dist/test.min.css created: 24 B → 17 B  
File static/dist/test2.min.css created: 25 B → 18 B

Running "htmlmin:dist" (htmlmin) task  
Minified static/dist/index.min.html 94 B → 77 B

Running "uglify:dist" (uglify) task  
File static/dist/concat.min.js created: 198 B → 131 B  
File static/dist/test.min.js created: 97 B → 64 B  
File static/dist/test2.min.js created: 100 B → 67 B

Done, without errors.

That’s all there is to minifying! We just successfully performed a CSS, HTML and JavaScript minification on our files.

Each of the dependencies that we used to perform our minification has a variety of different options that you can set. Here are the documentation pages of the dependencies that we used for our minification process:


Coming Full Circle

Now that we’ve completed all of our tasks, let’s take another look at our Gruntfile.js file:

// Register The Tasks
grunt.registerTask('lint', ['csslint', 'htmllint', 'jshint']);  
grunt.registerTask('minify', ['cssmin', 'htmlmin', 'uglify']);  
grunt.registerTask('default', ['lint', 'concat', 'minify']);

The following code snippet above gives us access to run the following commands from the CLI:

  • grunt lint (Runs our lint tasks.)
  • grunt concat (Runs our concat tasks.)
  • grunt minify (Runs our minification tasks.)
  • grunt (Runs whatever the default task is.)

The concat task is referenced from our tasks object. If you wanted to you could run grunt csslint or grunt <key> with any other key from our tasks object and this would effectively process the given task.

Now let’s take a look at this line:

grunt.registerTask('default', ['lint', 'concat', 'minify']);

This line is effectively telling GruntJS to run our lint, concat and minify together in that specific order whenever we run the grunt command. Let’s try it.

Go back to the CLI prompt and enter the following command:

grunt;

You should then see the following output displayed at the CLI prompt:

$ grunt;
Running "csslint:dist" (csslint) task  
>> 2 files lint free.

Running "htmllint:dist" (htmllint) task  
>> 1 file lint free.

Running "jshint:dist" (jshint) task  
>> 2 files lint free.

Running "concat:css" (concat) task  
File ./static/dist/concat.css created.

Running "concat:js" (concat) task  
File ./static/dist/concat.js created.

Running "cssmin:dist" (cssmin) task  
File static/dist/concat.min.css created: 50 B → 35 B  
File static/dist/test.min.css created: 24 B → 17 B  
File static/dist/test2.min.css created: 25 B → 18 B

Running "htmlmin:dist" (htmlmin) task  
Minified static/dist/index.min.html 94 B → 77 B

Running "uglify:dist" (uglify) task  
File static/dist/concat.min.js created: 198 B → 131 B  
File static/dist/test.min.js created: 97 B → 64 B  
File static/dist/test2.min.js created: 100 B → 67 B

Done, without errors.

We just effectively ran all of our tasks in a single command!


Looking Back

What did we accomplish? We successfully linted, concatenated and minified our CSS, HTML and JavaScript files with a single command using GruntJS.

The steps that we walked through are just a handful of many ways that you can preprocess your files. For instance, instead of hand typing out each require in our Gruntfile.jsfile, we could fetch the file listing from ./script/grunt and use a loop to automatically require each of our tasks.

The purpose of this guide is to give you a starting point and hopefully a better understanding of how to integrate GruntJS into your deployment process.

About Salvatore Garbesi