Technology
Jul 23, 2014
Modern Web Development Best Practices Powered by Grunt.js Part 2: Diving In
In part two of this series, I’ll discuss the basics, and we can start getting our hands dirty with Grunt.js.
Tutorial:
git clone git@github.com:prototypef/modern-web-dev-sandbox.git npm install bower install git checkout basic
The aim here is to create a basic web project, the bare minimum needed to display a page to a user in a web browser. However, we will set up this basic project with a good core that will allow us to easily extend and improve it. Here is what the structure should look like after installing NPM and Bower dependencies:
Before we dig in, let me explain what each of these files and folders are:
bower_components – By default, Bower will install all its packages in this folder. You can configure it to place the packages in a specific folder by creating a “.bowerrc” file and adding a few configuration options there.
js-src – This is where we will keep our application JavaScript source files.
node_modules – This is where NPM will store all its packages needed for the project.
.gitignore – This file contains a list of patterns that Git will ignore by default. It helps to eliminate the chance of adding unnecessary files to the source repository.
bower.json – This file contains some basic information and configuration for Bower. It essentially serves as a manifest file that tells the program which packages this project depends on. When you run “bower install,” it will look for this file and get the list of packages to download from there.
Gruntfile.js – This file will contain all the Grunt.js tasks and configurations.
index.html – Our basic app’s main page.
package.json – This is similar to the bower.json file, but for NPM.
Here is what the content of index.html should look like:
<!DOCTYPE html>
<html> <head> <meta charset="utf-8"> <title>Modern Web Development Sandbox</title> </head> <body> <header> </header> <div> <script type="text/x-handlebars" data-template-name="application"> <h1>{{#linkTo 'index'}}Modern Web Development Sandbox{{/linkTo}}</h1> <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris iaculis eget risus ut lobortis. Sed at augue bibendum, pharetra orci at, suscipit massa. </p> <div> {{outlet}} </div> </script> </div> <footer> </footer> <script src="js/app.js"></script> </body> </html>
It’s fairly standard-looking boilerplate HTML. At the bottom we see a reference to an “app.js” file inside of a “js” folder. If you loaded this page in your browser right now, your browser would render an empty page. (For quickly standing up a local HTTP server, I recommend using the http-server NPM package.) If you remember from the screenshot of the folder hierarchy above, there is no “js” folder. Instead, there is a “js-src” folder. What gives? The reason for this is because when you’re developing a web site or app, and JavaScript is needed, we ideally want to keep the source code separate from the final code that will be shipped. To achieve this, we’ll need something that takes all the JS source files and bundles them up into a single file. This process is commonly known as concatenation. For this, we’ll use the “grunt-contrib-concat” plugin. Let’s take a look at our Gruntfile.js file (don’t worry about looking at every line of code, I’ll explain the key parts afterward):
module.exports = function(grunt) { 'use strict'; // Declare JS dependencies and third-party libraries required var jsVendorSourceFiles = [ 'bower_components/jquery/jquery.js', 'bower_components/lodash/dist/lodash.js', 'bower_components/handlebars/handlebars.js', 'bower_components/ember/ember.js', 'bower_components/accounting/accounting.js', 'bower_components/moment/moment.js' ];
// Declare the App's source files var jsAppSourceFiles = [ 'js-src/app.js' ];
// Concatenate our file arrays to create a bundle we can use later var jsSourceFiles = jsVendorSourceFiles.concat(jsAppSourceFiles);
// Project configuration. grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), jshint: { options: { jshintrc: 'js-src/.jshintrc' }, files: { src: jsAppSourceFiles } }, concat: { dev: { files: [ { src: jsSourceFiles, dest: 'js/app.js' } ] } }, uglify: { prod: { files: [ { src: jsSourceFiles, dest: 'js/app.js' } ] }, }, });
// Load the plugin tasks. grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-concat'); grunt.loadNpmTasks('grunt-contrib-uglify');
// Default task(s). grunt.registerTask('default', ['jshint', 'concat:dev']); grunt.registerTask('build-prod', ['jshint', 'uglify:prod']); };
*If you haven’t already, I recommend you go over the Getting Started page for Grunt.js.
Here, we have configured Grunt.js to have two explicit tasks and have loaded up and configured three of its different plugins. We also defined an array of the JS source files for our app and its dependencies. To generate the concatenated “js/app.js” file, all we need to do is run:
grunt concat
Cool, huh? This command generated our “app.js” file by taking all the source files defined in the “jsSourceFiles” array and dumped them all into one big file. When you refresh the browser you will see the app run. This is great for development, since you’ll be able to see all of your code with all your comments and nice formatting. However, this big concatenated file is not ideal for deploying to production. What we need now is a way of shrinking the contents so it is as small as possible. Then when we deploy it to production and the user visits the page, they don’t have to wait too long for it to download.
To accomplish this we use a process known as minification, and one of the Grunt.js plugins we configured above already takes care of this for us. The “grunt-contrib-uglify” plugin uses the UglifyJS minification tool (other tools are available, notable ones include: Google Closure Compiler and JSMin). We have configured the plugin just like the concatenation plugin. It will look at the “jsSourceFiles” array and work its magic to compress the file, making it as small as possible. To generate this file, you can run:
grunt uglify
This will generate the minified file. If you open it up, you’ll notice the results are barely readable, but the file size is reduced drastically.
The other plugin we have installed and configured is the “grunt-contrib-jshint.” This lets you run JSHint over the source files to make sure there are no unsafe bits of code, syntax errors, etc. JSHint is an excellent code quality tool that is very configurable to suit your needs (in our example the configuration options are in the “js-src/.jshint” file). I recommend JSHint-ing your code as soon as possible to maintain a clean codebase. In my experience, it will save you the trouble of finding that pesky comma, semicolon, or bracket you forgot to put in. Since you really only care about JSHint-ing the code you write, we have configured the task to only look at your application’s source files (“jsAppSourceFiles” array) and not the third-party libraries or frameworks. You could use the task by running “grunt jshint,” but doing this manually is tedious, and eventually you’ll forget to do so. To solve this (and to showcase the awesomeness of Grunt.js) we created two tasks to make our life easier. The first one is the “default” task, which we’ll use during development. It can be started by simply running “grunt.” The second one is a “build-prod,” which we’ll use when getting ready for production.
grunt.registerTask('default', ['jshint', 'concat:dev']); grunt.registerTask('build-prod', ['jshint', 'uglify:prod']);
As you can see, the “default” task chains together the “jshint” task with the “concat” task, and the “build-prod” chains “jshint” and “uglify.” Note the “:dev” and “:prod” correspond to that “key” under the task configuration. See the “grunt.initConfig” section above and this guide for more details.
So that’s it for our basic configuration. Pretty much good to go, right? Not quite. In part three of this series, we’ll keep working on what was built here to add a few extra goodies. In the meantime, follow @CrederaMSFT on Twitter and Credera on LinkedIn for more great best practices.
To view the rest of the Modern Web Development Best Practices Powered by Grunt.js series click here.
Contact Us
Ready to achieve your vision? We're here to help.
We'd love to start a conversation. Fill out the form and we'll connect you with the right person.
Searching for a new career?
View job openings