AngularJS From Beginning: Automatic Workflow - Part 15

I am here to continue the discussion around AngularJS. Today, we will discuss how to perform automatic workflow for AngularJS projects using GRUNT. Also in case you have not had a look at our previous articles of this series, go through the following links:

In this article, we will discuss how to setup and deploy an angular project.

Every time we perform the same activity over and over again, it can be considered waste, and we stand to lose a great opportunity to automate it. Depending on the complexity of the workflow of our product, there are many steps that have to be performed, such as cleaning the temporary files from the last distribution, validating the code, concatenating and minifying the JavaScript files, copying resources such as images and configuration files, running the tests, and many others. Also, based on the environment, the workflow could be very different—development, staging, or production.

Automating the workflow with Grunt

Grunt is a JavaScript task runner that automates repetitive and boring tasks. Also, it can group the desired tasks in any sequence, thus creating a workflow. It works over plugins, and there is a huge ecosystem with thousands of choices just waiting to be downloaded! We also have the opportunity to create our own plugins and share them within the community.

Installation

Just like Karma, Grunt requires NodeJS and the Node Package Manager to be installed. To proceed with the installation, we just need to type in the following command:

  1. npm install -g grunt-cli grunt   

After this, we are ready to configure it!

Configuration

The Gruntfile.js file is the JavaScript file that defines the configuration of each task, plugin, and workflow. Let's take a look at its basic structure:

  1. module.exports = function(grunt)   
  2. {  
  3.     // 1 – Configuring each task    
  4.     grunt.initConfig({});  
  5.     // 2 - Loading the plug-ins    
  6.     grunt.loadNpmTasks('plugin name');  
  7.     // 3 - Creating a workflow by grouping tasks together    
  8.     grunt.registerTask('taskName', ['task1''task2''task3']);  
  9. }   
Also, it's really recommended that you generate the package.jsonfile file. It is important to track each dependency, and this file can be generated with the following command:
  1. npm init  

After this, you are ready to create your distribution package!

Creating a distribution package

Before being ready for the production environment, you need to create an optimized distribution package of your product. This is very important for the following reasons:

  • Performance 

    Through the concatenation step, you could drastically reduce the amount of requests that the browser needs to perform every time the application is loaded. All the scripts of the application are concatenated in just one file. After that, the minifying step removes all the white spaces, line breaks, and comments, and replaces the names of the local variables and functions and makes them shorter than the original. It contributes to improving the performance by reducing the amount of bytes that need to be transferred. Also, the HTML and CSS files can be minified, and the images can be optimized by specific processors. Grunt has a lot of plugins for performing its tasks.

  • Security

    By removing the white spaces, line breaks, comments, and replacing the local variable names, the JavaScript files become much harder to understand. However, take care before submitting an AngularJS application to this kind of process. As we saw in dependency injection section, we should apply the array notation for the dependency injection mechanism by declaring each dependency as a string; otherwise, the framework will not find the expected dependency, throwing an error.

  • Quality

    There are two steps that improve the quality of the distribution. The first one is the validating step. With tools such as JSLint or JSHint, the code is validated against rules that verify things such as the absence of semicolons, wrong indentation, undeclared or unused variables and functions, and many others. Also, the tests are executed, preventing the process from proceeding in the case of errors.

In order to follow each step of our workflow, we are now going to discover how to install and configure each plugin:

Step 1 - Cleaning step

The first step is about cleaning the files that were created in the last distribution. This can be done through the grunt-contrib-clean plugin. Take care about which directory will be configured, avoiding any accidental deletion of the wrong files. To install this plugin, type in the following command:

npm install grunt-contrib-clean --save-dev

After this, we just need to configure it inside the Gruntfile.js file, as follows:

  1. module.exports = function(grunt)  
  2. {  
  3.     grunt.initConfig  
  4.     ({  
  5.         clean:  
  6.         {  
  7.             dist: ["dist/"]  
  8.         }  
  9.     });  
  10.     grunt.loadNpmTasks("grunt-contrib-clean");  
  11.     grunt.registerTask("dist", ["clean"]);  
  12. }   
In this case, we are creating our distribution package inside the dist/directory; however, you are free to choose another directory.

Step 2 - Validating step

Now, it's time to configure the validation step. There is a plugin called grunt-contrib-jshint that can be installed with the following command:

npm install grunt-contrib-jshint --save-dev

