Skip to content

Grunt Cheatsheet

Grunt - The JavaScript Task Runner

Grunt is a JavaScript task runner that automates repetitive tasks like minification, compilation, unit testing, and linting. It uses a configuration-over-code approach with a vast ecosystem of plugins to handle virtually any task.

Table of Contents

Installation

Global Installation

# Install Grunt CLI globally
npm install -g grunt-cli

# Verify installation
grunt --version

Local Installation

# Initialize npm project
npm init -y

# Install Grunt locally
npm install --save-dev grunt

# Install common plugins
npm install --save-dev grunt-contrib-uglify grunt-contrib-cssmin grunt-contrib-concat grunt-contrib-watch

Project Setup

# Create project structure
mkdir my-grunt-project
cd my-grunt-project

# Initialize package.json
npm init -y

# Install Grunt and essential plugins
npm install --save-dev grunt grunt-contrib-uglify grunt-contrib-cssmin grunt-contrib-concat grunt-contrib-copy grunt-contrib-clean grunt-contrib-watch grunt-contrib-connect

# Create Gruntfile
touch Gruntfile.js

# Create source directories
mkdir -p src/{css,js,images,html}
mkdir dist

Package.json Configuration

{
  "name": "my-grunt-project",
  "version": "1.0.0",
  "description": "A Grunt-powered project",
  "scripts": {
    "build": "grunt build",
    "dev": "grunt dev",
    "watch": "grunt watch",
    "serve": "grunt serve",
    "test": "grunt test"
  },
  "devDependencies": {
    "grunt": "^1.6.1",
    "grunt-contrib-uglify": "^5.2.2",
    "grunt-contrib-cssmin": "^4.0.0",
    "grunt-contrib-concat": "^2.1.0",
    "grunt-contrib-copy": "^1.0.0",
    "grunt-contrib-clean": "^2.0.1",
    "grunt-contrib-watch": "^1.1.0",
    "grunt-contrib-connect": "^3.0.0",
    "grunt-contrib-jshint": "^3.2.0",
    "grunt-sass": "^3.1.0"
  }
}

Getting Started

Basic Gruntfile

// Gruntfile.js
module.exports = function(grunt) {

  // Project configuration
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),

    // Concatenate files
    concat: {
      js: {
        src: ['src/js/**/*.js'],
        dest: 'dist/js/bundle.js'
      },
      css: {
        src: ['src/css/**/*.css'],
        dest: 'dist/css/styles.css'
      }
    },

    // Minify JavaScript
    uglify: {
      options: {
        banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
      },
      build: {
        src: 'dist/js/bundle.js',
        dest: 'dist/js/bundle.min.js'
      }
    },

    // Minify CSS
    cssmin: {
      target: {
        files: {
          'dist/css/styles.min.css': ['dist/css/styles.css']
        }
      }
    },

    // Watch for changes
    watch: {
      js: {
        files: ['src/js/**/*.js'],
        tasks: ['concat:js', 'uglify']
      },
      css: {
        files: ['src/css/**/*.css'],
        tasks: ['concat:css', 'cssmin']
      }
    }
  });

  // Load plugins
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-cssmin');
  grunt.loadNpmTasks('grunt-contrib-watch');

  // Register tasks
  grunt.registerTask('default', ['concat', 'uglify', 'cssmin']);
  grunt.registerTask('build', ['concat', 'uglify', 'cssmin']);
  grunt.registerTask('dev', ['build', 'watch']);
};

Running Tasks

# Run default task
grunt

# Run specific task
grunt uglify
grunt cssmin

# Run multiple tasks
grunt concat uglify

# Run with options
grunt build --force
grunt watch --verbose

# List available tasks
grunt --help

Project Structure

my-grunt-project/
├── src/
   ├── css/
      ├── main.css
      └── components/
   ├── js/
      ├── main.js
      └── modules/
   ├── images/
   └── html/
├── dist/
   ├── css/
   ├── js/
   ├── images/
   └── index.html
├── Gruntfile.js
└── package.json

Gruntfile Configuration

Configuration Structure

module.exports = function(grunt) {
  grunt.initConfig({
    // Package information
    pkg: grunt.file.readJSON('package.json'),

    // Global options
    globalConfig: {
      src: 'src',
      dist: 'dist'
    },

    // Task configurations
    taskName: {
      options: {
        // Global options for this task
      },
      target1: {
        options: {
          // Target-specific options
        },
        src: 'source/files',
        dest: 'destination/file'
      },
      target2: {
        // Another target configuration
      }
    }
  });
};

Advanced Configuration

module.exports = function(grunt) {

  // Configuration
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),

    // Global configuration
    config: {
      src: 'src',
      dist: 'dist',
      tmp: '.tmp'
    },

    // Clean task
    clean: {
      dist: ['<%= config.dist %>'],
      tmp: ['<%= config.tmp %>']
    },

    // Copy task
    copy: {
      html: {
        expand: true,
        cwd: '<%= config.src %>',
        src: '**/*.html',
        dest: '<%= config.dist %>'
      },
      images: {
        expand: true,
        cwd: '<%= config.src %>/images',
        src: '**/*',
        dest: '<%= config.dist %>/images'
      }
    },

    // Sass compilation
    sass: {
      options: {
        implementation: require('sass'),
        sourceMap: true
      },
      dist: {
        files: {
          '<%= config.dist %>/css/main.css': '<%= config.src %>/scss/main.scss'
        }
      }
    },

    // PostCSS processing
    postcss: {
      options: {
        processors: [
          require('autoprefixer')({browsers: 'last 2 versions'}),
          require('cssnano')()
        ]
      },
      dist: {
        src: '<%= config.dist %>/css/*.css'
      }
    }
  });

  // Load plugins
  require('load-grunt-tasks')(grunt);

  // Custom tasks
  grunt.registerTask('build', [
    'clean:dist',
    'copy',
    'sass',
    'postcss',
    'uglify',
    'cssmin'
  ]);

  grunt.registerTask('default', ['build']);
};

