QRdvark.com

Concatination And Compressing Files With Grunt

Concatination And Compressing Files With Grunt

Abstract.

Grunt is a tool which allows developers to run tasks and to minimise and compress CSS and Javascript. The use of Grunt has become quite popular as it allows developers to group CSS and Javascript into small chunks of code, which Grunt can compress into a singel, minified, version.

Installing Grunt

Grunt uses Node as its backbone, so firstly, Node needs to be installed. Fortunately, this is quite simple, at least with ubuntu which this tutorial is using.

sudo apt-get update
sudo apt-get -y install nodejs
sudo apt-get -y install npm
sudo ln -s /usr/bin/nodejs /usr/bin/node

OK, now node is installed, Grunt can be installed in a per-project manner. First, lets create a simple project. For the purposes of this tutorial, the structure will be as follows

 html/
    index.php
    css/
       style.css
    js/
       tute.js
       more.js
       jquery-2.2.3.min.js
    images/

index.php

<html>
<head>
<title>QRdvark Grunt Tutorial</title>
<link rel="stylesheet" type="text/css" href="css/style.css">
</head>

<body id="myBody">

<div id="myId" class="normal">World End is Nigh!</div>
<div id="myId2"><span id="myId3">4</span> days to the rapture!</div>

</body>
<script src="js/jquery-2.2.3.min.js"></script>
<script src="js/tute.js"></script>
<script src="js/more.js"></script>

</html>

style.css

.normal{
        color:black;
        background-color:white;
}

.reverse{
        color:white;
        background-color:black;
}

tute.js

$( document ).ready(function() {
        // toggle the colors
        $( "#myId" ).click(function() {
                $( "#myId" ).toggleClass( "reverse" );
        });
});

more.js

$( document ).ready(function() {
        // add one
        $( "#myId2" ).click(function() {
                var num = $("#myId3").html();
                num++;
                $("#myId3").html(num);
        });
});

The above files create a basic project, which consists of a single stylesheet, and two javascript files, plus the jquery library, along with the index.php file which calls them. The two javascript files contain different logic for different parts of the page, and so are kept seperated for ease of maintenance. However, when this goes into production, each of the files, and the number of Javascript files could be many, requires a seperate request, and the filesize of each javascript file could be large. This is where Grunt shines.

Grunt is a task runner, so it needs to know what to do. The instructions to direct Grunt are stored in a file named package.json. To get started, lets create a basic file. In the root of the project, create a file named package.json and in it put the following code.

package.json

{
  "name": "my-project",
  "version": "0.0.1",
  "devDependencies": {
    "grunt": "~1.0.1"
  }
}

With the package.json file created, the npm (Node Package Manager) command is used to create node modules package.

npm install

When the npm install command is run, the output will look something like this

$ npm install
npm WARN package.json my-project@0.0.1 No description
npm WARN package.json my-project@0.0.1 No repository field.
npm WARN package.json my-project@0.0.1 No README data
grunt@1.0.1 node_modules/grunt
├── path-is-absolute@1.0.0
├── grunt-known-options@1.1.0
├── eventemitter2@0.4.14
├── rimraf@2.2.8
├── exit@0.1.2
├── iconv-lite@0.4.13
├── coffee-script@1.10.0
├── grunt-cli@1.2.0 (resolve@1.1.7)
├── nopt@3.0.6 (abbrev@1.0.7)
├── minimatch@3.0.0 (brace-expansion@1.1.3)
├── glob@7.0.3 (inherits@2.0.1, once@1.3.3, inflight@1.0.4)
├── findup-sync@0.3.0 (glob@5.0.15)
├── js-yaml@3.5.5 (esprima@2.7.2, argparse@1.0.7)
├── grunt-legacy-util@1.0.0 (getobject@0.1.0, hooker@0.2.3, async@1.5.2, underscore.string@3.2.3, which@1.2.4, lodash@4.3.0)
├── dateformat@1.0.12 (get-stdin@4.0.1, meow@3.7.0)
└── grunt-legacy-log@1.0.0 (hooker@0.2.3, colors@1.1.2, underscore.string@3.2.3, lodash@3.10.1, grunt-legacy-log-utils@1.0.0)

The npm install command will create a directory named node_modules, inside of which is a module named grunt. Grunt will be run from the command line, and so needs to be told so with the following command.

sudo npm install -g grunt-cli
$ sudo npm install -g grunt-cli
/usr/local/bin/grunt -> /usr/local/lib/node_modules/grunt-cli/bin/grunt
grunt-cli@1.2.0 /usr/local/lib/node_modules/grunt-cli
├── grunt-known-options@1.1.0
├── resolve@1.1.7
├── nopt@3.0.6 (abbrev@1.0.7)
└── findup-sync@0.3.0 (glob@5.0.15)

And then another package needs to be added which grunt will use to concat files together, to create a single master file.

