Real-world use cases
Hearing about the benefits of Grunt is all well and good, but what about actual use cases that the average web developer will face every day in the real world? In this section, we'll take an eagle-eye view of the most common use cases for Grunt.
These examples make use of configuration targets. Essentially, targets allow us to define multiple configurations for a task. We'll cover more on configuration targets in Chapter 2, Setting Up Grunt.
Static analysis or Linting
In programming, the term linting is the process of finding probable bugs and/or style errors. Linting is more popular in dynamically typed languages as type errors may only be resolved at runtime. Douglas Crockford popularized JavaScript linting in 2011 with the release of his popular tool, JSLint.
JSLint is a JavaScript library, so it can be run in Node.js or in a browser. JSLint is a set of predetermined rules that enforce correct JavaScript coding practices. Some of these rules may be optionally turned on and off, however, many cannot be changed. A complete list of JSLint rules can be found at http://gswg.io#jslint-options.
This leads us to JSHint. Due to Douglas Crockford's coding style being too strict for some, Anton Kovalyov has forked the JSLint project to create a similar, yet more lenient version, which he aptly named: JSHint.
I am a fan of Douglas Crockford and his book, JavaScript—The Good Parts (http://gswg.io#the-good-parts), but like Anton, I prefer a more merciful linter, so in this example below, we will use the Grunt plugin for JSHint: http://gswg.io#grunt-contrib-jshint.
//Code example 04-linting //Gruntfile.js module.exports = function(grunt) { // Load the plugin that provides the "jshint" task. grunt.loadNpmTasks('grunt-contrib-jshint'); // Project configuration. grunt.initConfig({ jshint: { options: { curly: true, eqeqeq: true }, target1: ['Gruntfile.js', 'src/**/*.js'] } }); // Define the default task grunt.registerTask('default', ['jshint']); }; //src/foo.js if(7 == "7") alert(42);
In the preceding code, we first load the jshint
task. We then configure JSHint to run on the Gruntfile.js
file itself, as well as all of the .js
files in the src
directory and its subdirectories (which is src/foo.js
in this case). We also set two JSHint options: curly
, which ensures that curly braces are always used in if
, for
, and while
statements; and eqeqeq
, which ensures that strict equality ===
is always used.
JSHint has retained most of the optional rules from JSLint and it has also added many more. These rules can be found at: http://gswg.io#jshint-options.
Finally, we can run the jshint
task with grunt
, and we should see the following:
$ grunt Running "jshint:target1" (jshint) task Linting src/foo.js...ERROR [L1:C6] W116: Expected '===' and instead saw '=='. if(7 == "7") alert(42); Linting src/foo.js...ERROR [L1:C14] W116: Expected '{' and instead saw 'alert'. if(7 == "7") alert(42); Warning: Task "jshint:target1" failed. Use --force to continue. Aborted due to warnings.
The result shows that JSHint found two warnings in the src/foo.js
file on:
Line 1, column 6—since we've enforced the use of strict equality,
==
is not allowed, so it must be changed to===
.Line 1, column 14—since we've enforced the use of the curly braces, the
if
statement body must explicitly use curly braces.
Once we've fixed these two issues as follows:
if(7 === "7") { alert(42); }
We can then re-run grunt
and we should see:
$ grunt Running "jshint:target1" (jshint) task >> 2 files lint free. Done, without errors.
Notice that two files were reported to be lint free. The second file was the Gruntfile.js
file, and if we review this file, we see it does not break either of the two rules we enabled.
In summary, JSHint is very useful as the first step of our Grunt build as it can help catch simple errors, such as unused variables or accidental assignments in if
statements. Also, by enforcing particular coding standards on the project's code base, it helps maintain code readability, as all code entering the shared repository will be normalized to a predetermined coding style.
Transcompilation
Transcompiling—also known as source-to-source compilation and often abbreviated to transpiling—is the process of converting the source code of one language to the source code of another. Within the web development community in recent years, there has been an increase in the use of transcompile languages such as Haml, Jade, Sass, LESS, Stylus, CoffeeScript, Dart, TypeScript, and more.
The idea of transcompiling has been around since the 1980s. A popular example was an original C++ compiler (Cfront) by Bjarne Stroustrup, which converted C++ (known as C with Classes at the time) to C.
CoffeeScript
CoffeeScript (http://gswg.io#coffeescript) is the most popular transpile language for JavaScript. It was released in 2009 by Jeremy Ashkenas and is now the 10th most popular language on GitHub with 3 percent of the all code in public Git repositories. Due to this popularity, a particularly common use case for the modern web developer is to compile CoffeeScript to JavaScript. This can be easily achieved with the Grunt plugin http://gswg.io#grunt-contrib-coffee.
In the following example, we'll use the grunt-contrib-coffee
plugin to compile all of our CoffeeScript files:
//Code example 05-coffeescript module.exports = function(grunt) { // Load the plugin that provides the "coffee" task. grunt.loadNpmTasks('grunt-contrib-coffee'); // Project configuration. grunt.initConfig({ coffee: { target1: { expand: true, flatten: true, cwd: 'src/', src: ['*.coffee'], dest: 'build/', ext: '.js' }, target2: { files: { 'build/bazz.js': 'src/*.coffee' } } } }); // Define the default task grunt.registerTask('default', ['coffee']); };
Inside the configuration, the coffee
object has two properties; each of which defines a target. For instance, we might wish to have one target to compile the application source and another target to compile the unit test source. We'll cover more on tasks, multitasks, and targets in Chapter 2, Setting Up Grunt.
In this case, the target1
target will compile each .coffee
file in the src
directory to a corresponding output file in the build
directory. We can execute this target explicitly with grunt coffee:target1
, which should produce the result:
$ grunt coffee:target1 Running "coffee:target1" (coffee) task File build/bar.js created. File build/foo.js created. Done, without errors.
Next, target2
will compile and combine each of the .coffee
files in the src
directory to a single file in the build
directory called bazz.js
. We can execute this target with grunt coffee:target2
, which should produce the result:
grunt coffee:target2 Running "coffee:target2" (coffee) task File build/bazz.js created. Done, without errors.
Combining multiple files into one has advantages and disadvantages, which we shall review in the next section Minification.
Jade
Jade (http://gswg.io#jade) compiles to HTML and, as with CoffeeScript to JavaScript, Jade has the semantics of HTML, though different syntax. TJ Holowaychuk, an extremely prolific open-source contributor, released Jade in July 2010. More information on the Grunt plugin for Jade can be found at http://gswg.io#grunt-contrib-jade.
We'll also notice the following example Gruntfile.js
file is quite similar to the previous CoffeeScript example. As we will see with many Grunt plugins, both these examples define some kind of transform from one set of source files to another set of destination files:
//Code example 06-jade module.exports = function(grunt) { // Load the plugin that provides the "jade" task. grunt.loadNpmTasks('grunt-contrib-jade'); // Project configuration. grunt.initConfig({ jade: { target1: { files: { "build/foo.html": "src/foo.jade", "build/bar.html": "src/bar.jade" } } } }); // Define the default task grunt.registerTask('default', ['jade']); };
In this example, target1
will do a one-to-one compilation, where src/foo.jade
and src/bar.jade
will be compiled into build/foo.html
and build/bar.html
respectively. As we have set the default
task to be the jade
task, we can run all of jade's targets with a simple grunt
command, which should produce:
$ grunt Running "jade:target1" (jade) task File "build/foo.html" created. File "build/bar.html" created. Done, without errors.
Stylus
Stylus (http://gswg.io#stylus) compiles to CSS, and as before, it has the semantics of CSS though different syntax. TJ Holowaychuk also created Stylus, which he officially released in February 2011. More information on the Stylus Grunt plugin can be found at http://gswg.io#grunt-contrib-stylus. Similarly to the examples above, the following example Gruntfile.js
file contains only slight differences. Instead of jade
, we're configuring stylus
, and instead of transpiling .jade
to .html
, we're transpiling .styl
to .css
:
//Code example 07-stylus module.exports = function(grunt) { // Load the plugin that provides the "stylus" task. grunt.loadNpmTasks('grunt-contrib-stylus'); // Project configuration. grunt.initConfig({ stylus: { target1: { files: { "build/foo.css": "src/foo.styl" } } } }); // Define the default task grunt.registerTask('default', ['stylus']); };
When we run grunt
, we should see the following:
$ grunt Running "stylus:target1" (stylus) task File build/foo.css created. Done, without errors.
Haml, Sass, and LESS
Grunt plugins that transpile code are very similar, as previously seen with CoffeeScript, Jade and Stylus. In some way or another, they define a set of input files and a set of output files, and also provide options to vary the compilation. For the sake of brevity, I won't go through each one, but instead I'll provide links to each preprocessor (transcompiler tool) and its respective Grunt plugins:
Haml—
http://gswg.io#haml—gswg.io#grunt-haml
Sass—
http://gswg.io#sass—gswg.io#grunt-contrib-sass
LESS—
http://gswg.io#less—gswg.io#grunt-contrib-less
At the end of the day, the purpose of using transcompile languages is to improve our development workflow, not to hinder it. If using these tools requires a lengthy setup for each, then the more tools we add to our belt, the longer it'll take our team to get up and running. With Grunt, we add each plugin to our package.json
and with one npm install
command, we have all the plugins we need and can start transpiling in minutes!