Template Processing

grunt.initConfig({
  pkg: grunt.file.readJSON('package.json'),

  // Using templates
  concat: {
    options: {
      banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' +
        '<%= grunt.template.today("yyyy-mm-dd") %> */\n'
    },
    dist: {
      src: ['src/js/**/*.js'],
      dest: 'dist/js/<%= pkg.name %>.js'
    }
  },

  // Dynamic file mapping
  uglify: {
    dynamic: {
      files: [{
        expand: true,
        cwd: 'src/js/',
        src: '**/*.js',
        dest: 'dist/js/',
        ext: '.min.js'
      }]
    }
  }
});

Core Tasks

File Operations

// Clean files and directories
clean: {
  build: ['dist/'],
  tmp: ['.tmp/'],
  css: ['dist/css/**/*.css'],
  js: ['dist/js/**/*.js']
}

// Copy files
copy: {
  main: {
    files: [
      // Copy all files from src to dist
      {
        expand: true,
        cwd: 'src/',
        src: ['**'],
        dest: 'dist/'
      },
      // Copy specific files
      {
        expand: true,
        cwd: 'src/assets/',
        src: ['fonts/**', 'images/**'],
        dest: 'dist/assets/'
      }
    ]
  },

  // Copy with renaming
  rename: {
    files: [{
      expand: true,
      cwd: 'src/',
      src: ['**/*.txt'],
      dest: 'dist/',
      ext: '.bak'
    }]
  }
}

// Concatenate files
concat: {
  options: {
    separator: ';',
    stripBanners: true,
    banner: '/*! <%= pkg.name %> - v<%= pkg.version %> */\n'
  },
  js: {
    src: [
      'src/js/vendor/*.js',
      'src/js/modules/*.js',
      'src/js/main.js'
    ],
    dest: 'dist/js/bundle.js'
  },
  css: {
    src: ['src/css/**/*.css'],
    dest: 'dist/css/styles.css'
  }
}

File Filtering

// File filtering and processing
copy: {
  filterFiles: {
    files: [{
      expand: true,
      cwd: 'src/',
      src: ['**/*'],
      dest: 'dist/',
      filter: function(filepath) {
        // Only copy files modified in last 24 hours
        return (Date.now() - fs.statSync(filepath).mtime) < 24*60*60*1000;
      }
    }]
  }
}

// Conditional file processing
uglify: {
  conditional: {
    files: [{
      expand: true,
      cwd: 'src/js/',
      src: ['**/*.js'],
      dest: 'dist/js/',
      ext: '.min.js',
      filter: function(filepath) {
        // Only minify files larger than 1KB
        return fs.statSync(filepath).size > 1024;
      }
    }]
  }
}

CSS Tasks

Sass Compilation

// Sass compilation
sass: {
  options: {
    implementation: require('sass'),
    sourceMap: true,
    outputStyle: 'expanded'
  },

  // Development build
  dev: {
    options: {
      sourceMap: true,
      outputStyle: 'expanded'
    },
    files: {
      'dist/css/main.css': 'src/scss/main.scss'
    }
  },

  // Production build
  prod: {
    options: {
      sourceMap: false,
      outputStyle: 'compressed'
    },
    files: {
      'dist/css/main.min.css': 'src/scss/main.scss'
    }
  },

  // Multiple files
  multiple: {
    files: [{
      expand: true,
      cwd: 'src/scss/',
      src: ['*.scss'],
      dest: 'dist/css/',
      ext: '.css'
    }]
  }
}

// Less compilation
less: {
  development: {
    options: {
      paths: ['src/less/includes']
    },
    files: {
      'dist/css/main.css': 'src/less/main.less'
    }
  },

  production: {
    options: {
      paths: ['src/less/includes'],
      cleancss: true,
      modifyVars: {
        imgPath: '"http://mycdn.com/path/to/images"',
        bgColor: 'red'
      }
    },
    files: {
      'dist/css/main.min.css': 'src/less/main.less'
    }
  }
}

CSS Processing

// CSS minification
cssmin: {
  options: {
    mergeIntoShorthands: false,
    roundingPrecision: -1
  },
  target: {
    files: {
      'dist/css/main.min.css': ['src/css/**/*.css']
    }
  },

  // Multiple targets
  multiple: {
    files: [{
      expand: true,
      cwd: 'src/css/',
      src: ['*.css', '!*.min.css'],
      dest: 'dist/css/',
      ext: '.min.css'
    }]
  }
}

// Autoprefixer
autoprefixer: {
  options: {
    browsers: ['last 2 versions', 'ie 8', 'ie 9']
  },
  single_file: {
    src: 'dist/css/main.css',
    dest: 'dist/css/main.prefixed.css'
  },
  multiple_files: {
    expand: true,
    flatten: true,
    src: 'dist/css/*.css',
    dest: 'dist/css/prefixed/'
  }
}

// PostCSS
postcss: {
  options: {
    map: true,
    processors: [
      require('pixrem')(),
      require('autoprefixer')({browsers: 'last 2 versions'}),
      require('cssnano')()
    ]
  },
  dist: {
    src: 'dist/css/*.css'
  }
}

