By

新型自动化构建工具gulp

让自动化构建飞起来

声明

本文翻译自Getting started with gulp,本人翻译时进行了必要的改动。

gulp简介

说到JavaScript自动化构建工具,大家都会想到ant和grunt。今天向大家介绍一个新的工具,gulp。使用gulp将避免像使用grunt一样书写很多的配置代码,学习起来更加的快捷。

gulp使用node.js的stream API(流)执行自动化构建任务。使用流的好处就是不用每执行一个任务都要读写一次文件,在接下来的例子中,我将给你展示这点所带来的巨大好处。你可以使用gulp输入一个文件,将文件流pipe到一些插件中进行操作,然后写出到文件。
在grunt中,你需要为每个插件书写输入文件,再书写输出文件。这样的话非常的不方便,特别是在项目文件结构变动的时候。下面的代码很能说明这个问题,这段代码是Gruntfile中的一个片段,它将Sass代码编译后传入autoprefixer进行自动前缀修饰。

sass: {
  dist: {
    options: {
      style: 'expanded'
    },
    files: {
      'dist/assets/css/main.css': 'src/styles/main.scss',
    }
  }
},

autoprefixer: {
  dist: {
    options: {
      browsers: [
        'last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'
      ]
    },
    src: 'dist/assets/css/main.css',
    dest: 'dist/assets/css/main.css'
  }
},

grunt.registerTask('styles', ['sass', 'autoprefixer']);

正如你所见,使用grunt需要分别配置每个插件,并且定义每个插件的输入输出文件地址。有些根本就是不需要的中间文件,也会产生。比如上面的代码中,sass编译后的文件要让autoprefixer读入,那么又要在autoprefixer中去设置一次。

那么如果用gulp则怎么做呢?你看到代码一定会大吃一惊。

gulp.task('sass', function() {
  return gulp.src('src/styles/main.scss')
    .pipe(sass({ style: 'compressed' }))
    .pipe(autoprefixer('last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'))
    .pipe(gulp.dest('dist/assets/css'))
});

用gulp,我们只需要读入一次文件,并将其内容通过流管道”流经”gulp的插件,之后输出到指定位置。看到这里,我就不信你不兴奋。那么就让我们开始着手学习使用gulp吧。

安装gulp

我们首先需要安装gulp,通过npm可以安装:

$ npm install gulp -g

这样我们就在系统全局安装了gulp,方便我们在CLI中调用它。接下来我们还需要在项目中安装gulp,并将其存到package.json

$ npm install gulp --save-dev

这样安装gulp后,还会把它加入到package.jsondevDependencies记录里。

安装插件

上面看到的sass和autoprefixer都是gulp的插件,在这里我们列出常用的几个插件:

  • Sass compile (gulp-ruby-sass)
  • Autoprefixer (gulp-autoprefixer)
  • Minify CSS (gulp-minify-css)
  • JSHint (gulp-jshint)
  • Concatenation (gulp-concat)
  • Uglify (gulp-uglify)
  • Compress images (gulp-imagemin)
  • LiveReload (gulp-livereload, requires tiny-lr)
  • Clean files for a clean build (gulp-clean)
  • Caching of images so only changed images are compressed (gulp-cache)
  • Notify of changes (gulp-notify)

完整的插件列表可以在文章开头给出的gulp的github主页找到。要安装上述插件,运行下面的命令

$ npm install gulp-ruby-sass gulp-autoprefixer gulp-minify-css gulp-jshint gulp-concat gulp-uglify gulp-imagemin gulp-clean gulp-notify gulp-rename gulp-livereload tiny-lr gulp-cache --save-dev

加载插件

接下来,我们在项目的根目录创建gulpfile.js并加载这些插件。

var gulp = require('gulp'),
    sass = require('gulp-ruby-sass'),
    autoprefixer = require('gulp-autoprefixer'),
    minifycss = require('gulp-minify-css'),
    jshint = require('gulp-jshint'),
    uglify = require('gulp-uglify'),
    imagemin = require('gulp-imagemin'),
    rename = require('gulp-rename'),
    clean = require('gulp-clean'),
    concat = require('gulp-concat'),
    notify = require('gulp-notify'),
    cache = require('gulp-cache'),
    livereload = require('gulp-livereload'),
    lr = require('tiny-lr'),
    server = lr();

哇塞!这看起来比grunt要写的还多嘛……LZ骗人T_T……

我没有骗你们,gulp的插件和grunt有一些小小的不同,gulp的每个插件执行的任务非常单一,都是专注于做好一件事情。比如,grunt的imagemin插件利用caching来避免重复压缩同样的文件,而gulp不一样,在gulp中cache插件与压缩插件是两个插件,而不像grunt一样一个插件包办两件事情。

