Gulp教程(一)入门篇

Gulp教程(一)入门篇

Gulp,用自动化构建工具增强你的工作流程!

如果你是一个F2E,那么我觉得你应该要接触一下gulp,无论你是从未接触过自动化构建工具的,或一直重度依赖grunt的开发者,我觉得你都应该学习一下gulp,一款易于学习和使用、快速构建、插件高质的优雅工作流构建器。

gulp

Gulp运用node.js的流,这使得它构建任务很快,因为没有磁盘文件的读写操作,如果你想了解更多关于流的知识,你可以看看流手册。Gulp允许你输入源文件,然后在一系列的管道插件中处理,最后输出,不像Grunt你需要为每个插件配置输入和输出。

安装

全局安装

gulp和一般的npm工具包一样,可以全局进行安装:

1
$ npm install --global gulp

作为开发依赖安装

1
$ npm install --save-dev gulp

创建任务

在执行gulp任务之前,必须为其定义任务,在项目的根路径下创建一个名为 gulpfile.js 的文件:

1
2
3
4
5
6
// gulpfile.js
var gulp = require('gulp');
gulp.task('default', function() {
// 将你的默认的任务代码放在这
console.log('task started.');
});

执行任务

通过运行gulp,运行默认名为default任务:

1
$ gulp

想要单独执行特定的任务(task),请输入 gulp <taskname>.

常用API

gulp.src()

在叙述gulp的api方法之前,首先区别一下Grunt和Gulp之间的差异。Grunt主要是通过建立文件来运行前端工作流的:如grunt开始了一项任务后,会将执行的结果写入到一个临时文件当中,然后继续后续的任务,后续任务会通过这个临时文件作为源进行后续的任务,后续任务完成后,会一样道理的把结果写入后续产生的临时文件当中,周而复始,我们可以发现随着任务数的增长,文件的读写开销会大大增长,这就影响了工作流的效率。

而在gulp中,使用的是nodejs的stream流,通过流的 pipe()方法把上一个任务得出的结果直接通过管道pipe入另外一个任务,作为该任务的源去处理,多个任务就像水管中的流,经过一个又一个的gulp插件方法处理,最后得到我们想要的最终结果,减少生成临时文件,大大提高任务的执行效率。

言归正传,src的方法用来读取流,这个流可以理解为原始文件的一个虚拟对象流Vinyl-files,该对象存储了原始文件的路径、文件名、内容等信息,操作它就类似操作原始文件一样:

1
gulp.src(globs[, options])

globs参数是文件匹配模式(类似正则表达式),用来匹配文件路径(包括文件名),当然这里也可以直接指定某个具体的文件路径。当有多个匹配模式时,该参数可以为一个数组。
options为可选参数。通常情况下我们不需要用到。

Gulp内部使用了node-glob模块来实现其文件匹配功能。我们可以使用下面这些特殊的字符来匹配我们想要的文件:

* 匹配文件路径中的0个或多个字符,但不会匹配路径分隔符,除非路径分隔符出现在末尾

** 匹配路径中的0个或多个目录及其子目录,需要单独出现,即它左右不能有其他东西了。如果出现在末尾,也能匹配文件。

? 匹配文件路径中的一个字符(不会匹配路径分隔符)

[...] 匹配方括号中出现的字符中的任意一个,当方括号中第一个字符为^或!时,则表示不匹配方括号中出现的其他字符中的任意一个,类似js正则表达式中的用法

!(pattern|pattern|pattern) 匹配任何与括号中给定的任一模式都不匹配的

?(pattern|pattern|pattern) 匹配括号中给定的任一模式0次或1次,类似于js正则中的(pattern|pattern|pattern)?

+(pattern|pattern|pattern) 匹配括号中给定的任一模式至少1次,类似于js正则中的(pattern|pattern|pattern)+

*(pattern|pattern|pattern) 匹配括号中给定的任一模式0次或多次,类似于js正则中的(pattern|pattern|pattern)*

@(pattern|pattern|pattern) 匹配括号中给定的任一模式1次,类似于js正则中的(pattern|pattern|pattern)

匹配多种模式

1
gulp.src(['js/*.js','css/*.css','*.html'])

使用数组的方式还有一个好处就是可以很方便的使用排除模式,在数组中的单个匹配模式前加上!即是排除模式,它会在匹配的结果中排除这个匹配,要注意一点的是不能在数组中的第一个元素中使用排除模式

1
2
gulp.src([*.js,'!b*.js']) //匹配所有js文件,但排除掉以b开头的js文件
gulp.src(['!b*.js',*.js]) //不会排除任何文件,因为排除模式不能出现在数组的第一个元素中