CSS Linting

// CSS Lint
csslint: {
  options: {
    csslintrc: '.csslintrc'
  },
  strict: {
    options: {
      import: 2
    },
    src: ['src/css/**/*.css']
  },
  lax: {
    options: {
      import: false
    },
    src: ['src/css/**/*.css']
  }
}

// Stylelint
stylelint: {
  all: ['src/css/**/*.css'],
  options: {
    configFile: '.stylelintrc',
    formatter: 'string',
    ignoreDisables: false,
    failOnError: true,
    outputFile: '',
    reportNeedlessDisables: false,
    syntax: ''
  }
}

JavaScript Tasks

JavaScript Minification

// UglifyJS
uglify: {
  options: {
    banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n',
    mangle: {
      reserved: ['jQuery', '$']
    },
    compress: {
      drop_console: true
    }
  },

  // Single file
  single: {
    src: 'src/js/main.js',
    dest: 'dist/js/main.min.js'
  },

  // Multiple files
  multiple: {
    files: [{
      expand: true,
      cwd: 'src/js/',
      src: '**/*.js',
      dest: 'dist/js/',
      ext: '.min.js'
    }]
  },

  // With source maps
  sourcemap: {
    options: {
      sourceMap: true,
      sourceMapName: 'dist/js/main.min.js.map'
    },
    src: 'src/js/main.js',
    dest: 'dist/js/main.min.js'
  }
}

// Terser (ES6+ minification)
terser: {
  options: {
    ecma: 2015,
    compress: {
      drop_console: true
    },
    output: {
      comments: false
    }
  },
  main: {
    files: {
      'dist/js/main.min.js': ['src/js/main.js']
    }
  }
}

JavaScript Transpilation

// Babel transpilation
babel: {
  options: {
    sourceMap: true,
    presets: ['@babel/preset-env']
  },
  dist: {
    files: [{
      expand: true,
      cwd: 'src/js/',
      src: ['**/*.js'],
      dest: 'dist/js/',
      ext: '.js'
    }]
  }
}

// TypeScript compilation
typescript: {
  base: {
    src: ['src/ts/**/*.ts'],
    dest: 'dist/js/app.js',
    options: {
      module: 'amd',
      target: 'es5',
      sourceMap: true,
      declaration: true
    }
  }
}

// CoffeeScript compilation
coffee: {
  compile: {
    files: {
      'dist/js/app.js': 'src/coffee/app.coffee'
    }
  },

  multiple: {
    expand: true,
    flatten: true,
    cwd: 'src/coffee/',
    src: ['*.coffee'],
    dest: 'dist/js/',
    ext: '.js'
  }
}

JavaScript Linting

// JSHint
jshint: {
  options: {
    curly: true,
    eqeqeq: true,
    eqnull: true,
    browser: true,
    globals: {
      jQuery: true
    }
  },
  uses_defaults: ['src/js/**/*.js'],
  with_overrides: {
    options: {
      curly: false,
      undef: true
    },
    files: {
      src: ['src/js/specific.js']
    }
  }
}

// ESLint
eslint: {
  options: {
    configFile: '.eslintrc.js',
    rulePaths: ['conf/rules']
  },
  target: ['src/js/**/*.js']
}

// JSLint
jslint: {
  client: {
    src: ['src/js/**/*.js'],
    directives: {
      browser: true,
      predef: ['jQuery']
    }
  }
}

Browserify Integration

browserify: {
  dist: {
    files: {
      'dist/js/bundle.js': ['src/js/main.js']
    },
    options: {
      transform: [['babelify', {presets: ['@babel/preset-env']}]]
    }
  },

  // With external libraries
  vendor: {
    src: [],
    dest: 'dist/js/vendor.js',
    options: {
      require: ['jquery', 'underscore']
    }
  },

  app: {
    src: ['src/js/main.js'],
    dest: 'dist/js/app.js',
    options: {
      external: ['jquery', 'underscore']
    }
  }
}

// Webpack integration
webpack: {
  options: {
    entry: './src/js/main.js',
    output: {
      path: 'dist/js/',
      filename: 'bundle.js'
    },
    module: {
      rules: [{
        test: /\.js$/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      }]
    }
  },
  dev: {
    mode: 'development',
    devtool: 'source-map'
  },
  prod: {
    mode: 'production'
  }
}

Image Processing

Image Optimization

// Image minification
imagemin: {
  dynamic: {
    files: [{
      expand: true,
      cwd: 'src/images/',
      src: ['**/*.{png,jpg,gif,svg}'],
      dest: 'dist/images/'
    }]
  },

  // With options
  options: {
    optimizationLevel: 3,
    svgoPlugins: [{removeViewBox: false}],
    use: [mozjpeg({quality: 80})]
  }
}

// Responsive images
responsive_images: {
  dev: {
    options: {
      engine: 'im',
      sizes: [{
        width: 320,
        suffix: '_small',
        quality: 80
      }, {
        width: 640,
        suffix: '_medium',
        quality: 80
      }, {
        width: 1024,
        suffix: '_large',
        quality: 80
      }]
    },
    files: [{
      expand: true,
      src: ['src/images/**/*.{jpg,gif,png}'],
      cwd: 'src/',
      dest: 'dist/'
    }]
  }
}

