Chapter 5

Project Management and Automation

Software development is not easy. There are many challenges that require the full concentration of a developer. Repetitive monotone tasks can lead to careless mistakes and slow down the development process. Therefore, it makes sense to utilize automation in order to be able to produce fast and, most importantly, stable results. Many tools have been developed recently to complete tasks and solve problems in the world of JavaScript development. Among others, these tools manage client-side dependencies, automate repetitive tasks, generate source code matrices and provide automatic test environments. In this chapter, we introduce the individual tools and look at Project Yeoman, a project that provides a pre-defined workflow for web development.

Node.js: The Runtime Environment for Tools

We will use Node.js again as the runtime environment for the tools introduced in this chapter. We have used Node.js for our BookMonkey application. The platform also provided the runtime environment for the http-server module and Karma.

Node.js is a platform based on Google’s V8 JavaScript engine. It executes Java source code on the server side. One of its primary strengths is that it has a non-blocking event-driven IO model, allowing you to write lightweight and efficient applications. The Node.js installer can be downloaded from its official website here:

http://nodejs.org

Package Management with npm

A lot of very useful packages and tools have been developed in the realm of frontend web development and you can now write JavaScript applications on the Node.js platform. For managing these packages, the platform provides npm (Node.js package manager). Npm is a command line tool for searching, managing, installing and uninstalling packages. Currently, over 50,000 packages are available from its official register at https://npmjs.org.

A Node.js module contains a data file named package.json. This file contains, among other things, information on package names, the version number and dependencies on other packages. Since an application can be defined as a Node.js module, you can define dependencies on other packages by using package.json. As shown in Listing 5.1, you can generate this data file with the npm init command. The script will ask a series of questions about the configuration.

Listing 5.1: Creating a package.json file

[app] $ npm init

This utility will walk you through
creating a package.json file.
It only covers the most common items,
and tries to guess sane defaults.

See 'npm help json' for definitive
documentation on these fields
and exactly what they do.

Use 'npm install <pkg> --save'
afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
name: (app) bookMonkey
version: (0.0.0) 1.0.0
description: Example Application
git repository:
keywords: javascript angularjs
author: Robin Boehm, Philipp Tarasiewicz
license: (BSD-2-Clause) MIT
About to write [...]

{
  "name": "bookmonkey",
  "version": "1.0.0",
  "description": "Example Application",
  "main": "index.js",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1"
  },
  "author": "Robin Boehm, Philipp Tarasiewicz",
  "license": "MIT",
  "dependencies": {},
  "devDependencies": {},
  "keywords": [
    "javascript",
    "angularjs"
  ]
}

To add dependencies to your project, use the npm install {packagename} command. Using the additional parameter --save, you can automatically enter your application dependencies in package.json. With the help of --save-dev, you can do the same for development dependencies. The following example installs the Node.js package http-server and allows it to be entered in your package.json under devDependencies.

npm install http-server --save-dev

The resulting package.json file is shown in Listing 5.2. You can use npm to install the other tools in this chapter.

Listing 5.2: Automatic entry of the installed package

{
  "name": "bookmonkey",
  "version": "1.0.0",

  [...]

  "dependencies": {},
  "devDependencies": {
    "http-server" : "0.6.1"
  },

  [...]
}

Node.js uses the Common JS module system to let you embed existing modules when creating a new module easily.

Summary

  • Node.js is a platform that executes JavaScript source code on the server side. Installers for all platforms are available from http://nodejs.org.
  • One of the primary strengths of this platform is that it has a non-blocking event-driven IO model.
  • With npm, Node.js brings with it a package manager that simplifies package management. You have access to over 50, 000 packages in its official register.
  • You have learned how to install Node.js modules and dependencies.
  • All modern tools that aim to simplify the web development process use Node.js as the runtime environment.

Managing Frontend Dependencies with Bower

