Aller au contenu

Feuilles de chaleur Grunt

Grunt - Le gestionnaire de tâches JavaScript

Grunt est un coureur de tâches JavaScript qui automatise les tâches répétitives comme la minification, la compilation, les tests unitaires et le lintage. Il utilise une approche de configuration-sur-code avec un vaste écosystème de plugins pour gérer pratiquement n'importe quelle tâche.

Copier toutes les commandes Générer PDF

Sommaire

  • [Installation] (#installation)
  • [Pour commencer] (#getting-started)
  • [Configuration du fichier de gestion] (#gruntfile-configuration)
  • [Tâches principales] (#core-tasks)
  • [Traitement des dossiers] (#file-processing)
  • [Tâches du CSS] (#css-tasks)
  • Tâches JavaScript
  • [Traitement de l'image] (#image-processing)
  • Tâches HTML
  • Serveur de développement
  • [Tâches de surveillance] (#watch-tasks)
  • [Compagnie de construction] (#build-pipeline)
  • [Écosystà ̈me de Plugin] (#plugin-ecosystem)
  • [Tâches douanières] (#custom-tasks)
  • [Tâches multi-cibles] (#multi-target-tasks)
  • [Template Processing] (#template-processing)
  • [Test de l'intégration] (#testing-integration)
  • [Déploiement] (#deployment)
  • [Optimisation du rendement] (#performance-optimization)
  • [Meilleures pratiques] (#best-practices)

Installation

Installation mondiale

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

# Verify installation
grunt --version

Installation locale

# 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
```_

### Configuration du projet
```bash
# 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
```_

### Je vous en prie. Configuration
```json
{
  "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"
  }
}

Commencer

Fichier Grunt de base

// 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']);
};

Tâches d'exécution

# 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

Structure du projet

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

Configuration du fichier Grunt

Structure de configuration

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

Configuration avancée

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']);
};

Traitement des modèles

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'
      }]
    }
  }
});

Tâches essentielles

Opérations de fichiers

// 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'
  }
}

Filtre de fichiers

// 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 Fonctions

Compilation de Sass

// 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 Traitement

// 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 Doublure

// 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 Fonctions

Minification JavaScript

// 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']
    }
  }
}

Transpilation JavaScript

// 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 Doublure

// 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: {
  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'
  }
}

Traitement des images

Optimisation de l'image

// 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
  }
}

Génération de sprites

// 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/'
    }
  }
}

Tâches HTML

Traitement HTML

// 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/'
    }]
  }
}

Validation HTML

// 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']
  }
}

Serveur de développement

Connecter le serveur

// 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;
      }
    }
  }
}

Recharger en direct

// 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'
    }
  }
}

Regarder les tâches

Configuration de la montre de base

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']
  }
}

Modèles de montres avancés

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

Regarder avec la manipulation des erreurs

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...');
  }
});

Construire un pipeline

Développement

// 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'
]);

Construction de production

// 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'
]);

Constructions multi-environnement

// 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'
]);

Écosystème greffon

Greffons essentiels

# 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

Greffons avancés

// 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']
  }
}

Chargement du plugin

// 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');

Tâches personnalisées

Tâches personnalisées de base

// 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'
  }
});

Tâches personnalisées avancées

// 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.');
  });
});

Dépendances des tâches

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

Tâches multicibles

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)

Multi-cible personnalisée Fonctions

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']
      }
    }
  }
});

Traitement des modèles

Modèles intégrés

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

Modèles personnalisés

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

Tester l'intégration

Essai en unité

// 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']
  }
}

Couverture du code

// 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'
]);

Essais de bout en bout

// 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'
  }
}

Déploiement

Construire et déployer

// 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
    }
  }
}

Déploiement Git

// 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'
]);

Déploiement Docker

// 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'
]);

Optimisation des performances

Traitement parallèle

// 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'
]);

Constructions supplémentaires

// 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']
  }
}

Cache

// 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
  }
}

Meilleures pratiques

Organisation du projet

// 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'
  ]);
};

Gestion de la configuration

// 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')
});

Gestion des erreurs

// 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');
  }
});

Meilleures pratiques en matière de rendement

  • Utiliser des tâches simultanées pour des opérations indépendantes
  • Mise en oeuvre des constructions progressives avec grunt-newer
  • Cache opérations coûteuses si possible
  • Optimiser les modèles de montre pour éviter les reconstructions inutiles
  • Utiliser spawn: false dans les tâches d'observation pour des reconstructions plus rapides

Pratiques exemplaires de maintenance

  • Garder régulièrement les plugins à jour
  • Utilisez la version sémantique pour vos constructions
  • Documentez vos tâches et configuration
  • **Mise en œuvre de la bonne gestion des erreurs* *
  • Utiliser les variables d'environnement pour les données sensibles
  • **Créer des fichiers de configuration modulaires* *
  • Construire la performance du moniteur avec time-grunt

Résumé

Grunt est un puissant coureur de tâches JavaScript qui excelle dans l'automatisation des tâches de développement répétitives par la configuration. Les principales caractéristiques sont les suivantes :

  • Configuration pilotée: configuration basée sur JSON pour une configuration facile
  • Écosystème externe de plugin: Des milliers de plugins pour chaque tâche
  • ** Multi-cible Tâches**: Exécuter différentes configurations pour la même tâche
  • Template System: Configuration dynamique avec modèles
  • ** Traitement des dossiers**: Powerful file globbing et capacités de traitement
  • Watch et Live Reload: Exécution automatique des tâches sur les changements de fichiers
  • Tâches douanières: Création facile de tâches et de workflows personnalisés
  • Écosystà ̈me mature: bien établi avec une documentation extensive

En tirant parti de l'approche axée sur la configuration de Grunt et de son vaste écosystème plugin, vous pouvez créer des processus de construction robustes et durables qui gèrent tout, de la simple concaténation des fichiers aux pipelines de déploiement complexes.