// WebP conversion
webp: {
  files: {
    expand: true,
    cwd: 'src/images/',
    src: ['**/*.{png,jpg,jpeg}'],
    dest: 'dist/images/webp/',
    ext: '.webp'
  },
  options: {
    binpath: require('webp-bin'),
    preset: 'photo',
    verbose: true,
    quality: 80,
    alphaQuality: 80,
    compressionMethod: 6,
    segments: 4,
    psnr: 42,
    sns: 50,
    filterStrength: 40,
    filterSharpness: 3,
    simpleFilter: true,
    partitionLimit: 50,
    analysisPass: 6,
    multiThreading: true,
    lowMemory: false,
    alphaMethod: 0,
    alphaFilter: 'best',
    alphaCleanup: true,
    noAlpha: false,
    lossless: false
  }
}

Sprite Generation

// CSS Sprites
sprite: {
  all: {
    src: 'src/images/icons/*.png',
    dest: 'dist/images/spritesheet.png',
    destCss: 'dist/css/sprites.css',
    cssFormat: 'css',
    algorithm: 'binary-tree',
    padding: 2
  }
}

// SVG Sprites
svgstore: {
  options: {
    prefix: 'icon-',
    svg: {
      viewBox: '0 0 100 100',
      xmlns: 'http://www.w3.org/2000/svg'
    }
  },
  default: {
    files: {
      'dist/images/icons.svg': ['src/images/icons/*.svg']
    }
  }
}

// Icon fonts
webfont: {
  icons: {
    src: 'src/images/icons/*.svg',
    dest: 'dist/fonts/',
    destCss: 'dist/css/',
    options: {
      font: 'icons',
      types: 'eot,woff,woff2,ttf,svg',
      syntax: 'bem',
      engine: 'node',
      autoHint: false,
      execMaxBuffer: 1024 * 200,
      htmlDemo: true,
      destHtml: 'dist/'
    }
  }
}

HTML Tasks

HTML Processing

// HTML minification
htmlmin: {
  dist: {
    options: {
      removeComments: true,
      collapseWhitespace: true,
      removeRedundantAttributes: true,
      removeScriptTypeAttributes: true,
      removeStyleLinkTypeAttributes: true,
      minifyJS: true,
      minifyCSS: true
    },
    files: [{
      expand: true,
      cwd: 'src/',
      src: ['**/*.html'],
      dest: 'dist/'
    }]
  }
}

// HTML includes
includes: {
  files: {
    src: ['src/pages/*.html'],
    dest: 'dist/',
    flatten: true,
    cwd: '.',
    options: {
      silent: true,
      includePath: 'src/includes/'
    }
  }
}

// Template processing
template: {
  dev: {
    options: {
      data: {
        title: 'Development Site',
        version: '<%= pkg.version %>',
        timestamp: '<%= grunt.template.today() %>'
      }
    },
    files: [{
      expand: true,
      cwd: 'src/templates/',
      src: ['**/*.html'],
      dest: 'dist/'
    }]
  }
}

HTML Validation

// HTML validation
htmlhint: {
  build: {
    options: {
      'tag-pair': true,
      'tagname-lowercase': true,
      'attr-lowercase': true,
      'attr-value-double-quotes': true,
      'doctype-first': true,
      'spec-char-escape': true,
      'id-unique': true,
      'head-script-disabled': true,
      'style-disabled': true
    },
    src: ['dist/**/*.html']
  }
}

// W3C validation
validation: {
  options: {
    reset: grunt.option('reset') || false,
    stoponerror: false,
    remotePath: 'http://localhost:9001/',
    remoteFiles: ['index.html', 'about.html'],
    relaxerror: ['Bad value X-UA-Compatible for attribute http-equiv on element meta.']
  },
  files: {
    src: ['dist/**/*.html']
  }
}

Development Server

Connect Server

// Basic server
connect: {
  server: {
    options: {
      port: 9000,
      hostname: 'localhost',
      base: 'dist',
      open: true
    }
  }
}

// Advanced server configuration
connect: {
  options: {
    port: 9000,
    hostname: 'localhost',
    livereload: 35729
  },

  livereload: {
    options: {
      open: true,
      middleware: function(connect) {
        return [
          connect.static('.tmp'),
          connect().use('/bower_components', connect.static('./bower_components')),
          connect.static('src')
        ];
      }
    }
  },

  test: {
    options: {
      port: 9001,
      middleware: function(connect) {
        return [
          connect.static('.tmp'),
          connect.static('test'),
          connect().use('/bower_components', connect.static('./bower_components')),
          connect.static('src')
        ];
      }
    }
  },

  dist: {
    options: {
      open: true,
      base: 'dist'
    }
  }
}

// Proxy server
connect: {
  proxies: [{
    context: '/api',
    host: 'localhost',
    port: 3000,
    https: false,
    xforward: false,
    headers: {
      'x-custom-added-header': 'value'
    },
    hideHeaders: ['x-removed-header']
  }],

  server: {
    options: {
      port: 9000,
      middleware: function(connect, options) {
        var middlewares = [];
        var directory = options.directory || options.base[options.base.length - 1];

        // Setup the proxy
        middlewares.push(require('grunt-connect-proxy/lib/utils').proxyRequest);

        // Serve static files
        if (!Array.isArray(options.base)) {
          options.base = [options.base];
        }
        options.base.forEach(function(base) {
          middlewares.push(connect.static(base));
        });

        // Make directory browse-able
        middlewares.push(connect.directory(directory));

        return middlewares;
      }
    }
  }
}

Live Reload

