Gulp

Starter

Gulp 基于流的自动化构建工具 (功能同Grunt)

安装

安装全局gulp

> npm install gulp -g
> gulp help

在项目中使用gulp

# 安装gulp
> npm install gulp --save-dev

# 编写gulpfile.js文件
> vim gulpfile.js
...

# 执行task
> gulp xxx

API

  • gulp.src(globs[, options])

    • options.buffer:true(默认),false表示stream方式读取文件内容
    • options.read:true(默认), false表示不读取文件内容
    • options.base:everything before a glob starts(默认)

      • eg: 'client/js/**/*.js',则base默认为client/jsclient/js/test.js, base默认为client/js

        gulp.src('client/js/**/*.js') // Matches 'client/js/somedir/somefile.js' and resolves `base` to `client/js/`
        .pipe(minify())
        .pipe(gulp.dest('build'));  // Writes 'build/somedir/somefile.js'
        
        gulp.src('client/js/**/*.js', { base: 'client' })
        .pipe(minify())
        .pipe(gulp.dest('build'));  // Writes 'build/js/somedir/somefile.js'
        
  • gulp.dest(path[, options])

    • options.cwd:process.cwd() 前脚本的工作目录的路径,当文件输出路径为相对路径将会用到
    • options.mode:0777 (默认),指定被创建文件夹的权限
  • gulp.task(name[, deps], fn)

    • name:任务名
    • deps :为一个数组,是当前定义的任务需要依赖的其他任务(可不设置),默认依赖任务会并行执行
    • fn:任务函数(deps中的任务先执行)
      gulp.task('mytask', ['array', 'of', 'task', 'names'], function() {
      // Do stuff
      });
      
  • gulp.watch(glob [, opts], tasks)
      gulp.watch('js/**/*.js', ['uglify','reload']);
    
  • gulp.watch(glob [, opts, cb])
      gulp.watch('js/**/*.js', function(event){
          console.log(event.type); //变化类型 added为新增,deleted为删除,changed为改变 
          console.log(event.path); //变化的文件的路径
      });
    

说明:

  • globs : String or Array
    • 文件匹配模式(类似正则表达式),Gulp内部使用了node-glob模块来实现其文件匹配功能
    • ***,?,!,[...],[^...],{...}

SyncTask

