Discussing package.json and gruntfile.js

Grunt projects have two important files that are required in order to define and configure Grunt: package.json and gruntfile.js. The package.json is a JSON file that is normally located at the root of a project using NPM to manage dependencies, and gruntfile.js is a JavaScript file that also exists in the project root directory. The combination of these two files provides the information about the project and configuration of the packages used in the project that are needed in order to create an automated build system. We will look at each in more detail before beginning to configure sample_project.

The package.json file

As mentioned, package.json is a JSON file that defines data about our project, also known as project metadata. In this file, as we have seen throughout our plugin installation process, we have registered our project's plugins in the devDependencies section.

The metadata that package.json defines, in addition to the project dependencies, is a set of properties that describes your project. Name and version are both required fields:

  • Name: This is the name of the application. It must be shorter than 214 characters, must not start with a dot or underscore, and must not contain any non-URL-safe characters.
  • Version: This is the version of your application. It must be able to be parsed by node-semver (https://github.com/npm/node-semver). The combination of the name and version is required because, together, they will create a unique application identifier.

In sample_project, there are some additional fields added during our Angular Seed project creation, which include private, description, repository, and licence:

  • Private: This instructs npm to never publish your project and prevents the possibility of accidentally publishing a private repository
  • Description: This is a brief excerpt that describes the project
  • Repository: This is the location of the project code
  • Licence: This defines how the application can be used by others

We have been watching the devDependencies section of the package.json develop as we have worked through the package installation process. The devDependencies is really just a simple JSON object whose properties define the package dependencies of our project. Notice each entry in devDependencies as it exists at this time:

"devDependencies": {
    "bower": "^1.3.1",
    "grunt": "^0.4.5",
    "grunt-contrib-jshint": "^0.11.3",
    "grunt-contrib-less": "^1.0.1",
    "grunt-contrib-uglify": "^0.9.2",
    "grunt-contrib-watch": "^0.6.1",
    "http-server": "^0.6.1",
    "jasmine-core": "^2.3.4",
    "karma": "~0.12",
    "karma-chrome-launcher": "^0.1.12",
    "karma-firefox-launcher": "^0.1.6",
    "karma-jasmine": "^0.3.5",
    "karma-junit-reporter": "^0.2.2",
    "protractor": "^2.1.0",
    "shelljs": "^0.2.6" 
}

Each line defined in the devDependencies object is a map and key-value pair that matches up an application name with a version. Both the name and version are strings and the version may include a descriptor. If the version has no descriptor, then the version string must be matched exactly. If the > version descriptor is used, then the version must match any version greater than the value. Other descriptors include the following:

  • >=: The version must be greater than or equal to the value
  • <: The version must be less than the value
  • <=: The version must be less than or equal to the value
  • ~: The version must be the approximate equivalent to the value
  • ^: The version must be compatible with the value
  • 1.2.x: The version may match any minor version, for example, 1.2.8
  • *: The version may match any versions

Note that the package.json file also contains a scripts dictionary object. This object defines scripts that should be run as part of the dependency and when the script should be run. Let's look at our scripts object as an example:

"scripts": {
    "postinstall": "bower install",
    "prestart": "npm install",
    "start": "http-server -a localhost -p 8000 -c-1",
    "pretest": "npm install",
    "test": "karma start karma.conf.js",
    "test-single-run": "karma start karma.conf.js  --single-run",
    "preupdate-webdriver": "npm install",
    "update-webdriver": "webdriver-manager update",
    "preprotractor": "npm run update-webdriver",
    "protractor": "protractor e2e-tests/protractor.conf.js"... truncated for brevaty
}

Notice that each object maps a command to a script. For example, the postinstall command is run after the package is installed, prestart is run by the npm start command, start is also run by the npm start command, and pretest, test, and test-single-run are all run by the npm test command. More information on the scripts dictionary can be found in the npm documentation at https://docs.npmjs.com/misc/scripts.

The gruntfile.js file

Up to this point, we have been putting off gruntfile.js as something that we will get into later. Finally, it is time to discuss gruntfile in detail. As mentioned previously, gruntfile, for the purposes of sample_project, is a valid JavaScript file. Note that it may also be written in CoffeeScript. Gruntfile should be located at the root of the project and part of your project source code.

A gruntfile will include a wrapper function that will contain all of the grunt-related code within. The wrapper function syntax looks as follows:

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

The module.exports encapsulates the code within to create a single module of code. The exports object becomes the result returned when a require call is made, and module.exports is set equal to a function that takes a grunt object as its argument. The wrapper function will contain the following parts:

  • Project and task configuration
  • Loading Grunt plugins and tasks
  • Custom tasks

In the wrapper method, there will be a call to the grunt.initConfig method. Most of the grunt plugins will use initConfig for configuration. In sample_project, one of the first items to be configured is getting the project's metadata from package.json so that we can use it in our automated build. We will get the name, version, and current date so that we can create a header banner that can be used in our build files. Here is a code example that we can walk through and discuss:

module.exports = function(grunt) {
  grunt.initConfig({
          pkg: grunt.file.readJSON('package.json'),
        banner: '/*! <%= pkg.name %>' + ' <%= grunt.template.today("yyyy-mm-dd") %> */
',
        uglify: {
          options: {
                banner: '<%= banner %>'
          },
            ...{
            }
        }
    });
};

The module.exports object contains all of our grunt-related code. The first section of our configuration is the grunt.initConfig method. In initConfig, we created an object property named pkg and assigned it to the value of the metadata contained in package.json. We then created a banner property that creates a comment string. This will be used as a banner comment in the minified JavaScript created by contrib-uglify. The banner property string uses template string variables identified by <% %>, which will be replaced with content from package.json. The <%= pkg.name %> template string variable gets the value of the name property from package.json and replaces the variable with this content. When the template string variable is replaced, it will read angular-seed, which is the name of the sample_project project. Now, we get to take a sneak peak of the plugin configuration in order to show how we can use the banner in the configuration of contrib-uglify. In the following abbreviated contrib-uglify configuration, we simply need to add the banner to the configuration options:

uglify: {
          options: {
                banner: '<%= banner %>'
          },
            ...{
            }
        }

The uglify method takes an optional banner configuration so that a comment header may be included in the minified output files with which it creates. Lets have a the banner example:

banner: '/*! <%= pkg.name %>' + ' <%= grunt.template.today("yyyy-mm-dd") %> */
,

The following comment will be created and added to the head of the generated minified JavaScript. The result is a template string variable replacement and concatenation of the name and date, obtained from the grunt .today method:

/*! angular-seed 2015-09-28 */

Moving beyond task configuration and into loading Grunt plugins, we need to understand the prerequisites of loading a plugin. All of the work that we have done thus far in installing plugins has created our plugin mappings in devDependencies. The plugin will be installed in the node-modules directory using npm install. Once the plugin exists in node-modules and is mapped in devDependencies, then it may be loaded in gruntfile.js. Loading, or enabling, a plugin is very simple and only takes a single line of code. The following is the code needed in order to load contrib-uglify:

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

Loading plugins takes place below the grunt.initConfig method, following its closing brace. For instance, loading contrib-uglify would look something like this:

module.exports = function(grunt) {
  grunt.initConfig({
          pkg: grunt.file.readJSON('package.json'),
        ...
    });
    grunt.loadNpmTasks('grunt-contrib-uglify'),
};

In Grunt, you can define a task to run as the default task. In fact, you can define multiple tasks to run as the default task. This is known as creating a custom task. When you define a custom default task, it will always run if nothing else is defined. Recall that each task had its own command that could be used to run the task from the command line manually. The contrib-uglify command can be run manually by issuing the grunt uglify command. Notice that we use the grunt command and then specify the task to be run. If we were to run the grunt command by itself, with a default task defined, it would run automatically. We can create a default command that controls the order with which our plugins run and only have to issue the grunt command to invoke our build process, if we so desire.

Let's take a look at how we create a custom task. First, lets look at the scenario that we just outlined and create a custom task that will automatically run contrib-uglify as the default task. The syntax to create a custom task is also simple. Here is our default task example:

grunt.registerTask('default', ['uglify']);

Here, we simply register the task, define it as default, and provide an array of tasks to be run. In this case, our array only has a single item. Did you catch that? As the registerTask method takes an array of tasks as an argument, we can define multiple tasks to run as our default task. What if we wanted to lint our JavaScript, then minify the linted scripts, and then compile our LESS into CSS, all in one build command? This would be very simple using the registerTask method:

grunt.registerTask('default',['jshint','uglify','less']);

Custom tasks aren't just limited to grunt plugins. Custom JavaScript can be used in a custom task. Javascript methods can be included in the registerTask method as follows:

grunt.registerTask('default', 'myTaskName', function() {
    //some javascript here
});

It is also possible to load external JavaScript files using the grunt.loadTasks method. The syntax to use this method is as follows:

grunt.task.loadTasks(pathToFile)

You can also use the following method:

grunt.loadTasks(pathToFile)

Grunt provides a sample gruntfile that can be used to get started quickly. While the sample may not be exactly what is needed, it is a good way to get started quickly with a configuration file that can be easily modified to meet project needs. To get started, we can get the sample gruntfile.js from the source:

module.exports = function(grunt) {

  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    concat: {
      options: {
        separator: ';'
      },
      dist: {
        src: ['src/**/*.js'],
        dest: 'dist/<%= pkg.name %>.js'
      }
    },
    uglify: {
      options: {
        banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */
'
      },
      dist: {
        files: {
          'dist/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>']
        }
      }
    },
    qunit: {
      files: ['test/**/*.html']
    },
    jshint: {
      files: ['Gruntfile.js', 'src/**/*.js', 'test/**/*.js'],
      options: {
        // options here to override JSHint defaults
        globals: {
          jQuery: true,
          console: true,
          module: true,
          document: true
        }
      }
    },
    watch: {
      files: ['<%= jshint.files %>'],
      tasks: ['jshint', 'qunit']
    }
  });

  grunt.loadNpmTasks('grunt-contrib-uglify'),
  grunt.loadNpmTasks('grunt-contrib-jshint'),
  grunt.loadNpmTasks('grunt-contrib-qunit'),
  grunt.loadNpmTasks('grunt-contrib-watch'),
  grunt.loadNpmTasks('grunt-contrib-concat'),

  grunt.registerTask('test', ['jshint', 'qunit']);

  grunt.registerTask('default', ['jshint', 'qunit', 'concat', 'uglify']);

};

Hopefully, the amount of content in the file isn't of concern; each section has already been discussed. Looking at the sample gruntfile.js, let's break it down section by section. Notice that everything in gruntfile.js is enclosed in the wrapper. At the top of the document, we have our grunt.initConfig method where all of our plugin configuration takes place.

The contents of package.json are imported to the pkg property so that we can get at the values defined in package.json, such as name, description, and version.

This configuration uses grunt-contrib-concat. The purpose of contrib-concat is to concatenate multiple files into a single file. It is important to be sure to use the correct separator when concatenating JavasSript files, so the options configuration defines a semicolon as the separator option. The dist options defines the source files to be concatenated and the destination files where the concatenated output will be written. Then, we have a closing brace and comma, which leads us to our next plugin configuration.

Next, contrib-uglify is configured to use the optional banner so that it will automatically include header comments in the minified output file. The dist option defines the filenames for source and destination and also uses template string variables for dynamic file naming within the files configuration:

files: {
    'dist/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>']
}

The files parameter defines the path to source, such as dist/angular-seed.min.js, and concatenates it with the file that was produced from the concat plugin (concat.dist.dest).

The sample gruntfile configures the contrib-qunit task that simply runs the unit test files. It takes a files parameter to define the path to the test runner; the asterisks are path wildcards:

files: ['test_runner/**/*.html']

Following qunit, we find the contrib-jshint configuration. The files parameter defines the files that will be linted:

files: ['gruntfile.js', 'src/**/*.js', 'test/**/*.js']

Notice that gruntfile.js is in the list of files to lint. This is very good practice and will help ensure that no JavaScript errors are introduced into the gruntfile itself. In the preceding example code, the files to be linted include gruntfile.js, all .js files under the src directory, and all .js files under the test directory.

The last plugin to configure in the sample gruntfile is the watch plugin. The contrib-watch plugin responds to changes in files defined by you and runs additional tasks upon being triggered by the changed file events. In this, we define the files to watch and the tasks to run if a watched file changes:

files: ['<%= jshint.files %>'],
tasks: ['jshint', 'qunit']

In this case, the files that are defined by the contrib-jshint plugin files parameter will be watched and the contrib-jshint and contrib-qunit tasks will be run if any of these watched files change.

Now that all of the configuration is complete for the sample gruntfile plugins, the next step is to load all of the plugins that we will need. This is done in the lines using the grunt.loadNpmTasks method. This is very straightforward and self-explanatory:

grunt.loadNpmTasks('grunt-contrib-uglify'),
grunt.loadNpmTasks('grunt-contrib-jshint'),
grunt.loadNpmTasks('grunt-contrib-qunit'),
grunt.loadNpmTasks('grunt-contrib-watch'),
grunt.loadNpmTasks('grunt-contrib-concat'),

The final step in the sample gruntfile is to define the custom tasks. The first custom task is set up to run the unit tests. The task is named 'test' and will run the jshint and qunit tasks using the grunt test command:

grunt.registerTask('test', ['jshint', 'qunit']);

Finally, the second custom task defines the default task for the gruntfile. This is the task that will define the actual automated build process. In the default task, the name is defined as default and the tasks that will be run are contrib-jshint, contrib-qunit, contrib-concat, and contrib-uglify. It is important to note that these tasks will be run in the order that they are added to the array of tasks argument to the registerTask method:

grunt.registerTask('default', ['jshint', 'qunit', 'concat', 'uglify']); 

The default task can be run using the grunt command or custom task name in the grunt.default command.

..................Content has been hidden....................

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