You have that old project with a Gulp build script and you need to make some updates. You’ve updated NPM and Node in the meantime so when you come to run Gulp it fails.

All the dependencies are out of date so you update them. You try Gulp. It blows up again.

It’s time to bite the bullet. You need to upgrade your Gulpfile.js so it works with v4.

This was the reality of the situation I found myself in recently. I couldn’t be bothered to do this, and if you are reading this, chances are, nor can you. But here we are. So, let’s just get our heads down and get through it.

TL;DR if you just want a basic Gulp v4 buildscript

Go take a look at the repository for the https://rwd.education website. It includes a package.json, Gulpfile.js and .browserlistrc. With those in the root of your folder, it assumes a styles.css source file, a rwd.ts typescript file (obviously just change names to suit your needs), afonts folder containing woff files, and an img folder for any images. When Gulp runs it builds the source files out to a \_build folder.

If you want to know what needs to change from a basic v3 gulpfile to a basic v4 gulpfile, read on.

Background

I have a basic ‘Gulpfile.js’ I created to handle developing the WordPress theme for this site. It post-processes the CSS, optimises the JavaScript, watches for changes and updates the browser on save.

About as simple as a Gulp build script gets.

The task before us

I’m not interested in doing anything fancy. I just want my v3 Gulpfile.js to work with v4 as quickly as possible.

Considerations for Gulp v4

The main thing I picked up from doing this process is that Gulp now wants two main changes in a gulpfile.js before it will work with v4:

  • it wants you to call out groups of tasks that can be run in parallel and therefore also which groups of tasks should be executed in order AKA serially.

  • it wants you to write normal JavaScript functions as opposed to gulp.task. And these functions all need to use a callback. More on that shortly.

We just want our existing Gulpfile to work in v4 remember? So, even though with v4 You can also do imports a different way, frankly I couldn’t be arsed to mess with that so I left them as is.

Comparison of the same gulpfile in v3 and v4 form

You may find it useful to consider the complete v3 gulpfile and the complete v4 gulpfile?

We’ll now look a little closer at the differences by examining individual functions.

Example Gulp v3 task:

Here is the largest `gulp.task’ I had in my v3 file. It’s the task for taking my CSS, doing all manner of post-processing and then spitting it out somewhere.

gulp.task("css", function () {
    var processors = [
        postcsseasyimport(),
        postcssMixins,
        simplevars,
        nested,
        assets({
            relative: true,
        }),
        postcssColorFunction(),
        autoprefixer({ browsers: ["defaults"] }),
        cssnano(),
    ];

    return gulp
        .src("./preCSS/styles.css")
        .pipe(postcss(processors))
        .pipe(gulp.dest("./css"))
        .pipe(notify("Boom! Up in yo CSS face!!"))
        .pipe(browserSync.stream());
});

Converted to v4 style:

Here is that task slightly amended to work for v4:

function css() {
    var processors = [
        postcsseasyimport(),
        postcssMixins,
        simplevars,
        nested,
        assets({
            relative: true,
        }),
        postcssColorFunction(),
        autoprefixer(),
        cssnano(),
    ];

    return gulp
        .src("./preCSS/styles.css", { sourcemaps: true })
        .pipe(postcss(processors))
        .pipe(gulp.dest("./css"))
        .pipe(notify("Boom! Up in yo CSS face!!"))
        .pipe(browserSync.stream());
}

I’m using a .browserlistrc file for Autoprefixer in the v4 example but that wasn’t a v4 requirement. But note that sourcemaps are just part of an options argument now instead of being a separate pipe() when you wanted to include them in v3.

Gulp v4 wants a callback. Except when it doesn’t!

The golden rule for v4 Gulp task writing is that there are no synchronous tasks. In practice, for our simple situation where we aren’t making use of promises, event emitters or observables, this means we either need to return a stream from a gulp function or provide an ‘error-first’ callback.

In our css() function above, we returned a stream (e.g. return gulp...), but if that isn’t what we will be returning, then we need to pass in a ‘error-first’ callback. That sounds far more involved than it is in practice. Look at this v3 version of the browser-sync task:

gulp.task("browser-sync", function() {
    browserSync({
        open: false,
        proxy: "localhost:8888"
    });
});

To convert it to work in v4 it looks like this (ignore the proxy address change, that was just because I was on a different system):

function serve(done) {
    server.init({
        open: false,
        proxy: "127.0.0.1/wordpress/",
    });
    done();
}

See the done callback that is passed in to the function and then executed at the end? Yes, that’s the ‘error-first’ callback. For this simplistic situation I’m not doing anything fancy with the callback — I just let Gulp blow up if I do something wrong.

For more ‘mission critical’ task writing you should probably think about doing something more meaningful with the error-handling. When/if the operation blows up with an error, that callback function is called with the error object passed to it, so it makes sense to consider doing something meaningful with it.

Watch tasks

Here is how I defined the watch items in v3:

gulp.task("watch", function() {
    // Watch .css files
    gulp.watch("preCSS/**/*.css", ["css", browserSync.reload]);

    // Watch .js files
    gulp.watch(["preJS/**/*.js"], ["js", browserSync.reload]);

    // Watch any files in root html, reload on change
    gulp.watch("**/*.php", browserSync.reload);
});

And then my default task looked like this (which fired off the watch task):

gulp.task("default", ["css", "browser-sync", "js", "watch"]);

Converted to v4 it looks like this:

const watchCSS = () => gulp.watch("preCSS/**/*.css", gulp.series(css, reload));
const watchJS = () => gulp.watch("preJS/**/*.js", gulp.series(js, reload));
const watchPHP = () => gulp.watch("**/*.php", reload);

And then the default task looks like this:

const dev = gulp.series(
    serve,
    gulp.parallel(css, js),
    gulp.parallel(watchCSS, watchJS, watchPHP)
);
exports.default = dev;

Notice how rather than sticking the jobs in an array with v3 where they would be run one after the other? With v4, by passing them to gulp.parallel it means they will be run, you guessed it, in parallel.

Let’s look at another. This was the job that processed my JavaScript in v3:

gulp.task("js", function () {
    return gulp
        .src("preJS/*.js")
        .pipe(concat("all.js"))
        .pipe(gulp.dest("js/"))
        .pipe(notify("Boom! Up in yo JS face!!"))
        .pipe(browserSync.stream());
});

Not much to change for v4:

function js(done) {
    gulp.src("preJS/*.js", { sourcemaps: true })
        .pipe(concat("all.js"))
        .pipe(gulp.dest("js/"))
        .pipe(notify("Boom! Up in yo JS face!!"))
        .pipe(browserSync.stream());
    done();
}

Again, very little change. It’s just a case of ensuring that when you aren’t returning a stream, you are passing the method a callback as the first argument. This could have been a stream too, as it was before but I decided to suffer for my art :).

Summary

With a few simple changes, it’s possible to get a v3 Gulpfile ported to v4 in short order. The biggest hurdle in a quick conversion is understanding that in v4, if you aren’t returning a stream, you need to be using an error-first callback.

And also, take the time to consider which of your tasks you can run in parallel and which need to run in serial; they will need encapsulating as such. No more just sticking tasks in an array and letting Gulp sort it out.

While there are plenty of other niceties these are the essentials to get up and running with Gulp v4.

Learn to use CSS effectively, 'Enduring CSS' is out now

Get $5 off HERE ↠

Write and maintain large scale modular CSS and embrace modern tooling including PostCSS, Stylelint and Gulp