想等待异步任务中的异步操作完成后再执行后续的任务,有三种方法可以实现:

  1. Passing in a callback
     gulp.task('sync', function (cb) {
         // setTimeout could be any async task
         setTimeout(function () {
             cb();
         }, 1000);
     });
    
  2. Returning a stream (适用于任务就是操作gulp.src获取到的流的情况)
     gulp.task('sync', function () {
         return gulp.src('js/*.js')
             .pipe(concat('script.min.js')
             .pipe(uglify())
             .pipe(gulp.dest('../dist/js');
     });
    
  3. Returning a Promise
     var Q = require('q');
     gulp.task('one',function(){
       var deferred = Q.defer();
       setTimeout(function() {
          deferred.resolve();
       }, 5000);
       return deferred.promise;
     });
    

测试:

gulp.task('secondTask', ['sync'], function () {
    // this task will not start until the sync task is all done!
    console.log('secondTask is done');
});

注意: 默认情况下,依赖任务们会并行执行 例如:

gulp.task('otherTask',function(){
    //no dependent tasks
    ...
});

//sync,otherTask会同时执行,然后执行default的fn
gulp.task('default', ['sync','otherTask'], function () {
   // do stuff
});

解决方案:

gulp.task('otherTask',["sync"],function(){
    //depends on sync
    ...
});
//先执行sync,然后执行otherTask,最后执行default的fn
gulp.task('default', ['sync','otherTask'], function () {
   // do stuff
});

书写太麻烦,推荐插件run-sequence

var runSequence = require('run-sequence');
gulp.task('some-task', function() {
    runSequence(
            ['task-1', 'task-2', 'task-3'], // These 3 can be done in parallel
            'task-4', // ...then just do this
            ['task-5', 'task-5'], // ...then do these things in parallel
            'task-6', // ...then do this
            // ....
    );
});

Vinyl

vinyl 文件对象 (一种虚拟文件格式)

  • 有一个 contents 属性来存放文件内容
  • 内容可以是以下三种形式:
    • Stream
    • Buffer
    • null
  • 判断 contents 属性存放内容的类型:
    • isBuffer(): Returns true if file.contents is a Buffer.
    • isStream(): Returns true if file.contents is a Stream.
    • isNull(): Returns true if file.contents is null.

Gulp:

  • gulp 中的流,预期操作的是 vinyl 文件对象
  • 不兼容:
    • 常规流与vinyl文件对象流是不同的
    • gulp api ,plugin 中操作vinyl对象内容类型(stream,buffer)可能会不同
  • 示例

    • fs.createReadStream(...) 常规读取流,得到的是数据块(chunk)
    • gulp.src(...) 默认得到的是把内容转换成 buffer 的 vinyl 文件对象,而不是数据块(chunk),可以通过设置 buffer: false 选项,可以让 gulp 禁用 buffer

      fs.createReadStream('/usr/share/dict/words').on('data', function(chunk) {  
      console.log('Read %d bytes of data', chunk.length);
      });
      
      输出结果:
      > Read 65536 bytes of data
      > Read 65536 bytes of data
      > Read 65536 bytes of data
      > Read 65536 bytes of data
      ...
      
      gulp.src('/usr/share/dict/words').on('data', function(file) {  
      console.log('Read %d bytes of data', file.contents.length);
      });
      
      输出结果:
      > Read 2493109 bytes of data
      
      gulp.src('/usr/share/dict/words', {buffer: false}).on('data', function(file) {  
      var stream = file.contents;
      stream.on('data', function(chunk) {
        console.log('Read %d bytes of data', chunk.length);
      });
      });
      
      输出结果:
      > Read 65536 bytes of data
      > Read 65536 bytes of data
      > Read 65536 bytes of data
      > Read 65536 bytes of data
      > ...
      

解决流不兼容问题:

  • 使用第三方插件:

    • vinyl-source-stream:常规流 => vinyl文件对象(Stream)
    • vinyl-buffer:vinyl文件对象(Stream) => vinyl文件对象(Buffer)

      var fs=require("fs");
      var source = require('vinyl-source-stream');
      var buffer = require('vinyl-buffer');
      var uglify = require('gulp-uglify');
      
      fs.createReadStream('./src/app.js')
          .pipe(source('app.min.js')) // 常规流 => vinyl 对象(stream)
          .pipe(buffer())             // vinyl 对象(stream) => vinyl文件对象(Buffer)
          .pipe(uglify())             // gulp-uglify 只支持处理vinyl文件对象(Buffer)
          .pipe(gulp.dest('dist/'));
      
      gulp.src('app.js', {buffer: false})
          .pipe(buffer())         // vinyl 对象(Stream) => vinyl文件对象(Buffer)
          .pipe(uglify())
          .pipe(gulp.dest('dist/'));
      
      gulp.src('app.js')
          .pipe(uglify())
          .pipe(gulp.dest('dist/'));
      
  • 使用gulp插件:

    • gulp-buffer:vinyl文件对象(Stream) => vinyl文件对象(Buffer)
    • gulp-streamify:vinyl文件对象(Stream) => vinyl文件对象(Buffer) => 其他处理 => vinyl文件对象(Stream)
  • 使用through2自定义处理

      var modify=modify(opts){
          return through2.obj(function(file,encoding,next){
              //file为上游读流产生的文件数据
              //next用于向下游传递处理后的数据
    
              console.log(file.path);
              console.log(file.isNull());
              console.log(file.isStream());
    
              var contents=String(file.contents);
              file.contents=new Buffer(contents);
              next(null,file);     // this.push(file); next();
          });
      }
    
      fs.createReadStream('/tmp/important.dat')
          .pipe(modify())
          .pipe(fs.createWriteStream('/tmp/out.txt'));
    
      gulp.src('/tmp/important.dat')
              .pipe(modify())
              .pipe(gulp.dest('dist/'));
    

综合测试:

fs.createReadStream("./posts/articles/"+filename)
    .pipe(source("2013-11-13-a1.md"))
    .pipe(gulp.dest("./dist/articles"))
    .pipe(through2.obj(function(file,enc,next){
        console.log("Now is "+(file.isStream()?"Stream":"Buffer"));
        next(null,file);
    }))
    .pipe(buffer())
    .pipe(through2.obj(function(file,enc,next){
        console.log("Now is "+(file.isStream()?"Stream":"Buffer"));
        next(null,file);
    }))
    ;
gulp.src("./posts/articles/????-??-??-*.md")
    .pipe(through2.obj(function(file,enc,next){
        console.log(file.path);
        console.log(path.basename(file.path,".md"));
        //console.log(path.basename(file.path));
        if(file.isNull()) {
            console.log("This file is Empty!");
        }
        else if(file.isStream()){
            console.log("This function is not support Stream!");
        }
        else if (file.isBuffer()){
            console.log("Do Marked!");
            var result=marked(String(file.contents));
            console.log(result.props);
            file.contents = Buffer(result.content);
        }
        next(null, file);
    }))
    .pipe(gulp.dest("./dist/articles"));

PS: though2还可以用于自定义gulp插件中

Doc

Gulp

Gulp

常用插件

  • css编译器: gulp-sass,gulp-less
  • file合并:gulp-concat
  • file最小化:gulp-uglify 压缩js,gulp-minifycss 压缩css,gulp-imagemin 压缩图像
  • file重命名:gulp-rename
  • file删除:gulp-clean
  • 静态服务器:gulp-server-livereload

使用举例:

重命名,压缩文件:

var rename=require("gulp-rename");
var uglify=require("gulp-uglify");
gulp.task('mini',function(){
    gulp.src(Config.react.dest+"/app.js")
        .pipe(rename("app.min.js"))
        .pipe(uglify())
        .pipe(gulp.dest(Config.react.prodDest));
});

实时编译sass文件:

var sass = require('gulp-sass');
function sassBuild(opts,filename){
    if(!filename)
        filename=opts.default;
    var sassOptions = {
      errLogToConsole: true,
      outputStyle: 'expanded',
      loadPath:opts.loadPath
    };
    function build(event){
        if(event)
            console.log('File ' + event.path + ' was ' + event.type);
        gulp.src(opts.src+filename+".scss")
            .pipe(sass(sassOptions).on('error', sass.logError))
            //.pipe(concat('style.css'))
            .pipe(gulp.dest(opts.dest));
    }
    build();
    gulp.watch(Config.sass.src+"**/*.scss",build)
        /*.on("change",function(event){
            console.log('File ' + event.path + ' was ' + event.type);
        })*/
        ;
}
gulp.task('sass', function () {
 sassBuild(Config.sass,argv.n);
});

架起静态服务器,实时预览:

var server = require('gulp-server-livereload');
function server(){
    var filterFun=function(filePath,cb){
       Config.server.filterReg.map(function(reg){
            if((new RegExp(reg)).test(filePath))
                cb(true);
        })
      /*if(/app.js/.test(filePath) || /style.css/.test(filePath)) {
        cb(true)
      } */
    }
    var serverOpts={
        livereload: {
            enable: true,
            filter:filterFun
          },
          open: true
    };
    gulp.src(Config.dist)
        .pipe(server(serverOpts));
}
gulp.task('server',function(){
    server();
});