Build CSS and JS with Gulp 4: A Practical Series/Parallel Task Guide
Use gulp.series and gulp.parallel to orchestrate CSS minification and JS bundling with image optimization in Gulp 4. This guide covers task completion signals, watch behavior, and common pitfalls like infinite rebuild loops.
13 Apr 2026, 12:23 UTC

The Problem: Sequential CSS Minification and Parallel Asset Processing
When building a frontend pipeline, you need CSS files to be minified and renamed before JS bundling starts, but image optimization and vendor library copying can happen simultaneously. Gulp 4's gulp.series and gulp.parallel let you express this dependency graph clearly.
Prerequisites
- Node.js 14+ installed
- Gulp 4.x as a local dev dependency (
npm install --save-dev gulp) - Basic familiarity with vinyl file streams
Verify Gulp 4 Installation
Run from your project root:
npx gulp --version
Expected output includes CLI Version: X.Y.Z and Local Version: 4.x.y. If the local version shows 3.x, install the correct version:
npm install --save-dev gulp@4
Define the Tasks
Create gulpfile.js with these task functions:
const { src, dest, series, parallel, watch } = require('gulp');
const sass = require('gulp-sass')(require('sass'));
const terser = require('gulp-terser');
const imagemin = require('gulp-imagemin');
const concat = require('gulp-concat');
// CSS pipeline: compile Sass, minify, rename
function styles() {
return src('src/scss/**/*.scss')
.pipe(sass().on('error', sass.logError))
.pipe(concat('main.min.css'))
.pipe(dest('dist/css'));
}
// JS pipeline: concatenate, then minify
function scripts() {
return src(['src/js/**/*.js', '!src/js/vendor/**/*.js'])
.pipe(concat('app.js'))
.pipe(terser())
.pipe(dest('dist/js'));
}
// Image optimization (can run in parallel)
function images() {
return src('src/images/**/*')
.pipe(imagemin())
.pipe(dest('dist/images'));
}
// Vendor copy (can run in parallel)
function vendor() {
return src('src/vendor/**/*')
.pipe(dest('dist/vendor'));
}
// Watch for changes
function watchFiles() {
watch('src/scss/**/*.scss', styles);
watch('src/js/**/*.js', scripts);
watch('src/images/**/*', images);
}
// Export the build sequence
exports.default = series(
parallel(styles, scripts), // Run CSS and JS together
parallel(images, vendor) // Then optimize images and copy vendor
);
exports.watch = series(
exports.default,
watchFiles
);
Run the Build
Execute from the project root:
npx gulp
This runs the default task: styles and scripts in parallel, then images and vendor in parallel. Check dist/ for the output files.
Verify Task Sequence
Add logging to confirm execution order:
function styles() {
console.log('[styles] Starting Sass compilation');
return src('src/scss/**/*.scss')
.pipe(sass().on('error', sass.logError))
.pipe(concat('main.min.css'))
.pipe(dest('dist/css'))
.on('end', () => console.log('[styles] Complete'));
}
Run npx gulp --no-color and verify the console output matches your intended sequence.
Common Pitfalls and Recovery
Task finishes too early: If a task uses gulp.src() but doesn't return the stream, Gulp assumes it completed instantly. Always return the stream or call the callback:
function badTask(done) {
gulp.src('src/*.txt').pipe(dest('dist/')); // Missing return
// done(); // Must call or return stream
}
function goodTask() {
return gulp.src('src/*.txt').pipe(dest('dist/'));
}
Watch loop on output files: If your watch glob matches generated files (e.g., dist/**/*.css), editing output triggers infinite rebuilds. Exclude the output directory:
watch('src/scss/**/*.scss', styles); // Good
watch('**/*.scss', styles); // Bad if src and dist overlap
Expected Checks
- Run
npx gulp --tasks-simple— should listdefaultandwatchtasks - After
npx gulp, verifydist/css/main.min.cssanddist/js/app.jsexist - With
npx gulp watch, edit a.scssfile and confirm only the styles task re-runs
Limitations
This guide assumes Gulp 4. If your project uses Gulp 3, the gulp.series and gulp.parallel functions don't exist. Migrate by converting dependency arrays to explicit series/parallel calls. Also, using a globally installed gulp CLI may resolve to a different major version than your local dependency; always prefer npx gulp or ./node_modules/.bin/gulp.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.