Next, we need to configure it inside our Gruntfile.js file as follows:

  1. module.exports = function(grunt)   
  2. {  
  3.     grunt.initConfig  
  4.     ({  
  5.         clean:   
  6.         {  
  7.             dist: ["dist/"]  
  8.         },  
  9.         jshint:   
  10.         {  
  11.             all: ['Gruntfile.js''js/**/*.js''test/**/*.js']  
  12.         }  
  13.     });  
  14.     grunt.loadNpmTasks("grunt-contrib-clean");  
  15.     grunt.loadNpmTasks("grunt-contrib-jshint");  
  16.     grunt.registerTask("dist", ["clean""jshint"]);  
  17.   

After we run this step, a report will be shown with the warnings and errors that it found in our code.

Step 3 - Minifying Step

Now, let's talk about the minifying step. As we mentioned before, it removes the white spaces, line breaks, comments, and replaces the names of the variables and functions. This can be done using the gruntcontrib- uglify plugin, which is installed using the following command:

npm install grunt-contrib-uglify --save-dev

The configuration is very similar to the grunt-contrib-concat plugin and involves the definition of the src and dest properties of the uglify object, as follows:

  1. module.exports = function(grunt)  
  2. {  
  3.     grunt.initConfig  
  4.     ({  
  5.         clean:   
  6.         {  
  7.             dist: ["dist/"]  
  8.         },  
  9.         jshint:   
  10.         {  
  11.             dist: ['Gruntfile.js''js/**/*.js''test/**/*.js']  
  12.         },  
  13.         concat:   
  14.         {  
  15.             dist:   
  16.             {  
  17.                 src: ["js/**/*.js"],  
  18.                 : "dist/js/scripts.js"  
  19.             }  
  20.         },  
  21.         uglify:   
  22.         {  
  23.             dist:   
  24.             {  
  25.                 src: ["dist/js/scripts.js"],  
  26.                 dest: "dist/js/scripts.min.js"  
  27.             }  
  28.         }  
  29.     });  
  30.     grunt.loadNpmTasks("grunt-contrib-clean");  
  31.     grunt.loadNpmTasks("grunt-contrib-jshint");  
  32.     grunt.loadNpmTasks("grunt-contrib-concat");  
  33.     grunt.loadNpmTasks("grunt-contrib-uglify");  
  34.     grunt.registerTask("dist", ["clean""jshint""concat""uglify"]);  
  35. }   

Step 4 - Copying step

There are many files such as images, fonts, and others that just need to be copied to the distribution without any change. This can be done using the grunt-contrib-copy plugin, which is capable of copying files and folders. The installation process is done with the following command:

npm install grunt-contrib-copy --save-dev

The configuration involves the source and destination of each folder and file, as follows:

  1. connect:   
  2. {  
  3.     production:   
  4.   {  
  5.         options:   
  6.     {  
  7.             port: 9001,  
  8.             base'dist/'  
  9.         }  
  10.     },  
  11.     development:   
  12.   {  
  13.         options:   
  14.     {  
  15.             port: 9002,  
  16.             base'/'  
  17.         }  
  18.     }  
  19. }  
  20. module.exports = function(grunt)  
  21. {  
  22.     grunt.initConfig  
  23.     ({  
  24.         clean:  
  25.       {  
  26.             dist: ["dist/"]  
  27.         },  
  28.         jshint:   
  29.       {  
  30.             dist: ['Gruntfile.js''js/**/*.js''test/**/*.js']  
  31.         },  
  32.         concat:   
  33.       {  
  34.             dist:   
  35.         {  
  36.                 src: ["js/**/*.js"],  
  37.                 dest: "dist/js/scripts.js"  
  38.             }  
  39.         },  
  40.         uglify: {  
  41.             dist: {  
  42.                 src: ["dist/js/scripts.js"],  
  43.                 dest: "dist/js/scripts.min.js"  
  44.             }  
  45.         },  
  46.         copy: {  
  47.             dist: {  
  48.                 src: ["index.html""lib/*""partials/*""css/*"],  
  49.                 dest: "dist/"  
  50.             }  
  51.         }  
  52.     });  
  53.     grunt.loadNpmTasks("grunt-contrib-clean");  
  54.     grunt.loadNpmTasks("grunt-contrib-jshint");  
  55.     grunt.loadNpmTasks("grunt-contrib-concat");  
  56.     grunt.loadNpmTasks("grunt-contrib-uglify");  
  57.     grunt.loadNpmTasks("grunt-contrib-copy");  
  58.     grunt.registerTask("dist", ["clean""jshint""concat""uglify""copy"]);  
  59. }  
Executing the workflow 

In order to execute any specific task or even the entire workflow, we just need to type in the following command:

grunt <name of the task or workflow>

In case we just want to clean the last distribution, we may call only the clean task as follows:

grunt clean

You could also create more than one configuration for each task. For instance, to configure two environments for the grunt-contrib-connect plugin, you could perform the following:

  1. connect:   
  2. {  
  3.     production:   
  4.   {  
  5.         options:   
  6.     {  
  7.             port: 9001,  
  8.             base'dist/'  
  9.         }  
  10.     },  
  11.     development:   
  12.   {  
  13.         options:   
  14.     {  
  15.             port: 9002,  
  16.             base'/'  
  17.         }  
  18.     }  
  19. }    

Also, you could generate two concatenated files, one with the sources and another with the libraries, as follows:

  1. concat:  
  2. {  
  3.     js:   
  4.   {  
  5.         src: ["js/**/*.js"],  
  6.         dest: "dist/js/scripts.js"  
  7.     },  
  8.     lib:   
  9.   {  
  10.         src: ["lib/**/*.js"],  
  11.         dest: "dist/lib/lib.js"  
  12.     }  
  13. }   

After this, you can run all the configurations by calling the task directly, as follows:

grunt concat

Or call any specific task by using a colon, as follows:

grunt concat:js

grunt concat:lib