// Live reload configuration
watch: {
  options: {
    livereload: true
  },

  html: {
    files: ['src/**/*.html'],
    tasks: ['includes', 'htmlmin']
  },

  css: {
    files: ['src/css/**/*.css'],
    tasks: ['concat:css', 'cssmin']
  },

  js: {
    files: ['src/js/**/*.js'],
    tasks: ['jshint', 'concat:js', 'uglify']
  },

  livereload: {
    options: {
      livereload: '<%= connect.options.livereload %>'
    },
    files: [
      'dist/**/*.html',
      'dist/css/**/*.css',
      'dist/js/**/*.js',
      'dist/images/**/*.{png,jpg,jpeg,gif,webp,svg}'
    ]
  }
}

// Browser Sync integration
browserSync: {
  dev: {
    bsFiles: {
      src: [
        'dist/css/*.css',
        'dist/js/*.js',
        'dist/*.html'
      ]
    },
    options: {
      watchTask: true,
      server: './dist'
    }
  }
}

Watch Tasks

Basic Watch Configuration

watch: {
  // Watch CSS files
  css: {
    files: ['src/css/**/*.css'],
    tasks: ['concat:css', 'cssmin'],
    options: {
      spawn: false
    }
  },

  // Watch JavaScript files
  js: {
    files: ['src/js/**/*.js'],
    tasks: ['jshint', 'concat:js', 'uglify'],
    options: {
      spawn: false
    }
  },

  // Watch Sass files
  sass: {
    files: ['src/scss/**/*.scss'],
    tasks: ['sass', 'autoprefixer', 'cssmin']
  },

  // Watch HTML files
  html: {
    files: ['src/**/*.html'],
    tasks: ['includes', 'htmlmin']
  },

  // Watch images
  images: {
    files: ['src/images/**/*.{png,jpg,gif,svg}'],
    tasks: ['imagemin']
  }
}

Advanced Watch Patterns

watch: {
  options: {
    spawn: false,
    livereload: true
  },

  // Conditional tasks based on file changes
  configFiles: {
    files: ['Gruntfile.js', 'package.json'],
    options: {
      reload: true
    }
  },

  // Watch with different intervals
  slow: {
    files: ['src/large-files/**/*'],
    tasks: ['process-large-files'],
    options: {
      interval: 5000
    }
  },

  // Watch with custom event handling
  custom: {
    files: ['src/special/**/*'],
    tasks: [],
    options: {
      event: ['added', 'deleted']
    }
  }
}

// Dynamic watch configuration
grunt.event.on('watch', function(action, filepath, target) {
  if (target === 'js') {
    // Only process the changed file
    grunt.config('jshint.single.src', filepath);
    grunt.config('uglify.single.src', filepath);
    grunt.config('uglify.single.dest', filepath.replace('src/', 'dist/').replace('.js', '.min.js'));
  }
});

Watch with Error Handling

watch: {
  options: {
    spawn: false,
    interrupt: true
  },

  sass: {
    files: ['src/scss/**/*.scss'],
    tasks: ['sass'],
    options: {
      spawn: false,
      interrupt: true,
      event: ['added', 'deleted', 'changed']
    }
  }
}

// Error handling in tasks
grunt.registerTask('safe-sass', function() {
  try {
    grunt.task.run('sass');
  } catch (e) {
    grunt.log.error('Sass compilation failed: ' + e.message);
    grunt.fail.warn('Sass task failed, but continuing...');
  }
});

Build Pipeline

Development Build

// Development build pipeline
grunt.registerTask('dev', [
  'clean:dist',
  'jshint',
  'sass:dev',
  'autoprefixer',
  'concat',
  'copy:dev',
  'includes',
  'connect:livereload',
  'watch'
]);

// Development build without server
grunt.registerTask('build-dev', [
  'clean:dist',
  'jshint',
  'sass:dev',
  'autoprefixer',
  'concat',
  'copy:dev',
  'includes'
]);

Production Build

// Production build pipeline
grunt.registerTask('build', [
  'clean:dist',
  'jshint',
  'sass:prod',
  'autoprefixer',
  'cssmin',
  'concat',
  'uglify',
  'imagemin',
  'copy:prod',
  'includes',
  'htmlmin',
  'rev',
  'usemin'
]);

// Build with testing
grunt.registerTask('test-build', [
  'build',
  'connect:test',
  'qunit'
]);

// Complete production pipeline
grunt.registerTask('production', [
  'build',
  'test-build',
  'compress',
  'deploy'
]);

Multi-Environment Builds

// Environment-specific configurations
grunt.initConfig({
  env: {
    dev: {
      NODE_ENV: 'development'
    },
    prod: {
      NODE_ENV: 'production'
    }
  },

  // Conditional configuration
  uglify: {
    options: {
      compress: {
        drop_console: '<%= process.env.NODE_ENV === "production" %>'
      }
    },
    build: {
      src: 'dist/js/bundle.js',
      dest: 'dist/js/bundle.min.js'
    }
  }
});

// Environment-specific tasks
grunt.registerTask('build-dev', ['env:dev', 'build-common']);
grunt.registerTask('build-prod', ['env:prod', 'build-common']);
grunt.registerTask('build-common', [
  'clean',
  'sass',
  'concat',
  'uglify',
  'copy'
]);

Plugin Ecosystem

Essential Plugins

# File operations
npm install --save-dev grunt-contrib-clean grunt-contrib-copy grunt-contrib-concat

# CSS processing
npm install --save-dev grunt-sass grunt-contrib-cssmin grunt-autoprefixer

# JavaScript processing
npm install --save-dev grunt-contrib-uglify grunt-contrib-jshint grunt-babel

