前端自动化构建工具:gulp入门

现在正在跟着做的项目用的是百度的FIS3,因为我还没有独立使用fis的经验,所以这里就不先说fis,今天正好和别人一起做个小博客,已经使用了gulp来进行项目的构建,所以,这里就说一说gulp吧,这是第一篇,入门,同时结合gulp源码进行学习。

环境:ubuntu 14.04

一、安装

1:全局安装ulp

sudo npm install -g gulp

2:然后,在项目里安装gulp

sudo npm install --save-dev gulp

二、使用gulp

1:在项目的根目录下创建gulpfile.js文件

var gulp = require("gulp");

gulp.task("default",function(){
    console.log("hello gulp");
})

2:执行gulp

一个基本的gulp配置格式就是这样,现在让我们执行看一下吧,在终端中直接输入gulp并回车:

gulp

此时,在命令行中,我们可以看到如下的提示:

QQ截图20151203230908

可以看到,在命令行中有“hello gulp”输出,说明我们的gulp配置文件已经成功运行。

另外需要注意的一点,在执行gulp的时候,如果省略任务名称的话,系统会默认执行default任务,如果没有default任务,终端则会报错:

Error:Task requires a name

3:一个真实的例子

比如:我们创建一个压缩js的任务:

var gulp = require("gulp"),
  uglify = require("gulp-uglify");

gulp.task("js",function(){
    gulp.src("js/*.js")
        .pipe(uglify)
        .pipe(gulp.dest("build"));
})

我们引入了一个插件:gulp-uglify,在执行gulp命令前,先安装这个插件:

sudo npm install --save-dev gulp-uglify

然后输入gulp命令执行该任务:

gulp js

此时任务执行完毕,我们会发现在项目根目录多了一个文件夹:build,并且里面会有和一些你的js文件,如果你js文件夹下有文件的话。

三、gulp.task()

需要解释的第一个API是gulp.task(),也是在gulpfile.js文件中最先看到的词汇,在本文刚开始的时候也见过一面。

想象一下,如果这里是米国情报中心,当前有一个代号为“飓风”的突袭行动,我们作为情报人员,用我们的情报格式来写下任务,有三种方案:

第一种方案:不需要FBI支持,我们自己行动
gulp.task("飓风",function(){
    //行动计划
})

//第二种方案:需要FBI支持,我们同时也行动
gulp.task("飓风",[FBI的情报支持],function(){
    //行动计划
})

//第三种方案,把任务下发给几个小组,依靠这些小组完成任务
gulp.task("飓风",["小组A","小组B","小组C"]);

这就是gulp.task()的三种格式,这里需要注意的是,第二种情况,需要依赖其他任务,则该任务会在依赖的任务结束后才会执行;第三种情况,全部定义为依赖的形式,则所有的依赖会在同一时间执行,没有先后顺序。

以上三种情况对应的实际代码:

//第一种:
gulp.task('somename', function() {
  // Do stuff
});

//第二种:
gulp.task('mytask', ['array', 'of', 'task', 'names'], function() {
  // Do stuff
});

//第三种
gulp.task('build', ['array', 'of', 'task', 'names']);

除此之外,function(){}作为承载行动计划的部分也至关重要。

function(){}的格式

function(){}的内部是通过pipe()(管子)来连接的,一段管子连接一句代码,这些管子有个形象的名字:流。

这个流的格式为:

gulp.src().pipe(plugin()).pipe(plugin())

另外,gulp支持创建异步任务:只要满足一下三种情况之一即可:

1:接受一个回调函数

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

2:返回流

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

3:返回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');

// takes in a callback so the engine knows when it'll be done
gulp.task('one', function(cb) {
    // do stuff -- async or otherwise
    cb(err); // if err is not null and not undefined, the run will stop, and note that it failed
});

// identifies a dependent task must be complete before this one begins
gulp.task('two', ['one'], function() {
    // task 'one' is done now
});

gulp.task('default', ['one', 'two']);

之前提到过,如果gulp.task()设置三个参数的话,则该任务必须在所依赖的任务结束后才会进行。

四、gulp.src()

gulp.src()是除了gulp.task()之外看到的第二个关键字,我们对于src比较熟悉,比如在html中指向某一路径。同样,在gulp中,src也是相同的道理,即只指向需要匹配的文件。

gulp.src()的返回值是一个可以传递给插件的数据流。然后接上管子(.pipe()),将数据流传递给插件(.pipe(uglify()))。

1、参数

//需要一个参数或者一个数组形式。这个参数被称作:glob。
gulp.src(glob or [globs])

2、Glob模式

glob模式是一种简化了的正则表达式。glob的语法可以看这里:https://github.com/isaacs/node-glob

其基本的语法如下:


*:表示匹配0次或多次任意字符,不匹配“/”
**:表示匹配0次或多次任意字符,可以匹配“/”
?:匹配一次
+:匹配一次货多次
!:匹配除了!后面的,和^功能类似