npm install grunt-contrib-concat --save-dev
$ npm install grunt-contrib-concat --save-dev
npm WARN package.json my-project@0.0.1 No description
npm WARN package.json my-project@0.0.1 No repository field.
npm WARN package.json my-project@0.0.1 No README data
grunt-contrib-concat@1.0.1 node_modules/grunt-contrib-concat
├── source-map@0.5.3
└── chalk@1.1.3 (escape-string-regexp@1.0.5, supports-color@2.0.0, ansi-styles@2.2.1, has-ansi@2.0.0, strip-ansi@3.0.1)

Whilst the output does not seem too exciting, behind the scenes, the package.json file has been updated to include this line.

grunt-contrib-concat": "^1.0.1"

Now grunt is ready to go.

Concat Files With Grunt

In the above section, the grunt-contrib-concat module was added to enable the concatenation of multiple files. The my-project project has three Javascript files, including the jquery library.

Grunt uses the grunt command to concat the file, and needs a Gruntfile.js in order to know what to do when the grunt command is run. The Grunfile.js will look like this for the my-project project.

module.exports = function(grunt) {

        // Configuration needs the name of package.json file
        grunt.initConfig({
                pkg: grunt.file.readJSON('package.json'),
        
        // Set the source and destination of the javascript files
        concat: {  
                dist: {
                        src: [  
                                'js/*.js'
                        ],
                        dest: 'js/my-project.js',
                }
        }

        });

        // tell grunt which pluging to use
        grunt.loadNpmTasks('grunt-contrib-concat');

        // Tell Grunt what to do when the grunt command is run
        grunt.registerTask('default', ['concat']);
};

Now, we simply run grunt.

$ grunt
Running "concat:dist" (concat) task

Done.

Now the contents of the file have all be concatenated into a single javascript file named my-project.js.

jquery-2.2.3.min.js
more.js
my-project.js
tute.js

So now the files are all bundled into a single javascript file, there needs to be only a single call from the server to include it, rather than the three calls initially made with the index.php file.

This is great, however, the filesize is still the same as having three individual files. So lets minify (uglify) them to reduce the file size.

Compress Files With Grunt

Compressing the javascript files is as simple as concatination with a few simple tweaks to the Gruntfile.js.

Node npm uses grunt-contrib-uglify to do the compression, so this module needs to be installed, in the same way the concat module was installed.

npm install grunt-contrib-uglify --save-dev
$ npm install grunt-contrib-uglify --save-dev
npm WARN package.json my-project@0.0.1 No description
npm WARN package.json my-project@0.0.1 No repository field.
npm WARN package.json my-project@0.0.1 No README data
npm WARN engine is-buffer@1.1.3: wanted: {"node":">=0.12"} (current: {"node":"0.10.25","npm":"1.4.21"})
npm WARN engine is-buffer@1.1.3: wanted: {"node":">=0.12"} (current: {"node":"0.10.25","npm":"1.4.21"})
grunt-contrib-uglify@1.0.1 node_modules/grunt-contrib-uglify
├── uri-path@1.0.0
├── chalk@1.1.3 (escape-string-regexp@1.0.5, supports-color@2.0.0, ansi-styles@2.2.1, strip-ansi@3.0.1, has-ansi@2.0.0)
├── lodash@4.11.1
├── maxmin@1.1.0 (figures@1.5.0, gzip-size@1.0.0, pretty-bytes@1.0.4)
└── uglify-js@2.6.2 (uglify-to-browserify@1.0.2, async@0.2.10, source-map@0.5.3, yargs@3.10.0)

With the plug-in installed, the Gruntfile.js just needs to know that the plug-in is installed, and to use it to minify the source files.

module.exports = function(grunt) {

        // Configuration needs the name of package.json file 
        grunt.initConfig({
                pkg: grunt.file.readJSON('package.json'),

        // Set the source and destination of the javascript files
        concat: {
                dist: {
                        src: [
                                'js/*.js'
                        ],
                        dest: 'js/my-project.js',
                }
        },
        uglify: {
                build: {
                        src: 'js/my-project.js',
                        dest: 'js/my-project.min.js'
                }
        }

        });

        // tell grunt which plugins to use
        grunt.loadNpmTasks('grunt-contrib-concat');
        grunt.loadNpmTasks('grunt-contrib-uglify');

        // Tell Grunt what to do when the grunt command is run
        grunt.registerTask('default', ['concat', 'uglify']);

};

And once again, simply run the grunt command.

$ grunt
Running "concat:dist" (concat) task

Running "uglify:build" (uglify) task
File js/my-project.min.js created: 171.92 kB → 172.76 kB
>> 1 file created.

Done.

The output tells that 1 file has been created, and a quick look in the js directory shows the my-project.min.js file is there, and can be loaded in place of all other js files saving on requests and on loading times.

Credits

Thanks to the artist formerly known as Prince.