Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web

Small Grunt.js examination

4.94/5 (11 votes)
26 May 2015CPOL11 min read 17.7K  
A small demo app that showcases how to use Grunt to do a few things

Introduction

Any developer worth their salt will of course be playing around with Visual Studio 2015, and those of us that are interested in web development would be of course trying out the new ASP vNext capabilities.

One thing that hit me straight away was that Microsoft have indeed stuck by their promises to embrace more "open source" works with ASP vNext. As such any new ASP vNext you do you will likely have to learn these things

  • NPM (Node package management system)
  • Bower (Client library package management system (requires node,js NPM))
  • Either Gulp / Grunt, for automating repetitive tasks
  • NuGet (for managing .NET package dependencies)

 

NPM and Bower are extremely easy to work with, so you should not fear them. As for NuGet, well if you are a .NET developer you have more than likely used NuGet before, nothing new there.

 

Me personally I had used Node and NPM before, I had not used Bower, but was comfortable with that within a day. As for Gulp / Grunt, I had obviously heard of these tools but had not used them. I looked at both and actually preferred the syntax of Grunt over Gulp. Both of these are essentially trying to do the same thing, which is they are both task runners. Where a task may be something that you want to do all the time, such as watch some files, and if they change do some action, minify content, move content from one folder to another etc etc. These are mundane tasks that can be automated. This is what  Gulp / Grunt are all about, that is what a task runner is.

 

One thing that Gulp claims to have done better is to stream files, such that they can be done asynchronously. That said I just felt more at home working with Grunt, so I decided to use that. One thing of note is that with ASP vNext you may use either Grunt or Gulp.

So decided to learn about Grunt, and just thought I would share my findings here with any of you that may be in the same ship as me (ie a  beginner with a JavaScript task runner).

 

Getting Started

The best place to get started with Grunt, is by actually going to the Grunt web site, where they are many useful guidelines to get you started. However the following sections should hopefully at least get you a little more familiar with Grunt, and by reading this and the Grunt resources at the Grunt web site you should be able to get up and running with Grunt pretty easily.

 

Where Is The Code?

As usual you can grab the code from github account :  https://github.com/sachabarber/SmallGruntDemo

 

Prerequisites

You should ensure that you do this before you carry on with the rest of the article

 

Installing Grunt

In order to install Grunt there are a couple of things you MUST do. These are outlined below.

  1. Install Node (as we need NPM to run Grunt. So we need Node as NPM needs Node)
  2. From the "Node.js command line" run this command "npm install -g grunt". This installs Grunt itself globally
  3. From the "Node.js command line" run this command "npm install -g grunt-cli". This installs the Grunt client globally

 

 

The Idea Behind Grunt

As I have already state Grunt is a task runner. It is written in JavaScript and has a configuration file (gruntfile.js) that details the tasks that need to be run. It is a bit like MSBuild / NANT in the fact that there is a default task.

 

Tasks can also call other tasks. Grunt also relies on NPM to install certain Grunt plugins. 

Huh, what is a Grunt plugin? Well a Grunt plugin, is simple a prebuilt Grunt task that has either been authored by the Grunt team or the Grunt community.

The official Grunt team plugins all follow the naming conventions of "grunt-contrib-XXXXXX". That way you can tell the "Official" packages from the non official packages.

These Grunt plugins (that are actually installed by using NPM) need to get loaded into the Grunt context. This is done using the following sort of line:

 

JavaScript
grunt.loadNpmTasks('grunt-contrib-clean');

 

Once a plugin is loaded it may be used by Grunt. Here is an example of how to use the loaded Grunt plugin, where it can be seen that the plugin has a name, and is a JSON object. Each plugin has its own API, so you will need to check the API documenation for the Grunt plugin you choose to use.

 

JavaScript
// Cleans directories
clean: {
    dist: [
        'dist/**/*'
    ],
    tmp: [
        'tmp/**/*'
    ]
},

 

 

 

The Demo App

Ok so now on to what the demo app does. The demo app is a very simple VS2013 solution that does the following things:

  • Will use the Typescript Greeter (hello world example in TypeScript) example
  • Will use jquery as an external module, and we will use the ambient type declarations for jquery for TypeScript in order to get the require/export package working as we need
  • Willl use grunt-browserify
  • Will use grunt-contrib-clean to clean certain directories
  • Will use grunt-ts to compile TypeScript files (Note that Visual Studio has excellent support for TypeScript compilation, but I just wanted to do everything I could using Grunt to prove something to myself)
  • Will use grunt-browserify to package the javascript source files to a single output file
  • Will use grunt-contrib-watch to watch for changes in source files, and run tasks when these files change
  • Will use grunt-contrib-uglify to minify the javascript source files (if the Grunt file task was the "prod" task) to a single output file

 

 

 

Extra Things You Will Need To Do For The Demo App

You need to carry out these steps if you want to work with the demo code.

 

Step 1 : Getting Missing NPM Modules

As is the case with NuGet where you would not submit the packages themselves to the source control repository (github for example). So it is with NPM, you would check in a configuration file (package.json) that tell NPM what packages are needed, from there each end user would be expected to install the packages locally using NPM.