# HTML processing
npm install --save-dev grunt-contrib-htmlmin grunt-includes

# Image processing
npm install --save-dev grunt-contrib-imagemin grunt-responsive-images

# Development
npm install --save-dev grunt-contrib-watch grunt-contrib-connect grunt-browser-sync

# Build tools
npm install --save-dev grunt-usemin grunt-rev grunt-filerev

Advanced Plugins

// File revisioning
rev: {
  files: {
    src: ['dist/js/**/*.js', 'dist/css/**/*.css']
  }
}

// Asset injection
usemin: {
  html: ['dist/**/*.html'],
  options: {
    assetsDirs: ['dist', 'dist/images']
  }
}

// Compression
compress: {
  main: {
    options: {
      archive: 'dist.zip'
    },
    files: [{
      expand: true,
      cwd: 'dist/',
      src: ['**/*'],
      dest: '.'
    }]
  }
}

// FTP deployment
'ftp-deploy': {
  build: {
    auth: {
      host: 'server.com',
      port: 21,
      authKey: 'key1'
    },
    src: 'dist',
    dest: '/public_html',
    exclusions: ['dist/**/.DS_Store', 'dist/**/Thumbs.db']
  }
}

Plugin Loading

// Load all grunt plugins
require('load-grunt-tasks')(grunt);

// Load specific plugins
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-cssmin');

// Load plugins with pattern
require('load-grunt-tasks')(grunt, {
  pattern: ['grunt-*', '!grunt-legacy-util']
});

// Load plugins from different locations
grunt.loadTasks('custom-tasks');

Custom Tasks

Basic Custom Tasks

// Simple custom task
grunt.registerTask('hello', 'Say hello', function() {
  grunt.log.writeln('Hello, World!');
});

// Task with parameters
grunt.registerTask('greet', 'Greet someone', function(name) {
  if (!name) {
    grunt.log.writeln('Hello, stranger!');
  } else {
    grunt.log.writeln('Hello, ' + name + '!');
  }
});

// Multi-task
grunt.registerMultiTask('log', 'Log stuff', function() {
  grunt.log.writeln(this.target + ': ' + this.data);
});

grunt.initConfig({
  log: {
    foo: 'Hello',
    bar: 'World'
  }
});

Advanced Custom Tasks

// Async task
grunt.registerTask('async-task', 'An async task', function() {
  var done = this.async();

  setTimeout(function() {
    grunt.log.writeln('Async task completed!');
    done();
  }, 1000);
});

// Task with options
grunt.registerTask('custom-build', function() {
  var options = this.options({
    minify: true,
    sourcemap: false
  });

  if (options.minify) {
    grunt.task.run('uglify');
  }

  if (options.sourcemap) {
    grunt.task.run('sourcemap');
  }
});

// File processing task
grunt.registerMultiTask('process-files', function() {
  var options = this.options({
    separator: '\n'
  });

  this.files.forEach(function(file) {
    var src = file.src.filter(function(filepath) {
      if (!grunt.file.exists(filepath)) {
        grunt.log.warn('Source file "' + filepath + '" not found.');
        return false;
      } else {
        return true;
      }
    }).map(function(filepath) {
      return grunt.file.read(filepath);
    }).join(options.separator);

    grunt.file.write(file.dest, src);
    grunt.log.writeln('File "' + file.dest + '" created.');
  });
});

Task Dependencies

// Task with dependencies
grunt.registerTask('build-all', [
  'clean',
  'jshint',
  'sass',
  'concat',
  'uglify',
  'cssmin'
]);

// Conditional task execution
grunt.registerTask('conditional-build', function() {
  if (grunt.option('production')) {
    grunt.task.run(['build-prod']);
  } else {
    grunt.task.run(['build-dev']);
  }
});

// Dynamic task creation
grunt.registerTask('create-tasks', function() {
  var files = grunt.file.expand('src/modules/*.js');

  files.forEach(function(file) {
    var taskName = 'process-' + path.basename(file, '.js');

    grunt.registerTask(taskName, function() {
      // Process individual file
      grunt.log.writeln('Processing: ' + file);
    });
  });
});

Multi-target Tasks

Configuration

grunt.initConfig({
  // Multi-target task configuration
  uglify: {
    options: {
      banner: '/*! Global banner */\n'
    },

    // Target 1: Main application
    app: {
      options: {
        banner: '/*! App banner */\n'
      },
      src: 'src/js/app.js',
      dest: 'dist/js/app.min.js'
    },

    // Target 2: Vendor libraries
    vendor: {
      src: ['src/js/vendor/*.js'],
      dest: 'dist/js/vendor.min.js'
    },

    // Target 3: Dynamic files
    dynamic: {
      files: [{
        expand: true,
        cwd: 'src/js/',
        src: '**/*.js',
        dest: 'dist/js/',
        ext: '.min.js'
      }]
    }
  }
});

// Run specific targets
// grunt uglify:app
// grunt uglify:vendor
// grunt uglify (runs all targets)

Custom Multi-target Tasks

grunt.registerMultiTask('custom-process', 'Process files', function() {
  var options = this.options({
    prefix: '',
    suffix: ''
  });

  grunt.log.writeln('Processing target: ' + this.target);

  this.files.forEach(function(file) {
    var contents = file.src.map(function(src) {
      return grunt.file.read(src);
    }).join('\n');

    var processed = options.prefix + contents + options.suffix;

    grunt.file.write(file.dest, processed);
    grunt.log.writeln('Created: ' + file.dest);
  });
});