可能有人不太清楚的区别,也比较简单,可以匹配所匹配的路径,以及所有的子路径。比如:

//*,只匹配js目录下的.js结尾的文件
gulp.src("js/*.js")
//**,匹配js目录下的,以及js子目录下的所有.js结尾的文件
gulp.src("js/**/*.js")

3、gulp.src()匹配实例

gulp.src("js/app.js");//精确匹配文件
gulp.src("js/*.js");仅匹配js目录下的所有以.js结尾的文件
gulp.src("js/**/*.js");匹配js下所有以.js结尾的文件,包括js的子目录
//注意,对于文档写匹配所有子目录也用一个*,这是错误的写法,不会报错,也不会匹配任何内容!
gulp.src("!js/app.js");从匹配结果中排出app.js,一般在数组glob中使用

4、gulp.src([globs]),传入数组

前面说了,gulp.src()可以接收一个数组的形式,比如:

gulp.src(['js/**/*.js', '!js/**/*.min.js'])

上面的代码表示:查找js目录及子目录下所有的js文件,不包括.min.js结尾的文件。

五、gulp.dest()

gulp.dest()是用来写文件的,表示把传过来的流最终写入哪里。因为可以看出,其参数要求的是一个路径:

1、gulp.dest()参数

gulp.dest(path[, options]);

比如上述我们用到过的"gulp.dest('build')",即表示把最终文件写入到build文件夹里。

从参数上我们还可以看出,gulp.dest()还有一个可选项,即[options],一般我们用不到。

注意:gulp.dest()只是负责指定文件流写入的目录,并不会指定文件名,文件名由其文件流自身决定。

在gulp源码中,gulp.dest()和gulp.src都是通过vinyl-fs实现的。

//gulp相关源码
var vfs = require('vinyl-fs');
Gulp.prototype.src = vfs.src;
Gulp.prototype.dest = vfs.dest;

如果想继续深入研究gulp.src()或者gulp.dest()内部实现的同学可以继续去找vinyl-fs

六、gulp.watch()

gulp.watch()用来监听文件的变化,如果监听的文件发生了变动,则执行某一任务,比如文件变化,就执行压缩或解析等:

//语法:
gulp.watch(glob[,opts],tasks);
//例如:
gulp.watch("template/*.plim",['build']);
//即,当模板文件发生变动时,自动执行build任务

其中,参数glob和gulp.src()中的glob相同。opts是可配置项,不常用。另外,第二个参数可以是任务数组,比如:['build','uplify']等。

另外,gulp.watch()还有一个格式:

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

第一个参数和上面相同,第二个参数是一个函数,表示监听发生变动时执行某一函数。

gulp.watch("template/*.plim",function(event){
    console.log(event.type)
})

该函数有一个参数即event,event有两个属性,分别是:type,path

event.type //文件调用事件的类型,比如修改文件其值就是changed
event.path //文件路径

也就是说,当我们进行监听的时候,会得到监听的文件的地址和对所监听文件的操作。

此外,event.type除了changed之外,还有其他四个值:

changed:修改文件
added:新建文件
deleted:删除文件

比如如下:

gulp.watch("js/*.js",function(event){
    console.log(event.type);
})

然后,我先删除一个js文件,然后在新建一个js文件,对这个文件进行修改,会有三个相应的操作类型输出。

Screenshot from 2015-12-05 21:15:26

注意:gulp.watch()功能是比较常用的,一般写在gulpfile中进行操作提示的输出,这样当出现什么问题的时候,能及时发现。

七、watcher

另外一种文件监听的方法就是指定一个监听器:

var watcher = gulp.watch("js/*.js",['build']);
watcher.on("change",function(event){
   console.log(event.type+"--"+event.path);
});

该监听器会监听change事件(无论是changed、added还是deleted,都是change),之后执行指定函数。

除了监听change,还有:

error:发生错误时触发

end:回调函数运行结束后触发

ready:当开始监听文件时触发,新添加文件时也会触发

nomatch:即glob没有匹配到结果

这些可以拿来综合使用,效果如下:

Screenshot from 2015-12-05 22:14:31

需要注意的是,尽管我们并不希望错误的出现,但是我们需要对错误进行提前的处理,所以error也应该是gulpfile的标配,这样出现问题的时候可以输出err.message或执行其他函数,而不是监听结束。错误管理也是一门学问。

如果你不习惯使用监听器的写法的话,也可以使用链式的写法:

gulp.watch("js/*.js",['build']).on("change",function(event){
    console.log(event.type)
}).pipe(uplify())

监听器方法:

watcher.end();停止监听

watcher.files();返回watcher监听的事件列表

watcher.add();将文件添加到watcher中

watcher.remove();从watcher中移除个别文件

比如,我们删除某些文件后想把之前生成的文件也删除,就可以配合这些方法使用。

八、常用插件

livereload

browsersync

发表评论

电子邮件地址不会被公开。 必填项已用*标注