Book Image

Mastering JavaScript Single Page Application Development

Book Image

Mastering JavaScript Single Page Application Development

Overview of this book

Single-page web applications—or SPAs, as they are commonly referred to—are quickly becoming the de facto standard for web app development. The fact that a major part of the app runs inside a single web page makes it very interesting and appealing. Also, the accelerated growth of browser capabilities is pushing us closer to the day when all apps will run entirely in the browser. This book will take your JavaScript development skills to the next level by teaching you to create a single-page application within a full-stack JavaScript environment. Using only JavaScript, you can go from being a front-end developer to a full-stack application developer with relative ease. You will learn to cross the boundary from front-end development to server-side development through the use of JavaScript on both ends. Use your existing knowledge of JavaScript by learning to manage a JSON document data store with MongoDB, writing a JavaScript powered REST API with Node.js and Express, and designing a front-end powered by AngularJS. This book will teach you to leverage the MEAN stack to do everything from document database design, routing REST web API requests, data-binding within views, and adding authentication and security to building a full-fledged, complex, single-page web application. In addition to building a full-stack JavaScript app, you will learn to test it with JavaScript-powered testing tools such as Mocha, Karma, and Jasmine. Finally, you will learn about deployment and scaling so that you can launch your own apps into the real world.
Table of Contents (20 chapters)
Mastering JavaScript Single Page Application Development
Credits
About the Authors
About the Reviewer
www.PacktPub.com
Preface
Free Chapter
1
Getting Organized with NPM, Bower, and Grunt
13
Testing with Mocha, Karma, and More

Grunt


Grunt is a JavaScript task runner for Node.js, and if you haven't used it before, it is perhaps the best tool you never knew you needed. You will find it useful for a myriad of tasks including CSS and JavaScript linting and minification, JavaScript template pre-compilation, LESS and SASS pre-processing, and so much more. There are indeed alternatives to Grunt, but none with as large an ecosystem of plugins (at the time of writing).

There are two components to Grunt: the Grunt CLI, and the Grunt task runner itself. The Grunt CLI allows you to run the Grunt task runner command from the command line within a directory that has Grunt installed. This allows you to have a different version of Grunt running for each project on your machine, making each app more maintainable. For more information, see gruntjs.com.

Installing the Grunt CLI

You will want to install the Grunt CLI globally, just as you did with Bower:

$ npm install -g grunt-cli

Remember that the Grunt CLI is not the Grunt task runner. It simply makes the grunt command available to you from the command line. This distinction is important, because while the grunt command will be globally available from the command line, it will always look for a local installation in the directory from which you run it.

Installing the Grunt task runner

You will install the Grunt task runner locally from the root of your app where your package.json file is located. Grunt is installed as a Node.js package:

$ npm install grunt --save-dev

Once you have Grunt installed locally, your package.json file should look like the following example:

{ 
  "name": "my-app", 
  "version": "1.0.0", 
  "author": "Philip Klauzinski", 
  "license": "MIT", 
  "devDependencies": { 
    "grunt": "^0.4.5" 
  } 
} 

You will notice a devDependencies object has been added to your package.json file, if it was not already there from a previous install.

Now that you have Grunt installed locally, let's begin installing some plugins to work with.

Installing Grunt plugins

All Grunt task plugins are Node.js packages, so they will be installed using NPM as well. There are thousands of Grunt plugins written by a multitude of authors, as Grunt is an open-source project. Every Node.js package for Grunt is prefixed with grunt in the name. The Grunt team, however, does maintain many plugins themselves. The officially maintained Grunt plugins are all prefixed with grunt-contrib, so this is how you can differentiate them if you wish to stick with only officially maintained Grunt plugins. To view and search all registered Grunt plugins, see gruntjs.com/plugins.

Since you will be writing a JavaScript SPA, let's begin by installing a JavaScript linting plugin for Grunt. Linting refers to running a program against your code to analyze it for errors and, in some cases, proper formatting. It is always a good idea to have a linting utility running to test your JavaScript code for valid syntax and formatting:

$ npm install grunt-contrib-jshint --save-dev

This will install the officially maintained Grunt plugin for JSHint and add it to the devDependencies object in your package.json file as shown in the following example:

{ 
  "name": "my-app", 
  "version": "1.0.0", 
  "author": "Philip Klauzinski", 
  "license": "MIT", 
  "devDependencies": { 
    "grunt": "^0.4.5", 
    "grunt-contrib-jshint": "^0.11.3" 
  } 
} 

JSHint is a popular tool for detecting errors and potential problems in your JavaScript code. The Grunt plugin itself will allow you to automate that process so that you can easily check your code as you develop.

Another invaluable Grunt plugin is grunt-contrib-watch. This plugin allows you to run a task which will automatically run other Grunt tasks when you add, delete, or edit files in your project that match a predefined set of rules.

$ npm install grunt-contrib-watch --save-dev

After installing the grunt-contrib-watch plugin, the devDependencies object in your package.json file should look like this:

  "devDependencies": { 
    "grunt": "^0.4.5", 
    "grunt-contrib-jshint": "^0.11.3", 
    "grunt-contrib-watch": "^0.6.1" 
  } 

Now that you have a couple of Grunt plugins installed, let's begin writing some tasks for them. In order to do that, you will first need to create a local configuration file for Grunt.

Configuring Grunt

Unlike NPM and Bower, Grunt does not provide an init command for initializing its configuration file. Instead, scaffolding tools can be used for this. Project scaffolding tools are designed to set up some basic directory structure and configuration files for a development project. Grunt maintains an official scaffolding tool called grunt-init, which is referenced on their website. The grunt-init tool must be installed separately from the grunt-cli global package and the local grunt package for any particular project. It is most useful if installed globally, so it can be used with any project.

$ npm install -g grunt-init

We will not go into further detail on grunt-init here, but if you would like to learn more, you can visit gruntjs.com/project-scaffolding.

The best way to learn about configuring Grunt is to write its configuration file by hand. The configuration for Grunt is maintained in a file called Gruntfile.js, referred to as a Gruntfile, located in the root directory of your project, along with package.json and bower.json. If you are not familiar with Node.js and its concept of modules and exports, the syntax for a Gruntfile may be a bit confusing at first. Since Node.js files run on the server and not in a browser, they do not interact in the same way that files loaded in a browser do, with respect to browser globals.

Understanding Node.js modules

In Node.js, a module is a JavaScript object defined within a file. The module name is the name of the file. For instance, if you want to declare a module named foo, you will create a file named foo.js. In order for the foo module to be accessible to another module, it must be exported. In its most basic form, a module looks something like the following example:

module.exports = { 
    // Object properties here 
};

Every module has a local exports variable that allows you to make the module accessible to others. In other words, the object module within a file refers to the current module itself, and the exports property of module makes that module available to any other module (or file).

Another way of defining a module is by exporting a function, which is of course a JavaScript object itself:

module.exports = function() { 
    // Code for the module here 
};

When you call for a Node.js module from within a file, it will first look for a core module, all of which are compiled into Node.js itself. If the name does not match a core module, it will then look for a directory named node_modules beginning from the current or root directory of your project. This directory is where all of your local NPM packages, including Grunt plugins, will be stored. If you performed the installs of grunt-contrib-jshint and grunt-contrib-watch from earlier, you will see that this directory now exists within your project.

Now that you understand a bit more about how Node.js modules work, let's create a Gruntfile.

Creating a Gruntfile

A Gruntfile uses the function form of module.exports as shown previously. This is referred to as a wrapper function. The grunt module itself is passed to the wrapper function. The grunt module will be available to your Gruntfile because you installed the grunt NPM package locally:

module.exports = function(grunt) { 
    // Grunt code here 
};

This example shows what your initial Gruntfile should look like. Now let's flesh it out some more. In order to configure Grunt and run tasks with it, you will need to access the grunt module that is passed in to your Gruntfile.

module.exports = function(grunt) { 
    'use strict'; 
    grunt.initConfig({ 
        pkg: grunt.file.readJSON('package.json') 
});
};

This basic format is what you will be working with the rest of the way. You can see here that the grunt.initConfig method is called and passed a single configuration object as a parameter. This configuration object is where all of your Grunt task code will go. The pkg property shown in this example, which is assigned the value of grunt.file.readJSON('package.json'), allows you to pass in information about your project directly from your package.json file. The use of this property will be shown in later examples.

Defining Grunt task configuration

Most Grunt tasks expect their configuration to be defined within a property of the same name as the task, which is the suffix of the package name. For example, jshint is the Grunt task name for the grunt-contrib-jshint package we previously installed:

module.exports = function(grunt) { 
    'use strict'; 
    grunt.initConfig({ 
        pkg: grunt.file.readJSON('package.json'), 
        jshint: { 
            options: { 
                curly: true, 
                eqeqeq: true, 
                eqnull: true, 
                browser: true, 
                newcap: false, 
                es3: true, 
                forin: true, 
                indent: 4, 
                unused: 'vars', 
                strict: true, 
                trailing: true, 
                quotmark: 'single', 
                latedef: true, 
                globals: { 
                    jQuery: true 
                } 
            }, 
            files: { 
                src: ['Gruntfile.js', 'js/src/*.js'] 
            } 
        } 
    }); 
}; 

Here you can see that the jshint property of the configuration object is defined and is assigned its own properties which apply to the jshint Grunt task itself. The options property defined within jshint holds the settings you wish to validate against when linting your JavaScript files. The files property defines a list of the files you wish to validate. For more information on the supported options for JSHint and what they mean, see jshint.com/docs/.