You should now ensure you do this:

From Node.js command line, Change to directory which has package.json in it, and run the following command line
"npm install". This will fetch all the packages (from the internet, so make sure you have a connection) using the information contained within your package.json file. You should end up with something like this, where the "node_modules" directory will contain all the downloaded packages.

 

Image 1

 

A Look At The Project Structure

The project structure that we are using for this small demo app looks like this

Image 2

 

  • The src folder holds our source files (which for this demo app are all TypeScript)
  • The tmp folder will hold the transpiled TypeScript -> JavaScript files
  • The dist folder will hold the final (perhaps minified) distributable single file app.js, which will be referenced by Index.html

 

It can also be seen that this is where the NPM package.json and Grunt gruntfile.js files live.

 

 

A Look At The Grunt File (gruntfile.js)

Here is the complete demo app Grunt gruntfile.js file.

JavaScript
module.exports = function (grunt) {
    'use strict';


    grunt.loadNpmTasks('grunt-contrib-clean');
    grunt.loadNpmTasks('grunt-ts');
    grunt.loadNpmTasks('grunt-browserify');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-contrib-uglify');



    //INSTRUCTIONS
	//1. Install Node.js
	//2. From Node.js command line run this command "npm install -g grunt"
	//3. From Node.js command line run this command "npm install -g grunt-cli"
	//3. From Node.js command line, Change to directory which has package.json in it, 
	//   and run the following command lines 
	//   a) "npm install"
	//   b) "grunt watch"
 

    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),

        // Cleans directories
        clean: {
            dist: [
                'dist/**/*'
            ],
            tmp: [
                'tmp/**/*'
            ]
        },

        // Typescript transpiler (Typescript to JS)
        ts: {
            options: {
                fast: 'never',
                target: 'es5',
                module: 'commonjs',
                sourceMap: true
            },
            index: {
                src: ['./src/**/*.ts'],
                outDir: './tmp'
            }
        },

        // Package 
        browserify: {
            './dist/app.js': ['./tmp/*.js', './src/Jquery/jquery.js']
        },


        // Minify 
        uglify: {
            my_target: {
                options: {
                    sourceMap: 'dist/<%= pkg.name %/>.map',
                    sourceMapRoot: '..',
                    sourceMappingURL: '/<%= pkg.name %/>.map'
                },
                files: {
                    './dist/app.js': ['./dist/app.js']
                }
            }
        },		
		
	// Watch 
        watch: {
		  scripts: {
			files: ['./src/**/*.ts'],
			tasks: ['dev'],
			options: {
			  event: ['all'],
			}
		  }
		}


    });

    // setup default task to run dev task 
    grunt.registerTask('default', ['dev']);

    // +++++++++++++++++++++++++++++++++++++++++++
    // Prod task
    // +++++++++++++++++++++++++++++++++++++++++++
    // 1. Cleans directories
    // 2. Runs typescript transpiling task
    // 3. Package using Browserify
    // 4. Minify using Uglify
    grunt.registerTask('prod', [
		'clean',
		'ts:index',
        'browserify',
        'uglify'
    ]);


    // +++++++++++++++++++++++++++++++++++++++++++
    // Dev task
    // +++++++++++++++++++++++++++++++++++++++++++
    // 1. Cleans directories
    // 2. Runs typescript transpiling task
    // 3. Package using Browserify
    grunt.registerTask('dev', [
       'clean',
       'ts:index',
       'browserify'
    ]);

};

 

As you can see the gruntfile.js is made up of  a number of tasks, each with its own section. We will look at each of these in turn below.

 

 

The Demo Tasks A Deeper Look

This section will dive into each of the demo apps Grunt tasks in more detail.

 

The "default" Task

The "default" task is what would be called if you ran the "grunt" command line from the directory that contains the gruntfile.js file. It simply runs the following other tasks in order

  1. clean
  2. ts:index
  3. browserify

We will talk more about these tasks later

 

 

The "dev" Task

The "dev" task is what would be called if you ran the "grunt dev" command line from the directory that contains the gruntfile.js file.  It is intended to be run when you are developing an app, as such no minification of the code is performed. It simply runs the following other tasks in order

  1. clean
  2. ts:index
  3. browserify

We will talk more about these tasks later

 

 

The "prod" Task

The "prod" task is what would be called if you ran the "grunt prod" command line from the directory that contains the gruntfile.js file.  It is intended to be run when you are about to deploy an app, as such minification of the code IS performed. It simply runs the following other tasks in order

  1. clean
  2. ts:index
  3. browserify
  4. Uglify

We will talk more about these tasks later

 

 

 

The "watch" Task

The "watch" task makes use of the following NPM package, and its sole job is to monitor a set of input files for changes, and then run x-many other tasks.

  • grunt-contrib-watch

 

In order to initiate this watching of the files you MUST issue the following Grunt command line "grunt watch". Here is the relevant section of the Grunt gruntfile.js

