JavaScript Automation, Part III - Configuring Grunt Tasks

Having installed and configured Grunt in Part I and Part II of our series on automating JavaScript builds, we proceed to the heart of the matter - configuring Grunt tasks to automate away the tedium of building our JavaScript client app from source files.

In Part II, we created a grunt folder to house the config files for our Grunt tasks. Let's go ahead and add our config files for our automated build process. 

 

grunt-contrib-watch

Starting at the end of the list, let's look at how we configure grunt-watch to watch for changes to our source code. See the code below:

watch.js

// module format
module.exports = {
        // each sub-task has a name
        // watch our CSS source files and generate SASS files
css : {
files : 'includes/styles/src/*.css',
tasks : [ 'newer:concat:css', 'newer:sass' ],
options : {
spawn : false
}
},
        // restart the watch task when our config changes
grunt: {
files : ['Gruntfile.js','grunt/*.js'],
options : {
reload : true
}
},
        // nifty trick for Mustache/Handlebars users
        // separate your templates in source for easier management
        // and concatenate for distribution
templates:{
files:'includes/templates/src/*.html', 
tasks: [ 'newer:concat:templates' ],
options : {
spawn : false
}
},
        // the heart of our app, JavaScript source files
scripts : {
files : [ 'includes/javascript/*.js', 
          'includes/javascript/modules/*.js'],
tasks : [ 'newer:concat:scripts', 'newer:uglify:app' ],
options : {
spawn : false
}
}
};

As you will note, each sub-task of the grunt-watch has its own named configuration block. Use whatever names fit your setup, just remember to modify your config files appropriately.

Each sub-task has several configuration options:

  1. files - location of source files, either a single listing or an array format. Wildcards allowed.
  2. tasks - the tasks to run for the subtask. Note the use of "newer" to only grab changed files, and the "task:subtask" format of the command, which tells us the config file and subtask where we will find the task being executed.
  3. options - optional parameters for task configuration. See the grunt-contrib-watch page on Github for more details.

With this configuration file, we have configured grunt-watch to monitor four sets of code and react appropriately:

  1. Changes to CSS source code will trigger our SASS build
  2. Changes to the Grunt config will trigger a reload of the grunt-watch task
  3. Changes to our HTML templates will trigger concatenation of our source HTML templates
  4. Changes to our JavaScript source code will trigger concatenation and uglification of our JavaScript code into a build with source maps for debugging.

What we are not doing in this configuration is linting in real-time. While linting may help us find some errors, for the most part we don't need to lint with every save. The uglify task will fail on JavaScript parsing errors like misplaced commas, and we can use linting later on if we need to track down less obvious bugs. Keep in mind that linting takes time and we're looking for real-time builds for debugging purposes.

 

grunt-contrib-uglify

Next let's review our uglify.js configuration, which minifies and obfuscates our Javascript build while providing source maps that we can use for debugging.

uglify.js

module.exports = {

app:{
options:{
sourceMap: true,
sourceMapName: 'app.min.js.map'
},
src: ['includes/javascript/*.js',
'includes/javascript/modules/*.js'],
dest :  'includes/javascript/build/app.min.js'

}
};

If you recall from our watch.js file, we are running newer:uglify:app when our JavaScript source files change. This task takes our JavaScript source files, minifies them, creates a source map, and pushes the build file to our build folder. There are far more options for this module than we are using here, see the Grunt-contrib-uglify Github page for more details.

For our purposes, we get exactly what we need - a new distribution build of our JavaScript code and source maps for debugging.

 

grunt-contrib-sass

Next let's review our SASS config file. We use SASS to make our lives easier when dealing with CSS. While this isn't directly JavaScript code, changes to CSS directly affect the operation of our app, so we need to automate our CSS builds as part of our process.

sass.js

module.exports = {
dist : {
options : {
style : 'compressed'
},
files : {
'includes/styles/build/style.min.css' : 'includes/styles/style.scss'
}
}
};

The most important thing you will see in this file is that we are not operating directly on our CSS source. Recalling our watch.js file, we run two tasks when our CSS files change: newer:concat:css and newer:sass. So we're operating on a SCSS file: style.scss, concatenated from our CSS sources. We'll see that process in a moment. In the meantime, note the options directive that the style should be compressed. We're generating a minified CSS file to save space and load time. The sass task has lots of options, for more details see the grunt-contrib-sass Github page.

 

grunt-contrib-concat

Next, let's look at the concat config. In this case, we'll see our CSS source and HTML template source files.

concat.js

module.exports = {
css: {
src : [ 'includes/styles/src/*.css'
dest : 'includes/styles/style.scss'
},
templates: {
src: 'includes/templates/src/*.html',
dest: 'includes/templates/templates.html'
}
};

Although our example is very simple, the concat module has a large number of options that you may find useful depending on your configuration. See the grunt-contrib-concat Github page for more details.

For our purposes, we get what we needed:

  1. a single CSS source file that the SASS task can operate on
  2. A single HTML templates file for use with Mustache.js

That's it for our basic configuration. We'll cover imagemin and jslint in the last part of the series.

 

Putting it Together

Now that we have configured our Grunt tasks, what is left? Go to the console, to the root of your project and type:

$ grunt watch

That's it. If everything is configured correctly, you will get a message like this:

 

Running "watch" task

Waiting...

 

Leave the console open in the background, then go to your code editor of choice and make a change to your JavaScript source code. Again, if everything is configured correctly, as soon as you save the file, you will see something like this in the console:

Running "watch" task
Waiting...
>> File "includes/styles/src/style.css" changed.

Running "newer:concat:css" (newer) task

Running "concat:css" (concat) task
File includes/styles/style.scss created.

Running "newer-postrun:concat:css:3:<path>/node_modules/grunt-newer/.cache" (newer-postrun) task

Running "newer:sass" (newer) task

Running "newer:sass:dist" (newer) task

Running "sass:dist" (sass) task

Running "newer-postrun:sass:dist:4:<path>/node_modules/grunt-newer/.cache" (newer-postrun) task

Running "watch" task
Completed in 1.488s at Fri Mar 18 2016 23:28:39 GMT-0700 (PDT) - Waiting...

Note: <path> will be your local path to your project folder.

Congratulations! If you have made it this far in this series, you now have an automated Javascript build process that frees you of the tedium and delays associated with managing a complex JavaScript application. 

As you may imagine, the examples in this series were simplified for the sake of brevity. How you specifically configure your environment will depend on the particulars of your application and you personal preferences.

In the last part of the series, we will cover imagemin and jslint as optional grunt tasks to include in our automation environment.