Gulp
Build Tool
Gulp
Overview
Gulp is a Node.js-based JavaScript task runner developed by Eric Schoffstall in 2013, designed as a "streaming build system." In contrast to Grunt's configuration-based approach, Gulp adopts a code-based approach and leverages Node.js stream APIs to efficiently execute file transformation processes. Tasks are defined in gulpfile.js and combined with plugins to automate development workflows including Sass/LESS compilation, JavaScript minification, file watching, and live reload. While many projects have migrated to Webpack or Vite, Gulp is still chosen for simple processing tasks.
Details
Key Features
- Streaming Processing: Memory-efficient file transformation processing
- Code-based Configuration: Flexible task definition using JavaScript
- Plugin Ecosystem: Rich collection of over 4,000 plugins
- File Watching: File change detection and automatic task execution
- Parallel & Serial Processing: Efficient task execution order control
- Glob Patterns: Flexible file selection functionality
- Error Handling: Detailed error processing and debugging features
Architecture
Built on Node.js stream APIs with file system abstraction through vinyl-fs. Efficient processing flow through plugin transformation pipelines and gulp task system.
Ecosystem
gulp-* plugin collection, gulp-cli, vinyl, orchestrator. Currently superseded by modern bundlers like Webpack, Rollup, and Parcel.
Pros and Cons
Pros
- High Performance: Fast file operations through streaming processing
- Code-based: Intuitive task description using JavaScript knowledge
- Low Learning Curve: Easy to understand with Node.js knowledge
- Flexibility: Programmable control and customization
- Memory Efficiency: In-memory processing without creating intermediate files
- Rich Plugins: Extensive plugins for diverse use cases
- Easy Debugging: Standard debugging techniques available with JavaScript
Cons
- Learning Curve: Understanding of streaming concepts required
- Configuration Complexity: Complex configuration in large projects
- Plugin Dependencies: Many features depend on plugins
- Gap with Modern Tools: Feature gaps compared to Webpack and Vite
- Maintenance Status: Stagnating new feature development
- Error Handling: Difficult error tracking in stream processing
Reference Links
- Gulp Official Site
- Gulp Quick Start
- Gulp API Documentation
- Gulp Plugin Search
- Gulp GitHub Repository
- Gulp Recipes
Usage Examples
Installation and Project Setup
# Install Gulp CLI globally
npm install -g gulp-cli
# Install Gulp in project
npm install gulp --save-dev
# Initialize project
npm init -y
# Install basic plugins
npm install gulp-sass --save-dev
npm install gulp-uglify --save-dev
npm install gulp-concat --save-dev
npm install gulp-cssnano --save-dev
npm install gulp-imagemin --save-dev
npm install gulp-watch --save-dev
npm install browser-sync --save-dev
# Create gulpfile.js
touch gulpfile.js
# Run tasks
gulp # Run default task
gulp build # Run build task
gulp watch # Run watch task
gulp --tasks # Show task list
Basic gulpfile.js
const gulp = require('gulp');
const sass = require('gulp-sass')(require('sass'));
const uglify = require('gulp-uglify');
const concat = require('gulp-concat');
const cssnano = require('gulp-cssnano');
const imagemin = require('gulp-imagemin');
const browserSync = require('browser-sync').create();
// Path definitions
const paths = {
styles: {
src: 'src/scss/**/*.scss',
dest: 'dist/css/'
},
scripts: {
src: 'src/js/**/*.js',
dest: 'dist/js/'
},
images: {
src: 'src/images/**/*',
dest: 'dist/images/'
},
html: {
src: 'src/**/*.html',
dest: 'dist/'
}
};
// Sass compilation task
function styles() {
return gulp.src(paths.styles.src)
.pipe(sass({
outputStyle: 'expanded',
includePaths: ['node_modules']
}))
.on('error', sass.logError)
.pipe(cssnano())
.pipe(gulp.dest(paths.styles.dest))
.pipe(browserSync.stream());
}
// JavaScript processing task
function scripts() {
return gulp.src(paths.scripts.src)
.pipe(concat('app.js'))
.pipe(uglify())
.pipe(gulp.dest(paths.scripts.dest))
.pipe(browserSync.stream());
}
// Image optimization task
function images() {
return gulp.src(paths.images.src)
.pipe(imagemin([
imagemin.gifsicle({interlaced: true}),
imagemin.mozjpeg({quality: 80, progressive: true}),
imagemin.optipng({optimizationLevel: 5}),
imagemin.svgo({
plugins: [
{removeViewBox: true},
{cleanupIDs: false}
]
})
]))
.pipe(gulp.dest(paths.images.dest));
}
// HTML copy task
function html() {
return gulp.src(paths.html.src)
.pipe(gulp.dest(paths.html.dest))
.pipe(browserSync.stream());
}
// Browser sync task
function serve(done) {
browserSync.init({
server: {
baseDir: './dist'
},
port: 3000
});
done();
}
// File watching task
function watchFiles() {
gulp.watch(paths.styles.src, styles);
gulp.watch(paths.scripts.src, scripts);
gulp.watch(paths.images.src, images);
gulp.watch(paths.html.src, html);
}
// Task exports
exports.styles = styles;
exports.scripts = scripts;
exports.images = images;
exports.html = html;
exports.serve = serve;
exports.watch = watchFiles;
// Composite tasks
const build = gulp.series(
gulp.parallel(styles, scripts, images, html)
);
const dev = gulp.series(
build,
gulp.parallel(serve, watchFiles)
);
// Default task
exports.build = build;
exports.dev = dev;
exports.default = dev;
TypeScript and Linting Integration
const gulp = require('gulp');
const ts = require('gulp-typescript');
const eslint = require('gulp-eslint');
const sourcemaps = require('gulp-sourcemaps');
const browserify = require('browserify');
const source = require('vinyl-source-stream');
const buffer = require('vinyl-buffer');
const uglify = require('gulp-uglify');
const rename = require('gulp-rename');
// TypeScript project configuration
const tsProject = ts.createProject('tsconfig.json');
// Path configuration
const paths = {
typescript: {
src: 'src/ts/**/*.ts',
dest: 'dist/js/'
},
javascript: {
src: 'src/js/**/*.js',
dest: 'dist/js/'
}
};
// TypeScript compilation
function typescript() {
return gulp.src(paths.typescript.src)
.pipe(sourcemaps.init())
.pipe(tsProject())
.on('error', function(error) {
console.log('TypeScript compilation error:', error.toString());
this.emit('end');
})
.pipe(uglify())
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest(paths.typescript.dest));
}
// ESLint
function lint() {
return gulp.src([paths.javascript.src, paths.typescript.src])
.pipe(eslint())
.pipe(eslint.format())
.pipe(eslint.failAfterError());
}
// Browserify bundling
function bundle() {
return browserify({
entries: 'src/js/main.js',
debug: true
})
.bundle()
.pipe(source('bundle.js'))
.pipe(buffer())
.pipe(sourcemaps.init({loadMaps: true}))
.pipe(uglify())
.pipe(rename({suffix: '.min'}))
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest('dist/js/'));
}
// Test execution
function test() {
return gulp.src('test/**/*.js')
.pipe(require('gulp-mocha')({
reporter: 'spec',
timeout: 5000
}));
}
// Watch and live reload
function watchFiles() {
gulp.watch(paths.typescript.src, gulp.series(lint, typescript));
gulp.watch(paths.javascript.src, gulp.series(lint, bundle));
gulp.watch('test/**/*.js', test);
}
// Task exports
exports.typescript = typescript;
exports.lint = lint;
exports.bundle = bundle;
exports.test = test;
exports.watch = watchFiles;
// Composite tasks
exports.build = gulp.series(
lint,
gulp.parallel(typescript, bundle)
);
exports.dev = gulp.series(
exports.build,
gulp.parallel(test, watchFiles)
);
exports.default = exports.dev;
Advanced Workflow and Plugin Integration
const gulp = require('gulp');
const sass = require('gulp-sass')(require('sass'));
const postcss = require('gulp-postcss');
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');
const webpack = require('webpack-stream');
const babel = require('gulp-babel');
const imagemin = require('gulp-imagemin');
const webp = require('gulp-webp');
const svgSprite = require('gulp-svg-sprite');
const rev = require('gulp-rev');
const revRewrite = require('gulp-rev-rewrite');
const del = require('del');
const notify = require('gulp-notify');
const plumber = require('gulp-plumber');
// Environment variables
const isDevelopment = process.env.NODE_ENV !== 'production';
// Error handling
const handleErrors = function() {
return plumber({
errorHandler: notify.onError({
title: 'Gulp Error',
message: '<%= error.message %>'
})
});
};
// Cleanup
function clean() {
return del(['dist/**', '!dist']);
}
// Style processing
function styles() {
const processors = [
autoprefixer({browsers: ['last 2 versions']}),
...(isDevelopment ? [] : [cssnano()])
];
return gulp.src('src/scss/**/*.scss')
.pipe(handleErrors())
.pipe(sass({
includePaths: ['node_modules'],
outputStyle: isDevelopment ? 'expanded' : 'compressed'
}))
.pipe(postcss(processors))
.pipe(gulp.dest('dist/css/'));
}
// JavaScript processing (Webpack integration)
function scripts() {
return gulp.src('src/js/main.js')
.pipe(handleErrors())
.pipe(webpack({
mode: isDevelopment ? 'development' : 'production',
output: {
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
},
devtool: isDevelopment ? 'source-map' : false
}))
.pipe(gulp.dest('dist/js/'));
}
// Image optimization and WebP conversion
function images() {
return gulp.src('src/images/**/*.{jpg,jpeg,png,gif}')
.pipe(imagemin([
imagemin.mozjpeg({quality: 80, progressive: true}),
imagemin.optipng({optimizationLevel: 5}),
imagemin.gifsicle({interlaced: true})
]))
.pipe(gulp.dest('dist/images/'))
.pipe(webp({quality: 80}))
.pipe(gulp.dest('dist/images/'));
}
// SVG sprite generation
function svgSpriteGeneration() {
return gulp.src('src/icons/**/*.svg')
.pipe(svgSprite({
mode: {
symbol: {
sprite: '../sprite.svg',
example: {
dest: '../sprite-preview.html'
}
}
}
}))
.pipe(gulp.dest('dist/images/'));
}
// Asset revision (cache busting)
function revision() {
return gulp.src(['dist/**/*.{css,js,jpg,jpeg,png,gif,webp,svg}'])
.pipe(rev())
.pipe(gulp.dest('dist/'))
.pipe(rev.manifest())
.pipe(gulp.dest('dist/'));
}
// Update asset references in HTML
function revisionRewrite() {
const manifest = gulp.src('dist/rev-manifest.json');
return gulp.src('dist/**/*.html')
.pipe(revRewrite({manifest}))
.pipe(gulp.dest('dist/'));
}
// Development server
function serve(done) {
const browserSync = require('browser-sync').create();
browserSync.init({
server: {
baseDir: './dist'
},
port: 3000,
open: true,
notify: false
});
// File watching
gulp.watch('src/scss/**/*.scss', styles);
gulp.watch('src/js/**/*.js', scripts);
gulp.watch('src/images/**/*', images);
gulp.watch('src/**/*.html', copyHtml);
// Browser reload
gulp.watch('dist/**/*').on('change', browserSync.reload);
done();
}
// HTML copying
function copyHtml() {
return gulp.src('src/**/*.html')
.pipe(gulp.dest('dist/'));
}
// File watching
function watchFiles() {
gulp.watch('src/scss/**/*.scss', styles);
gulp.watch('src/js/**/*.js', scripts);
gulp.watch('src/images/**/*', gulp.series(images, svgSpriteGeneration));
gulp.watch('src/**/*.html', copyHtml);
}
// Task exports
exports.clean = clean;
exports.styles = styles;
exports.scripts = scripts;
exports.images = images;
exports.svgSprite = svgSpriteGeneration;
exports.serve = serve;
exports.watch = watchFiles;
// Development build
exports.dev = gulp.series(
clean,
gulp.parallel(styles, scripts, images, svgSpriteGeneration, copyHtml),
serve
);
// Production build
exports.build = gulp.series(
clean,
gulp.parallel(styles, scripts, images, svgSpriteGeneration, copyHtml),
revision,
revisionRewrite
);
// Default task
exports.default = exports.dev;
Custom Plugins and Stream Operations
const gulp = require('gulp');
const through2 = require('through2');
const Vinyl = require('vinyl');
const path = require('path');
// Custom plugin example: Add file information
function addFileInfo() {
return through2.obj(function(file, encoding, callback) {
if (file.isNull()) {
return callback(null, file);
}
if (file.isBuffer()) {
const info = {
filename: path.basename(file.path),
size: file.contents.length,
modified: file.stat ? file.stat.mtime : new Date()
};
// Add file information as header comment
const header = `/* File: ${info.filename}, Size: ${info.size} bytes, Modified: ${info.modified} */\n`;
file.contents = Buffer.concat([
Buffer.from(header),
file.contents
]);
}
callback(null, file);
});
}
// Custom plugin: Bundle multiple files into one
function bundleFiles(filename) {
let files = [];
return through2.obj(
function(file, encoding, callback) {
if (file.isNull()) {
return callback();
}
files.push(file);
callback();
},
function(callback) {
if (files.length === 0) {
return callback();
}
const bundled = new Vinyl({
path: filename,
contents: Buffer.concat(files.map(f => f.contents))
});
this.push(bundled);
callback();
}
);
}
// Conditional pipeline
function conditionalPipe(condition, transform) {
return condition ? transform : through2.obj(function(file, encoding, callback) {
callback(null, file);
});
}
// Parallel stream processing
function processFiles() {
const jsStream = gulp.src('src/js/**/*.js')
.pipe(addFileInfo())
.pipe(conditionalPipe(process.env.NODE_ENV === 'production', require('gulp-uglify')()))
.pipe(gulp.dest('dist/js/'));
const cssStream = gulp.src('src/css/**/*.css')
.pipe(addFileInfo())
.pipe(conditionalPipe(process.env.NODE_ENV === 'production', require('gulp-cssnano')()))
.pipe(gulp.dest('dist/css/'));
return require('merge-stream')(jsStream, cssStream);
}
// File transformation stream
function transformMarkdown() {
const marked = require('marked');
return through2.obj(function(file, encoding, callback) {
if (file.isNull()) {
return callback(null, file);
}
if (file.isBuffer() && path.extname(file.path) === '.md') {
const html = marked(file.contents.toString());
file.contents = Buffer.from(html);
file.path = file.path.replace('.md', '.html');
}
callback(null, file);
});
}
// Complex file operations example
function advancedProcessing() {
return gulp.src('src/**/*.{js,css,md}')
.pipe(through2.obj(function(file, encoding, callback) {
// File type-specific branching
const ext = path.extname(file.path);
switch(ext) {
case '.js':
// JavaScript-specific processing
console.log(`Processing JavaScript: ${file.path}`);
break;
case '.css':
// CSS-specific processing
console.log(`Processing CSS: ${file.path}`);
break;
case '.md':
// Markdown processing
console.log(`Processing Markdown: ${file.path}`);
break;
}
callback(null, file);
}))
.pipe(transformMarkdown())
.pipe(gulp.dest('dist/'));
}
// Stream with error handling
function robustProcessing() {
return gulp.src('src/**/*.js')
.pipe(through2.obj(function(file, encoding, callback) {
try {
// Some processing
const processed = someComplexProcessing(file.contents);
file.contents = processed;
callback(null, file);
} catch (error) {
// Propagate error to stream
callback(new Error(`Processing failed for ${file.path}: ${error.message}`));
}
}))
.on('error', function(error) {
console.error('Stream error:', error.message);
this.emit('end'); // End stream normally
})
.pipe(gulp.dest('dist/'));
}
// Mock complex processing function
function someComplexProcessing(contents) {
// Actual processing logic
return contents;
}
// Task exports
exports.addInfo = function() {
return gulp.src('src/**/*.js')
.pipe(addFileInfo())
.pipe(gulp.dest('dist/'));
};
exports.bundle = function() {
return gulp.src('src/lib/**/*.js')
.pipe(bundleFiles('bundle.js'))
.pipe(gulp.dest('dist/'));
};
exports.process = processFiles;
exports.markdown = advancedProcessing;
exports.robust = robustProcessing;
exports.default = processFiles;
Performance Optimization and Debugging
const gulp = require('gulp');
const log = require('fancy-log');
const colors = require('ansi-colors');
const size = require('gulp-size');
const debug = require('gulp-debug');
const cache = require('gulp-cache');
const remember = require('gulp-remember');
const cached = require('gulp-cached');
const newer = require('gulp-newer');
const filter = require('gulp-filter');
// Performance measurement
function measurePerformance(taskName) {
const start = Date.now();
return through2.obj(function(file, encoding, callback) {
callback(null, file);
}, function(callback) {
const duration = Date.now() - start;
log(colors.blue(`Task ${taskName} completed in ${duration}ms`));
callback();
});
}
// Incremental builds
function incrementalStyles() {
return gulp.src('src/scss/**/*.scss')
.pipe(cached('styles')) // Cache check
.pipe(debug({title: 'Processing:'}))
.pipe(sass())
.pipe(remember('styles')) // Save to cache
.pipe(concat('main.css'))
.pipe(gulp.dest('dist/css/'))
.pipe(size({showFiles: true, title: 'Styles'}));
}
// Process only changed files
function onlyChanged() {
return gulp.src('src/images/**/*')
.pipe(newer('dist/images/')) // Only newer files
.pipe(debug({title: 'New images:'}))
.pipe(imagemin())
.pipe(gulp.dest('dist/images/'))
.pipe(size({showFiles: true, title: 'Images'}));
}
// Conditional filtering
function conditionalProcessing() {
const jsFilter = filter('**/*.js', {restore: true});
const cssFilter = filter('**/*.css', {restore: true});
return gulp.src('src/**/*.{js,css}')
.pipe(jsFilter)
.pipe(debug({title: 'JS files:'}))
.pipe(uglify())
.pipe(jsFilter.restore)
.pipe(cssFilter)
.pipe(debug({title: 'CSS files:'}))
.pipe(cssnano())
.pipe(cssFilter.restore)
.pipe(gulp.dest('dist/'));
}
// Memory usage monitoring
function monitorMemory() {
const memUsage = process.memoryUsage();
log(colors.yellow(`Memory usage: ${Math.round(memUsage.heapUsed / 1024 / 1024)}MB`));
}
// Optimized watching
function optimizedWatch() {
// Initial build
gulp.series(incrementalStyles)();
gulp.watch('src/scss/**/*.scss', incrementalStyles)
.on('unlink', function(filepath) {
// Clear cache on file deletion
delete cached.caches.styles[filepath];
remember.forget('styles', filepath);
});
}
// Task with debug information
function debugBuild() {
return gulp.src('src/**/*')
.pipe(debug({
title: 'Files:',
showCount: true,
showFiles: true
}))
.pipe(measurePerformance('debug-build'))
.pipe(gulp.dest('dist/'))
.pipe(size({
title: 'Total size:',
showFiles: false,
showTotal: true
}));
}
// Task exports
exports.incremental = incrementalStyles;
exports.changed = onlyChanged;
exports.conditional = conditionalProcessing;
exports.debug = debugBuild;
exports.watch = optimizedWatch;
// Default task with performance monitoring
exports.default = gulp.series(
function startMessage(done) {
log(colors.green('Starting optimized build...'));
monitorMemory();
done();
},
gulp.parallel(incrementalStyles, onlyChanged),
function endMessage(done) {
log(colors.green('Build completed!'));
monitorMemory();
done();
}
);