Gulp

Task RunnerJavaScriptStreamingAutomationFrontendNode.js

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

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();
  }
);