In this post, you will learn why you should concatenate and minify your scripts and stylesheets into one small file using the task runner called Grunt.js. You will also learn how to concatenate and minify your scripts and stylesheets with Grunt.
Concatenation of Files
For each script that you force your client to request, the client has to wait for a full request/response cycle before being able to load the code from that script. If you have 10 scripts and 3 stylesheets being loaded in your index.html
, that is at least 13 full roundtrips to the server and back before the client is ready to engage with the user — not a good user experience.
For production purposes — deploying your code — you should concatenate all of your scripts into a single script, and load that script in your index.html
, thereby only needing to make 1 trip to the server and back.
I tend to write 3 tasks to concatenate my files: a task for concatenating all of my client-side javascripts, a task for concatenating all of my vendor and third-party client-side javascripts, and a task for concatenating all of my stylesheets. This allows me to place three tags on my index.html
— a javascripts tag, a vendor javascripts tag, and a stylesheets link. Three requests from the client and three round-trips to the server and back — regardless of how many source files were concatenated.
tl;dr — the purpose of concatenation is to reduce the requests needed to get your app running on the client, by smushing together all of the files of a single type into one file, separated by some meaningful delimiter (like ;
in JavaScript). The client can then load all of the files with a single request.
For example, do you think that the user has a better experience waiting for the client to request and receive each of the following files?
Or, these files?
Enough said. Let’s move on to another type of production optimization — file minification.
Minification of Files
If concatenation is the process of stringing together multiple files into one file, minification is the process of compressing the code from one file into one really small version of that code. The compression takes place by removing whitespace and new line characters, and sometimes changing variable names to single letters.
The purpose of concatenation is to reduce the number of roundtrips that need to be made to get all of the code onto the client. The purpose of minifcation is to reduce the amount of data that needs to be sent on a single response, by removing extraneous and non-semantic characters in the file that the response will send.
For example, do you think it would take longer for a server to send the first code example below to a client or the second code example? And, which do you think would take longer for the client to download? By the way, the first example is only showing 60 of 1200+ lines of code, and the second example, takes up 30 lines.
tl;dr — obviously, the minified version is much smaller than the unminified version. Smaller usually means faster to upload and download.
So, now that we understand both concatenation and minification in terms of result and purpose, we can understand how to combine them. The process goes like this:
- combine lots of different files into a single file (concatentation)
- strip that single combined file — the output of the concatenation operation — of all extraneous space and characters (minification)
The result of those two operations is a single and small file (relative to the unminified version) that can be sent to the client in a single and speedy trip.
How would you go about implementing the two step process that I just outlined?
Hopefully, your mind went rather quickly from a manual solution to an automated solution. You don’t want to have to copy and paste everytime any source file changes — error prone and ridiculous. Time matters to me; I don’t want to waste time doing things like copying and pasting. Let’s figure out how to automate this concatenation/minification task. I suggest a build tool or task runner, like Grunt. Here we go…
Build Tools and Task Runners
Build tools are just software utilities used to build new instances of applications. Task runners are just software utilities used to automate the running of tasks. Building a new version of an application is a task, so you can automate your build process with a task runner, like Grunt.js.
Grunt
Grunt will help you automate pretty much any task that you can think of, but we will only be using it for concatenation and minification. Also, please note that there are a lot of competitors to Grunt that will do the same thing. Grunt and Gulp tend to be the most popular task runners out there right now for JavaScript.
Grunt puts an abstraction layer between the intent of your task and how to execute that task in a given environment, meaning that you can write your task once and it will run on pretty much any environment without having to rewrite it — very cool.
Now that we have enough background on Grunt, let’s dive into concatenation and minification with Grunt.
Grunt Setup
- globally install the Grunt Command-Line Interface tool
npm install -g grunt-cli
(must be run as a user with administrator access)
- change directories into the root of your project
- add a
Gruntfile.js
at the root level of your project- leave this empty for now
- add a
package.json
at the root level of your project- Grunt and its plugins are managed via npm, so you need a
package.json
file in your project to interact with Grunt - here are the contents of our
package.json
file:
- Grunt and its plugins are managed via npm, so you need a
- this
package.json
acts as a manifest to declare the name, version number, description, main entry point, license, publication visibility, and development dependencies of the application - npm will manage these dependencies for you if you run
npm install
- this command will install your
devDependencies
in your local project folder — i.e., npm installs the packages we will be using:grunt
,grunt-contrib-concat
,grunt-contrib-cssmin
, andgrunt-contrib-uglify
- this command will install your
- Note: any Grunt plugin that contains the word
contrib
in the title is officially maintained by the Grunt development team; So, you should probably lean towardscontrib
packages, all else being equal - if you need more plugins later, you can install as needed with
npm install plugin_name --save-dev
, which will automatically write the plugin to yourpackage.json
as a dependency, as well as install the plugin to your local project
We are finished with the Grunt setup, so if you haven’t already, before moving on, please run npm install
from the root-level of your local project directory. To the code!
Gruntfile.js for Concatenation and Minification
The Gruntfile.js
always follows the same pattern — initialize and configure Grunt and plugins, load the plugins, and register tasks. The configuration of the plugins can happen before or after the loading step — doesn’t usually matter.
It will be easier to explain the Gruntfile.js
if we have it in front of our mind’s eye, so here it is:
We start by exporting a function, on line 1, that Grunt can invoke with a grunt
object as an argument. The majority of the work in this function will be done by interacting with the grunt
object. The rest of the Gruntfile.js
is made up of the three parts that we discussed earlier — configuration of grunt
and plugins, loading of all necessary plugins, and registering tasks on the grunt
object.
Configuration
Lines 7 – 45 invoke the initConfig
method on the grunt
object, providing the configuration settings for your tasks as an object. Line 9 provides Grunt with the package settings by reading the JSON of the package.json
file with grunt.file.readJSON('package.json')
. All of the task and plugin configuration happens between lines 11 and 44.
concat
- lines 11 – 23: define the configuration for our concatenation task,
concat
- line 12: sets one option for the
concat
task — the separator between each file in the concatenation (;
works in JavaScript because the interpreter treats;
as the end of a meaningful piece of code) - lines 14 – 17: define our
js
sub-task configuration, which will be used to concatenate all of our non-vendor JavaScript for delivery to the client- line 15: sets the location of the
src
files — the files to concatenate together - in our case, we are telling Grunt to look in the
src
directory for any.js
files nested any level of sub-directories deep - line 16: sets the location of the
dest
file — the single file that will hold all of the concatenated JavaScript code from thesrc
files - in our case, we are telling Grunt to dump all the concatenated code in a new file in the
dist
directory titled with the package name from thepkg
property we set above
- line 15: sets the location of the
- lines 19 – 22: define our
vendor
sub-task configuration, which will be used to concatenate all of our vendor JavaScripts for delivery to the client- line 20: sets the location of the
src
files — the files to concatenate together - in our case, we are telling Grunt to look through an array of files in a specific order because of order-dependencies between the vendor libraries
- we are specifying the
jquery.js
file in ourvendor
directory to be concatenated first, then theunderscore.js
file, followed by any other.js
file any level deep within thevendor
directory - line 21: sets the location of the
dest
file — the single file that will hold all of the concatenated JavaScript code from thesrc
vendor files - in our case, we are telling Grunt to dump all the concatenated code in a new file in the
dist
directory titledvendors.js
- line 20: sets the location of the
- line 12: sets one option for the
That is the whole process for getting all of your files concatenated — just run grunt concat
from the command-line and watch the magic happen (assuming that you have loaded the plugins as seen below). Now, you can replace all of the script tags in your HTML with just two tags — one to the dist/[NAME OF PACKAGE].js
and one point to the dist/vendors.js
file.
uglify
- lines 25 – 38: define the configuration for our minification task,
uglify
- **lines 26 – 29 **: sets the options for the
uglify
task- line 27: sets the
banner
option so that each minified file gets a banner comment at the top with some information for to make your life easier - line 28: sets the
mangle
option tofalse
, which instructs theuglify
plugin to avoid changing variable names and function names (even if that might shorten the code)
- line 27: sets the
- lines 30 – 34: define a sub-task configuration called
js
- lines 31 – 32: declare the files to minify, and store them in an array as the value of a property that has a key set to the destination file in which to put the minified code
- lines 35 – 38: define a sub-task configuration called
vendor
and repeat the same pattern as thejs
sub-task, except for your vendor scripts
- **lines 26 – 29 **: sets the options for the
That is the whole process for getting all of your files minified — just run grunt concat
then grunt uglify
from the command-line and watch the magic happen (assuming that you have loaded the plugins as seen below). Now, you can replace all of the script tags in your HTML that point to unminified JavaScript with just two tags — one to the dist/[NAME OF PACKAGE].min.js
and one point to the dist/vendors.min.js
file.
cssmin
- lines 40 – 44: define the configuration for our CSS minification task,
cssmin
- lines 41 – 43: set the
target
property to the files that you want to minify and the file that you would like to use as the destination for the minified code- in our case, we are minifying
style.css
from thestyles
directory, and we are outputting the code intostyle.min.css
in thedist
directory
- in our case, we are minifying
- lines 41 – 43: set the
That is the whole process for getting all of your CSS files minified — just run grunt cssmin
from the command-line and watch the magic happen (assuming that you have loaded the plugins as seen below). Now, you can replace the stylesheet link references to unminified CSS in your HTML with just one tag pointing to a minified styles/style.min.css
.
Now that all of the plugin task configurations have been completed, we are going to load all of the plugin packages using grunt.loadNpmTasks
.
Load Plugins
- lines 51 – 53: load the plugins you are utilizing in the tasks so that you have access to them
- in our case, we are using a plugin for
concat
,uglify
, andcssmin
, so we load all three of them withgrunt.loadNpmTasks('PLUGIN NAME');
- in our case, we are using a plugin for
After all of the require plugins are loaded, we can register any ‘composite’ tasks, and end the Gruntfile.js
.
Register Tasks
This is where you register any tasks that are made up of other tasks or sub-tasks. This is very powerful — allowing you to build highly flexible and composable units of functionality that can be rearranged depending on need. We are going to demonstrate an example of that power by registering a composite task called build
. This build
task is just going to synchronously execute the concat
task, then the uglify
task, and finally the cssmin
task.
- lines 59 – 63: register a task on the
grunt
object that is calledbuild
build
is composed of the plugin sub-tasks that we configured above:concat
,uglify
, andcssmin
in that order
We can now issue the grunt build
command at the command-line and the end result will be concatenated and minified JavaScript and CSS files! BOOM.
Summary
- reducing the number of script tags in your HTML reduces the number of requests the client has to make to the server for the given files — this is achieved by concatenating all the files into a single file, and only loading that file
- reducing the size of a single file reduces the time that a single response takes to get passed between the server and the client (and the amount of time for the client to digest the response) — this is accomplished by minifying your files, i.e., reducing extraneous characters and space
- Grunt.js is a task runner that helps you compose and run tasks like file concatenation and minification
Automate the silly stuff and then use that earned time to do fun stuff in the future.