前端工程构建之gulp

#前面的废话
一直在思考前端工程师的价值体现在哪里,或者说优秀前端工程师的竞争力在哪里?

  • 小公司喜欢什么样的人?
    • 能快速搞出东西来的人,使用各种工具、插件,分分钟起页面,切图切个三四天?怎么可能!管你什么ie6,能出东西就行了,最好再适应个移动端。。。
  • 大公司喜欢什么样的人?
    • 能写出可维护代码
    • 基本功扎实
    • 能够搞定ie6
    • 工程化/结构化/模块化,解决大规模前端代码问题

很不巧,gulp这种东西,对于小公司能快速起页面,对于大公司能承担起工程化代码的作用,不得不用啊。当然各个逼格高一点的公司都会用自己的类gulp工具,比如百度的fis,我司的fekit等等,但是实际上从社区的角度来说,肯定无法与gulp比拟,在个人开发的时候,还是要选择gulp。

#grunt VS gulp
可以看这篇文章Gulp vs Grunt

#start

gulp是典型的极简主义,导致其官方get start什么都没有。。。

  • 1 全局安装
    npm install --global gulp
  • 2 在你的开发环境安装gulp
    npm install --save-dev gulp
    //这一步不能省,全局安装的gulp类似于一个命令行工具,在具体的业务中还需要引进gulp module
    
  • 3 创建一个叫 gulpfile.js 的文件,内容如下

    var gulp = require('gulp');
    gulp.task('default', function() {
    // place code for your default task here
    });
    
  • 4 执行 gulp 命令

    一个简单的应用就这么结束了,实际上 ,几乎所有应用都是这个套路。 gulp用起来就和写代码一样,你把需要的插件引进来,写进task,顺序执行就ok了。

gulp API

  • gulp API设计也非常简单,就下面几个

##gulp.src(globs[,options])

gulp.src('client/templates/*.jade')
  .pipe(jade())
  .pipe(minify())
  .pipe(gulp.dest('build/minified_templates'));

src可以使用通配符匹配一个或多个,对于参数gulp是使用了开源的 glob 来完成: node-glob
返回值是一种格式的流Vinyl files也开源了,感兴趣的可以了解一下,返回值就可以被pipe方法操作操作。

gulp.dest(path[,options])

gulp.src('./client/templates/*.jade')
  .pipe(jade())
  .pipe(gulp.dest('./build/templates'))
  .pipe(minify())
  .pipe(gulp.dest('./build/minified_templates'));

看名字就知道,他和src是反的,类似于写的感觉,作为输出,可以输出到不同的文件夹,如果文件夹不存在就会建立新的文件夹。可以输出多个文件。

gulp.task(name[,deps], fn)

gulp.task('somename', function() {
  // Do stuff
});

其中deps参数是一个数组,作为在你要执行的任务之前要执行的任务队列,它会先执行完数组里面的任务队列,再来执行你的任务。

同时,task可以支持异步,如果回调函数存在下面三种用法,就会实现异步:

接受一个回调函数

// run a command in a shell
var exec = require('child_process').exec;
gulp.task('jekyll', function(cb) {
  // build Jekyll
  exec('jekyll build', function(err) {
    if (err) return cb(err); // return error
    cb(); // finished task
  });
});

返回了一个流

gulp.task('somename', function() {
  var stream = gulp.src('client/**/*.js')
    .pipe(minify())
    .pipe(gulp.dest('build'));
  return stream;
});

返回一个promise对象

var Q = require('q');

gulp.task('somename', function() {
  var deferred = Q.defer();

  // do async stuff
  setTimeout(function() {
    deferred.resolve();
  }, 1);

  return deferred.promise;
});

上述是官方文档的描述,我们理解一下这几句话,什么叫可以是异步的?

跑下面这一段代码:

var gulp = require('gulp');

gulp.task('test1' , function() {
    setTimeout(function(){
        console.log("test1 finish");
    },1000);
});

gulp.task('test2',function() {
    setTimeout(function(){
        console.log("test2 finish");
    },500);
});

gulp.task('default', ['test1','test2']);

执行gulp,控制台输出:

