frontend life is easy with Grunt
This post is part of the series "the road to Visual Studio free frontend development":
- Frontend life with Visual Studio
- Frontend life without Visual Studio
- Frontend life is easy with Grunt
You can browse the code of this post on github.
If you think that the term build is used only in backend development you are in trouble.
Nowadays a lot of frontend projects require to manage tasks such as expansion of SASS and LESS, minification of CSS/JS, running JSHint or JSLint, compressing the images, running unit tests and so on: a client build system aka task manager is mandatory.
before you start
A lot of tools used to automate the management of the frontend projects are written in Node.js: it's sounds strange, but node.js is not only a technology used for writing a server side api, but it's widely spread in development of automation tasks. If you haven't yet node.js installed in your machine, do it.
Now it's time to migrate my website to Grunt. My first goal is to make grunt able working like the powershell script I showed in the first post of this series; moreover, I want to remove the useless files and change as little code as possible.
On github you can see the status of the project before and after its migration to grunt.
let's start
Node.js uses Npm to manage dependencies of the project, so let's start creating a folder, opening a command prompt and typing:
npm init
The command creates a package.json file in which there are information about your project (name, author, repository url...). In particular we're interested in the dependencies section. The command npm install
downloads the local dependencies and put them in the node_module
folder located in the root of the project.
We go on installing grunt:
npm install grunt --save-dev
npm install -g grunt-cli
grunt is shipped in two parts: the command line (grunt-cli) and the task runner (grunt). The command line is installed globally to the machine (note the option -g) so you can execute the grunt
command wherever you are.
If you take a look at the package.json file
{
"name": "gianluca.carucci.org",
"version": "2.0.0",
"description": "my personal website",
"repository": {
"type": "git",
"url": "https://github.com/rucka/gianluca.carucci.org.git"
},
"author": "Gianluca Carucci",
"devDependencies": {
"grunt": "~0.4.2"
}
}
Note that grunt is placed inside the devdependecies
section.
project structure
Now the project folder contains only the package.json
file; I created a client
folder under the root, where I copied all the assets (javascript, css and html files). The project structure is almost complete: it's missing only the Gruntfile.js
.
tasks configuration
The simplest Grunt file is similar to the following:
module.exports = function(grunt) {
grunt.initConfig({
//import project metadata
pkg: grunt.file.readJSON('package.json')
});
};
The function grunt.initConfig
receives a structure as parameter in which you can define and configure the tasks.
In this project I'll use three grunt task plugins that I can download and install like this:
npm install grunt-contrib-clean --save-dev
npm install grunt-contrib-copy --save-dev
npm install grunt-contrib-uglify --save-dev
Before using the plugins you have to load them using grunt.loadNpmTasks
function in the following way:
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-copy');
Finally it's the time to make the task builder useful: grunt will put a copy of website in the publishing folder transforming the assets to the minified pairs.
clean publishing folder
The first task to configure is to clean the publishing folder in which I'll put the assets. In the grunt file I declare a clean task section as follows:
grunt.initConfig({
//previous declarations...
clean: {
publish: 'publish'
},
//next declarations...
));
Configuring a task is a pretty simple job so starting from now I'll explain only the valuable code; for a detailed guide you can take a look at grunt documentation.
publishing assets
The next task will copy the assets to the publishing folder:
grunt.initConfig({
//previous declarations...
copy: {
target: {
cwd: 'client',
expand: true,
src:['**','!css/**', '!scripts/**','!vendor/**'],
dest: 'publish/'
},
cssdev: {
cwd: 'client',
expand: true,
src:['css/**'],
dest: 'publish/'
}
},
//next declarations...
));
minify the asset files
You can note that the copy
task excludes the javascript files: the reason is that the files will be minified by the uglify task:
grunt.initConfig({
//previous declarations...
uglify: {
options : {
sourceMap: true,
sourceMapIncludeSources:true,
},
javascript : {
files : [{
cwd: 'client/',
src: ['**/*.js'],
dest: 'publish/',
expand: true,
}]
}
}
//next declarations...
));
Uglify does the magic, but it's not limited to the minification magic, it does more: if configured, it can create the source map of the source code; with a source map I can use the same file (the minified version) for both the development and the production website navigation so the browser will always download a small size file instead of the real, potentially large file.
Ok, but what does that have to do with the source map?
If you use a browser compatible source map (at the moment the latest versions of the main browsers do it), when you inspect the code using the debugger, the source map is downloaded so the debugger can translate the minified file into the original file.
I found the source map awesome because:
- You are debugging the same javascript you will deploy in production
- The source map file is downloaded on demand
- If you use the source map with all languages able to compile to JavaScript (like CoffeeScript, TypeScript, ClojureScript...) the debugger shows you the source code in your original language
If you want to know more about the source map you can start from here.
run tasks
Now it's time to run the tasks. From the command line I can run the tasks individually, typing grunt clean
, grunt copy
or grunt uglify
. The interesting thing is that I can register a default task which run the sequence of tasks I prefer when I type the command grunt
without any parameter. In my case I configured the default task like this:
grunt.registerTask('default', ['clean','copy', 'uglify']);
test your site
At the end I have a little issue to solve: how can I test my website when I write my code with a simple editor (for example, vim)?
Don't be afraid, Npm - again - make you safe.
Run it once time for all:
npm install -g http-server
Run it once time again for every development session:
http-server [your path project root]/publish
Now open your browser and your website is available from http://localhost:8080/index.html (or whathever page you want).
development workflow
Summarizing: when your Grunt file is completed, your development workflow is composed of these steps:
- modify the code inside your editor and save your changes
- run the command
grunt
from the terminal - refresh your browser page
Is it good for you?
no, too many steps!
You are right; Grunt could do better: I'll show you how to reduce the development workflow to only the first step, so that when you save your file, Grunt watches it and reloads your browser page automatically without any effort!
ok, but I want more!
I showed you only the basic features of Grunt. What I love about this tool is the huge number of awesome plugins that exist for it, so take your time and try grunt out to:
- run your tests
- minimize CSS
- compile Sass or Less files to CSS
- validate your JavaScript code
- minify your images
- manage different environment configurations
- mock your real services during the development
- add vendor prefix automatically to your CSS files
development
That's all for now: next time I'll speak about how to manage client dependencies.