Using Grunt to generate WebP image assets

I've previously written about using <picture> and WebP to dramatically reduce the weight of your pages, but if all your assets are PNG or JPG you might need a way to generate WebP images from them. There's probably a plugin for your image editor of choice that can export your images as WebP, but this kind of repetitive manual job is exactly what task runners like Grunt and Gulp are really good at.

If you've not used one before, the gist of a task runner is that you equip it with a set of capabilities by installing modules, and then give it a list of tasks to perform. In your task runner's configuration you tell it where to find your project files, what tasks you'd like to run against them, and where to place any resulting new files.

In this brief overview, we'll use my favourite task runner, Grunt, to take all the JPG and PNG images in a project and output WebP versions of them in a separate folder.

Getting started: installing dependencies

Grunt requires Node.js and NPM. You can find detailed instructions on getting Grunt installed in its documentation. Grunt uses two files in your project: package.json for a list of modules to install, and gruntfile.js for instructions on what tasks to perform. The task we're going to have it carry out is automated bulk conversion of image files to the WebP format, and we'll be using a module called grunt-cwebp to do that.

Configuring Grunt: package.json

We need a package.json file that lists the grunt-cwebp module as a project dependency for NPM to install it for us. Here's a quick example file that will install grunt-cwebp when we run the command npm install in the terminal from the root of our project:

{
    "name": "my-package-file",
    "version": "1.0.0",
    "description": "A quick demonstration of using Grunt to convert image files",
    "author": "Mike Babb",
    "repository": { "type": "git",
    "url": "" },
    "dependencies": { "grunt": "~1.0.1",
    "load-grunt-tasks": "^3.5.0" },
    "devDependencies": { "grunt-cwebp": "2.1.0" }
}

Configuring Grunt: gruntfile.js

With our node module installed, we now need a gruntfile.js to define the WebP conversion task we'd like Grunt to perform. In the sample file below, we configure a task named cwebp, with parameters that specify the quality we would like to maintain during the conversion, where to find the files we'd like converted, the file formats to convert, and finally a destination folder for the resulting WebP images.

module.exports = function(grunt) { 
    grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), 
        // WebP conversion task
        cwebp: {
            dynamic: {
                options: {
                    // Quality of WebP images: 
                    q: 75 
                }, 
                files: [{
                    // Output a new WebP file for every source image:
                     expand: true, 
                     // Where to find the images we'd like to be converted: 
                     cwd: 'images/img_original/', 
                     // The file formats to convert: 
                     src: ['**/*.{png,jpg,gif}'], 
                     // Where to place the resulting WebP files: 
                     dest: 'images/img_webp/' 
                }] 
            }
        }
    }); 
    require('load-grunt-tasks')(grunt);
    // Run the task when we type "grunt dev" in to the terminal
    grunt.registerTask('dev', ['cwebp:dynamic']);
};

We'll place our original JPG and PNG images in the folder specified under cwd in the gruntfile: images/img_original. To have Grunt do all the image conversion heavy lifting for us, we run the command grunt cwebp:dynamic from the terminal, and watch as it takes our originals, crunches them to the quality specified, and drops shiny new WebP versions in the images/img_webp folder. And we're done! We now have a new WebP file for every source file in the images/img_original folder.

You might have noticed that we defined a task named dev at the bottom of our gruntfile, which runs cwebp:dynamic for us when we run the command grunt dev in the terminal. This might seem unnecessary for a single task, but on more complicated projects shortcut commands like dev and dist are used to run a whole series of commands one after the other, without typing them all in to the terminal. This is particularly handy when you have lots of Grunt tasks set up to do things like checking your JavaScript files for errors, concatenating and minifying JavaScript and CSS files, or transpiling things like Sass to base CSS.

A note on image quality

In our gruntfile we set the desired quality of our new WebP images at 75. Depending on the original source image, I've found this level to maintain a virtually indistinguishable visual quality with a smaller file size, but it's worth noting that not all images will be smaller in WebP format than they might be in JPG or PNG. Experiment with it, and if you find that the WebP version of a given image is the same size (or perhaps larger!) than the source JPG or PNG, you'd be better off linking to the original source file in your markup instead. Where a smaller file size is realised though, WebP is an excellent way to reduce page weights, sometimes quite significantly.