In this article, we will talk about Grunt. It is a Task Runner, which in free Portuguese means “Task Executor.” It was created in 2012 and now has around 423,000 weekly downloads on npm. But how does it help us? It aids us in a very important matter, automation.
What is automating?
The word automate basically means to convert some service into an automatic task. In other words, it allows a repetitive service to become easier to perform mechanically. This helps us execute the service in a more agile and optimized manner.
Automation is something that humans are used to creating. Just think of two examples: 1) consider how over time the need to create automatic garage doors was perceived, so that we wouldn’t have to get out of our cars to open and close them anymore; or 2) think about how it was necessary to create an electric screwdriver, so that a task that used to take a few minutes could be accomplished in just a few seconds. These two examples are quite different from one another, but they both have automation in common.
When we work with Web Development, it is no different. We perform many tasks that can gradually become repetitive. Here are a few examples: 1) we know that it is very interesting to have a CSS or JS file that contains all the codes developed in these languages. This helps keep our HTML file smaller. 2) It is also recommended that we remove all spaces and line breaks from our external files, that is, minifying the files. This is important for our website to be lighter. These two tasks are necessary, but at the same time repetitive. Wouldn’t it be nice if we could automate them? With Grunt, this is possible!
How to install Grunt?
It is built entirely in JavaScript and runs in the Terminal. Node is required to install it (using npm) and to run it as well. If you do not have Node, you can check how to install it in one of our videos on YouTube by clicking here.
After installing Node, we will install Grunt on our computer. For that, open a Terminal and run the following command:
npm install grunt-cli -g
Node will use our Internet to download Grunt CLI and install it globally on our system. Thus, we will be able to use its resources in any project we desire.
We will create a small project to test Grunt. This project will contain a folder called assets, which will store our CSS and JavaScript files. Each CSS file will contain specific styles for a section of our site. And each JavaScript file will contain a useful function for our system. We will also have a folder called dist, which will contain third-party files. The folder structure will look like this:
Grunt will work a lot with Node, mainly downloading dependencies. Therefore, it will be necessary to initialize a Node project in this directory. To do this, we will execute the following command:
npm init -y
Adding Grunt to the Project
To add Grunt to our project, we will download it. We might ask ourselves: “But didn’t we just install Grunt globally a little while ago?” Actually, that was Grunt CLI, responsible for calling Grunt commands via the command line. However, the actual Grunt that will provide us with all the automation resources will be installed now. We will do this with the following command:
npm install grunt
The next step is to create the Gruntfile.js file. We will add all the code related to Grunt functions in it. Since we are working with Node.js, we will export a module in this file. Its basic syntax will be as follows:
module.exports = grunt => {
};
Remember that we teach everything about native modules and third-party modules in our Complete Node.js Course, where we develop amazing projects using Node.
Let’s now create our first automation with Grunt. Remember we talked about the good practice of combining all CSS and JS files into one? Grunt has a module that does that, grunt-contrib-concat. To use it, we need to install it using npm. Let’s run the following in the Terminal:
npm install grunt-contrib-concat
Then, in our Gruntfile.js, we will implement it in our code using the loadNpmTasks() method, which is specific to Grunt. The code will look like this:
module.exports = grunt => {
grunt.loadNpmTasks("grunt-contrib-concat");
};
To start configuring Grunt, we will use the initConfig() method. It expects an object with configurations. The first one we will perform is the concat. The code will look like this:
grunt.initConfig({
concat: {
}
});
The key “concat” is a standard name that comes from the concat module. Inside this key, we will inform the CSS and JS files that we want to join using the following code:
grunt.initConfig({
concat: {
css: {
src: [
'./assets/css/header.css',
'./assets/css/section.css',
'./assets/css/footer.css'
],
dest: './dist/all.css'
},
js: {
src: [
'./assets/js/site.js',
'./assets/js/utils.js'
],
dest: './dist/all.js'
}
}
});
Explaining: we separate what Grunt will execute between CSS and JS, with two keys of the object. Inside each of these objects, we have two options:
1 - src. This defines which files we want to combine. It expects an array of paths to the files.
2 - dest. Defines what the final file or destination file will be, where all the codes from the above files will be gathered. It expects a string with the path to this file. Something very interesting is that if this file does not exist, it will create it for us.
Let’s test this module. We will go back to our Terminal and execute grunt concat. The following will be returned:
The task was executed correctly, without errors. If we go to the dist folder, we will find the files all.css and all.js, which will look like this:
Something interesting is that since we separated this module into two namespaces, we can simply execute grunt concat:css and only the CSS files will be affected. We can do the same for the JS files.
These styles and functions were in separate files, but we were able to combine their contents through Grunt. The concat module is quite useful, isn’t it?
Let’s learn one more module, related to file minification. Let’s start with CSS. There is a module called grunt-contrib-cssmin. To implement it in our project, we will follow the same steps we took with concat.
First, let’s download it using npm with the following command:
npm install grunt-contrib-cssmin
We will request it in our code with the following line of code:
grunt.loadNpmTasks("grunt-contrib-cssmin");
Finally, we will add it to our Grunt configuration. Its standard key name is cssmin. It will look like this in our code:
cssmin: {
styles: {
files: {
'./dist/all.min.css': ['./dist/all.css']
}
}
}
Explanation: the keys styles and files are necessary for cssmin to be executed correctly. Inside the files option, we first inform the destination path in which the minified file will be created and then the original file that will undergo minification.
To test if everything is working, let’s execute the following in our Terminal:
grunt cssmin
The following message will be returned in the Terminal:
The execution was a success. At this point, a file all.min.css should already have been created in our dist directory. This will be its content:
Incredible! Our file, which already had all its styles united in one place, is now also minified with the help of Grunt. However, notice that if we change something in the original CSS file and want to repeat the process, we will need to run the two commands again. Wouldn’t it be better if we could execute the command just once and it would do everything for us? This is possible with Grunt, through the registerTask() method. This method registers a custom task, it's like an “event listener” for Grunt. We need to create this method outside the configuration function variable. Its syntax will look like this:
grunt.registerTask('styles', function() {
grunt.task.run(['concat:css', 'cssmin']);
});
Explanation: the first parameter is the name of the custom task. We can choose any name we want. The second parameter is a function, and inside it, we call the native Grunt method called run(). It is like a trigger for Grunt, and it triggers the call of one or more modules. In this case, we call the two modules related to CSS.
To test if this is working, let’s add one more CSS style to the footer.css file. We will add the style text-decoration: none, just as an example. Now, just run grunt styles in our Terminal, which is the task we created. When we execute this in the Terminal, we see the following return:
Note that the two tasks were executed. Now, if we check our all.css and all.min.css files, we will see the following:
Both files were altered using Grunt's custom task.
The next module we will learn about will be a module for minifying JavaScript files. Its name is uglify. We will repeat the installation process; we are getting pretty good at this :).
First, we will execute in our Terminal:
npm install grunt-contrib-uglify
We will add this module to the project with the loadNpmTasks() method:
grunt.loadNpmTasks("grunt-contrib-uglify");
And now let’s create its configuration in the initConfig() method:
uglify: {
functions: {
files: {
'./dist/all.min.js': ['./dist/all.js']
}
}
}
Notice that the structure is quite similar to cssmin. We just changed the key “styles” to “functions.” How about we practice creating custom tasks using registerTask()? Let’s combine everything related to JavaScript using the task “scripts,” which we will create now:
grunt.registerTask('scripts', () => {
grunt.task.run(['concat:js', 'uglify']);
grunt.log.ok('All good!');
});
The syntax is quite similar to the last custom task we created. Just a small difference: note that we used the property .log.ok() in this example. This Grunt property allows us to write feedback messages in the Terminal, informing whether everything went well or not. The ok() method is for success, but there are other methods:
1 - warn() to inform a warning.
2 - error() to report an error.
3 - writeln() to write a line message.
4 - header() and subhead() which define a header for the message.
Back to the tests, let’s run grunt scripts in our Terminal. This will be the result:
See that the success message was returned in green, as we programmed earlier, and we were also informed that both tasks were executed successfully. If we check the all.min.js file, we will see the following:
Our code was correctly minified. If we make any changes to our original JS files, just run this command again in the Terminal and the compilation will happen again.
We learned some really interesting modules, but we will learn one more that helps us a lot. Imagine that we are using Grunt in our development and we changed the CSS file. We will need to execute the task that minimizes the CSS in the Terminal. If we change the JS, we will do the same. However, these changes are very recurring, which can make the task of executing the commands in the Terminal a bit repetitive. Wouldn’t it be more interesting to have a module that automatically executed the call for these tasks? That’s what the watch module is for. It is like a “watcher” that keeps an eye on changes in our code. When we save a file, it executes all the tasks we have already programmed in the Terminal.
Let’s implement it in our application, repeating the process we have already done a few times. First, let’s run npm install grunt-contrib-watch in our Terminal. We will bring this module into our application with the following line of code:
grunt.loadNpmTasks("grunt-contrib-watch");
And, to configure its operation in our initConfig() method, we will use the following code:
watch: {
css: {
files: ['./assets/css/*.css'],
tasks: ['concat:css', 'cssmin']
},
js: {
files: './assets/js/*.js',
tasks: ['concat:js', 'uglify']
}
}
We will divide the module again between CSS and JS files. The files property expects an array with the files it needs to watch, that is, to stay alert to changes. Notice that in both CSS and JavaScript we used the * (asterisk) so that watch monitors all the files with the extensions .css and .js. And the tasks property expects an array of tasks that should be executed when a file of that extension is saved. We basically inserted the tasks we had already created in the custom tasks.
To test Grunt, let’s run grunt watch in our Terminal. We will see the following result:
See that it returns a “Waiting” message. This module is different as it does not terminate its process, but keeps the Terminal running continuously, waiting for changes. If we change any file now and save it, we will see the following in the Terminal:
Grunt can detect which file was changed, in this case, header.css, which tasks were executed, and even the date when it occurred. Amazing, isn’t it?
We can make just one more change. The registerTask() method can create custom tasks, but it also has default tasks. One of these tasks is called “default,” which defines what will happen if we execute grunt in the Terminal. Let’s create this task with the following code:
grunt.registerTask('default', ['watch']);
This shows that we can inform the registerTask() method with just an array of tasks if we want, instead of a function.
Now, if we execute just grunt in the Terminal, we will see the following:
Now the watch task is triggered by default, and we can keep this task running while we are programming our projects.
We learned some Grunt modules in this article, but there are many others. You can find a complete list of other modules or plugins by clicking here.
You can also find the codes developed in this article on our GitHub by clicking here.
In this article, we learned how Grunt can help us automate repetitive tasks in our projects and saw how practical it really is. However, there is another tool that has the same objective and has even become more well-known than Grunt: we are talking about Gulp. We will talk more about this automator in the next article: Gulp: Automating tasks with Ease, which comes out tomorrow, see you there! :)