GULP
精通gulp的关键:文件路径匹配模式globs

简单说来,gulp的api非常简单。只有4个,他们分别是gulp.src, gulp.dest, gulp.task, gulp.watch 因此,想要简单的使用gulp很容易,但是想要将gulp使用到得心应手的地步并不是一件简单的事情。 而其中最关键的地方在于,对nodejs文件路径匹配模式globs的理解。

gulp的4个api中, gulp.task创建一个任务, gulp.src表示创建的任务是针对文件目录中的那些位置 gulp.dest表示目标文件被处理完毕之后会在哪个位置重新生成, gulp.watch表示监听那些位置文件的变化

除了task,其他三个api都与文件路径密切相关,而gulp.src与gulp.watch更是直接以golbs为第一个参数, gulp的各个插件需要知道自己处理的是那些位置的文件,而golbs则负责指定需要被处理文件的位置

每个人对前端的理解有所差异,造成了前端的项目结构也是各不一样,因此golbs的配置也就显得更加重要 下面就一起来了解一下,golbs的匹配规则。

粗略读一遍可能并不能掌握,所以建议大家收藏起来,留着以后慢慢看

* 匹配文件中0个或者多个字符,但是不会匹配路径中的分隔符,除非路径分隔符出现在末尾 例如下面的写法

// 匹配./style目录下所有的js文件
./style/*.js

// 匹配./style目录下所有的文件
./style/*.*

// 只要层级相同,可以匹配任意目录下的任意js文件 比如./style/a/b.js
./style/*/*.js

** 匹配文件路径中的0个或者多个层级的目录,需要单独出现,如果出现在末尾,也可匹配文件


// 匹配style目录及其所有子目录下的所有js文件,如能匹配
// ./style/a.js
// ./style/lib/res.js
// ./style/mudules/b/a.js
./style/**/*.js

// 匹配style目录下的所有目录和文件,比如能匹配
// ./style/a.js
// ./style/bb
// ./style/images/c.png
./style/**/*

? 匹配一个字符,不会匹配路径分隔符

// 能匹配文件名只有一个字符的js文件,如a.js, b.js ,但不能匹配文件名为2个字符及其以上的js的文件
?.js

[...] 由多个规则组成的数组,可以匹配数组中符合任意一个子项的文件,当子项中第一个字符为!或者^时,表示不匹配该规则

// 匹配style目录下的a0.js, a1.js, a2.js, a3.js
'./style/a[0-3].js'

// 除开node_modules目录之外,匹配项目根目录下的所有html文件
['./**/*.html', '!node_modules/**']

{...} 展开模式,根据里面的内容展开为多个规则,能匹配所有展开之后的规则 将上面的例子扩展一下,可以如下写

// 除开build,simple,images,node_modules目录,匹配根目录下所有的html与php文件
['./**/*.{html, php}', '!{build, simple, images, node_modules}/**']

!(pattern|pattern|pattern) 每一个规则用pattern表示,这里指排除符合这几个模式的所有文件

// 匹配排除文件名为一个字符的js文件,以及排除jquery.js之后的所有js文件
'./style/!(?|jquery).js'

// 排除build与node_modules目录,并排除其他目录下以下划线_开头的html与php文件,匹配其余的html与php文件
// 这种比较复杂的规则在实际开发中会常常用到,需要加深了解
['./**/!(_)*.{html, php}', '!{build, node_modules}/**']

?(pattern|pattern|pattern) 匹配括号中给定的任一模式0次或者1次

// 匹配style目录下的a.js, a2.js, b.js
// 不能组合
// 匹配0次或者1次
'./style/?(a|a2|b).js'

@(pattern|pattern|pattern) 匹配多个模式中的任一个

// 匹配style目录下的a.js, b.js, c.js
// 不能组合
// 匹配一次,不能为空,注意与?的区别
'./style/@(a|b|c).js'

+(pattern|pattern|pattern) 匹配括号中给定任一模式1次或者多次,这几个模式可以组合在一起匹配

// 可以匹配style目录下的a.js, a2.js, b.js
// 也可以匹配他们的组合 ab.js, aa2.js, a2b.js等
// 至少匹配一次,为空不匹配
'./style/+(a|a2|b).js'

*(pattern|pattern|pattern) 匹配括号中给定任一模式0次或者多次,这几个模式可以组合在一起匹配

// 可以匹配style目录下的a.js, b.js, c.js
// 也可以匹配他们的组合 ab.js, bc.js, ac.js
// 匹配0次或者多次
'./style/*(a|b|c).js'

几乎能用到的规则就都在上面了,在实际使用的时候,我们需要根据自己的实际情况来使用 只要掌握了globs模式,距离精通gulp也不太远了。

API

gulp.src(globs[, options])

彻底了解了globs,那么回过头来学习gulp的这几个api就变得非常简单。 gulp.src指定gulp任务的目标文件位置 它的参数中,第一个参数globs为必选,中括号表示可选参数,options为一个配置json对象 globs用于指定目标文件的位置,在上面已经做过史上最详细的介绍,这里对option做一个简单的介绍即可

