Master Gulp 4 Task Orchestration: series, parallel, and incremental builds
Learn how to orchestrate Gulp 4 tasks with series and parallel, handle errors, and enable incremental builds for faster, reliable JavaScript/TypeScript pipelines.
16 Jan 2026, 14:38 UTC

Desired outcome
By the end of this guide you will be able to build a reliable, fast, and maintainable Gulp 4 pipeline that:
- Runs tasks in the exact order you specify using
gulp.series. - Runs independent tasks concurrently with
gulp.parallelto reduce build time. - Handles errors gracefully so that a failure stops the chain and reports the problem.
- Processes only changed files with
gulp-changedorgulp-newerto enable incremental builds. - Integrates a watch loop that rebuilds on file changes and triggers a live reload.
Prerequisites
- Node.js 10+ (Gulp 4 requires at least Node 8, but newer LTS releases are recommended).
- A project with
package.jsonand a source tree (e.g.,src/for TypeScript/JS,assets/for images). - Basic knowledge of npm scripts and command‑line usage.
Setting Up a Basic Pipeline
- Install Gulp CLI globally (for convenience) and as a dev dependency in the project.
npm install --global gulp-cli npm install --save-dev gulp - Create a
gulpfile.jsat the project root. Use CommonJS or ESM; this guide uses CommonJS for maximum compatibility.const { src, dest, series, parallel, watch } = require('gulp'); const ts = require('gulp-typescript'); const changed = require('gulp-changed'); const newer = require('gulp-newer'); const browserSync = require('browser-sync').create(); // TypeScript project configuration const tsProject = ts.createProject('tsconfig.json'); - Define the core tasks. Each task must return a stream, a promise, or be declared async.
// Clean the output directory function clean(cb) { // Using the built‑in fs module for simplicity const fs = require('fs'); const out = 'dist'; if (fs.existsSync(out)) { fs.rmdirSync(out, { recursive: true }); } cb(); } // Compile TypeScript function compile() { return src('src/**/*.ts') .pipe(changed('dist')) .pipe(tsProject()) .on('error', function (err) { console.error(err.toString()); this.emit('end'); }) .pipe(dest('dist')); } // Copy static assets function assets() { return src('assets/**/*') .pipe(newer('dist/assets')) .pipe(dest('dist/assets')); } // Serve with live reload function serve(cb) { browserSync.init({ server: { baseDir: 'dist' } }); cb(); } // Reload browser when files change function reload(done) { browserSync.reload(); done(); }
Using series() and parallel()
Gulp 4 replaces the old task‑dependency array with explicit composition. The following export shows a typical build flow:
exports.build = series(
clean,
parallel(compile, assets),
serve
);
Execution order:
- Run
clean. - Run
compileandassetsat the same time. - After both finish, start
serve.
Because each task returns a stream or calls cb(), Gulp knows when a step finishes and can trigger the next. If any task throws an error, the chain stops and Gulp emits an error event.
Error Handling
Uncaught errors in streams will crash the process. Attach an .on('error') handler or use async/await with try/catch. Example:
function riskyTask() {
return src('src/**/*.js')
.pipe(/* plugin that may error */)
.on('error', err => {
console.error('riskyTask error:', err.message);
this.emit('end'); // allow the series to continue or stop based on policy
})
.pipe(dest('dist'));
}
For async functions, simply throw or reject:
async function asyncTask() {
try {
await someAsyncOperation();
} catch (err) {
throw err; // Gulp will catch and stop the series
}
}
Incremental Builds
Processing only changed files saves time. Use gulp-changed for simple mtime/size checks or gulp-newer for more robust comparisons. The lastRun helper can skip entire tasks if nothing has changed since the last execution.
const { lastRun } = require('gulp');
function lint() {
return src(['src/**/*.ts', '!node_modules/**'], { since: lastRun(lint) })
.pipe(/* lint plugin */)
.pipe(/* fail on error */);
}
Watching for Changes
Set up a watch that triggers the build pipeline and reloads the browser.
exports.watch = series(
build,
function watchFiles() {
watch('src/**/*.ts', series(compile, reload));
watch('assets/**/*', series(assets, reload));
}
);
Run gulp watch. The first run builds everything; subsequent changes only rebuild the affected parts.
Common Pitfalls and Recovery
- Forgot to return a stream or promise: Gulp will think the task finished instantly, causing race conditions. Verify by checking console logs for order.
- Parallel tasks writing to the same folder: Use
merge-streamor separate output directories to avoid file clashes. - Unhandled stream errors: Attach
.on('error')or useplumberto keep the watch alive. - Node version mismatch: Run
node -v; if below 8.0.0, upgrade or stay on Gulp 3.x (unmaintained). - Plugin incompatibility: Ensure all plugins support Gulp 4; many older plugins have been updated.
Expected Checks and Verification
- Run
gulp --tasksto confirm exported tasks appear. - Execute
gulp buildand observe the console: tasks should run in the defined order. - Introduce a deliberate error in
compile(e.g., syntax error in a .ts file) and verify the series stops with an error message. - After a successful build, modify a single source file and run
gulp watchto confirm only that file’s pipeline runs (check timestamps indist). - Inspect
distto ensure assets are copied correctly and browser reloads when files change.
Summary
Gulp 4’s series and parallel give you explicit control over task ordering and concurrency. Combine them with robust error handling and incremental build plugins to create fast, reliable build pipelines for JavaScript and TypeScript projects. Test each step, verify the order, and watch for subtle race conditions when tasks share output directories.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.