





















































In this article by Jurie-Jan Botha, author of the book Grunt Cookbook, has covered the following recipes:
(For more resources related to this topic, see here.)
Once our web application is built and its stability ensured, we can start preparing it for deployment to its intended market. This will mainly involve the optimization of the assets that make up the application. Optimization in this context mostly refers to compression of one kind or another, some of which might lead to performance increases too.
The focus on compression is primarily due to the fact that the smaller the asset, the faster it can be transferred from where it is hosted to a user's web browser. This leads to a much better user experience, and can sometimes be essential to the functioning of an application.
In this recipe, we make use of the contrib-htmlmin (0.3.0) plugin to decrease the size of some HTML documents by minifying them.
In this example, we'll work with the a basic project structure.
The following steps take us through creating a sample HTML document and configuring a task that minifies it:
<html> <head> <title>Test Page</title> </head> <body> <!-- This is a comment! --> <h1>This is a test page.</h1> </body> </html>
htmlmin: { dist: { src: 'src/index.html', dest: 'dist/index.html', options: { removeComments: true, collapseWhitespace: true } } }
The removeComments and collapseWhitespace options are used as examples here, as using the default htmlmin task will have no effect. Other minification options can be found at the following URL:
https://github.com/kangax/html-minifier#options-quick-reference
Running "htmlmin:dist" (htmlmin) task Minified dist/index.html 147 B ? 92 B
<html> <head> <title>Test Page</title> </head> <body> <h1>This is a test page.</h1> </body> </html>
In this recipe, we'll make use of the contrib-cssmin (0.10.0) plugin to decrease the size of some CSS documents by minifying them.
In this example, we'll work with a basic project structure.
The following steps take us through creating a sample CSS document and configuring a task that minifies it.
body { /* Average body style */ background-color: #ffffff; color: #000000; /*! Black (Special) */ }
cssmin: { dist: { src: 'src/style.css', dest: 'dist/style.min.css' } }
Running "cssmin:dist" (cssmin) task
File dist/style.css created: 55 B ? 38 B
body{background-color:#fff;color:#000;/*! Black (Special) */}
The cssmin task provides us with several useful options that can be used in conjunction with its basic compression feature. We'll look at prefixing a banner, removing special comments, and reporting gzipped results.
In the case that we'd like to automatically include some information about the compressed result in the resulting CSS file, we can do so in a banner. A banner can be prepended to the result by supplying the desired banner content to the banner option, as shown in the following example:
cssmin: { dist: { src: 'src/style.css', dest: 'dist/style.min.css', options: { banner: '/* Minified version of style.css */' } } }
Comments that should not be removed by the minification process are called special comments and can be indicated using the "/*! comment */" markers. By default, the cssmin task will leave all special comments untouched, but we can alter this behavior by making use of the keepSpecialComments option.
The keepSpecialComments option can be set to either the *, 1, or 0 value. The * value is the default and indicates that all special comments should be kept, 1 indicates that only the first comment that is found should be kept, and 0 indicates that none of them should be kept. The following configuration will ensure that all comments are removed from our minified result:
cssmin: { dist: { src: 'src/style.css', dest: 'dist/style.min.css', options: { keepSpecialComments: 0 } } }
Reporting is useful to see exactly how well the cssmin task has compressed our CSS files. By default, the size of the targeted file and minified result will be displayed, but if we'd also like to see the gzipped size of the result, we can set the report option to gzip, as shown in the following example:
cssmin: { dist: { src: 'src/main.css', dest: 'dist/main.css', options: { report: 'gzip' } } }
In this recipe, we'll make use of the contrib-imagemin (0.9.4) plugin to decrease the size of images by compressing them as much as possible without compromising on their quality. This plugin also provides a plugin framework of its own, which is discussed at the end of this recipe.
In this example, we'll work with the basic project structure.
The following steps take us through configuring a task that will compress an image for our project.
imagemin: { dist: { src: 'src/image.jpg', dest: 'dist/image.jpg' } }
Running "imagemin:dist" (imagemin) task Minified 1 image (saved 13.36 kB)
The imagemin task provides us with several options that allow us to tweak its optimization features. We'll look at how to adjust the PNG compression level, disable the progressive JPEG generation, disable the interlaced GIF generation, specify SVGO plugins to be used, and use the imagemin plugin framework.
The compression of a PNG image can be increased by running the compression algorithm on it multiple times. By default, the compression algorithm is run 16 times. This number can be changed by providing a number from 0 to 7 to the optimizationLevel option. The 0 value means that the compression is effectively disabled and 7 indicates that the algorithm should run 240 times. In the following configuration we set the compression level to its maximum:
imagemin: { dist: { src: 'src/image.png', dest: 'dist/image.png', options: { optimizationLevel: 7 } } }
Progressive JPEGs are compressed in multiple passes, which allows a low-quality version of them to quickly become visible and increase in quality as the rest of the image is received. This is especially helpful when displaying images over a slower connection.
By default, the imagemin plugin will generate JPEG images in the progressive format, but this behavior can be disabled by setting the progressive option to false, as shown in the following example:
imagemin: { dist: { src: 'src/image.jpg', dest: 'dist/image.jpg', options: { progressive: false } } }
An interlaced GIF is the equivalent of a progressive JPEG in that it allows the contained image to be displayed at a lower resolution before it has been fully downloaded, and increases in quality as the rest of the image is received.
By default, the imagemin plugin will generate GIF images in the interlaced format, but this behavior can be disabled by setting the interlaced option to false, as shown in the following example:
imagemin: { dist: { src: 'src/image.gif', dest: 'dist/image.gif', options: { interlaced: false } } }
When optimizing SVG images, the SVGO library is used by default. This allows us to specify the use of various plugins provided by the SVGO library that each performs a specific function on the targeted files.
Refer to the following URL for more detailed instructions on how to use the svgo plugins options and the SVGO library:
https://github.com/sindresorhus/grunt-svgmin#available-optionsplugins
Most of the plugins in the library are enabled by default, but if we'd like to specifically indicate which of these should be used, we can do so using the svgoPlugins option. Here, we can provide an array of objects, where each contain a property with the name of the plugin to be affected, followed by a true or false value to indicate whether it should be activated. The following configuration disables three of the default plugins:
imagemin: { dist: { src: 'src/image.svg', dest: 'dist/image.svg', options: { svgoPlugins: [ {removeViewBox:false}, {removeUselessStrokeAndFill:false}, {removeEmptyAttrs:false} ] } } }
In order to provide support for the various image optimization projects, the imagemin plugin has a plugin framework of its own that allows developers to easily create an extension that makes use of the tool they require.
You can get a list of the available plugin modules for the imagemin plugin's framework at the following URL:
The following steps will take us through installing and making use of the mozjpeg plugin to compress an image in our project. These steps start where the main recipe takes off.
[email protected] node_modules/imagemin-mozjpeg
var mozjpeg = require('imagemin-mozjpeg');
imagemin: { dist: { src: 'src/image.jpg', dest: 'dist/image.jpg', options: { use: [mozjpeg()] } } }
Running "imagemin:dist" (imagemin) task Minified 1 image (saved 9.88 kB)
In this recipe, we'll make use of the contrib-jshint (0.11.1) plugin to detect errors and potential problems in our JavaScript code. It is also commonly used to enforce code conventions within a team or project. As can be derived from its name, it's basically a Grunt adaptation for the JSHint tool.
In this example, we'll work with the basic project structure.
The following steps take us through creating a sample JavaScript file and configuring a task that will scan and analyze it using the JSHint tool.
sample = 'abc';
console.log(sample);
jshint: {
main: {
options: {
undef: true
},
src: ['src/main.js']
}
}
The undef option is a standard JSHint option used specifically for this example and is not required for this plugin to function. Specifying this option indicates that we'd like to have errors raised for variables that are used without being explicitly defined.
Running "jshint:main" (jshint) task src/main.js 1 |sample = 'abc'; ^ 'sample' is not defined. 2 |console.log(sample); ^ 'console' is not defined. 2 |console.log(sample); ^ 'sample' is not defined. >> 3 errors in 1 file
The jshint task provides us with several options that allow us to change its general behavior, in addition to how it analyzes the targeted code. We'll look at how to specify standard JSHint options, specify globally defined variables, send reported output to a file, and prevent task failure on JSHint errors.
The contrib-jshint plugin provides a simple way to pass all the standard JSHint options from the task's options object to the underlying JSHint tool.
A list of all the options provided by the JSHint tool can be found at the following URL:
The following example adds the curly option to the task we created in our main recipe to enforce the use of curly braces wherever they are appropriate:
jshint: { main: { options: { undef: true, curly: true }, src: ['src/main.js'] } }
Making use of globally defined variables is quite common when working with JavaScript, which is where the globals option comes in handy. Using this option, we can define a set of global values that we'll use in the targeted code, so that errors aren't raised when JSHint encounters them.
In the following example, we indicate that the console variable should be treated as a global, and not raise errors when encountered:
jshint: { main: { options: { undef: true, globals: { console: true } }, src: ['src/main.js'] } }
If we'd like to store the resulting output from our JSHint analysis, we can do so by specifying a path to a file that should receive it using the reporterOutput option, as shown in the following example:
jshint: { main: { options: { undef: true, reporterOutput: 'report.dat' }, src: ['src/main.js'] } }
The default behavior for the jshint task is to exit the running Grunt process once a JSHint error is encountered in any of the targeted files. This behavior becomes especially undesirable if you'd like to keep watching files for changes, even when an error has been raised.
In the following example, we indicate that we'd like to keep the process running when errors are encountered by giving the force option a true value:
jshint: { main: { options: { undef: true, force: true }, src: ['src/main.js'] } }
In this recipe, we'll make use of the contrib-uglify (0.8.0) plugin to compress and mangle some files containing JavaScript code.
For the most part, the process of uglifying just removes all the unnecessary characters and shortens variable names in a source code file. This has the potential to dramatically reduce the size of the file, slightly increase performance, and make the inner workings of your publicly available code a little more obscure.
In this example, we'll work with the basic project structure.
The following steps take us through creating a sample JavaScript file and configuring a task that will uglify it.
var main = function () { var one = 'Hello' + ' '; var two = 'World'; var result = one + two; console.log(result); };
uglify: { main: { src: 'src/main.js', dest: 'dist/main.js' } }
Running "uglify:main" (uglify) task >> 1 file created.
The uglify task provides us with several options that allow us to change its general behavior and see how it uglifies the targeted code. We'll look at specifying standard UglifyJS options, generating source maps, and wrapping generated code in an enclosure.
The underlying UglifyJS tool can provide a set of options for each of its separate functional parts. These parts are the mangler, compressor, and beautifier. The contrib-plugin allows passing options to each of these parts using the mangle, compress, and beautify options.
The available options for each of the mangler, compressor, and beautifier parts can be found at each of following URLs (listed in the order mentioned):
https://github.com/mishoo/UglifyJS2#mangler-options
The following example alters the configuration of the main recipe to provide a single option to each of these parts:
uglify: { main: { src: 'src/main.js', dest: 'dist/main.js', options: { mangle: { toplevel: true }, compress: { evaluate: false }, beautify: { semicolons: false } } } }
As code gets mangled and compressed, it becomes effectively unreadable to humans, and therefore, nearly impossible to debug. For this reason, we are provided with the option of generating a source map when uglifying our code.
The following example makes use of the sourceMap option to indicate that we'd like to have a source map generated along with our uglified code:
uglify: { main: { src: 'src/main.js', dest: 'dist/main.js', options: { sourceMap: true } } }
Running the altered task will now, in addition to the dist/main.js file with our uglified source, generate a source map file called main.js.map in the same directory as the uglified file.
When building your own JavaScript code modules, it's usually a good idea to have them wrapped in a wrapper function to ensure that you don't pollute the global scope with variables that you won't be using outside of the module itself.
For this purpose, we can use the wrap option to indicate that we'd like to have the resulting uglified code wrapped in a wrapper function, as shown in the following example:
uglify: { main: { src: 'src/main.js', dest: 'dist/main.js', options: { wrap: true } } }
If we now take a look at the result dist/main.js file, we should see that all the uglified contents of the original file are now contained within a wrapper function.
In this recipe, we'll make use of the contrib-requirejs (0.4.4) plugin to package the modularized source code of our web application into a single file.
For the most part, this plugin just provides a wrapper for the RequireJS tool. RequireJS provides a framework to modularize JavaScript source code and consume those modules in an orderly fashion. It also allows packaging an entire application into one file and importing only the modules that are required while keeping the module structure intact.
In this example, we'll work with the basic project structure.
The following steps take us through creating some files for a sample application and setting up a task that bundles them into one file.
require.config({ baseUrl: 'app' });
define(function (require) { return function () { console.log('Sample Module'); } });
require(['sample'], function (sample) { sample(); });
requirejs: { app: { options: { mainConfigFile: 'src/config.js', name: 'main', out: 'www/js/app.js' } } }
The mainConfigFile option points out the configuration file that will determine the behavior of RequireJS.
The name option indicates the name of the module that contains the application entry point. In the case of this example, our application entry point is contained in the app/main.js file, and app is the base directory of our application in the src/config.js file. This translates the app/main.js filename into the main module name.
The out option is used to indicate the file that should receive the result of the bundled application.
Running "requirejs:app" (requirejs) task
The requirejs task provides us with all the underlying options provided by the RequireJS tool. We'll look at how to use these exposed options and generate a source map.
The RequireJS optimizer is quite an intricate tool, and therefore, provides a large number of options to tweak its behavior. The contrib-requirejs plugin allows us to easily set any of these options by just specifying them as options of the plugin itself.
A list of all the available configuration options for the RequireJS build system can be found in the example configuration file at the following URL:
https://github.com/jrburke/r.js/blob/master/build/example.build.js
The following example indicates that the UglifyJS2 optimizer should be used instead of the default UglifyJS optimizer by using the optimize option:
requirejs: { app: { options: { mainConfigFile: 'src/config.js', name: 'main', out: 'www/js/app.js', optimize: 'uglify2' } } }
When the source code is bundled into one file, it becomes somewhat harder to debug, as you now have to trawl through miles of code to get to the point you're actually interested in.
A source map can help us with this issue by relating the resulting bundled file to the modularized structure it is derived from. Simply put, with a source map, our debugger will display the separate files we had before, even though we're actually using the bundled file.
The following example makes use of the generateSourceMap option to indicate that we'd like to generate a source map along with the resulting file:
requirejs: { app: { options: { mainConfigFile: 'src/config.js', name: 'main', out: 'www/js/app.js', optimize: 'uglify2', preserveLicenseComments: false, generateSourceMaps: true } } }
In order to use the generateSourceMap option, we have to indicate that UglifyJS2 is to be used for optimization, by setting the optimize option to uglify2, and that license comments should not be preserved, by setting the preserveLicenseComments option to false.
This article covers the optimization of images, minifying of CSS, ensuring the quality of our JavaScript code, compressing it, and packaging it all together into one source file.
Further resources on this subject: