gulp Asset Optimization and Minification


Example


So, Before writing out optimiser functions , we need to install a couple of caching plugins.

bash $ npm install --save-dev gulp-cached
bash $ npm install --save-dev gulp-remember

You might wonder why two caches eh!. gulp-cached , passes only modified or new content down the pipeline to other plugins. So, Since we want files without change to be used for concatenating into a single file per asset( css | js ) as well, we need gulp-remember in addition to gulp-cached

First we use gulp-cached to build a list of files that have changed

Second , we need gulp-remember to keep a track of all files that are passed through by that list in memory.

First Run : No files are cached , so gulp-cached will pass them all to gulp-remember

Subsequent runs : Only modified or new files are piped down by gulp-cached. Since the content of the current file has changed , gulp-remember updates its cache.

Cool , Let us write our first optimizer

Style Optimizer


// Goal

/*

1. cache existing files on the first run
2. each file , 
       a. Is autoprefixed with cross browser compatible prefixes for any css property ( justify-content for e.g)
       b. Is concatenated into app.css
3. app.css is minified
4. On subsequent runs , the above process is implemented on file modifications/additions only

*/

/*
*@src - input a glob pattern - a string eg 'images/**/*' or '**/*.css' or, an array eg ['glob1','glob2']
*/
var optimizeStyles = function(src) {

return $.src(src).
pipe($$.cached('styles')).
pipe($$.autoprefixer({
browsers: ['last 2 versions']
})).
pipe($$.remember('auto-prefixed-stylesheets')).
pipe($$.concat('app.css')).
pipe($.dest('build/css')).
pipe($$.cleanCss()).
pipe($$.rename({
suffix: '.min'
})).
pipe($.dest('build/css'))
}

Note

$ - > gulp

$$ - > gulp-load-plugins

$.src - > builds file streams matching the glob passed as src

$.dest - > saves the manipulated file in the path specified

Script Optimiser


// Goal

/*

1. cache existing files on the first run
2. each file , 
       a. Is linted with jshint 
       b. Is concatenated into app.js
3. app.js is minified
4. On subsequent runs , the above process is implemented on file modifications/additions only

*/

/*
*@src - input a glob pattern - a string eg 'js/**/*' or '**/*.js' or, an array eg ['glob1','glob2']
*/

var optimizeScripts = function(src) {

    return $.src(src).
    pipe($$.cached('scripts')).
    pipe($$.jshint()).
    pipe($$.jshint.reporter('default')).
    pipe($$.jshint.reporter('fail')).
    pipe($$.remember('linted-scripts')).
    pipe($$.concat('app.js')).
    pipe($.dest('build/js')).
    pipe($$.uglify()).
    pipe($$.rename({
        suffix: '.min'
    })).
    pipe($.dest('build/js'))


}

Note

$ - > gulp

$$ - > gulp-load-plugins

$.src - > builds file streams matching the glob passed as src

$.dest - > saves the manipulated file in the path specified

Generate Responsive Images

Let us now move on to image processing. So, the aim , is to have an array of sizes for each image you are going to serve.

Why?

Well , to understand why we need a battery of images with a range of widths , we need to ponder over the fact , that there are probably zillions of devices with varied resolutions. We need an image to scale without much pixelation. At the same time , we need to improve page load times , by downloading just the one image , which fits the width it is contained by , and is also with the smallest possible dimension , to do so. There are scholarly blogs like the one Eric Portis wrote , which highlights the ineffectiveness of just media queries and serves as a comprehensive guide to understanding concepts like srcsets and sizes.

You can refer to Eric Portis' epic write up here

Now, Our function , needs to take a glob , and a width as inputs, and do its magic and push the file each run generates , to a destination and minify the responsified image.

We have installed two image compression plugins in our first example

Since these plugins DO NOT start with a "gulp-" prefixed, we need to manually load them onto our gulpfile.

SO Let us require them manually , after the gulp-load-plugins declaration at the top of our gulpfile.

like so:

var compressJpg = require('imagemin-jpeg-recompress');
var pngquant = require('imagemin-pngquant');

It is worthwhile to note , that gulp-responsive comes with the sharp image processor , which is better than imagemagick BY FAAAAR!. Sharp is what is used by gulp-responsive , to crop your images to desired widths.

you might look at gulp-responsive-configuration-options,for a comprehensive list of configuration params. I have only used

  • width - to crop our images to a width w, passed as a parameter
  • rename - to add a suffix to the image name, so that it remains unique

in my configuration function below. so our function , will crop the image to the width passed as input , for all matching images deciphered via the glob input. then, each image is compressed using jpeg-recompress or pngquant and saved inside build/images.

With that in mind, our function would be like so:

/*
@generateResponsiveImages
*@Description:takes in a src of globs, to stream matching image files , a width,
*to resize the matching image to, and a dest to write the resized and minified files to
*@src - input a glob pattern - a string eg 'images/** /*' or 'images/*' or, an array
eg ['glob1','glob2']
*@return returns a stream
*/
var generateResponsiveImages = function(src, width, dest) {

    //declare a default destination
    if (!dest)
        dest = 'build/images';
    //case 1: src glob -  images/**/*
    // the base is the directory immediately preceeding the glob - images/ in this case
    //case 2: images/fixed/flourish.png : the base here is images/fixed/ - we are overriding
    // that by setting base to images.This is done so that, the path beginning after images/
    // - is the path under our destination - without us setting the base, dest would be,
    //build/images/flourish.png.But with us setting the base, the destination would be
    // build/images/fixed/flourish.png
    return $.src(src, {
        base: 'images'
    })

    //generate resized images according to the width passed
    .pipe($$.responsive({
            //match all pngs within the src stream
            '**/*.png': [{
                width: width,
                rename: {
                    suffix: '-' + width
                },
                withoutEnlargement: false,
            }],
            //match all jpgs within the src stream
            '**/*.jpg': [{
                width: width,
                rename: {
                    suffix: '-' + width
                },
                progressive: true,
                withoutEnlargement: false,
            }]
        }, {

            errorOnEnlargement: false,
            errorOnUnusedConfig: false,
            errorOnUnusedImage: false

        }))
        //once the file is resized to width, minify it using the plugins available per format
        .pipe($$.if('*.jpg', compressJpg({
            min: 30,
            max: 90,
            target: 0.5
        })()))
        //use file based cache gulp-cache and it will minify only changed or new files
        //if it is not a new file and if the contents havent changed, the file is served from cache
        .pipe($$.cache($$.imagemin({
            verbose: true
        })))


    //write to destination - dest + path from base
    .pipe($.dest(dest));
}

Note

$ - > gulp

$$ - > gulp-load-plugins

$.src - > builds file streams matching the glob passed as src

$.dest - > saves the manipulated file in the path specified

Further references

HTML Minifier

*
 *@minifyHtml
 *Description:takes in a glob src, and minifies all '.html' files matched by the glob
 *@src - input a glob pattern - a string eg '/**/*.html /*' or '*.html' or, an array eg ['glob1','glob2']
 *@dest=file.base means, the modified html file will be in the same directory as the src file being minified
 *often means, the process is just a modification on the existing src file
 *@return returns a stream
 */
var minifyHtml = function(src) {
    return $.src(src)
        .pipe($$.minifyHtml())
        .pipe($.dest(function(file) {
            //file is provided to a dest callback -
            // Refer here https://github.com/gulpjs/gulp/blob/master/docs/API.md#path
            return file.base;
        }));
}