Bower (http://bower.io) is an open source tool for managing dependencies in web frontend applications. Project specific dependencies are easily defined in a JSON file named bower.json. With the help of an easy command in the command line, all defined packages can be loaded into the respective project. On the other hand, it is also possible to activate Bower using a JavaScript API within a Node.js process.

Packages that have not been configured are stored in a directory called bower_components. A package can contain any content and provides web components. With Bower, projects such as Twitter Bootstrap, jQuery as well as AngularJS can be easily embedded into your own project. Packages can be referenced in one of many different ways, including

  • An officially registered package in the Bower register
  • A Git repository
  • A short form that directly refers to GitHub, e.g. angular/angular.js
  • A URL to a file in .zip or .tar.gz format

Since Bower uses Node.js as its runtime environment, you install Bower using npm. You should install Bower as a global package so that you can use it from anywhere in the system. You can achieve this with the following command.

npm install -g bower

Defining the bower.json File

You can define Bower-specific information of your package in a bower.json file. In addition to the definition of dependencies, it contains information about project names, the version number and a description. You don’t have to create this file yourself; you can use the bower init command to generate the file interactively. As shown in Listing 5.3, you can call this command in the command line. It will then ask a series of questions. Your answers will be used to create an initial bower.json file. After you answer all the questions, you will get an output in the form of generated configuration. If you reply yes to the last question (“Looks good?”), a bower.json file will be created in the current directory with the corresponding content.

Listing 5.3: Generating a bower.json file with bower init

[my-angular-project] $ bower init
[?] name: my-angular-project
[?] version: 1.0.0
[?] description: Sample project for the bower chapter
[?] main file: path/to/app.js
[?] keywords: angularjs, bower, book, example
[?] authors: Robin Böhm, Philipp Tarasiewicz
[?] license: MIT
[?] homepage: http://www.angularjs.de
[?] set currently installed components as dependencies? Yes
[?] add commonly ignored files to ignore list? Yes
[?] would you like to mark this package as private
    which prevents it from being accidentally
    published to the registry? No

{
  name: 'my-angular-project',
  version: '1.0.0',
  description: 'Sample project for the bower chapter',
  main: 'path/to/app.js',
  keywords: [
    'angularjs',
    'bower',
    'book',
    'example'
  ],
  authors: [
    'Robin Böhm',
    'Philipp Tarasiewicz'
  ],
  license: 'MIT',
  homepage: 'http://www.angularjs.de',
  ignore: [
    '**/.*',
    'node_modules',
    'bower_components',
    'test',
    'tests'
  ]
}

[?] Looks good? Yes

One of the most important properties in your configuration is the main property. With this property, you can define a subset of the delivered package, which can be embedded into other projects. This is necessary, because there often exist different versions—e.g. a development and a minimized version—in the same package. In such a case, an automatic processing would be impossible or very difficult. In addition, a lot of files in the project repository that Bower should not download can be listed in the ignore property. These include the definitions of the build process of the project.

Defining Dependencies

The actual primary task of this file is to define a project’s external dependencies using the dependencies property. In addition, dependencies can be listed in the devDependencies property to indicate that they are only needed during development and are not part of the final artifact. Using the bower install {packagename} command, you can install individual packages. You can install AngularJS with bower install angular. As shown in Listing 5.4, you can add an installed package automatically into an existing bower.json configuration file with the --save or –save-dev parameter.

Listing 5.4: Installing external dependencies

bower install angular --save
bower install angular-route --save
bower install angular-mocks --save-dev

After executing this command, the resulting bower.json of our my-angular-project project will have dependencies on the angular and angular-route packages. In addition, the angular-mocks package will be provided as development dependency (dev-dependencies). With the search argument, you can do a full text search against the Bower register via the command line. Alternatively, you can search the register by going to its website at http://sindresorhus.com/bower-components. To delete a package from this project, you can use bower uninstall angular. A section of our current bower.json file is printed in Listing 5.5.

Listing 5.5: The structure of a bower.json file

{
  "name": "my-angular-project",
  "version": "1.0.0",
  "dependencies": {
    "angular": "1.2.3",
    "angula-router": "1.2.3",
  },
  "devDependencies": {
    "angular-mocks": "1.2.3"
  }

  [...]

}

Including Installed Packages

If the project directory contains your configuration, you can download all dependencies defined in the configuration with bower install. The bower_components directory is generally not imported when using a version management tool such as Git and the directory is listed in Git’s .gitignore file. After exporting a project, you can reload all the necessary packages in the correct version by executing the bower install command.

After you have installed the dependencies, you can implement the following as you desire. Listing 5.6 shows an index.html example that uses the <script> tag to include dependencies.

Listing 5.6: Including packages that have been installed by Bower

<!DOCTYPE html>
<html ng-app>
<head>
    <title>AngularJS.DE Bower Example</title>
</head>

<body>

<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-route/angular-route.js">
</script>
</body>
</html>

Configuring Bower

You can configure Bower with a .bowerrc file. You can do this specifically for a project by creating the file in your project directory and adding configuration to the file. In addition, you can create a global configuration for a user in the user’s home directory. Project-specific configuration overwrites any global parameters. Within this file, you can set the following configuration parameters:

  • directory. The install location for components
  • endpoint. The definition of your own bower register
  • json. The name of Bower’s metadata file in the project
  • searchpath. The list of additional Bower registers
  • shorthand_resolver. The template for shorthands.

Listing 5.7 shows a sample .bowerrc file.

Listing 5.7: The structure of a .bowerrc file

{
  "directory": "bower_components",
  "endpoint": "https://bower.mycompany.com",
  "json": "myOwnBower.json",
  "searchpath": [
    "https://bower.herokuapp.com"
  ],
  "shorthand_resolver":
    "git://example.com/organization/package.git"
}

To register your own package in the official Bower register, you need a Git repository that is publicly available. This repository must contain a valid bower.json file in the main directory and the file must contain the required metadata for the package. In this case, you have to make sure that you describe the different versions of your package as Git tags and they comply with semantic versioning (http://semver.org). After meeting the requirements, you can register the package in the public register with the command in Listing 5.8.

Listing 5.8: Registering a package in the Bower register

bower register <my-package-name> <git-endpoint>

Currently it is not possible to delete a registered package from the public register independently. At present, this is being managed inelegantly via an entry in the Bower project’s issue tracker in Github. However, it is likely that there will be a new mechanism for resolving this issue in the near future.

Creating A Private Register

It is not always desirable to publish a package in an external and publicly available repository. Fortunately, you can use the endpoint configuration parameter to register a package in a private register. To this end, you have to create your own register. This can be done with relatively little effort. The register application is a very simple Ruby program that can be downloaded from Github at this URL:

https://github.com/bower/registry

Other public and alternative registers can be defined using the searchpath configuration parameter. You can list multiple registers in an array (See Listing 5.9).

Listing 5.9: .bowerrc with its own endpoint

{
  "endpoint": "https://bower.mycompany.com",
  "searchpath": [
    "https://bower.herokuapp.com",
    "https://bower.anothercompany.com"
  ],
}

Potential Problems with Proxy Servers

If you need to operate Bower behind a proxy server, you can reroute network communication via the proxy server by using the HTTP_PROXY and HTTPS_PROXY environment variables. A problem that often appears in a company network is the company-wide firewall that blocks port 22 for SSH communication. Since many repositories work with SSH in the Bower register, problems arise relatively quickly. In order to get around these problems, you can configure Git so that all SSH communications will be automatically redirected over http or https. You can use this Git command:

git config url."http://".insteadOf git://

Summary

  • Bower is a tool for managing front-end dependencies. Bower is based on Node.js.
  • You can define project-specific dependencies in a JSON file named bower.json. You can have this file generated interactively using the bower init command. In addition, you can install new dependencies with the command bower install {packagename} in the command line. To uninstall a package, use the command bower uninstall {packagename}.
  • Bower differentiates between project dependencies and development dependencies.
  • You can define configuration in a .bowerrc file and thus use your own company register.
  • Problems related to proxy servers may occur. You can resolve these by configuring Git.

Automating Tasks with Grunt

Grunt (http://gruntjs.com) is an open source tool to simplify and, in many cases, completely eliminate tiring completion of repetitive tasks. The tool automates the following tasks.

  • compilation tasks
  • minimizing
  • executing tests
  • executing tools for quality assurance, such as JSLint (http://www.jslint.com)

To use Grunt, you need Node.js version 0.8 or later and the npm package manager. A typical project construction with Grunt contains a package.json file, which defines a Node.js module. You use this file to define the dependency to the Node.js modules that your project requires. Since Grunt is also a Node.js module, you can install it in your project with the following command and specify it as a development dependency in your package.json file:

npm install grunt --save-dev

Making Grunt Useful with the Command Line

A Node.js module named grunt-cli is available to make using Grunt easier. This module allows you to use Grunt commands from the command line. The script executed searches the respective local project directory for an installed Grunt package and delegates the calls from the command line to this location. This delegation is important, because you need different versions from Grunt for different projects. A global installation of Grunt is thus not recommended. You can install Grunt using this command. (Sometimes administrator rights are required!)

npm install -g grunt-cli

After the installation, Grunt is available system-wide. If you run the Grunt command now in a fresh project, you will get an error message as shown in Listing 5.10. If you see this, do no panic. You have just forgotten to install the Node.js module Grunt in your current project.

Listing 5.10: A potential error message when using Grunt-CLI

[app] $ grunt
grunt-cli: The grunt command line interface. (v0.1.9)

Fatal error: Unable to find local grunt.

If you're seeing this message, either a Gruntfile
wasn't found or grunt hasn't been installed
locally to your project. For more information about
installing and configuring grunt,
please see the Getting Started guide:

http://gruntjs.com/getting-started

Configuring Tasks

To configure the tasks that Grunt should execute for you, use the script in the Gruntfile.js file. Grunt expects you to export a function as a CommonJS module within this script. In this function, you can configure all the tasks that you want Grunt to automatically execute. When doing so, Grunt executes the exported function and delegates it to the so-called Grunt API, which you can access. At this point, you should make sure that the JavaScript source code is contained in the Gruntfile.js file and no configuration in JSON format such as in package.json is included.

Listing 5.11: The basic structure of a Gruntfile.js file

module.exports = function(grunt) {
  // do some stuff
};

Registering Tasks

You can register JavaScript functions with Grunt. Such functions in Grunt’s world are called tasks. You register a task using the grunt.registerTask function. This function expects three arguments. The first argument provides a name you can use to retrieve the function. The second argument contains a description that will be displayed when you type grunt -help. The third argument specifies a function that should be executed when this task is called. Listing 5.12 shows the definition of a very simple task.

Listing 5.12: Defining your own function within Grunt

module.exports = function(grunt) {

  // A very basic default task.
  grunt.registerTask('log', 'Log some stuff.', function() {
    grunt.log.write(’Logging some stuff...’);
  });

};

You can execute this task with the command line command grunt. You need to specify the name of the task that should be executed as a parameter to the grunt command. The result of the execution is shown in Listing 5.13.

Listing 5.13: Executing your own function using the command line

[app] $ grunt log
Running "log" task
Logging some stuff...

Done, without errors.

You can also register multiple functions with the grunt.registerTask function. However, the functions should have a unique name, because they would otherwise overwrite each other. In the latter case, you could only access the most recently defined function.

You can pass arguments to a task. Two arguments should be separated with a colon. Listing 5.14 shows you how to pass arguments to a task.

Listing 5.14: Executing your own commands using the command line

[app (master)] $ grunt count
Running "count" task
Arguments: 0

Done, without errors.
[app (master)] $ grunt count:1:2:3
Running "count:1:2:3" (count) task
Arguments: 3

Done, without errors.

};

You can also use the grunt.registerTask function to link multiple tasks with a name. In this case, you need to provide two arguments. As before, the first argument specifies the name of the task. As a second argument, you specify an array containing the names of the desired tasks. The order in which you define the tasks in the array determines the order of execution. Listing 5.15 shows a Gruntfile.js file that defines three tasks. The last task is called all and is associated with the previous two tasks.

Listing 5.15: Defining your own functions in Grunt

module.exports = function(grunt) {

  // A very basic log task
  grunt.registerTask('log', 'Log some stuff.', function() {
    grunt.log.writeln('Logging some stuff...'),
  });

  // A very basic task with arguments
  grunt.registerTask('count', 'Count given Arguments.', function()
  {
    grunt.log.writeln('Arguments: '+arguments.length);
  });

  // An aggregation task
  grunt.registerTask('all', ['log', 'count:a:b']);

};

In addition, you can pass as a parameter to a task a JavaScript object that contains project-specific configuration using the grunt.initConfig() function. Listing 5.16 shows an example of configuring the log task. You can access these configuration settings using the grunt.config method, passing a JavaScript expression. In this case, it is grunt.config('log.prefix').

Listing 5.16: Configuring Grunt tasks

module.exports = function(grunt) {

  grunt.initConfig({
          log: {
            prefix: "LOG"
          }
        });

  // A very basic log task with prefix
  grunt.registerTask('log', 'Log some stuff.', function() {
      grunt.log.write('[' + grunt.config('log.prefix') + ']'),
      grunt.log.writeln('Logging some stuff...'),
  });
};

Defining Multi-Tasks

In addition to registering simple tasks, you can also register multi-tasks with the grunt.registerMultiTask() function. A multi-task is a task that can receive a number of known configuration values called targets. If no target is defined when executing the task, all targets registered by this task will be executed.

In a multi-task function, you have access to the names of the targets being executed. You can access a target using this.target. Using this.data, you have access to the enclosed data. You can enclose the definition of these targets in the form of a configuration object by using the API function grunt.initConfig(). Listing 5.17 presents a log task that displays the number of enclosed arguments, the current target and the enclosed data.

Listing 5.17: Defining Multi-Tasks in Grunt

module.exports = function(grunt) {

  grunt.initConfig({
      log: {
        string: 'Logging some stuff...',
        array: [1, 2, 3],
        object: { x: 5 }
      }
  });

  // Register Multi Task
  grunt.registerMultiTask('log', 'Log stuff.', function() {
    grunt.log.writeln('args: '
                 + arguments.length
                 + '
'
                 + this.target
                 + ': '
                 + this.data);
  });

};

The example shows configuration for the registered multi-task as well as the multi-task itself. For this, we use the familiar grunt.initConfig() method. You can define targets for a specific multi-task by using the name of the configuration object’s property. The name of this target corresponds to the name of the selected object property, which in our case is log. A target definition can be any kind of JavaScript data structure. If you follow the naming convention, the data structure defined by grunt.initConfig() is connected to the property this.data in the multi-task function. Thus, you do not have to export this data by using grunt.config, because you have direct access to the data structure. In Listing 5.18, you can see the retrieval of the multi-task log with various targets that also can be specified using colons. It is also possible to enclose more arguments by using colons after the target selection.

Listing 5.18: Executing the log multi-task with different targets

[app] $ grunt log
Running "log:string" (log) task
args: 0
string: Logging some stuff...

Running "log:array" (log) task
args: 0
array: 1,2,3

Running "log:object" (log) task
args: 0
object: [object Object]

Done, without errors.
[app] $ grunt log:array
Running "log:array" (log) task
args: 0
array: 1,2,3

Done, without errors.
[app] $ grunt log:array:hello:world
Running "log:array:hello:world" (log) task
args: 2
array: 1,2,3

Done, without errors.

Normally, a multi-task expects a JavaScript object that defines a specific configuration which you can then use within your multi-task function. It is thus possible to define generic tasks.

Outsourcing Tasks

With Grunt you can outsource tasks to different files. All you have to do is load the tasks in your Grunt file when needed. The command that facilitates this is called grunt.loadTasks. With this command, you can provide a path to a task directory that will be searched for the JavaScript files.

Let us transfer our multi-task function log to a directory named log. The name of the file is not important in this case, because Grunt will search the whole directory. The only requirement is that the file name has .js extension. The content of this file is given in Listing 5.19.

Listing 5.19: Transferring a log task to an external file

module.exports = function(grunt) {

  // Register Multi Task
  grunt.registerMultiTask('log', 'Log stuff.', function() {
    grunt.log.writeln('args: '
                 + arguments.length
                 + '
'
                 + this.target
                 + ': '
                 + this.data);
  });
};

Listing 5.20 shows a Grunt file that contains the statement grunt.loadTasks('log'). With this, you can search the log directory and load the definitions of the multi-task. By using grunt.initConfig, you can define different tasks as usual. With this mechanism, you can build generic reusable modules. In the world of Grunt, these special modules are called plugins.

Listing 5.20: Loading a task from an external file

module.exports = function(grunt) {

  grunt.loadTasks('log'),

  grunt.initConfig({
      log: {
        string: 'Logging some stuff...',
        array: [1, 2, 3],
        object: { x: 5 }
      }
  });

};

Loading Grunt Plugins

You can use npm to register Grunt plugins and make them publicly available. You and other developers can also install these published plugins and add them to a package.json file. This will allow you to define a Node.js module and its dependencies. Just as you have installed Grunt as a module, you can now install Grunt plugins by using npm.

Listing 5.21 shows a sample package.json file after the installation of two Grunt plugins contrib-copy and contrib-uglify. Both are listed under devDependencies.

Listing 5.21: The structure of a package.json file with its own Grunt plugins as dependencies

{
  [...]

  "devDependencies": {
    "grunt": "0.4.1",
    "grunt-contrib-copy": "0.4.1",
    "grunt-contrib-uglify": "0.2.7"
  }

  [...]
}

There are a lot of Grunt plugins available over npm. You can read the descriptions of these plugins through the “plugins” tab on the official project website at http://gruntjs.com/plugins.

You can load Grunt plugins that have been installed by using npm with the help of the function grunt.loadNpmTasks. As you can see in Listing 5.22, you just have to add the name of the installed plugin as an argument.

Listing 5.22: Loading Node.js Grunt Plugins

module.exports = function(grunt) {

  [...]

  grunt.loadNpmTasks('grunt-contrib-uglify'),
  grunt.loadNpmTasks('grunt-contrib-clean'),

  [...]
};

Just like with your own tasks, the configuration of this task occurs via a JavaScript object that you provide to the grunt.initConfig function. We will not discuss the configuration details of these two plugins here. However, you can see a small example configuration in Listing 5.23.

Listing 5.23: An example configuration of Grunt plugins

module.exports = function(grunt) {

  grunt.initConfig({
    uglify: {
      build: {
        src: 'src/app.js',
        dest: 'build/app.min.js'
      },

    clean: ['build/**/*'],
  }
});

  grunt.registerTask('build', ['clean' , 'uglify']);
};

At the end of this configuration, you have created a new task named build with the grunt.registerTask function. This will execute the tasks clean and uglify in the correct order. If you call Grunt with parameter build in the command line, you will get the result that is printed in Listing 5.24.

Listing 5.24: Executing an example configuration

[app] $ grunt build
Running "clean:dist" (clean) task

Running "uglify:build" (uglify) task
File "build/app.min.js" created.

Done, without errors.

Using Templates

You can work with a small template mechanism in Grunt. In the context of Grunt, a template is a character string that begins with <% and ends with %>. In between, you can use template variables that correspond to object access expressions to access the object properties. The latter is very dependent on the notation that you use in JavaScript. Let us expand our example and define a pkg property in our configuration object. This property allocates an object that holds some data from our application. Listing 5.25 shows the expanded configuration of our uglify task through templates.

Listing 5.25: Using templates

module.exports = function(grunt) {

  grunt.initConfig({
    pkg: {
            name: 'app',
            version: '0.0.1'
         }

    uglify: {
      build: {
        src: 'src/<%= pkg.name %>-<%= pkg.version %>.js',
        dest: 'build/<%= pkg.name %>-<%= pkg.version %>.min.js'
      },

     clean: ['build/**/*'],
    }
  });

  grunt.registerTask('build', ['clean' , 'uglify']);
};

To reduce redundancy, you can access the package.json file in Grunt and allocate the content of its property. This is shown in Listing 5.26. It is a good thing that Grunt offers a grunt.file.readJSON function to help you with this task.

Listing 5.26: Importing a file as a configuration object

module.exports = function(grunt) {

  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),

    uglify: {
      build: {
        src: 'src/<%= pkg.name %>-<%= pkg.version %>.js',
        dest: 'build/<%= pkg.name %>-<%= pkg.version %>.min.js'
      },

    clean: ['build/**/*'],
   }
  });

  grunt.registerTask('build', ['clean' , 'uglify']);
};

As a result, you can define project specific information centrally in one place, namely in the package.json file, much easier than when all pieces of information were scattered around the project.

Appropriate Packages for Development

Next, we would like to discuss a couple of public Grunt plugins that are almost always essential in AngularJS development. In practice, there are a few Grunt plugins that can be inserted into almost every program.

load-grunt-tasks

Right after loading the first package, you quickly come to a point at which repeating lines, in addition to the loadNpmTasks statement, make configuration hard to understand. In order to get around this problem, you can use a Node.js module called load-grunt-tasks (https://npmjs.org/package/load-grunt-tasks). An example is given in Listing 5.27.

Listing 5.27: A sample configuration for load-grunt-tasks

module.exports = function(grunt) {
  require('load-grunt-tasks')(grunt);
};

This plugin uses the convention that all Node.js packages that have been specially created for Grunt have the prefix grunt-. Thus, this plugin can export your node_modules directory and load all modules that match the regular expression grunt-*. You can use npm to install this plugin using this command.

npm install load-grunt-tasks --save-dev

If you need this functionality in a more generic version, you can use the alternative module matchdep (https://npmjs.org/package/matchdep).

ng-min

The package grunt-ngmin (https://npmjs.org/package/grunt-ngmin) is a pre-minifier for AngularJS applications. You need this package because the implementation of dependency injection uses the parameter names of your functions in AngularJS in order to cancel the dependencies at this point. Since this is substituted when minimizing, the mechanism does not work after this process.

Listing 5.28: Example for the effects of ng-min

// before ng-min
angular.module('bmApp').controller('MyCtrl',
function ($scope, $http) {

    [...]

});

// after ng-min
angular.module('bmApp').controller('MyCtrl',
['$scope', '$http', function ($scope, $http) {

    [...]

}]);

In order to allow minification in AngularJS projects, there is a convention for function annotations within AngularJS. It states that the function should be given as the last element in an array and that all parameters before the dependencies are passed as strings. Since the strings are not affected by the minification, cancellation of the dependencies will work again after this point. As usual, the configuration must be added to your Gruntfile.js file. The parameters src and dest used here represent the source and target files. The exact directory of all parameters with explanation can be removed from the project documentation.

Listing 5.29: Example configuration for ng-min

module.exports = function(grunt) {
  grunt.initConfig({

  [...]

    ngmin: {
      files: {
        src: 'build/**/*.js',
        dest: 'build/'
      }
    },

  [...]

  });

  grunt.registerTask('build', [
    'clean',
    'copy:build',
    'ngmin',
    'uglify'
  ]);
};

You can use npm to install this plugin using this command.

npm install grunt-ngmin --save-dev

concat and uglify

With the help of the uglify package (https://github.com/gruntjs/grunt-contrib-uglify), it is possible to reduce the file size of an application. This can be achieved with smart substitutions of variable names. However, this will cause readability to suffer for human readers. It is unsurprisingly due to this fact that this project got its name. It is worthwhile to perform minification to make loading time as short as possible for the user.

Listing 5.30: Sample configuration for concat and uglify

module.exports = function(grunt) {

  grunt.initConfig({

    [...]


    concat: {
        src: ['src/module.js' , 'src/book.js', [...] ],
        dest: 'build/bookmonkey.js',
    },

    uglify: {
        files: {
          'build/bookmonkey.min.js':
              [
                build/bookmonkey.js'
              ]
        }
    }

    [...]

  });
};

In the configuration shown in Listing 5.30, the bookmonkey.js file is converted to a bookmonkey.min.js file. This task is usually done last in the building process. In this example, it comes after we concatenate our JavaScript files using Concat (https://github.com/gruntjs/grunt-contrib-concat) to a bookmonkey.js file.

You can use npm to install both plugins using these commands.

npm install grunt-contrib-concat --save-dev
npm install grunt-contrib-uglify --save-dev

Summary

  • Grunt is an open source tool based on Node.js for automating the processing of repetitive tasks. You can use npm to install it.
  • You can define your own tasks that can take parameters. In addition, there is a concept of multi-tasks, which allows you to use different targets from a configuration.
  • There are a lot of public plugins that you can also install with npm.
  • You can configure these tasks by using a file named Gruntfile.js.
  • You have looked at a few plugins that are essential for AngularJS as well as their configurations. These include the ng-min plugin.

Executing Tests Automatically with Karma

Karma (http://karma-runner.github.io) is a runtime environment for testing JavaScript applications automatically. It is an open source project created for AngularJS and was developed by same team that developed AngularJS. Karma’s main objective is to make testing an internet application as easy as possible. The defined test specifications are executed in real devices to minimize discrepancies in the end product. Karma starts its own web server and sends a page to any device that connects to it. A device to be tested must be able to handle HTTP and interpret JavaScript. The page contains an iframe used as the actual runtime environment. Higher-level data is guided by this. Every connected browser receives the commands directly from the running Karma process via a web socket connection or a corresponding fallback transport mechanism such as long polling.

Since Karma is executed on the Node.js platform like most of the previously discussed tools, you can use npm to install it with this command:

npm install -g karma

You can now start Karma directly by using the karma start command, as shown in Listing 5.31.

Listing 5.31: Starting Karma

[bookMonkey] $ karma start
INFO [karma]: Karma v0.10.4 server
started at http://localhost:9876/

Since you have not written any configuration, this phase is fairly uneventful. As such, let us look at the next section to find out how you can configure Karma.

Configuration

Karma configuration is done through a JSON file. If no explicit configuration file provided was found, Karma will search for a file named karma.conf.js in the directory in which you have executed it. If you want to use another file as a configuration file, you have to provide the path to this file as a second parameter to karma.

karma start myproject.unit.js

When executing the karma command, you can overwrite a parameter in the configuration file just for this session by using passing arguments to karma. This can be very useful in the case that you wish to overwrite individual values from the configuration in a CI (continuous integration) environment without creating a new file. More information about Karma’s configuration file can be found on its official website at this address.

http://karma-runner.github.io/0.10/config/configuration-file.html

Listing 5.32 shows an excerpt from a Karma configuration file.

Listing 5.32: An example of Karma configuration

'use strict';

module.exports = function(config) {
    config.set({
        basePath: 'src/main/webapp/app/',

        frameworks: ['jasmine'],

        files: [
            'components/angular/angular.js',
            'components/angular-mocks/angular-mocks.js',

            'scripts/module.js',
            'scripts/**/*.js',
        ],

        reporters: ['progress'],
        port: 9876,
        logLevel: config.LOG_WARN,
        singleRun: true,
        autoWatch: false,
        browsers: ['Chrome']
    });
};

The Most Important Parameters

At this point, we briefly explain the most important parameters for the configuration. A complete and always current list can be found online in the official Karma documentation at this URL:

http://karma-runner.github.io/0.10/config/configuration-file.html

basePath

To save significant typing chores, you can set the basePath property as the base path for other paths, including paths that are defined by the files and exclude properties.

files and exclude

You can use the files and exclude properties to specify a list of files that should be included or excluded when running Karma.

frameworks

You use the frameworks property to embed different test frameworks in your environment. You can view the list of available framework adapters in the npm register here:

https://npmjs.org/browse/keyword/karma-adapter

To use this test framework, you first have to use npm to install it.

npm install karma-<test-framework> --save-dev

In addition, you have to specify the framework in your configuration. Listing 5.33 shows how you can specify the popular test framework Jasmine.

Listing 5.33: Embedding Jasmine as a test framework

'use strict';

module.exports = function(config) {
    config.set({
    [...]

        frameworks: ['jasmine'],

    [...]
    });
};

browsers

You can use the browsers property to select a variety of browsers that Karma should automatically start to execute your tests. Karma offers native support for the Chrome and PhantomJS browsers. However, you can use npm to install packages for Firefox, Opera, Internet Explorer and Safari.

Generating an Initial Karma Configuration File

You do not have to create the initial Karma configuration file yourself. Karma comes with a generator that can create an initial configuration file when you run Karma with the karma init command. By answering some questions about your desired testing environment, the tool generates your specific karma.conf.js file and stores it in the directory in which you executed the command. For our BookMonkey project, the dialogue would look like the on in Listing 5.34.

Listing 5.34: Generating a Karma configuration file via the command line

[bookMonkey] $ karma init

Which testing framework do you want to use?
Press tab to list possible options.
Enter to move to the next question.
> jasmine

Do you want to use Require.js?
This will add Require.js plugin.
Press tab to list possible options.
Enter to move to the next question.
> no

Do you want to capture a browser automatically?
Press tab to list possible options.
Enter empty string to move to the next question.
> Chrome
>

What is the location of your source and test files?
You can use glob patterns, eg. "js/*.js"
or "test/**/*Spec.js".
Enter empty string to move to the next question.
> scripts/**/*.js
Should any of the files included
by the previous patterns be excluded?
You can use glob patterns, eg. "**/*.swp".
Enter empty string to move to the next question.
>

Do you want Karma to watch all the files
and run the tests on change?
Press tab to list possible options.
> no

Config file generated at
"/Users/AngularJS/bookMonkey/karma.conf.js".

You can now extend or modify the resulting file. The format and the essential parameters are already set so you can immediately run your test cases. Sources of errors such as forgetting some required parameters and deviating from a supported format are thus reduced to a minimum.

Using Karma Extensions

You can extend Karma. There are already a lot of plugins available. You can search for and install them using the npm register at this URL.

https://npmjs.org/browse/keyword/karma-plugin

To achieve this, use the already familiar npm command.

npm install karma-<plugin name> --save-dev

In this case, the following convention applies: Karma automatically includes all npm modules with the prefix karma-. If you need a plugin that does not follow this convention, you can extend your Karma configuration with the plugins property.

Listing 5.35: Embeding plugins in Karma configuration

plugins: [
  'karma-*',
  'plugin-example'
]

Executing Tests Directly in WebStorm

Currently WebStorm IDE is one of the most popular JavaScript development environments. We will discuss it in Chapter 6, “Debugging.” For this reason, we will discuss how to install Karma in this environment. We start with a system that already has Node.js and Karma installed. Since WebStorm 7, you can use the official Karma plugin that makes WebStorm integration very easy. As shown in Figure 5.1, you can select a test type under the “Karma” node in the Run/Debug Configurations dialog.

Figure 5.1: Starting Karma in WebStorm

You have to enter some path information in the dialog. First, you have to provide the install directory of Node.js. Subsequently, you have to type in the install directory of Karma, with which you want to execute the tests. In the case that WebStorm recognizes your Node.js and Karma installations, the two fields will be filled with the right paths automatically. If you want to execute your tests with another version of Node.js or Karma, you can configure it here. It should come as no surprise that you also have to provide the karma.conf.js file for the testing environment. If you have entered these parameters correctly, you can start the execution via “run”. Execution in debug mode is also possible. Figure 5.2 shows the result of our test execution.

Figure 5.2: Result of a test execution

Test Frameworks

As already hinted at in the sample configuration, you can use testing frameworks other than Karma. Currently there are plugins for Jasmine (http://pivotal.github.io/jasmine/), Mocha (http://visionmedia.github.io/mocha) and Qunit (http://qunitjs.com). You can also create your own plugins for other test frameworks and embed them. Jasmine and Mocha are examples of behavior-driven development (BDD) frameworks. As shown in Listings 5.36 and 5.37, their syntax is also very similar.

Listing 5.36: A Jasmine test specification

describe("A suite", function() {
  it("contains spec with an expectation", function() {
    expect(true).toBe(true);
  });
});

Listing 5.37: A Mocha test specification

describe('A suite', function(){
  it('contains spec with an expectation', function(){
    ok(true);
  })
})

Listing 5.38: A QUnit test specification

test( "a basic test example", function() {
  var value = "hello";
  equal(value, "hello", "We expect value to be hello");
});

Regardless of which test framework you decide to use, you can use npm to install it and embed it in your Karma configuration by using the frameworks property.

npm install karma-jasmine --save-dev

As an example, Listing 5.39 shows how you can embed Jasmine.

Listing 5.39: Embedding the Jasmine framework

frameworks: ['jasmine'],

Continuous Integration

The theme continuous integration (CI) is also covered by Karma. Currently, there is an integration for the CI systems Jenkins, Travis and Teamcity. In this section, we primarily discuss using Jenkins and Maven together, because it is very popular configuration in corporate structures.

At this point, we assume that Node.js and Karma have already been installed. In your pom.xml file (the project file of Maven), you can now initiate the execution of the Karma process by using the Maven Antrun plugin (http://maven.apache.org/plugins/maven-antrun-plugin). Listing 5.40 shows a summary from the pom.xml file.

Listing 5.40: Starting Karma from Maven with the Maven Antrun plugin

<plugin>
    <artifactId>maven-antrun-plugin</artifactId>
    <version>1.7</version>
    <executions>
        <execution>
            <phase>generate-sources</phase>
            <configuration>
                <target name="building">
                    <exec
                        executable="cmd"
                        dir="${project.basedir}"
                        osfamily="windows">
                        <arg line="/c karma start "/>
                    </exec>
                    <exec
                        executable="karma"
                        dir="${project.basedir}"
                        osfamily="unix">
                        <arg line="start"/>
                    </exec>
                </target>
            </configuration>
            <goals>
                <goal>run</goal>
            </goals>
        </execution>
    </executions>
</plugin>

You must now redirect the results of the test back to Jenkins. To this end, you can configure Karma so that it produces a test report in JUnit XML format after the execution of the test. For this, you first have to install the karma-junit-reporter plugin. As always, you can use npm to do this.

npm install karma-junit-reporter --save-dev

After you have successfully run the installation, you can activate and configure the plugin in your Karma configuration (See Listing 5.41).

Listing 5.41: Generating a JUnit report with Karma

reporters = [ 'junit'];
junitReporter = {
  outputFile: 'test-results.xml'
};

Finally, the xUnit plugin from Jenkins (https://wiki.jenkins-ci.org/display/JENKINS/xUnit+Plugin) allows you to embed the generated test results by using the JUnit report file. To this end, you have to install this plugin and select the option “publish JUnit test result report” under “post build action.” You can now select your report file with the test results, which will be imported and added after each run. In addition, you can define threshold values that determine when a complete build can be regarded as failed.

Summary

  • Karma is a tool that is based on the Node.js platform. It allows you to automatically execute tests.
  • Karma starts its own web server, which the test devices can connect to. Every browser on every device that accesses this server on the network is a potential area for a test execution. In this way, the execution can also be easily done on mobile devices.
  • Configuration occurs via a simple file in JSON format. This file is usually called karma.conf.js.
  • Karma can be integrated into the WebStorm IDE.
  • You can embed different test frameworks that you can use for your test cases. We have introduced the most popular JavaScript frameworks.
  • Karma offers a mechanisms that allows easy integration in a CI system. You can initiate the system to export the results in an XML file. This file acts as an interface with other systems.
  • By using the example, we have introduced the integration in an existing system with Maven and Jenkins.

Yeoman: A Defined Workflow

What is Yeoman?

Yeoman (http://yeoman.io) is a workflow that has been proven to work. Used with Bower and Grunt, it helps you focus completely on the actual application development. You can let the tools complete the accompanying additional tasks such as determining an appropriate directory structure, managing dependencies or selecting the right syntax for application components. Yeoman also offers a solution for tasks which affect development infrastructure. The solution especially includes the provision of a web server and the creation of a build process.

Installing Yeoman

Since Yeoman is a Node.js module, you can use npm to install it.

npm install -g yo

The installation contains the tools Bower and Grunt as well as a generator called Yo. After a successful installation, you will be able to use the commands yo, bower and grunt on the command line. The official installation instructions are also available online at http://yeoman.io/gettingstarted.html. To actually use the Yo generator, you first need a generator for the framework you decided to use. These generators are also Node.js modules and can also be installed using npm. For instance, to install the generator for AngularJS, you would use this command.

npm install -g generator-angular

There are many other generators you can install. One way to find a generator is by searching for it with npm. Another way is to look at the generator list at the official Yeoman project at GitHub (https://github.com/yeoman). To start the corresponding search inquiry with npm, use this command:

npm search generator-%name%

Generating Application Modules

In every new project, you start by creating the underlying structures for the project. In the case of a web application, this includes primarily the creation of specific files such as an index.html file and often directories for images, CSS files and JavaScript files (the “assets”). That being said, there is no easy way to create these underlying structures. Quite the opposite: as a developer, you are often spoiled for choice. The final selection is often influenced by personal preference. Some developers introduce an in-between directory with assets. Others name this directory res or refrain completely from doing so. To be honest with you, these repetitive tasks are arduous, error-prone and often lead to many different project structures. Why can’t we automate and homogenize these tasks?

Inspired by Rails, the project Yo (http://yeoman.io) was created. Yo deals with the completion of these tasks for client-side web development. Yo is a command line tool for generating the underlying structures for new projects automatically and consistently. The definitions for this tool are provided in generators. Yo also comes with generators for many currently relevant frameworks, including a generator for AngularJS (https://github.com/yeoman/generator-angular). The AngularJS generator is actually one of the first generators developed in this project. In addition to the generator for AngularJS, there are generators for the following frameworks:

  • Ember.js
  • Backbone.js
  • jQuerry
  • Jasmine
  • Mocha
  • Twitter Flight

The use of a generator has the positive side effect that the project structures are always unified and thus make it easier for new developers to adapt to already existing projects.

Yo for AngularJS Projects

The generator for AngularJS was one of the first generators created in the Yeoman project. The generator generates typical AngularJS application modules and automatically enters these settings in an index.html file. In addition, a test suite is provided right away for every created application module.

We discuss the different sub-generators in this chapter. We start by presenting in Listing 5.42 the underlying directory structure created by the AngularJS generator. On the first level, you have two directories, app and test, for the application and the tests, respectively. Within the app directory, you have directories for scripts, style sheets and template files. In the scripts directory, the generator creates directories for different components of a typical AngularJS application (controllers, services, directives, etc.).

Listing 5.42: The directory structure generated with the AngularJS Generator

app/
  scripts/
    controllers/
    decorators/
    directives/
    filters/
    services/
  styles/
  views/
test/
  spec/
    controllers/
    decorators/
    directives/
    filters/
    services/

In the test directory, there is subdirectory named spec that contains the specifications for the different modules of your application. The spec directory exists because there can be another parallel directory called e2e, in which E2E tests are defined. This type of test is not easily generated because they vary wildly. Therefore, you have to create them yourself.

You can provide a tool with the desired generator by passing a parameter. For example, to use AngularJS, use the parameter angular.

yo angular

If you do not include any further specification, Yo will generate the underlying project structure for an AngularJS application. You can select one of sub-generators on the list to generate application modules by separating it with a colon. If you want to add a new controller, you can execute Yo with the parameter angular:controller. You can define the name of the controller to-be generated using an extra parameter. The available sub-generators are listed in Listing 5.43.

Listing 5.43: Summary of valid AngularJS generator commands

yo angular [applicationName]
yo angular:controller controllerName
yo angular:decorator serviceName
yo angular:directive directiveName
yo angular:filter filterName
yo angular:route routeName
yo angular:service serviceName
yo angular:view viewName

You should pay special attention to the naming convention of components when using a generator. The name provided as a parameter is transformed from the Underscore.js JavaScript library into the CamelCase convention with the help of the camelize() function (https://github.com/epeli/underscore.string). This means that an AngularJS controller generated using the following command will be named MyControllerCtrl.

yo angular:controller my-controller

The generated file will have the name my-controller.js and can be found under app/scripts/controllers/. This can be confusing in some situations. Therefore, you should be familiar with this convention.

In addition to creating the components, the generator also makes sure that the generated file is automatically entered at the correct point in the index.html file. Since AngularJS has been developed with testability in mind, it is not surprising that the generator can also generate the required test files for your components.

Figure 5.3: Overview of the modules of a Yeoman generator

Yo Angular

You can create a new AngularJS application with the yo command. Internally, it uses the sub-generator angular:app. angular:app is defined as the default sub-generator if no sub-generator is specified explicitly. You can determine the name of the application with an optional parameter. If you invoke the generator without a name, the name of the current directory is used as the application name. In both cases, the string “App” is added to the application name so that you will have an AngularJS application called UserManagerApp with the parameter UserManager. When this sub-generator is executed, the initial application it generates can already run. The following initialization tasks are thus completed.

  • The underlying directory structure
  • An index.html file with an already embedded AngularJS
  • The first route including a template and a controller
  • General files such as a 404 template or a .htaccess file
  • Test environment for unit and integration tests
  • Basic configuration for dependency management

In addition, you can optionally include the following modules:

  • Twitter Bootstrap
  • Angular Resource
  • Angular Cookies
  • Angular Sanitize

Additionally, Yo asks a relevant question when generating, which you can answer with an entry request. Finally, the script starts the script with the command npm install, which also installs the dependencies for the build process. These are defined in the package.json file. We discuss this aspect in more detail in the next section.

yo angular:controller

With the controller sub-generator, you can create a new controller. The name is provided as usual with the help of the first parameter. It is important to note that the name in the template is modified by the Underscore.js function classify() and is suffixed with the string “Ctrl”. Thus, the file name myController and the internal component name MyControllerCtrl result from the parameter my-controller. Additionally, the generated file is entered in index.html and the corresponding test file is generated. You can understand the context by looking at the template’s source code in Listing 5.44.

Listing 5.44: Template for yo angular:controller

angular.module('<%= _.camelize(appname) %>App')
.controller('<%= _.classify(name) %>Ctrl',
  ['$scope', function ($scope) {
    // Your logic here
  }]);

yo angular:directive

You can use the directive sub-generator to create a directive. The template in Listing 5.45 is used for generating a directive.

Listing 5.45: Template for yo angular:directive

angular.module('<%= _.camelize(appname) %>App')
  .directive('<%= _.camelize(name) %>', [function () {
    return {
      template: '<div></div>',
      restrict: 'E',
      link: function postLink(scope, element, attrs) {
        element.text('<%= _.camelize(name) %> directive'),
      }
    };
  }]);

The generated file is stored in the app/scripts/directives/ directory and is added automatically in the index.html file.

yo angular:filter

You create a filter with the command yo angular:filter. The generated filter will be stored in the app/scripts/filters/ directory. The template for this can be found in Listing 5.46.

Listing 5.46: Template for yo angular:filter

angular.module('<%= _.camelize(appname) %>App')
  .filter('<%= _.camelize(name) %>', [function () {
    return function (input) {
      return '<%= _.camelize(name) %> filter: ' + input;
    };
  }]);

yo angular:route

You can use the route sub-generator to add a new route to your application. The script produces the files for the controller and the template automatically and adds the route automatically to the route provider’s configuration. The controller is automatically added in index.html as usual.

Listing 5.47: Extending route configuration using yo angular:route

$routeProvider
.when('/', {
  templateUrl: 'views/main.html',
  controller: 'MainCtrl'
})

// this route has been generated by yo angular:route
.when('/myRoute', {
  templateUrl: 'views/myRoute.html',
  controller: 'MyRouteCtrl'
})}

.otherwise({
  redirectTo: '/'
});

yo angular:service

You can generate a new service using the service sub-generator. What makes this service unique is that it gives you the opportunity to choose a service type. You can select one of the following types:

  • constant
  • factory
  • provider
  • service
  • value

If no type is provided, Yo generally selects factory as the default. You can see the corresponding template in Listing 5.48.

Listing 5.48: Template for yo angular:service

angular.module('<%= _.camelize(appname) %>App')
  .factory('<%= _.camelize(name) %>', [function() {

    var meaningOfLife = 42;

    // Public API here
    return {
      someMethod: function() {
        return meaningOfLife;
      }
    };
  }]);

yo angular:decorator

The Decorator pattern is a very useful way of expanding or modifying components. It allows you to belatedly modify an existing service. This could be an extension or a replacement of a particular function. With the decorator sub-generator, you can create a new decorator for the service that has been provided as a parameter. The decorator is generated in the app/scripts/decorators directory and its file name generally consists of the service name followed by the string “Decorator”. Since multiple decorators can decorate the same service, it is possible that a file with the same name already exists. In this case, the sub-generator asks for an alternative name with an entry request. You can find the corresponding template in Listing 5.49.

Listing 5.49: Template for yo angular:decorator

angular.module('<%= _.camelize(appname) %>App')
.config(['$provide', function ($provide) {
  $provide.decorator('<%= _.camelize(name) %>',
    function ($delegate) {
      // decorate the $delegate
      return $delegate;
    });
  }]
);

Generated Grunt Configuration

Yo also generates a basic configuration for Grunt with the command yo angular. In this configuration, three primary tasks are defined. We will describe these tasks now. You can find a more detailed description of Grunt and especially the construction of the Gruntfile.js file in the section about Grunt in this chapter.

grunt server

When you start Grunt with the parameter server, a minimal web server is started to serve your web application. Additionally, Grunt opens your web application in the default browser. Grunt then connects to the default browser. The Grunt process monitors your project for changes continuously and causes a complete reloading of the application in the browser when a change occurs. Thus, arduous manual reloading is no longer necessary.

grunt test

With the command grunt test, you can execute a unit test in your application. Since the AngularJS generator embeds the test environment Karma in your project, you can also activate a continuous test execution in the karma.conf.js file with the parameter singleRun. When you use this function, your tests will be executed every time there is a change in the project directory. We have discussed Karma and its valid parameters in the configuration in detail in the section on Karma.

grunt

When you start Grunt without parameters, you can initiate your application's build process. In this case, the following tasks will be executed:

  • minification of JavaScript code
  • concatenation of JavaScript code
  • minification of CSS code
  • concatenation of CSS code
  • embedding of AngularJS via CDN
  • compression of JavaScript code (ugly)

The artifact created by this process can normally be found in the dist directory.

Summary

  • Yeoman is a defined workflow for creating web applications.
  • With the Yo tool, you can create new application components fairly easily with the help of generators.
  • There are generators for all relevant JavaScript frameworks, especially for AngularJS components.
  • Installation can be done through npm.
  • These tools are automatically installed and configured for our projects by the dependencies to Bower, Grunt and Karma.
  • The generator template for AngularJS gives you a complete configuration of Grunt tasks for the development, testing and delivery of your application.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.219.130