gulp.src('./**/!(_).{php, html}', {
    buffer: true,
    read: true,
    base: './'
})

options.buffer

类型: Boolean 默认:true 如果该项被设置为false,那么将会以stream方式返回file.contents,而不是文件buffer的形式。 这在处理一些大文件的时候非常有用。 另外需要注意点是,gulp插件可能不支持stream

options.read

类型: Boolean 默认: true 如果被设置为false,那么file.contents会返回空值(null) 也就是并不会去读取文件

options.base

类型: String 默认值: 第一个参数目录中,glob匹配模式之前的路径 理解base至关重要,它将影响到文件处理完毕后,新文件生成的目录 我们需要结合gulp.dest来理解它。

比如在一个路径为static/scripts/libs的目录中,有一个js组件叫做dialog.js

// 匹配static/scripts/libs/dialog.js,并将base解析成 static/scripts
gulp.src('static/scripts/**/*.js')
  .pipe(minify())

  // 处理完毕的文件,将会用build替换掉base,即为 build/libs/dialog.js
  .pipe(gulp.dest('build'));

// 手动设置base的值
gulp.src('static/scripts/**/*.js', { base: 'static' })
  .pipe(minify())

  // 使用build替换base,结果为 build/scripts/libs/dialog.js
  .pipe(gulp.dest('build'));

gulp.dest(path[, options])

能被pipe进来,并且将会写文件。会重新输出所有数据。 也就是说会新建处理完毕之后的文件。 我们可以将他pipe到多个文件夹。如果文件夹不存在,则会自动新建。 关于使用方法,可以结合上面的例子进行理解。

指定的路径用于替换gulp.src参数options中的base,结果就是新文件的位置

gulp.dest的options没怎么使用过,感觉没上面用,就略过了。

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

定义个gulp任务

gulp.task('somename', function() {
  // dosomething
})

name 类型:string 任务的名字,如果你需要在命令行中运行你的某些任务,那么不要在任务名字中使用空格

deps 类型: array 一个包含任务列表的数组,这些任务会在你当前任务运行之前完成

gulp.task('mytask', ['array', 'of', 'task', 'names'], function() {
  // dosomething
})

请一定要确保你所依赖的任务列表中的任务都使用了正确的异步执行方式:使用一个 callback,或者返回一个 promise 或 stream。

fn 该函数定义任务所需要执行的一些操作。通常来说,它会是这样中形式

gulp.src().pipe(someplugin())

异步任务支持 如果fn能够做到以下其中一点,就可以异步执行了

接受一个callback

// 在 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

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();

  // 执行异步的操作
  setTimeout(function() {
    deferred.resolve();
  }, 1);

  return deferred.promise;
});

默认的,task 将以最大的并发数执行,也就是说,gulp 会一次性运行所有的 task 并且不做任何等待。如果你想要创建一个序列化的 task 队列,并以特定的顺序执行,你需要做两件事:1. 给出一个提示,来告知 task 什么时候执行完毕 2.并且再给出一个提示,来告知一个 task 依赖另一个 task 的完成。

对于这个例子,让我们先假定你有两个 task,"one" 和 "two",并且你希望它们按照这个顺序执行:

a 在 "one" 中,你加入一个提示,来告知什么时候它会完成:可以再完成时候返回一个 callback,或者返回一个 promise 或 stream,这样系统会去等待它完成。

b 在 "two" 中,你需要添加一个提示来告诉系统它需要依赖第一个 task 完成。

因此,这个例子的实际代码将会是这样:

var gulp = require('gulp');

// 返回一个 callback,因此系统可以知道它什么时候完成
gulp.task('one', function(cb) {
    // 做一些事 -- 异步的或者其他的
    // 如果 err 不是 null 或 undefined,则会停止执行,且注意,这样代表执行失败了
    cb(err);
});

// 定义一个所依赖的 task 必须在这个 task 执行之前完成
gulp.task('two', ['one'], function() {
    // 'one' 完成后
});

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

gulp.watch(glob [, options], tasks) 或 gulp.watch(glob [, options, cb])

监视文件,并且可以在文件发生改动时候做一些事情。它总会返回一个 EventEmitter 来发射(emit) change 事件。

gulp.watch(glob[, options], tasks)

glob与options略过。

tasks 类型: 数组 需要在文件变动后执行的一个或者多个通过 gulp.task() 创建的 task 的名字,

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

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

glob与options略过

cb(event) 类型: Function 每次变动需要执行的回调函数

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

callback 会被传入一个名为 event 的对象。这个对象描述了所监控到的变动:

event.type 类型: String 发生变动的行为类型: added, changed, deleted

event.path 类型: String 触发该事件的文件路径

好啦,gulp的api与最关键的globs都介绍完了,基本上gulp也算是掌握完毕了。 接下来我们要做的事情就是逐步去了解完成各种任务的插件了。很多插件都比较简单, 在npm上都能够找到使用方法,所以也就没什么好担心的了。