[19:19:12] Starting 'test1'...
[19:19:12] Finished 'test1' after 438 μs
[19:19:12] Starting 'test2'...
[19:19:12] Finished 'test2' after 86 μs
[19:19:12] Starting 'default'...
[19:19:12] Finished 'default' after 8.21 μs
test2 finish
test1 finish

可以发现gulp默认是按顺序执行你的任务,但是它 不会等待 ,test1虽然其实要在1s钟之后才会完毕,但是它按同步顺序执行,很快就结束了,然后开始test2,同样很快就结束了。
我们按上面的方法加入最简单的回调:

var gulp = require('gulp');

gulp.task('test1' , function() {
    setTimeout(function(){
        console.log("test1 finish");
    },1000);
    cb();
});

gulp.task('test2',function() {
    setTimeout(function(){
        console.log("test2 finish");
    },500);
});

gulp.task('default', ['test1','test2']);

[19:22:35] Starting 'test1'...
[19:22:35] Starting 'test2'...
[19:22:35] Finished 'test2' after 151 μs
test2 finish
test1 finish
[19:22:36] Finished 'test1' after 1.01 s
[19:22:36] Starting 'default'...
[19:22:36] Finished 'default' after 11 μs
可以发现,控制台结果 test2 finish 和 test1 finish虽然没有变化,但是对于 **gulp** 来说,test1这个task是在1000ms之后才完成的,test2没有设置异步,所以很快完成。 有人会说了,这尼玛有啥用啊,反正task1和task2都会执行,而且执行顺序也不受影响。 是的,在上面这个例子中,你不期望task1和task2的顺序,的确不用callback去告诉gulp他们正确的执行顺序,假如你希望task1执行完了再执行task2,我们试下面这段代码。
var gulp = require('gulp');

gulp.task('test1' , function() {
    setTimeout(function(){
        console.log("test1 finish");
    },1000);
});

gulp.task('test2',['test1'],function() {
    setTimeout(function(){
        console.log("test2 finish");
    },500);
});

gulp.task('default', ['test1','test2']);

看console:

[19:28:38] Starting 'test1'...
[19:28:38] Finished 'test1' after 592 μs
[19:28:38] Starting 'test2'...
[19:28:38] Finished 'test2' after 179 μs
[19:28:38] Starting 'default'...
[19:28:38] Finished 'default' after 8.21 μs
test2 finish
test1 finish
坑爹了,我们按照官方定义的,给task2多传了一个参数告诉task2要在task1执行之后再去执行task1,没有问题,但是问题是test2 finish先输出,显然不对! 这就是因为你没有告诉gulp task1是异步的,不是马上就结束了,它不知道,然后傻叉的以为task1马上结束了,按你的吩咐去执行了task2,肯定不对撒,cb/stream/promise 就用在这儿,看我小改一下:
var gulp = require('gulp');

gulp.task('test1' , function(cb) {
    setTimeout(function(){
        console.log("test1 finish");
        cb();
    },1000);
});

gulp.task('test2',['test1'],function() {
    setTimeout(function(){
        console.log("test2 finish");
    },500);
});

gulp.task('default', ['test1','test2']);
看console:
[19:33:21] Starting 'test1'...
test1 finish
[19:33:22] Finished 'test1' after 1.01 s
[19:33:22] Starting 'test2'...
[19:33:22] Finished 'test2' after 109 μs
[19:33:22] Starting 'default'...
[19:33:22] Finished 'default' after 9.03 μs
test2 finish

果然完美了,说了要相信科学

从上面的例子可以看出来,当你需要顺序执行几个task的时候,第一,你需要在task2的参数上面声明需要先于它执行的task1,而且,如果task1是异步的,一定要记得告诉gulp,task1什么时候执行结束!

##gulp.watch

gulp.watch(glob [, opts], tasks) or gulp.watch(glob [, opts, cb])

watch就是典型的hook,很好理解,当某个/些文件变动时,去执行某些命令或者执行回调:

task用法
var watcher = gulp.watch('js/**/*.js', ['uglify','reload']);
watcher.on('change', function(event) {
  console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
});
cb用法:
gulp.watch('js/**/*.js', function(event) {
  console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
});

很好理解就不解释了。

#结语
gulp很好很强大,上述只是官网的翻译,下一篇来一个完整的例子,gulp实战~~~

喝杯咖啡,交个朋友