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.
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¶
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']
}
}
}
Navigateur de l'intégration¶
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: falsedans 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.