当然了,我们也可以自动去加载这些插件,不必要每次都要写这一长串(其实你也不会用到这么多)。如果对自动加载插件感兴趣,请点这里

注意下,LiveReload这个插件应该有不少人可以猜出来是用来做即时开发的,也就是页面会随着代码变化而变化,但是他还需要额外的工具tiny-lr支持。更多的信息,则需要去阅读其github主页的文档哦。

实战!创建任务

这次我们的任务是编译Sass代码,进行autoprefix,然后minify。

gulp.task('styles', function() {
  return gulp.src('src/styles/main.scss')
    .pipe(sass({ style: 'expanded' }))
    .pipe(autoprefixer('last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'))
    .pipe(gulp.dest('dist/assets/css'))
    .pipe(rename({suffix: '.min'}))
    .pipe(minifycss())
    .pipe(gulp.dest('dist/assets/css'))
    .pipe(livereload(server))
    .pipe(notify({ message: 'Styles task complete' }));
});

我们来一步步地看这份代码。

gulp.task('taskname', function() { ... } )

定义一个任务,第一个参数给出的是任务名,第二个参数的函数即任务的主体。给出任务名以后,就可以在CLI下面直接运行$ taskname来执行某项具体任务,如果执行$ gulp则运行default任务。(这点跟grunt是一样的)

return gulp.src('src/styles/main.scss')

这是一个API——gulp.src,在这里我们可以定义输入文件(看src的字面意思便知)。它支持glob通配符,因此也可以是类似/**/*.scss的文件名输入。这样的话将会处理多个文件。

.pipe(sass({ style: 'expanded' }))

利用.pipe(plugin)将文件通过流管道”流通”,plugin即前面加载的任何插件。插件的参数一般都可以在他们的github主页找到。

.pipe(gulp.dest('dist/assets/css'));

gulp.dest这个API负责输出文件。这点很酷,我们可以在这中间输出文件多次(比如上面代码中的两次文件输出)。有时候同时需要非minify的版本和minify的版本,这个时候这样的特性就派上用场了。我建议大家看一次gulp的API文档,才能对其功能有所了解。英文并不可怕!它很好读懂。想要了解其他的插件的使用方式,也都可以在他们的github主页找到!

default任务

我们如果在cli运行$ gulp就可以启动default任务。在default任务中尽量不要动项目代码,而是通过gulp.start API启动其他的任务。这样可以让我们的gulpfile更好维护。我们都是工程师,都喜欢干净,整洁,符合规范的东西。

gulp.task('default', ['clean'], function() {
    gulp.start('styles', 'scripts', 'images');
});

大观

我们来看一个比较完整的gulpfile,并且拿一个gruntfile作对比。它们两个分别都实现了相同的任务功能。看看,你喜欢哪个?别忘了告诉我:)

// gulpfile.js
// Load plugins
var gulp = require('gulp'),
    sass = require('gulp-ruby-sass'),
    autoprefixer = require('gulp-autoprefixer'),
    minifycss = require('gulp-minify-css'),
    jshint = require('gulp-jshint'),
    uglify = require('gulp-uglify'),
    imagemin = require('gulp-imagemin'),
    rename = require('gulp-rename'),
    clean = require('gulp-clean'),
    concat = require('gulp-concat'),
    notify = require('gulp-notify'),
    cache = require('gulp-cache'),
    livereload = require('gulp-livereload'),
    lr = require('tiny-lr'),
    server = lr();
 
// Styles
gulp.task('styles', function() {
  return gulp.src('src/styles/main.scss')
    .pipe(sass({ style: 'expanded', }))
    .pipe(autoprefixer('last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'))
    .pipe(gulp.dest('dist/styles'))
    .pipe(rename({ suffix: '.min' }))
    .pipe(minifycss())
    .pipe(livereload(server))
    .pipe(gulp.dest('dist/styles'))
    .pipe(notify({ message: 'Styles task complete' }));
});
 
// Scripts
gulp.task('scripts', function() {
  return gulp.src('src/scripts/**/*.js')
    .pipe(jshint('.jshintrc'))
    .pipe(jshint.reporter('default'))
    .pipe(concat('main.js'))
    .pipe(gulp.dest('dist/scripts'))
    .pipe(rename({ suffix: '.min' }))
    .pipe(uglify())
    .pipe(livereload(server))
    .pipe(gulp.dest('dist/scripts'))
    .pipe(notify({ message: 'Scripts task complete' }));
});
 
