使用 gulp ,主要知道4個API就能完成大部分需求:gulp.task()
,gulp.src()
,gulp.dest()
,gulp.watch()
,所以很快就能掌握,但是這4個API有幾個地方需理解透徹才行,我會在下面一一說明。為了避免出現理解偏差,建議先看一遍下面的文檔。
在介紹這個 API 之前我們首先來說一下 Grunt.js 和 Gulp.js 工作方式的一個區(qū)別。Grunt 主要是以文件為媒介來運行它的工作流的,比如在 Grunt 中執(zhí)行完一項任務后,會把結果寫入到一個臨時文件中,然后可以在這個臨時文件內容的基礎上執(zhí)行其它任務,執(zhí)行完成后又把結果寫入到臨時文件中,然后又以這個為基礎繼續(xù)執(zhí)行其它任務...就這樣反復下去。而在 Gulp 中,使用的是 Nodejs 中的 stream(流),首先獲取到需要的 stream,然后可以通過 stream 的pipe()
方法把流導入到你想要的地方,比如 Gulp 的插件中,經過插件處理后的流又可以繼續(xù)導入到其他插件中,當然也可以把流寫入到文件中。所以 Gulp 是以 stream 為媒介的,它不需要頻繁的生成臨時文件,這也是 Gulp 的速度比 Grunt 快的一個原因。再回到正題上來,gulp.src()
方法正是用來獲取流的,但要注意這個流里的內容不是原始的文件流,而是一個虛擬文件對象流(Vinyl files),這個虛擬文件對象中存儲著原始文件的路徑、文件名、內容等信息,這個我們暫時不用去深入理解,你只需簡單的理解可以用這個方法來讀取你需要操作的文件就行了。其語法為:
gulp.src(globs[, options])
globs參數是文件匹配模式(類似正則表達式),用來匹配文件路徑(包括文件名),當然這里也可以直接指定某個具體的文件路徑。當有多個匹配模式時,該參數可以為一個數組。
options為可選參數。通常情況下我們不需要用到。
下面我們重點說說 Gulp 用到的 glob 的匹配規(guī)則以及一些文件匹配技巧。
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)下面以一系列例子來加深理解
*
能匹配 a.js
,x.y
,abc
,abc/
,但不能匹配a/b.js
*.*
能匹配 a.js
,style.css
,a.b
,x.y
*/*/*.js
能匹配 a/b/c.js
,x/y/z.js
,不能匹配a/b.js
,a/b/c/d.js
**
能匹配 abc
,a/b.js
,a/b/c.js
,x/y/z
,x/y/z/a.b
,能用來匹配所有的目錄和文件**/*.js
能匹配 foo.js
,a/foo.js
,a/b/foo.js
,a/b/c/foo.js
a/**/z
能匹配 a/z
,a/b/z
,a/b/c/z
,a/d/g/h/j/k/z
a/**b/z
能匹配 a/b/z
,a/sb/z
,但不能匹配a/x/sb/z
,因為只有單**
單獨出現才能匹配多級目錄?.js
能匹配 a.js
,b.js
,c.js
a??
能匹配 a.b
,abc
,但不能匹配ab/
,因為它不會匹配路徑分隔符[xyz].js
只能匹配 x.js
,y.js
,z.js
,不會匹配xy.js
,xyz.js
等,整個中括號只代表一個字符[^xyz].js
能匹配 a.js
,b.js
,c.js
等,不能匹配x.js
,y.js
,z.js
當有多種匹配模式時可以使用數組
//使用數組的方式來匹配多種文件
gulp.src(['js/*.js','css/*.css','*.html'])
使用數組的方式還有一個好處就是可以很方便的使用排除模式,在數組中的單個匹配模式前加上!
即是排除模式,它會在匹配的結果中排除這個匹配,要注意一點的是不能在數組中的第一個元素中使用排除模式
gulp.src([*.js,'!b*.js']) //匹配所有js文件,但排除掉以b開頭的js文件
gulp.src(['!b*.js',*.js]) //不會排除任何文件,因為排除模式不能出現在數組的第一個元素中
此外,還可以使用展開模式。展開模式以花括號作為定界符,根據它里面的內容,會展開為多個模式,最后匹配的結果為所有展開的模式相加起來得到的結果。展開的例子如下:
a{b,c}d
會展開為 abd
,acd
a{b,}c
會展開為 abc
,ac
a{0..3}d
會展開為 a0d
,a1d
,a2d
,a3d
a{b,c{d,e}f}g
會展開為 abg
,acdfg
,acefg
a{b,c}d{e,f}g
會展開為 abdeg
,acdeg
,abdeg
,abdfg
gulp.dest()方法是用來寫文件的,其語法為:
gulp.dest(path[,options])
path為寫入文件的路徑
options為一個可選的參數對象,通常我們不需要用到
要想使用好gulp.dest()
這個方法,就要理解給它傳入的路徑參數與最終生成的文件的關系。
gulp的 使用流程一般是這樣子的:首先通過gulp.src()
方法獲取到我們想要處理的文件流,然后把文件流通過pipe方法導入到gulp的插件中,最后把經過插件處理后的流再通過 pipe 方法導入到gulp.dest()
中,gulp.dest()
方法則把流中的內容寫入到文件中,這里首先需要弄清楚的一點是,我們給gulp.dest()
傳入的路徑參數,只能用來指定要生成的文件的目錄,而不能指定生成文件的文件名,它生成文件的文件名使用的是導入到它的文件流自身的文件名,所以生成的文件名是由導入到它的文件流決定的,即使我們給它傳入一個帶有文件名的路徑參數,然后它也會把這個文件名當做是目錄名,例如:
var gulp = require('gulp');
gulp.src('script/jquery.js')
.pipe(gulp.dest('dist/foo.js'));
//最終生成的文件路徑為 dist/foo.js/jquery.js,而不是dist/foo.js
要想改變文件名,可以使用插件 gulp-rename
下面說說生成的文件路徑與我們給gulp.dest()
方法傳入的路徑參數之間的關系。
gulp.dest(path)
生成的文件路徑是我們傳入的path參數后面再加上gulp.src()
中有通配符開始出現的那部分路徑。例如:
var gulp = reruire('gulp');
//有通配符開始出現的那部分路徑為 **/*.js
gulp.src('script/**/*.js')
.pipe(gulp.dest('dist')); //最后生成的文件路徑為 dist/**/*.js
//如果 **/*.js 匹配到的文件為 jquery/jquery.js ,則生成的文件路徑為 dist/jquery/jquery.js
再舉更多一點的例子
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.src()
方法配置參數中的base
屬性,我們可以更靈活的來改變gulp.dest()
生成的文件路徑。
當我們沒有在gulp.src()
方法中配置base
屬性時,base
的默認值為通配符開始出現之前那部分路徑,例如:
gulp.src('app/src/**/*.css') //此時base的值為 app/src
上面我們說的gulp.dest()
所生成的文件路徑的規(guī)則,其實也可以理解成,用我們給gulp.dest()
傳入的路徑替換掉gulp.src()
中的base
路徑,最終得到生成文件的路徑。
gulp.src('app/src/**/*.css') //此時base的值為app/src,也就是說它的base路徑為app/src
//設該模式匹配到了文件 app/src/css/normal.css
.pipe(gulp.dest('dist')) //用dist替換掉base路徑,最終得到 dist/css/normal.css
所以改變base路徑后,gulp.dest()
生成的文件路徑也會改變
gulp.src(script/lib/*.js) //沒有配置base參數,此時默認的base路徑為script/lib
//假設匹配到的文件為script/lib/jquery.js
.pipe(gulp.dest('build')) //生成的文件路徑為 build/jquery.js
gulp.src(script/lib/*.js, {base:'script'}) //配置了base參數,此時base路徑為script
//假設匹配到的文件為script/lib/jquery.js
.pipe(gulp.dest('build')) //此時生成的文件路徑為 build/lib/jquery.js
用gulp.dest()
把文件流寫入文件后,文件流仍然可以繼續(xù)使用。
gulp.task
方法用來定義任務,內部使用的是 Orchestrator,其語法為:
gulp.task(name[, deps], fn)
name 為任務名
deps 是當前定義的任務需要依賴的其他任務,為一個數組。當前定義的任務會在所有依賴的任務執(zhí)行完畢后才開始執(zhí)行。如果沒有依賴,則可省略這個參數
fn 為任務函數,我們把任務要執(zhí)行的代碼都寫在里面。該參數也是可選的。
gulp.task('mytask', ['array', 'of', 'task', 'names'], function() { //定義一個有依賴的任務
// Do something
});
gulp.task()
這個API沒什么好講的,但需要知道執(zhí)行多個任務時怎么來控制任務執(zhí)行的順序。
gulp 中執(zhí)行多個任務,可以通過任務依賴來實現。例如我想要執(zhí)行one
,two
,three
這三個任務,那我們就可以定義一個空的任務,然后把那三個任務當做這個空的任務的依賴就行了:
//只要執(zhí)行default任務,就相當于把one,two,three這三個任務執(zhí)行了
gulp.task('default',['one','two','three']);
如果任務相互之間沒有依賴,任務會按你書寫的順序來執(zhí)行,如果有依賴的話則會先執(zhí)行依賴的任務。
但是如果某個任務所依賴的任務是異步的,就要注意了,gulp 并不會等待那個所依賴的異步任務完成,而是會接著執(zhí)行后續(xù)的任務。例如:
gulp.task('one',function(){
//one是一個異步執(zhí)行的任務
setTimeout(function(){
console.log('one is done')
},5000);
});
//two任務雖然依賴于one任務,但并不會等到one任務中的異步操作完成后再執(zhí)行
gulp.task('two',['one'],function(){
console.log('two is done');
});
上面的例子中我們執(zhí)行 two 任務時,會先執(zhí)行 one 任務,但不會去等待 one 任務中的異步操作完成后再執(zhí)行 two 任務,而是緊接著執(zhí)行 two 任務。所以 two 任務會在 one 任務中的異步操作完成之前就執(zhí)行了。
那如果我們想等待異步任務中的異步操作完成后再執(zhí)行后續(xù)的任務,該怎么做呢?
有三種方法可以實現:
第一:在異步操作完成后執(zhí)行一個回調函數來通知 gulp 這個異步任務已經完成,這個回調函數就是任務函數的第一個參數。
gulp.task('one',function(cb){ //cb為任務函數提供的回調,用來通知任務已經完成
//one是一個異步執(zhí)行的任務
setTimeout(function(){
console.log('one is done');
cb(); //執(zhí)行回調,表示這個異步任務已經完成
},5000);
});
//這時two任務會在one任務中的異步操作完成后再執(zhí)行
gulp.task('two',['one'],function(){
console.log('two is done');
});
第二:定義任務時返回一個流對象。適用于任務就是操作 gulp.src 獲取到的流的情況。
gulp.task('one',function(cb){
var stream = gulp.src('client/**/*.js')
.pipe(dosomething()) //dosomething()中有某些異步操作
.pipe(gulp.dest('build'));
return stream;
});
gulp.task('two',['one'],function(){
console.log('two is done');
});
第三:返回一個 promise 對象,例如
var Q = require('q'); //一個著名的異步處理的庫 https://github.com/kriskowal/q
gulp.task('one',function(cb){
var deferred = Q.defer();
// 做一些異步操作
setTimeout(function() {
deferred.resolve();
}, 5000);
return deferred.promise;
});
gulp.task('two',['one'],function(){
console.log('two is done');
});
gulp.task()
就這些了,主要是要知道當依賴是異步任務時的處理。
gulp.watch()
用來監(jiān)視文件的變化,當文件發(fā)生變化后,我們可以利用它來執(zhí)行相應的任務,例如文件壓縮等。其語法為
gulp.watch(glob[, opts], tasks)
glob 為要監(jiān)視的文件匹配模式,規(guī)則和用法與gulp.src()
方法中的glob
相同。
opts 為一個可選的配置對象,通常不需要用到
tasks 為文件變化后要執(zhí)行的任務,為一個數組
gulp.task('uglify',function(){
//do something
});
gulp.task('reload',function(){
//do something
});
gulp.watch('js/**/*.js', ['uglify','reload']);
gulp.watch()
還有另外一種使用方式:
gulp.watch(glob[, opts, cb])
glob和opts參數與第一種用法相同
cb參數為一個函數。每當監(jiān)視的文件發(fā)生變化時,就會調用這個函數,并且會給它傳入一個對象,該對象包含了文件變化的一些信息,type
屬性為變化的類型,可以是added
,changed
,deleted
;path
屬性為發(fā)生變化的文件的路徑
gulp.watch('js/**/*.js', function(event){
console.log(event.type); //變化類型 added為新增,deleted為刪除,changed為改變
console.log(event.path); //變化的文件的路徑
});
更多建議: