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.
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:
In sample_project, there are some additional fields added during our Angular Seed project creation, which include private, description, repository, and licence:
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 value1.2.x
: The version may match any minor version, for example, 1.2.8*
: The version may match any versionsNote 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.
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:
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.
3.131.133.159