Blog | CoWhite Softwarehttp://django.cowhite.com/blog/2017-07-12T05:24:26+00:00BlogAutomated front-end builds with grunt task runner2017-07-12T05:24:26+00:00ravi/blog/author/ravi/http://django.cowhite.com/blog/automated-front-end-builds-with-grunt-task-runner/<p><a href="https://gruntjs.com/" target="_blank">Grunt</a> is a javascript task runner used to automate the building and packaging of all front-end components. Grunt has numerous plugins for all repetitive tasks like minification, linting, compilation etc.</p>
<p>Javascript being extremely popular language for sometime now, there are good alternatives to grunt such as <a href="http://gulpjs.com/" target="_blank">gulp</a> and <a href="https://webpack.js.org/" target="_blank">webpack</a>. We will limit this post to just <em>grunt</em> and how to perform some basic tasks with it.</p>
<h3>Installing grunt</h3>
<p><em>grunt</em> is a node package. So we need <em>node</em> and <em>npm</em> installed in our system. If you don't have them installed already, follow our guide <a href="https://django.cowhite.com/blog/managing-front-end-javascript-dependencies-using-npm/" target="_blank">managing front-end javascript dependencies using Node Package Manager and bower</a>.
<br>
Install <code>grunt-cli</code> globally with</p>
<div class="codehilite"><pre>npm install -g grunt-cli
</pre></div>
<p>Since we are installing it globally, we need superuser credentials (on linux) or administrator rights (on windows). Once the <em>grunt-cli</em> is installed, we can access it from any directory.</p>
<p><br> </p>
<p>Initiate node project and then install <em>grunt</em> with </p>
<div class="codehilite"><pre>npm install --save-dev grunt
</pre></div>
<p>Now we can run grunt in our project directory with the command <code>grunt</code>. But that will only raise an error like this</p>
<div class="codehilite"><pre>A valid Gruntfile could not be found. Please see the getting started guide for
more information on how to configure grunt: http://gruntjs.com/getting-started
Fatal error: Unable to find Gruntfile.
</pre></div>
<p>This is because grunt looks for a file named <code>gruntfile.js</code> that describes the tasks and the configuration of individual tasks. The above error is caused when it fails to find <code>gruntfile.js</code>.</p>
<h3>Creating grunt tasks</h3>
<p>Let us create the file <code>gruntfile.js</code>. The grunt file is basically of the form</p>
<div class="codehilite"><pre><span class="n">module</span><span class="p">.</span><span class="n">exports</span> <span class="o">=</span> <span class="n">function</span><span class="p">(</span><span class="n">grunt</span><span class="p">)</span> <span class="p">{</span>
<span class="o">//</span> <span class="n">List</span> <span class="n">the</span> <span class="n">grunt</span> <span class="n">tasks</span> <span class="n">here</span><span class="p">.</span>
<span class="p">}</span><span class="err">;</span>
</pre></div>
<p>All the tasks and configurations are defined within this function. </p>
<p><br></p>
<p>To understand how grunt works, let us install a grunt plugin called <code>grunt-contrib-uglify</code> which is used to minify javascript files. Install it with</p>
<div class="codehilite"><pre>npm install --save-dev grunt-contrib-uglify
</pre></div>
<p>We need to load the plugin in the grunt function to import the tasks from it. Load it with </p>
<div class="codehilite"><pre>grunt.loadNpmTasks('grunt-contrib-uglify');
</pre></div>
<p>Though this loads all the tasks, uglify still needs some configuration. All the configuration to grunt is passed as an object to the methd <code>initConfig</code> as follows.</p>
<div class="codehilite"><pre>grunt.initConfig({
// Configuration in this object
});
</pre></div>
<p>We provide configuration for <code>uglify</code> plugin with this object as the value to the key <em>'uglify'</em>. The configuration looks something like this.</p>
<div class="codehilite"><pre>grunt.initConfig({
uglify: {
target: {
files: {
'dist/output.min.js': ['src/file1.js', 'src/file2.js']
}
}
}
});
</pre></div>
<p>To be clear the complete file look like this.</p>
<div class="codehilite"><pre><span class="n">module</span><span class="p">.</span><span class="n">exports</span> <span class="o">=</span> <span class="n">function</span><span class="p">(</span><span class="n">grunt</span><span class="p">)</span> <span class="p">{</span>
<span class="n">grunt</span><span class="p">.</span><span class="n">initConfig</span><span class="p">({</span>
<span class="n">uglify</span><span class="p">:</span> <span class="p">{</span>
<span class="n">target</span><span class="p">:</span> <span class="p">{</span>
<span class="n">files</span><span class="p">:</span> <span class="p">{</span>
<span class="c">'dist/output.min.js': ['src/file1.js', 'src/file2.js']</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">})</span><span class="err">;</span>
<span class="n">grunt</span><span class="p">.</span><span class="n">loadNpmTasks</span><span class="p">(</span><span class="c">'grunt-contrib-uglify');</span>
<span class="p">}</span><span class="err">;</span>
</pre></div>
<p>Before we try to understand the configuration completely, create a directory named <code>src</code> with two files <code>file1.js</code> and <code>file2.js</code>. Let us put the following contents in those files.</p>
<div class="codehilite"><pre><span class="kd">function</span> <span class="nx">greeting_one</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">'Hello world! This is the first file.'</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">greeting_one</span><span class="p">();</span>
</pre></div>
<p>in the first file and </p>
<div class="codehilite"><pre><span class="kd">function</span> <span class="nx">greeting_two</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">'Hello world! This is the second file.'</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">greeting_two</span><span class="p">();</span>
</pre></div>
<p>in the second file. Now let us look at the configuration. The object with key <code>files</code> is of interest here. The key in the object is <code>'dist/output.min.js'</code> and its value is the list <code>['src/file1.js', 'src/file2.js']</code>. Uglify takes the files specified in the list and minifies them and saves the result as the given key <code>'dist/output.min.js'</code>. Now run uglify task with</p>
<div class="codehilite"><pre>grunt uglify
</pre></div>
<p>Grunt will run the uglify task, which will concatinate the 'src/file1.js' and 'src/file2.js' files and remove uncessary white spaces and save the result as 'dist/output.min.js'. The content of the <code>dist/output.min.js</code> file will be like this.</p>
<div class="codehilite"><pre><span class="kd">function</span> <span class="nx">greeting_one</span><span class="p">(){</span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">"Hello world! This is the first file."</span><span class="p">)}</span><span class="kd">function</span> <span class="nx">greeting_two</span><span class="p">(){</span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">"Hello world! This is the second file."</span><span class="p">)}</span><span class="nx">greeting_one</span><span class="p">(),</span><span class="nx">greeting_two</span><span class="p">();</span>
</pre></div>
<p>Yes, it is ugly. You can run this file with node with command</p>
<div class="codehilite"><pre>node dist/output.min.js
</pre></div>
<p>The output will be </p>
<div class="codehilite"><pre>ravi$ node dist/output.min.js
Hello world! This is the first file.
Hello world! This is the second file.
</pre></div>
<p>It is difficult to specify each and every file that needs to be minified. Grunt allows wildcards to specify source files. To include all the .js files from the <code>src</code> directory, the configuration would be</p>
<div class="codehilite"><pre>files: {
'dist/output.min.js': ['src/*.js']
}
</pre></div>
<p>We can specify to include all the .js files from all the sub-directories with </p>
<div class="codehilite"><pre>files: {
'dist/output.min.js': ['src/**/*.js']
}
</pre></div>
<p>This will combine all the js files from all the sub-directories in <code>src</code>, minify and save as <code>dist/output.min.js</code>. Other options provided by <em>uglify</em> are listed and explained on <a href="https://github.com/gruntjs/grunt-contrib-uglify#grunt-contrib-uglify-v301--" target="_blank"> uglify's gihub page</a>.</p>
<h3>Create a default task for grunt</h3>
<p><code>uglify</code> plugin will have defined the task named <code>uglify</code> which we ran with <code>grunt uglify</code>. How do we create our own custom task? We create a task with the <code>registerTask()</code> method</p>
<div class="codehilite"><pre><span class="nx">grunt.registerTask</span><span class="p">(</span><span class="s1">'<custom_taskname>'</span><span class="p">,</span> <span class="err">[</span><span class="s1">'<task 1>'</span><span class="p">,</span> <span class="s1">'<task 2>'</span><span class="p">,</span> <span class="nx">...</span><span class="cp">]</span>);
</pre></div>
<p>The task registered with above method is aggregation of the list of tasks passed and second argument. For example, we can define the 'default' task that executes 'uglify' with</p>
<div class="codehilite"><pre>grunt.registerTask('default', ['uglify']);
</pre></div>
<p>We can execute the default task with <code>grunt default</code>. Grunt by default looks for a task named 'default' and execute it when we do not specify any task.</p>
<div class="codehilite"><pre>grunt
</pre></div>
<p>is equivalent to </p>
<div class="codehilite"><pre>grunt default
</pre></div>
<p>We can add multiple tasks to single task and execute all of them as if it is a single task. The tasks are run in the specified order.</p>
<p><br> </p>
<p>For more information on how tasks work and are created, visit <em>grunt</em>'s official tutorial <a href="https://gruntjs.com/creating-tasks" target="_blank">creating tasks</a> and the <a href="https://gruntjs.com/api/grunt" target="_blank">grunt's API documentation</a>.</p>
<h3>Compile Sass to CSS in grunt</h3>
<p>Let us also configure our gruntfile to compile Sass files into CSS files. This can be done with the grunt plugin <a href="https://github.com/sindresorhus/grunt-sass" target="_blank">grunt-sass</a>. Install grunt-sass with </p>
<div class="codehilite"><pre>npm install --save-dev grunt-sass
</pre></div>
<p>Load the <em>grunt sass</em> tasks with </p>
<div class="codehilite"><pre>grunt.loadNpmTasks('grunt-sass');
</pre></div>
<p>Then configure <em>sass</em> with the </p>
<div class="codehilite"><pre>grunt.initConfig({
sass: {
dist: {
files: {
'dist/css/app.css': ['src/styles/*.scss']
}
}
}
});
</pre></div>
<p>Also add the sass task to the <em>'default'</em> task with the line</p>
<div class="codehilite"><pre>grunt.registerTask('default', ['sass']);
</pre></div>
<p>My gruntfile at this point (along with uglify tasks) looks like this.</p>
<div class="codehilite"><pre><span class="n">module</span><span class="p">.</span><span class="n">exports</span> <span class="o">=</span> <span class="n">function</span><span class="p">(</span><span class="n">grunt</span><span class="p">)</span> <span class="p">{</span>
<span class="n">grunt</span><span class="p">.</span><span class="n">initConfig</span><span class="p">({</span>
<span class="n">uglify</span><span class="p">:</span> <span class="p">{</span>
<span class="n">target</span><span class="p">:</span> <span class="p">{</span>
<span class="n">files</span><span class="p">:</span> <span class="p">{</span>
<span class="c">'dist/output.min.js': ['src/file1.js', 'src/file2.js']</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="n">sass</span><span class="p">:</span> <span class="p">{</span>
<span class="n">dist</span><span class="p">:</span> <span class="p">{</span>
<span class="n">files</span><span class="p">:</span> <span class="p">{</span>
<span class="c">'dist/css/app.css': ['src/styles/*.scss']</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">})</span><span class="err">;</span>
<span class="n">grunt</span><span class="p">.</span><span class="n">loadNpmTasks</span><span class="p">(</span><span class="c">'grunt-contrib-uglify');</span>
<span class="n">grunt</span><span class="p">.</span><span class="n">loadNpmTasks</span><span class="p">(</span><span class="c">'grunt-sass');</span>
<span class="n">grunt</span><span class="p">.</span><span class="n">registerTask</span><span class="p">(</span><span class="c">'default', ['uglify', 'sass']);</span>
<span class="p">}</span><span class="err">;</span>
</pre></div>
<p>This configuration will compile all the .scss files in <code>src/styles/</code> directory and save the output as the file <code>dist/css/app.css</code>. To test this create a file in <code>src/styles/</code> named <code>main.scss</code> with the following contents.</p>
<div class="codehilite"><pre><span class="na">.container</span> <span class="err">{</span>
<span class="na">.right</span> <span class="err">{</span>
<span class="nl">text-align:</span> <span class="nf">right</span><span class="err">;</span>
<span class="err">}</span>
<span class="err">}</span>
</pre></div>
<p>Save the file and then run grunt with</p>
<div class="codehilite"><pre>grunt
</pre></div>
<p>The .scss sass file will be compiled and the resulting file will be placed in <code>dist/css</code> as <code>app.css</code> with the contents</p>
<div class="codehilite"><pre><span class="na">.container</span> <span class="no">.right</span> <span class="err">{</span>
<span class="nl">text-align:</span> <span class="nf">right</span><span class="err">;</span> <span class="err">}</span>
</pre></div>
<p><code>grunt-sass</code> works with <code>node-sass</code>. The options will be same as that of <code>node-sass</code> which can be found at <a href="https://github.com/sass/node-sass#options" target="_blank">https://github.com/sass/node-sass#options</a>.</p>