gulp.dest()

dest()方法用于写入文件,生成文件对象到指定路径,语法为

1
gulp.dest(path[,options])

其中path为写入文件的路径,options为可选配置。

要想使用好gulp.dest(),必须理解清楚gulp.src()的通配规则:src方法获取到我们要的文件流,通过pipe导入到各个gulp插件中,最后在得出最后结果的时候,给dest方法传入路径参数,因此为其初始指定的参数只能为生成的文件目录,而不是生成的最后文件名,其生成的文件的名字依赖于src方法适配到的路径文件,如:

1
2
3
4
var gulp = require('gulp');
gulp.src('script/jquery.js')
.pipe(gulp.dest('dist/foo.js'));
//最终生成的文件路径为 dist/foo.js/jquery.js,而不是dist/foo.js

此处一个有个常见的情况就是,如果通配到了多级的文件目录时,则最后dest出来的文件就是dest传入参数+多级目录+src源文件名;如果通配到了根目录的文件时,则dest出来的文件就是dest传入参数+src源文件名;如果没通配符时,则生成dest传入路径+src源文件名:

1
2
3
4
5
6
7
8
9
10
11
gulp.src('script/avalon/avalon.js') //没有通配符出现的情况
.pipe(gulp.dest('dist')); //最后生成的文件路径为 dist/avalon.js
//有通配符开始出现的那部分路径为 **/underscore.js
gulp.src('script/**/underscore.js')
//假设匹配到的文件为script/util/underscore.js
.pipe(gulp.dest('dist')); //则最后生成的文件路径为 dist/util/underscore.js
gulp.src('script/*') //有通配符出现的那部分路径为 *
//假设匹配到的文件为script/zepto.js
.pipe(gulp.dest('dist')); //则最后生成的文件路径为 dist/zepto.js

gulp.task()

gulp.task方法用来定义任务,内部使用的是Orchestrator,其语法为:

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

其中name该任务的名字,deps为依赖任务(数组),fn为该任务执行的函数。

名字没有什么要注意的,deps为依赖数组,我们可以为当前任务,设置一个依赖任务数组,在执行当前任务前,必须完成依赖数组中的所有问题:

1
2
3
gulp.task('currentTask', ['array', 'of', 'dependent', 'task', 'names'], function() {
// 做一些事
});

注意: 你的任务是否在这些前置依赖的任务完成之前运行了?请一定要确保你所依赖的任务列表中的任务都使用了正确的异步执行方式:使用一个 callback,或者返回一个 promise 或 stream。

fn
该函数定义任务所要执行的一些操作。通常来说,它会是这种形式:gulp.src().pipe(someplugin())

async task

任务是可以异步执行的,若fn做到以下其中一点:
callback,在异步操作完成时,发起callback去通知这个async task已经完成,callback为fn的第一个参数:

1
2
3
4
5
6
7
8
9
// 在 shell 中执行一个命令
var exec = require('child_process').exec;
gulp.task('jekyll', function(cb) {
// 编译 Jekyll
exec('jekyll build', function(err) {
if (err) return cb(err); // 返回 error
cb(); // 完成 task
});
});

stream,定义async task时,注意返回一个stream结果,告诉当前任务何时完成:

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

promise,定义async task时,注意返回一个promise结果,告诉当前任务何时完成:

1
2
3
4
5
6
7
8
9
10
11
12
var Q = require('q'); //异步处理库
gulp.task('somename', function() {
var deferred = Q.defer();
// 执行异步的操作
setTimeout(function() {
deferred.resolve();
}, 1);
return deferred.promise;
});

gulp.watch()

watch方法用于监视文件,并且在文件发生改动的时候做出处理,它总会返回一个EventEmitter来emitchange事件:

用法一,gulp.watch(glob[, opts], tasks)

glob参数监控哪些文件监控,opts为可选配置,tasks为文件变动后执行的任务名称。

用法二,gulp.watch(glob[, opts, cb])

glob参数监控哪些文件监控,opts为可选配置,cb为文件变动后执行的函数(传入event对象)。

event对象

1
2
3
gulp.watch('js/**/*.js', function(event) {
console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
});

event.type为发生变化的类型:有added,changeddeleted,分别对应增加、改变和删除文件;
event.path为发生变化的文件路径

总结

到此,Gulp的基础知识已经讲完,虽然很大边幅,但是实际应用中并没有十分复杂,通过配置你个人的gulp工作流,我相信你会拥有不一样的开发体验!