// Images
gulp.task('images', function() {
  return gulp.src('src/images/**/*')
    .pipe(cache(imagemin({ optimizationLevel: 3, progressive: true, interlaced: true })))
    .pipe(livereload(server))
    .pipe(gulp.dest('dist/images'))
    .pipe(notify({ message: 'Images task complete' }));
});
 
// Clean
gulp.task('clean', function() {
  return gulp.src(['dist/styles', 'dist/scripts', 'dist/images'], {read: false})
    .pipe(clean());
});
 
// Default task
gulp.task('default', ['clean'], function() {
    gulp.start('styles', 'scripts', 'images');
});
 
// Watch
gulp.task('watch', function() {
 
  // Listen on port 35729
  server.listen(35729, function (err) {
    if (err) {
      return console.log(err)
    };
 
    // Watch .scss files
    gulp.watch('src/styles/**/*.scss', ['styles']);
 
    // Watch .js files
    gulp.watch('src/scripts/**/*.js', ['scripts']);
 
    // Watch image files
    gulp.watch('src/images/**/*', ['images']);
 
  });
 
});
// Gruntfile.js
/*!
 * Grunt
 * $ npm install grunt-contrib-uglify grunt-autoprefixer grunt-contrib-cssmin grunt-contrib-imagemin grunt-contrib-sass grunt-contrib-watch grunt-contrib-concat grunt-contrib-clean grunt-contrib-jshint grunt-notify --save-dev
 */
 
module.exports = function(grunt) {
 
  grunt.initConfig({
 
    // Sass
    sass: {
      dist: {
        options: {
          style: 'expanded'
        },
        files: {
          'dist/styles/main.css': 'src/styles/main.scss'
        }
      }
    },
 
    // Autoprefix
    autoprefixer: {
      options: {
        browsers: [
          'last 2 version', 'safari 5', 'ie 8', 'ie 9', 'opera 12.1', 'ios 6', 'android 4'
        ]
      },
      dist: {
        src: 'dist/styles/main.css'
      }
    },
 
    // CSS minify
    cssmin: {
      dist: {
        files: {
          'dist/styles/main.min.css': 'dist/styles/main.css'
        }
      }
    },
 
    // JShint
    jshint: {
      files: ['src/scripts/**/*.js'],
      options: {
        jshintrc: '.jshintrc'
      }
    },
 
    // Concat
    concat: {
      js: {
        src: ['src/scripts/**/*.js'],
        dest: 'dist/scripts/main.js'
      },
    },
 
    // Uglify
    uglify: {
      dist: {
        src: 'dist/scripts/main.js',
        dest: 'dist/scripts/main.min.js'
      },
    },
 
    // Imagemin
    imagemin: {
      dist: {
        options: {
          optimizationLevel: 3,
          progressive: true,
          interlaced: true
        },
        files: [{
          expand: true,
          cwd: 'src/images',
          src: ['**/*.{png,jpg,gif}'],
          dest: 'dist/images'
        }]
      }
    },
 
    // Clean
    clean: {
      build: ['dist/styles', 'dist/scripts', 'dist/images']
    },
 
    // Notify
    notify: {
      styles: {
        options: {
          message: 'Styles task complete',
        }
      },
      scripts: {
        options: {
          message: 'Scripts task complete',
        }
      },
      images: {
        options: {
          message: 'Images task complete',
        }
      },
    },
 
    // Watch
    watch: {
      styles: {
        files: 'src/styles/**/*.scss',
        tasks: ['sass', 'autoprefixer', 'cssmin', 'notify:styles'],
      },
      scripts: {
        files: 'src/scripts/**/*.js',
        tasks: ['concat', 'uglify', 'notify:scripts'],
      },
      images: {
        files: 'src/images/**/*',
        tasks: ['imagemin', 'notify:images'],
      },
      livereload: {
        options: { livereload: true },
        files: [
          'dist/styles/**/*.css',
          'dist/scripts/**/*.js',
          'dist/images/**/*'
        ]
      }
    }
  });
 
  // Default task
  grunt.registerTask('default', [
    'jshint',
    'clean',
    'concat',
    'uglify',
    'sass',
    'autoprefixer',
    'cssmin',
    'imagemin'
  ]);
 
  // Load plugins
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-autoprefixer');
  grunt.loadNpmTasks('grunt-contrib-cssmin');
  grunt.loadNpmTasks('grunt-contrib-sass');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-imagemin');
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-notify');
 
};

我想就我个人而言,gulp更具有描述性,更符合我们的思维方式。符合思维方式的东西用起来一定是易于维护的。

总结

gulp和grunt、ant,孰优孰劣,由你定夺。但是总是多了一条路让人选择,而且确实看着就让人兴奋。

Written by
前端开发工程师