Grunt — Getting Started
Table of Contents
Introduction
Setup
The Process HTML Plugin
Folder Structure
Grunt Configuration
Grunt Code
Troubleshooting
Conclusion
Introduction
Automation of tasks related to web development. Grunt, “The JavaScript Task Runner”. In addition to file concatenation, linting, and minification, Grunt delivers a plugin that offers the equivalent of PHP’s include. Static content such as page headers, navigation bars, or footers are automatically inserted across several HTML files, or web pages. Write header.html and footer.html in standalone files and Grunt automatically inserts them in all of your production .html files for you on demand. Without Grunt, change the footer on one page and you must then manually replicate, i.e. copy and paste, that update across all of your HTML files that contain that footer content.
Adopting and deploying Grunt is not trivial but this is a situation where one takes 1 step back to make leaps forward. Admittedly, I spent several hours reading and experimenting before getting Grunt running properly. The effort has already paid off. This article should enable you to get up and running with Grunt in minutes. Write once, paste everywhere, in milliseconds.
Setup
Developers use an Integrated development environment, IDE, rather than a simple text editor. Eclipse is a free favorite. I use Cloud9, an online IDE. Now install Grunt. There is no reason to regurgitate the installation steps which are already published by the Grunt team. Install Grunt now.
Grunt is modular in that the features are installed as plugins. Each plugin installs in a matter of seconds. Quick and easy. The example in this article utilizes grunt-processhtml
, grunt-contrib-uglify
, grunt-contrib-concat
, grunt-contrib-htmlmin
, and grunt-contrib-cssmin
. Install these now.
The Process HTML Plugin
The processhtml plugin looks for specifically worded HTML comment sections and replaces them with the HTML snippets you specify. For example, a centralized footer snippet located at includes/footer.html gets inserted into any HTML file that includes the below code, at that location in the target document(s).
<! - build:include includes/footer.html →
<! - /build →
NOTE: the specified filepath in HTML files located in subfolders should specify reverse directory lookup, i.e. .., as in includes/footer.html
. This is because the includes directory is one level up relative to subfolders in your source folder.
Folder Structure
The Grunt code below assumes a specific folder structure on your development machine / IDE. This code should be in Gruntfile.js in the root of your directory. Create a source, staging, and public folder in your root directory. Inside your source folder you have 4 folders — articles, css, includes, and js. Folder Structure ImageAll your .html files such as index.html, about.html, etc go here in the source folder. Your JavaScript goes in the js, CSS in the css flder, and finally I have an articles section on my webpage / webapp — thus the articles folder. You might have a different subfolder or set of subfolders, but this example shows how it’s done.
Grunt Configuration
Starting out, one specifies each file that Grunt should touch as well as what operations Grunt should perform. This is spelled out in your Grunt file, or Gruntfile.js
. Add a new HTML file, or webpage, and you must manually tell Grunt to process it by including it in your Gruntfile.js
. This manual approach grows unwieldy as the number of HTML files, or webpages, your app has so automating Grunt with wildcards is key. Configured as below Grunt touches every HTML file in my source folder as well as my articles subfolder each time I run Grunt. The staging folder is a midway holding point that Grunt circles back to before performing its final task of dropping all files to the production public folder. Note the expand: true
and src: [‘*.html’, ‘articles/*.html’, ‘!**/includes/**’]
. This is what tells Grunt to touch all HTML files.
Grunt Code
module.exports = function(grunt) {
grunt.initConfig({
processhtml: {
dist: {
options: {
process: true,
},
files: [{
expand: true,
cwd: 'source/',
src: ['*.html', 'articles/*.html', '!**/includes/**'],
dest: 'staging/',
ext: '.html'
}]
}
},
uglify: {
my_target: {
files: {
'staging/script.min.js': ['source/js/script.js']
}
}
},
concat: {
js: {
src: ['source/js/firebase.js', 'source/js/firestore.js', 'source/js/mdc-web.min.js', 'staging/script.min.js'],
dest: 'public/app.min.js',
},
},
htmlmin: { // Task
dist: { // Target
options: { // Target options
removeComments: true,
collapseWhitespace: true
},
files: [{
expand: true,
cwd: 'staging/',
src: ['**/*.html'],
dest: 'public/',
ext: '.html'
}]
}
},
cssmin: {
target: {
files: {
'public/app.min.css': ['source/css/mdc-web.min.css', 'source/css/style.css']
}
}
}
});
grunt.loadNpmTasks('grunt-processhtml');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-htmlmin');
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.registerTask('default', ['processhtml', 'uglify', 'concat', 'htmlmin', 'cssmin']);
};
Troubleshooting
There is a useful — verbose switch that shows task details so use grunt — verbose if you are having issues. One issue I encountered was using the % sign in one of my .html files since Grunt sees these as argument delimiters. The fix is to simply use the HTML character code.
Conclusion
Grunt is not a toy and it is raw. One missing letter or comma in a single file and it will fail and throw a funky error. However, the time savings of using the Grunt tool are well worth it. If you are a professional developer, automating mundane tasks is mandatory and Grunt is a widely used automation tool. I am very pleased with Grunt and will continue using it.