importpromisegulpgulp-imagemin

How can I prevent a Gulp task with a dynamic import from being asynchronous?


I want to use gulp-imagemin to minify images. The relevant part of my gulpfile.js looks like this:

const gulp = require('gulp');
// a couple more require('')s

function minifyImages(cb) {
    import('gulp-imagemin')
        .then(module => {
            const imagemin = module.default;
            gulp.src('src/img/**/*')
                .pipe(imagemin())
                .pipe(gulp.dest('img'));
            cb();
        })
        .catch(err => {
            console.log(err);
            cb();
        });
}

function buildCSS(cb) { /* ... */ }

exports.build = gulp.series(buildCSS, minifyImages);

The reason I'm using a dynamic import here is because I think I have to - gulp-imagemin doesn't support the require('') syntax, and when I say import imagemin from 'gulp-imagemin I get an error saying "Cannot use import statement outside a module".

I would expect the build task to only finish after minifyImages has finished. After all, I'm calling cb() only at the very end, at a point where the promise should be resolved.

However, build seems to finish early, while minifyImages is still running. This is the output I get:

[21:54:47] Finished 'buildCSS' after 6.82 ms
[21:54:47] Starting 'minifyImages'...
[21:54:47] Finished 'minifyImages' after 273 ms
[21:54:47] Finished 'build' after 282 ms
<one minute later>
[21:55:49] gulp-imagemin: Minified 46 images (saved 5.91 MB - 22.8%)

How can I make sure the task doesn't finish early, and all tasks are run in sequence?

Let me know if there's something wrong with my assumptions; I'm somewhat new to gulp and importing.


Solution

  • Streams are always asynchronous, so if the cb() callback is called just after creating the gulp stream as in your then handler, it's only obvious that the stream by that time has not finished yet (in fact, it hasn't even started).

    The simplest solution to call a callback when the gulp.dest stream has finished is using stream.pipeline, i.e.:

    function minifyImages(cb) {
        const { pipeline } = require('stream');
        return import('gulp-imagemin')
            .then(module => {
                const imagemin = module.default;
                pipeline(
                    gulp.src('src/img/**/*'),
                    imagemin(),
                    gulp.dest('img'),
                    cb
                );
            })
            .catch(cb);
    }
    

    Or similarly, with an async function.

    async function minifyImages(cb) {
        const { pipeline } = require('stream');
        const { default: imagemin } = await import('gulp-imagemin');
        return pipeline(
                gulp.src('src/img/**/*'),
                imagemin(),
                gulp.dest('img'),
                cb
        );
    }
    

    Another approach I have seen is to split the task in two sequential sub-tasks: the first sub-task imports the plugin module and stores it in a variable, and the second sub-task uses the plugin already loaded by the previous sub-task to create and return the gulp stream in the usual way. Then the two sub-tasks can be combined with gulp.series.