Watch, Async Completion & Modern Alternatives
gulp.watch() — Rebuild on Save
const { watch, series } = require('gulp');
function watchFiles() {
watch('src/scss/**/*.scss', styles);
watch('src/js/**/*.js', scripts);
}
exports.default = series(exports.build, watchFiles);Signaling Async Completion
Gulp needs to know when a task finishes — return the stream/promise, or call the provided callback. Forgetting this in Gulp 4 raises "Did you forget to signal async completion?" — a common beginner gotcha.
function copyAssets(cb) {
fs.copyFileSync('src/favicon.ico', 'dist/favicon.ico');
cb(); // explicit completion signal for a non-stream task
}Resilient Watch with gulp-plumber
An unhandled error mid-pipeline (like a Sass syntax error) can crash the whole watch process. gulp-plumber prevents the crash so watch keeps running and picks up the next successful save.
Source Maps
const sourcemaps = require('gulp-sourcemaps');
src('src/js/**/*.js')
.pipe(sourcemaps.init())
.pipe(uglify())
.pipe(sourcemaps.write('.'))
.pipe(dest('dist/js'));
// lets devtools map minified output back to original source linesWhen Gulp Still Makes Sense
Modern bundlers (webpack, Vite, esbuild) absorbed much of what task runners were needed for — module bundling plus build automation in one tool. Gulp remains useful for custom, heterogeneous build pipelines that don't map cleanly onto one bundler's built-in flow (image optimization + static copying + Sass for a non-bundled site + deployment steps).
Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free