JavaScript
module.exports = function (grunt) {
    'use strict';


    grunt.loadNpmTasks('grunt-contrib-watch');

    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
		
	// Watch 
        watch: {
		  scripts: {
			files: ['./src/**/*.ts'],
			tasks: ['dev'],
			options: {
			  event: ['all'],
			},
		  },
		},

    });
};

 

It can seen that this task monitors the files in the src folder and will run the "dev" task when any event (such as add, change, delete, rename) of these files occur.

 

 

 

The "clean" Task

The "clean" task makes use of the following NPM package, and its sole job is to clean directories

  • grunt-contrib-clean

 

Here is the relevant section of the Grunt gruntfile.js

JavaScript
module.exports = function (grunt) {
    'use strict';


    grunt.loadNpmTasks('grunt-contrib-clean');

    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
		
        // Cleans directories
        clean: {
            dist: [
                'dist/**/*'
            ],
            tmp: [
                'tmp/**/*'
            ]
        },
    });
};

 

It can seen that this task cleans a set of files contained with the targets specified by the JSON objects properties. Like I said EVERY  Grunt task will be different, so you MUST consult the Grunt tasks API docs.

 

 

 

The "ts" Task

As I have stated the demo app uses TypeScript. Now Visual Studio has excellent support for TypeScript right in the box.  You can see that in the following screen shot.

Image 3

Thing was I wanted to try out Grunt, so I decided to use a Grunt task for doing the TypeScript stuff too. The TypeScript files are pretty simple, but this is what we are looking to achieve:

  1. Use TypeScript
  2. Use external modules (which for the demo app is jQuery). In order to use jQuery as an external module you need to get the ambient TypeScript type definitions for it. Luckily there is a great project for all sorts of JavaScript libraries (that have been done by the community) to expose the TypeScript typings. You can check that out here : http://definitelytyped.org/

 

So once we have some TypeScript in place and we are able to use jQuery as an external module as follows:

JavaScript
/// <reference path="typings/jquery.d.ts" />
import Foo = require('./foo');
import $ = require('jquery');



$(document).ready(function () {
    .....
    .....
});

 

We are then able to look at the actual Grunt task. As I say Visual Studio and the TypeScript compiler (tsc.exe) can do everything you are about to see in the Grunt task, but I just wanted to test my Grunt know how, so decided to use Grunt tasks to do the TypeScript compilation too.

 

The "ts" task makes use of the following NPM package, which is responsible for transpiling the TypeScript into JavaScript

  • grunt-ts

 

Here is the relevant section of the Grunt gruntfile.js

JavaScript
module.exports = function (grunt) {
    'use strict';

    grunt.loadNpmTasks('grunt-ts');

    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),

        // Typescript transpiler (Typescript to JS)
        ts: {
            options: {
                fast: 'never',
                target: 'es5',
                module: 'commonjs',
                sourceMap: true
            },
            index: {
                src: ['./src/**/*.ts'],
                outDir: './tmp'
            }
        },
    });
};

 

It can be see that the grunt-ts module (Grunt task really) allows us to specify exactly the same sort of things as we did using the TypeScript tab in Visual Studio. For example the above Grunt task does the following:

  • Tells the TypeScript to target ecmascript 5
  • Tells the TypeScript to use Common.js style packages (not AMD)
  • Tells the TypeScript to create source maps
  • Tells the TypeScript to what source file to use for the transpiling
  • Tells the TypeScript where to place the output files

 

 

 

The "browserify" Task

The "browserify" task makes use of the following NPM package, and its sole job is to deal with packaging up the source files into a distributable package.

  • grunt-browserify

 

Here is the relevant section of the Grunt gruntfile.js

JavaScript
module.exports = function (grunt) {
    'use strict';

    grunt.loadNpmTasks('grunt-browserify');

    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),

        // Package 
        browserify: {
            './dist/app.js': ['./tmp/*.js', './src/Jquery/jquery.js']
        },
    });
};

 

It can be seen that this task takes several disparate source(s) and produced a final output file in the dist folder.

 

 

The "uglify" Task

The "uglify" task makes use of the following NPM package, and its sole job is to minify the source file(s) and produce a single minified output file

  • grunt-contrib-uglify

 

Here is the relevant section of the Grunt gruntfile.js

 

JavaScript
module.exports = function (grunt) {
    'use strict';


    grunt.loadNpmTasks('grunt-contrib-uglify');
 

    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),


		
		
        // Minify 
        uglify: {
            my_target: {
                options: {
                    sourceMap: 'dist/<%= pkg.name %>.map',
                    sourceMapRoot: '..',
                    sourceMappingURL: '<%= pkg.name %>.map'
                },
                files: {
                    './dist/app.js': ['./dist/app.js']
                }
            }
        },

    });
}

 

The <%= pkg.name %> will be replaced by the name of the current file. So app in this case.

 

Here is what the resulting app.js file looks like when it is finished being outputted by the "uglify" task. See how it is also minified.

 

Image 4

 

 

 

 

That's It

Anyway that is all I wanted to say this time. As always if you liked what you saw, you can always leave a vote/comment they are always appreciated

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)