How to Migrate to Gulp.js 4.0

    Craig Buckler
    Share

    Despite competition from webpack and Parcel, Gulp.js remains one of the most popular JavaScript task runners. Gulp.js is configured using code which makes it a versatile, general-purpose option. As well as the usual transpiling, bundling and live reloading, Gulp.js could analyze a database, render a static site, push a Git commit, and post a Slack message with a single command. Learn how to migrate to Gulp.js 4.0.

    For an introduction to Gulp, take a look at the following:

    Gulp.js 4.0

    Gulp.js 3.x has been the default for around half a decade. Until recently, npm install gulp would have installed 3.9.1 — the version referenced in the tutorials above.

    Gulp.js 4.0 has been available throughout that time, but had to be explicitly installed with npm install gulp@next. This was partly owing to ongoing development and because Gulp.js 4 gulpfile.js configuration files are not compatible with those developed for version 3.

    On December 10, 2018, Gulp.js 4.0 was announced as the default and published to npm. Anyone using npm install gulp on a new project will receive version 4.

    Is it Necessary to Migrate to Gulp.js 4?

    No. Gulp.js 3 has been deprecated and is unlikely to receive further updates, but it can still be used. Existing projects won’t update unless the version is explicitly changed in the dependencies section of package.json. For example:

    "dependencies": {
      "gulp": "^4.0.0"
    }
    

    You an also install Gulp.js 3 in new projects using:

    npm install gulp@^3.9.1
    

    It’s possibly best to stick with Gulp.js 3.x if you have a particularly complex, mission-critical build system.

    However, existing Gulp.js plugins should be compatible and most gulpfile.js configurations can be migrated in an hour or two. There are several benefits to upgrading, which will become apparent throughout this tutorial.

    Upgrade to Gulp.js 4.0

    Update your package.json dependencies as shown above, then run npm install to upgrade. You can also update the command-line interface using npm i gulp-cli -g, although this hasn’t changed at the time of writing.

    To check the installation, enter gulp -v at the command line:

    $ gulp -v
    [15:15:04] CLI version 2.0.1
    [15:15:04] Local version 4.0.0
    

    Migrating gulpfile.js

    Running any task is now likely to raise scary-looking errors. For example:

    AssertionError [ERR_ASSERTION]: Task function must be specified
      at Gulp.set [as _setTask] (/node_modules/undertaker/lib/set-task.js:10:3)
      at Gulp.task (/node_modules/undertaker/lib/task.js:13:8)
      at /gulpfile.js:102:8
      at Object.<anonymous> (/gulpfile.js:146:3)
      at Module._compile (internal/modules/cjs/loader.js:688:30)
      at Object.Module._extensions..js (internal/modules/cjs/loader.js:699:10)
      at Module.load (internal/modules/cjs/loader.js:598:32)
      at tryModuleLoad (internal/modules/cjs/loader.js:537:12)
      at Function.Module._load (internal/modules/cjs/loader.js:529:3)
      at Module.require (internal/modules/cjs/loader.js:636:17)
      at require (internal/modules/cjs/helpers.js:20:18)
      at execute (/gulp-cli/lib/versioned/^4.0.0/index.js:36:18)
      at Liftoff.handleArguments (/gulp-cli/index.js:175:63)
      at Liftoff.execute (/gulp-cli/node_modules/liftoff/index.js:203:12)
      at module.exports (/gulp-cli/node_modules/flagged-respawn/index.js:51:3)
      at Liftoff.<anonymous> (/gulp-cli/node_modules/liftoff/index.js:195:5)
    

    It’s daunting, but you can ignore everything except the first reference of gulpfile.js, which shows the line where an error was encountered (102 in this example).

    Fortunately, most of these errors are caused by the same type of problem. The following sections use the CSS tasks tutorial code as an example. The code is available on GitHub and provides the original Gulp.js 3 gulpfile.js and the migrated Gulp.js 4 equivalent.

    Convert Task Arrays to series() Calls

    Gulp.js 3 allowed arrays of synchronous tasks to be specified. This was typically used when a watch event was triggered or a task had dependencies. For example, run the images task before the css task:

    gulp.task('css', ['images'], () =>
    
      gulp.src(cssConfig.src)
        // .other plugins
        .pipe(gulp.dest(cssConfig.build));
    
    );
    

    Gulp.js 4.0 introduces the series() and parallel() methods to combine tasks:

    • series(...) runs tasks one at a time in the order specified, and
    • parallel(...) runs tasks simultaneously in any order.

    Complex tasks can be nested to any level — something which would have been difficult to achieve in Gulp.js 3. For example, run tasks a and b in parallel then, once both are complete, run tasks c and d in parallel:

    gulp.series( gulp.parallel(a, b), gulp.parallel(c, d) )
    

    The css task above can be migrated to Gulp.js 4 by changing the array to a series() method call:

    gulp.task('css', gulp.series('images', () =>
    
      gulp.src(cssConfig.src)
        // .other plugins
        .pipe(gulp.dest(cssConfig.build));
    
    )); // remember the extra closing bracket!
    

    Async Completion

    Gulp.js 3 permitted synchronous functions but these could introduce errors which were difficult to debug. Consider tasks which don’t return the gulp streamed value. For example, a clean task to delete the build folder:

    const
      gulp = require('gulp'),
      del = require('del'),
      dir = {
        src         : 'src/',
        build       : 'build/'
      };
    
    gulp.task('clean', () => {
    
      del([ dir.build ]);
    
    });
    

    Gulp.js 4.0 throws a warning because it needs to know when a task has completed in order to manage series() and parallel() sequences:

    [15:57:27] Using gulpfile gulpfile.js
    [15:57:27] Starting 'clean'...
    [15:57:27] The following tasks did not complete: clean
    [15:57:27] Did you forget to signal async completion?
    

    To solve this, Gulp.js 4 will accept a returned Promise (which is supported by the del package):

    gulp.task('clean', () => {
    
      return del([ dir.build ]);
    
    });
    
    // or use the simpler implicit return:
    // gulp.task('clean', () => del([ dir.build ]) );
    

    Alternatively, pass a callback function which is executed on completion (del also provides a synchronous sync() method):

    gulp.task('clean', (done) => {
    
      del.sync([ dir.build ]);
      done();
    
    });
    

    Here’s a more complex Gulp.js 3 example with watch tasks:

    gulp.task('default', ['css', 'server'], () => {
    
      // image changes
      gulp.watch(imgConfig.src, ['images']);
    
      // CSS changes
      gulp.watch(cssConfig.watch, ['css']);
    
    });
    

    These can be migrated to Gulp.js 4 series() methods and a done() callback:

    gulp.task('default', gulp.series('css', 'server', (done) => {
    
      // image changes
      gulp.watch(imgConfig.src, gulp.series('images'));
    
      // CSS changes
      gulp.watch(cssConfig.watch, gulp.series('css'));
    
      done();
    
    }));
    

    Bonus Tip: Convert Task Methods to ES6 Modules

    Most gulpfile.js configurations will work in Gulp.js 4.0 once task arrays are converted to series() calls and asynchronous functions signal completion.

    Although the task() definition method is still supported, the newer ES6 module exports pattern offers several benefits:

    1. Private tasks can be defined which can be called within gulpfile.js but not from the gulp command.
    2. Functions can be passed by reference rather than a string name so syntax errors can be highlighted by editors.
    3. The same function can be referenced using any number of task names.
    4. It’s easier to define complex dependencies in series() and/or parallel().

    Take the following Gulp.js 3 images and css tasks:

    gulp.task('images', () =>
    
      gulp.src(imgConfig.src)
        // .other plugins
        .pipe(gulp.dest(imgConfig.build))
    
    );
    
    gulp.task('css', ['images'], () =>
    
      gulp.src(cssConfig.src)
        // .other plugins
        .pipe(gulp.dest(cssConfig.build))
    
    );
    

    These could be converted to use the Gulp.js 4 module pattern:

    function images() {
    
      return gulp.src(imgConfig.src)
        // .other plugins
        .pipe(gulp.dest(imgConfig.build));
    
    }
    exports.images = images;
    exports.pics = images;
    
    
    function css() {
    
      return gulp.src(cssConfig.src)
        // .other plugins
        .pipe(gulp.dest(cssConfig.build));
    
    }
    exports.css = gulp.series(images, css);
    exports.styles = exports.css;
    

    Note: return statements must be added because the ES6 arrow => functions with an implicit return have been changed to standard function definitions.

    In this Gulp.js 4 example:

    • either gulp images or gulp pics can be used to run the images() task
    • either gulp css or gulp styles will run images() followed by css().

    Set exports.default to define a default task run when gulp is executed from the command line without a specific task.

    Gulp Goodness

    The CSS tasks example, which optimizes images, compiles Sass, minifies CSS, and live-reloads on changes, took less than one hour to convert. Examine the code on GitHub to see:

    It’s taken a while to (properly) arrive, but Gulp.js 4 provides opportunities for defining tasks which would have been impractical in version 3. Updating software can seem like a waste of development effort, but you’ll be rewarded with a faster, more robust set of tasks, which will save time in the long run.