grunt.initConfig({
  'custom-process': {
    options: {
      prefix: '/* Global prefix */\n'
    },

    css: {
      options: {
        suffix: '\n/* CSS suffix */'
      },
      files: {
        'dist/css/processed.css': ['src/css/**/*.css']
      }
    },

    js: {
      options: {
        suffix: '\n/* JS suffix */'
      },
      files: {
        'dist/js/processed.js': ['src/js/**/*.js']
      }
    }
  }
});

Template Processing

Built-in Templates

grunt.initConfig({
  pkg: grunt.file.readJSON('package.json'),

  // Using package.json data
  concat: {
    options: {
      banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' +
        '<%= grunt.template.today("yyyy-mm-dd") %> */\n'
    },
    dist: {
      src: ['src/js/**/*.js'],
      dest: 'dist/js/<%= pkg.name %>.js'
    }
  },

  // Date templates
  copy: {
    timestamped: {
      src: 'src/config.js',
      dest: 'dist/config-<%= grunt.template.today("yyyymmdd") %>.js'
    }
  }
});

Custom Templates

// Custom template data
grunt.initConfig({
  config: {
    app: {
      name: 'MyApp',
      version: '1.0.0',
      author: 'John Doe'
    },
    build: {
      timestamp: '<%= grunt.template.today("isoDateTime") %>',
      environment: process.env.NODE_ENV || 'development'
    }
  },

  // Using custom data
  replace: {
    config: {
      src: ['src/js/config.js'],
      dest: 'dist/js/config.js',
      replacements: [{
        from: '{{APP_NAME}}',
        to: '<%= config.app.name %>'
      }, {
        from: '{{VERSION}}',
        to: '<%= config.app.version %>'
      }, {
        from: '{{BUILD_TIME}}',
        to: '<%= config.build.timestamp %>'
      }]
    }
  }
});

// Template processing task
grunt.registerTask('process-templates', function() {
  var config = grunt.config('config');

  grunt.file.expand('src/templates/**/*.html').forEach(function(file) {
    var content = grunt.file.read(file);
    var processed = grunt.template.process(content, {
      data: config
    });

    var dest = file.replace('src/templates/', 'dist/');
    grunt.file.write(dest, processed);
  });
});

Testing Integration

Unit Testing

// QUnit
qunit: {
  files: ['test/**/*.html']
}

// Mocha
mocha: {
  test: {
    src: ['test/**/*.html'],
    options: {
      run: true,
      reporter: 'Spec'
    }
  }
}

// Jasmine
jasmine: {
  src: 'src/js/**/*.js',
  options: {
    specs: 'test/spec/**/*.js',
    helpers: 'test/helpers/**/*.js'
  }
}

// Karma
karma: {
  unit: {
    configFile: 'karma.conf.js'
  },
  continuous: {
    configFile: 'karma.conf.js',
    singleRun: true,
    browsers: ['PhantomJS']
  }
}

Code Coverage

// Istanbul coverage
instrument: {
  files: 'src/js/**/*.js',
  options: {
    lazy: true,
    basePath: 'instrumented/'
  }
}

storeCoverage: {
  options: {
    dir: 'coverage/'
  }
}

makeReport: {
  src: 'coverage/**/*.json',
  options: {
    type: 'lcov',
    dir: 'coverage/',
    print: 'detail'
  }
}

// Coverage task
grunt.registerTask('coverage', [
  'clean:coverage',
  'instrument',
  'qunit',
  'storeCoverage',
  'makeReport'
]);

End-to-End Testing

// Protractor
protractor: {
  options: {
    configFile: 'protractor.conf.js',
    keepAlive: true,
    noColor: false
  },
  e2e: {
    options: {
      args: {
        suite: 'e2e'
      }
    }
  }
}

// WebDriver
webdriver: {
  chrome: {
    tests: ['test/e2e/**/*.js'],
    options: {
      timeout: 10000,
      reporter: 'spec',
      browser: 'chrome'
    }
  }
}

// Nightwatch
nightwatch: {
  options: {
    standalone: true,
    jar_version: '2.53.0',
    jar_url: 'http://selenium-release.storage.googleapis.com/2.53/selenium-server-standalone-2.53.0.jar'
  }
}

Deployment

Build and Deploy

// Build for deployment
grunt.registerTask('deploy-build', [
  'clean',
  'jshint',
  'test',
  'sass:prod',
  'autoprefixer',
  'cssmin',
  'concat',
  'uglify',
  'imagemin',
  'htmlmin',
  'rev',
  'usemin'
]);

// FTP deployment
'ftp-deploy': {
  production: {
    auth: {
      host: 'ftp.example.com',
      port: 21,
      authKey: 'production'
    },
    src: 'dist/',
    dest: '/public_html/',
    exclusions: [
      'dist/**/.DS_Store',
      'dist/**/Thumbs.db',
      'dist/tmp'
    ]
  }
}

// SFTP deployment
sftp: {
  production: {
    files: {
      './': 'dist/**'
    },
    options: {
      path: '/var/www/html/',
      host: 'example.com',
      username: 'deploy',
      privateKey: grunt.file.read('deploy_key'),
      showProgress: true
    }
  }
}

Git Deployment

// Git deployment
'gh-pages': {
  options: {
    base: 'dist'
  },
  src: ['**']
}

// Git push
gitpush: {
  production: {
    options: {
      remote: 'production',
      branch: 'master'
    }
  }
}