Let's now add an additional configuration for the grunt-contrib-watch plugin watch task below the jshint task configuration:

watch: { 
    jshint: { 
        files: ['js/src/*.js'], 
        tasks: ['jshint'] 
    } 
} 

Here we add an additional namespace of jshint underneath the watch task, which allows for other targets to be defined within the same configuration property and run separately if needs be. This is what is known as a multitask. Targets within a multitask can be named arbitrarily and will simply be run in the order which they are defined if the multitask is called alone. A target can be called directly as well, and doing so will ignore any of the other targets defined within the multitask's configuration:

$ grunt watch:jshint

This particular configuration for the target jshint tells the watch task that if any files matching js/src/*.js are changed, then to run the jshint task.

Now you have your first two Grunt task configurations defined within your Gruntfile, but in order to use them, we must load the Grunt tasks themselves.

Loading Grunt plugins

You have already installed the grunt-contrib-jshint plugin as a Node.js module, but in order to execute the jshint task, you must load the plugin within your Gruntfile. This is done after the grunt.initConfig call:

grunt.loadNpmTasks('grunt-contrib-jshint'); 

This is the same method call you will use to load all Grunt tasks within your Gruntfile, and any Grunt task will not be accessible without doing so. Let's do the same for grunt-contrib-watch:

grunt.loadNpmTasks('grunt-contrib-watch'); 

Your full Gruntfile should now look like this:

module.exports = function(grunt) { 
    'use strict'; 
    grunt.initConfig({ 
        pkg: grunt.file.readJSON('package.json'), 
        jshint: { 
            options: { 
                curly: true, 
                eqeqeq: true, 
                eqnull: true, 
                browser: true, 
                newcap: false, 
                es3: true, 
                forin: true, 
                indent: 4, 
                unused: 'vars', 
                strict: true, 
                trailing: true, 
                quotmark: 'single', 
                latedef: true, 
                globals: { 
                    jQuery: true 
                } 
            }, 
            files: { 
                src: ['Gruntfile.js', 'js/src/*.js'] 
            } 
        }, 
        watch: { 
            jshint: { 
                files: ['js/src/*.js'], 
                tasks: ['jshint'] 
            } 
        } 
    }); 
    grunt.loadNpmTasks('grunt-contrib-jshint'); 
    grunt.loadNpmTasks('grunt-contrib-watch'); 
}; 
Running the jshint Grunt task

Now that you have the plugin loaded, you can simply run grunt jshint from the command line to execute the task with its defined configuration. You should see the following output:

$ grunt jshint
Running "jshint:files" (jshint) task
>> 1 file lint free.
Done, without errors

This will run your JSHint linting options against the defined files, which as of now consist of only Gruntfile.js. If it looks like the example file shown and includes the call to grunt.loadNpmTasks('grunt-contrib-jshint'), then it should pass without errors.

Now let's create a new JavaScript file and intentionally include some code which will not pass the JSHint configuration so we can see how the errors are reported. First, create the js/src directory, which is defined in the files property of the jshint task:

$ mkdir -p js/src

Then create a file named app.js within this directory and place the following code in it:

var test = function() { 
    console.log('test'); 
}; 

Now run grunt jshint again from the command line. You should see the following output:

$ grunt jshint
Running "jshint:files" (jshint) task
   js/src/app.js
      2 |    console.log('test');
             ^ Missing "use strict" statement.
      1 |var test = function() {
             ^ 'test' is defined but never used.
>> 2 errors in 2 files
Warning: Task "jshint:files" failed. Use --force to continue.
Aborted due to warnings.

You will notice that two errors are reported for js/src/app.js based on the jshint task configuration options. Let's fix the errors by changing the code in app.js to the following:

var test = function() { 
    'use strict'; 
    console.log('test'); 
}; 
test(); 
 

Now if you run grunt jshint from the command line again, it will report that the files are lint free and have no errors:

$ grunt jshint
Running "jshint:files" (jshint) task
>> 2 files lint free.
Done, without errors.
Running the watch Grunt task

As mentioned earlier, when the watch task is run it will wait for changes that match the file patterns defined in its configuration and run any corresponding tasks. In this case, we configured it to run jshint when any files matching js/src/*.js are changed. Since we defined a target within the watch task called jshint, the watch task can be run in two different ways:

$ grunt watch

Running grunt watch will watch for changes matching all target configurations defined within the watch task:

$ grunt watch:jshint

Running grunt watch:jshint with the colon (:) syntax runs watch for just the file patterns matching that target configuration. In our case, only one target is defined, so let's just run grunt watch and see what happens in the console:

$ grunt watch
Running "watch" task
Waiting...

You will see that the task now shows a status of Waiting... on the command line. This indicates that the task is running to watch for matching changes within its configuration, and if any of those changes are made, it will automatically run the corresponding tasks. In our example with the jshint task, it will allow your code to automatically be linted every time you make changes to your JavaScript files and save them. If a JSHint error occurs, the console will alert you and display the error.

Let's test this by opening a text editor and changing js/src/app.js again:

var test = function() { 
    console.log('test'); 
}; 
test() 

Here, we removed the opening use strict statement and the semicolon after the call to test() at the end of the file. This should raise two JSHint errors:

>> File "js/src/app.js" changed.
Running "jshint:files" (jshint) task

   js/src/app.js
      2 |    console.log('test');
             ^ Missing "use strict" statement.
      4 |test()
               ^ Missing semicolon.
>> 2 errors in 2 files
Warning: Task "jshint:files" failed. Use --force to continue.
Aborted due to warnings.

Now let's correct these errors and return the file to the way it was before:

var test = function() { 
    'use strict'; 
    console.log('test'); 
}; 
test(); 

Press Ctrl + C from the command line at any time to abort the watch task, or any Grunt task, while it is running.

Defining the default Grunt task

Grunt allows you to define a default task which will run when you simply type grunt on the command line with no parameters. To do this, you will use the grunt.registerTask() method:

grunt.registerTask('default', ['jshint', 'watch:jshint']);

This example sets the default Grunt task to run the defined jshint task first and then the watch:jshint multitask target. You can see that the tasks passed to the default task are in an array, so you can set the default task for Grunt to run any number of tasks by simply typing grunt on the command line:

$ grunt
Running "jshint:files" (jshint) task
>> 2 files lint free.
Running "watch:jshint" (watch) task
Waiting...

From looking at the output, you can see that the jshint task was run once initially, and then watch:jshint was run to wait for additional changes to the configured file patterns.

Defining custom tasks

Grunt allows you to define your own custom tasks, in the same way that you defined the default task. In this way, you can actually write your own custom tasks directly within the Gruntfile, or you can load them from an external file, just as you did with grunt-contrib-jshint and grunt-contrib-watch.

Alias tasks

One way of defining a custom task is to simply call one or more existing tasks in the order you want them to be run:

grunt.registerTask('my-task', 'My custom task.', ['jshint']); 
 

In this example, we have simply defined a task named my-task to serve as a proxy for jshint. The second parameter is an optional description of the task, which must be a string. The third parameter, which passes an array, including only jshint in this example, must always be an array. You can also forgo the second parameter with the description and pass in your array of tasks there instead. This way of defining a task is known as an alias task.

Basic tasks

When you define custom Grunt tasks, you are not limited to only calling other tasks that exist within your configuration, but you can write JavaScript code to be called directly as a function. This is called a basic task:

grunt.registerTask('my-task', 'My custom task.', function() { 
    grunt.log.writeln('This is my custom task.'); 
}); 

In this example, we simply write a string to the command line output for the task. The output should look like this:

$ grunt my-task
Running "my-task" task
This is my custom task.

Let's expand upon this example and pass in some arguments to our basic task function, as well as access the arguments from within the function:

grunt.registerTask('my-task', 'My custom task.', function(arg1, arg2) { 
    grunt.log.writeln(this.name + ' output...'); 
    grunt.log.writeln('arg1: ' + arg1 + ', arg2: ' + arg2); 
}); 

You will notice that there is a property available to the basic task, this.name, which is simply a reference to the name of the task. In order to call a basic task from the command line and pass arguments in, you will use a colon after the task name to define each argument in succession. This syntax is just like the syntax for running a multitask target; however, in this case you are passing in arbitrary arguments:

$ grunt my-task:1:2

Running this will output the following:

Running "my-task:1:2" (my-task) task
my-task output...
arg1: 1, arg2: 2

If you do not pass in the arguments to a task that is expecting them, it will simply resolve them as undefined:

$ grunt my-task
Running "my-task" task
my-task output...
arg1: undefined, arg2: undefined
Done, without errors.

You can also call other tasks from within a custom task:

grunt.registerTask('foo', 'My custom task.', function() { 
    grunt.log.writeln('Now calling the jshint and watch tasks...'); 
    grunt.task.run('jshint', 'watch'); 
}); 

In this example, we have created a task, foo, that defines a custom function that calls the existing jshint and watch tasks:

$ grunt foo
Running "foo" task
Now calling the jshint and watch tasks...
Running "jshint:files" (jshint) task
>> 2 files lint free.
Running "watch" task
Waiting...

For more information on creating custom tasks with Grunt, see gruntjs.com/creating-tasks.

These examples of tasks only scratch the surface of what is capable with Grunt, but you should be able to glean from them the power of it and begin to think about what might be possible with Grunt tasks when building your own SPA.