// Complete deployment pipeline
grunt.registerTask('deploy', [
  'deploy-build',
  'ftp-deploy:production'
]);

grunt.registerTask('deploy-github', [
  'deploy-build',
  'gh-pages'
]);

Docker Deployment

// Docker build
shell: {
  docker_build: {
    command: 'docker build -t myapp:latest .'
  },

  docker_push: {
    command: 'docker push myapp:latest'
  },

  docker_deploy: {
    command: 'docker run -d -p 80:80 myapp:latest'
  }
}

// Docker deployment task
grunt.registerTask('deploy-docker', [
  'deploy-build',
  'shell:docker_build',
  'shell:docker_push',
  'shell:docker_deploy'
]);

Performance Optimization

Parallel Processing

// Concurrent task execution
concurrent: {
  dev: ['sass', 'coffee', 'jshint'],
  prod: ['cssmin', 'uglify', 'imagemin'],
  options: {
    logConcurrentOutput: true
  }
}

// Use in build pipeline
grunt.registerTask('build-fast', [
  'clean',
  'concurrent:dev',
  'concat',
  'concurrent:prod'
]);

Incremental Builds

// Newer files only
newer: {
  options: {
    cache: '.grunt/newer'
  }
}

// Use with tasks
grunt.registerTask('build-incremental', [
  'newer:jshint',
  'newer:sass',
  'newer:imagemin'
]);

// Watch with newer
watch: {
  js: {
    files: ['src/js/**/*.js'],
    tasks: ['newer:jshint', 'newer:uglify']
  }
}

Caching

// Cache configuration
cache: {
  options: {
    cache: '.grunt/cache'
  }
}

// Cached image processing
imagemin: {
  dynamic: {
    files: [{
      expand: true,
      cwd: 'src/images/',
      src: ['**/*.{png,jpg,gif}'],
      dest: 'dist/images/'
    }]
  },
  options: {
    cache: false // Disable cache for this task
  }
}

Best Practices

Project Organization

// Organized Gruntfile structure
module.exports = function(grunt) {

  // Time how long tasks take
  require('time-grunt')(grunt);

  // Load all grunt tasks matching the ['grunt-*', '@*/grunt-*'] patterns
  require('load-grunt-tasks')(grunt);

  // Configurable paths
  var config = {
    src: 'src',
    dist: 'dist',
    tmp: '.tmp'
  };

  grunt.initConfig({
    // Reference the config
    config: config,

    // Load package.json
    pkg: grunt.file.readJSON('package.json'),

    // Task configurations
    // ... (organized by type)
  });

  // Custom tasks
  grunt.registerTask('serve', function(target) {
    if (target === 'dist') {
      return grunt.task.run(['build', 'connect:dist:keepalive']);
    }

    grunt.task.run([
      'clean:server',
      'concurrent:server',
      'autoprefixer',
      'connect:livereload',
      'watch'
    ]);
  });

  grunt.registerTask('build', [
    'clean:dist',
    'useminPrepare',
    'concurrent:dist',
    'autoprefixer',
    'concat',
    'cssmin',
    'uglify',
    'copy:dist',
    'rev',
    'usemin',
    'htmlmin'
  ]);

  grunt.registerTask('default', [
    'newer:jshint',
    'test',
    'build'
  ]);
};

Configuration Management

// External configuration
var config = require('./grunt.config.js');

grunt.initConfig(config);

// Environment-specific configs
var env = process.env.NODE_ENV || 'development';
var config = require('./config/' + env + '.js');

// Modular configuration
grunt.initConfig({
  // Load task configs
  sass: require('./grunt/sass.js'),
  uglify: require('./grunt/uglify.js'),
  watch: require('./grunt/watch.js')
});

Error Handling

// Graceful error handling
grunt.option('force', true); // Continue on errors

// Custom error handling
grunt.registerTask('safe-build', function() {
  try {
    grunt.task.run(['jshint', 'sass', 'uglify']);
  } catch (e) {
    grunt.log.error('Build failed: ' + e.message);
    grunt.fail.warn('Build task failed, but continuing...');
  }
});

// Conditional error handling
grunt.registerTask('conditional-fail', function() {
  if (grunt.option('strict')) {
    grunt.fail.fatal('Strict mode enabled, failing on any error');
  }
});

Performance Best Practices

  • Use concurrent tasks for independent operations
  • Implement incremental builds with grunt-newer
  • Cache expensive operations when possible
  • Optimize watch patterns to avoid unnecessary rebuilds
  • Use spawn: false in watch tasks for faster rebuilds

Maintenance Best Practices

  • Keep plugins updated regularly
  • Use semantic versioning for your builds
  • Document your tasks and configuration
  • Implement proper error handling
  • Use environment variables for sensitive data
  • Create modular configuration files
  • Monitor build performance with time-grunt

Summary

Grunt is a powerful JavaScript task runner that excels at automating repetitive development tasks through configuration. Key features include:

  • Configuration-driven: JSON-based configuration for easy setup
  • Extensive Plugin Ecosystem: Thousands of plugins for every task
  • Multi-target Tasks: Run different configurations for the same task
  • Template System: Dynamic configuration with templates
  • File Processing: Powerful file globbing and processing capabilities
  • Watch and Live Reload: Automatic task execution on file changes
  • Custom Tasks: Easy creation of custom tasks and workflows
  • Mature Ecosystem: Well-established with extensive documentation

By leveraging Grunt's configuration-driven approach and vast plugin ecosystem, you can create robust, maintainable build processes that handle everything from simple file concatenation to complex deployment pipelines.