gulp備忘錄專題文章,
自從gulp面世以后,我就拋棄了Grunt加入了gulp的陣營。從易用性以及性能方面來說,無疑gulp更具優(yōu)勢。具體的可以看我之前的一篇文章gulp vs. grunt。
可以說,我很早就開始使用gulp了,那為何會有這幾篇gulp的相關(guān)文章呢?是因?yàn)榘l(fā)現(xiàn)我在使用gulp的時候,仍然處于一個僅僅會用的層面上,當(dāng)需要使用一些插件時,往往是在npm找到對應(yīng)的插件,然后照著example抄一遍,對gulp的有些特性還不是很清楚。所以我覺得很有必要寫一些東西來深度學(xué)習(xí)一下gulp。
首先來介紹一下gulp。
gulp是一款前端構(gòu)建工具,它將構(gòu)建操作抽象成一系列的任務(wù)。做的事情與grunt是一樣的。不過它信奉的原則是代碼優(yōu)于配置,基于Node.js中的stream來操作文件。
我們可以通過以下方式來安裝gulp,
$ npm install -g gulp
安裝成功之后你就可以直接在任何路徑上使用gulp
命令了。不過在實(shí)際項(xiàng)目中使用gulp時,往往需要安裝一個local版本的gulp,
$ npm install gulp --save-dev
采用--save-dev
將gulp寫進(jìn)package.json
的devDependencies
依賴中。
至于為何全局安裝gulp之后還需要在項(xiàng)目中安裝gulp,可以參見stackoverflow上的問題why-do-we-need-to-install-gulp-globally-and-locally。基本上這么做的原因是為了靈活的去使用gulp,如果實(shí)在不能理解的話也不必去糾結(jié)。你大可以不用全局安裝gulp,僅在項(xiàng)目中安裝。
在項(xiàng)目中添加gulp依賴并安裝完畢之后,我們需要在項(xiàng)目的根目錄中創(chuàng)建一個名叫gulpfile.js
的文件。我們將在這個gulpfile.js文件中完成所有構(gòu)建任務(wù)的設(shè)計。
一般地,我們gulpfile.js文件內(nèi)容如下,
var gulp = require('gulp');
gulp.task('default', function () {
console.log('default finished.');
});
然后我們直接在命令行中輸入gulp
就可以開始構(gòu)件任務(wù),
[00:54:32] Using gulpfile ~/code/test/test-gulp/gulpfile.js
[00:54:32] Starting 'default'...
default finished.
[00:54:32] Finished 'default' after 98 μs
在本例中,我們在default任務(wù)中啥事也沒干,僅僅輸出一條提示信息。
基本上gulp的使用就是這樣的模式。
gulp的API非常簡潔,常用的API就4個,他們是gulp.task
,gulp.src
,gulp.dest
,gulp.watch
。
gulp.task
gulp.task
用于定義任務(wù)。其內(nèi)部使用的是一個名為Orchestrator任務(wù)調(diào)度庫。它的用法很簡單,
|
|
參數(shù) | 類型 | 是否可選 | 說明 |
---|---|---|---|
name |
String | 必須 | 任務(wù)的名稱 |
deps |
Array | 可選 | 是一個數(shù)組,表示當(dāng)前任務(wù)所依賴的其他任務(wù) |
fn |
Function | 必須 | 任務(wù)具體要執(zhí)行的操作 |
下面是一個例子,
gulp.task('mytask', ['one', 'two', 'three'], function () {
doSomething();
});
gulp.task('mytask2', function() {
doSomething();
});
gulp.task('mytask3', ['one', 'two']);
如示例所示,當(dāng)某一個任務(wù)沒有依賴任務(wù)時,可以省略deps
參數(shù)。當(dāng)一個任務(wù)有依賴任務(wù)時,也是可以省略fn
參數(shù)的。
當(dāng)gulp.task
定義任務(wù)并有依賴任務(wù)時,gulp默認(rèn)將以最大的并發(fā)數(shù)去同時執(zhí)行這些依賴任務(wù)。所以gulp默認(rèn)是不保證依賴任務(wù)之間的異步操作等待的。
若有下面這樣一個示例,
gulp.task('one', function() {
setTimeout(function() {
console.log('one is done.');
}, 2000);
});
gulp.task('two', ['one'], function() {
console.log('two is done.');
});
gulp.task('default', ['one', 'two']);
如果我們的one
,two
這兩個任務(wù)彼此之間有順序依賴的話(示例中采用setTimeout來模擬異步任務(wù)),即two
要在one
的異步操作完畢之后執(zhí)行。如果我們直接執(zhí)行gulp
,得到結(jié)果如下,
[01:25:08] Using gulpfile ~/code/test/test-gulp/gulpfile.js
[01:25:08] Starting 'one'...
[01:25:08] Finished 'one' after 578 μs
[01:25:08] Starting 'two'...
[01:25:08] two is done.
[01:25:08] Finished 'two' after 2.33 ms
[01:25:08] Starting 'default'...
[01:25:08] Finished 'default' after 35 μs
[01:25:10] one is done.
可以看到,任務(wù)one
和任務(wù)two
幾乎是在同一時刻就同時執(zhí)行的。但是在01:25:08任務(wù)two
就已經(jīng)完成了,而任務(wù)one
在01:25:10才完成。所以這并沒有達(dá)到我們的要求。那么,我們該如何做呢?
通常,我們會有三種方式來達(dá)到這一目的。
第一種方式:在異步操作完成后執(zhí)行一個回調(diào)函數(shù)來通知gulp這個異步任務(wù)已經(jīng)完成,這個回調(diào)函數(shù)就是任務(wù)函數(shù)的第一個參數(shù)。
下面我們來改寫一下任務(wù)one
,
gulp.task('one', function(callback) {
setTimeout(function() {
console.log('one is done.');
callback();
}, 2000);
});
此時我們再執(zhí)行gulp
其結(jié)果如下,
[01:40:02] Using gulpfile ~/code/test/test-gulp/gulpfile.js
[01:40:02] Starting 'one'...
[01:40:04] one is done.
[01:40:04] Finished 'one' after 2.01 s
[01:40:04] Starting 'two'...
[01:40:04] two is done.
[01:40:04] Finished 'two' after 659 μs
[01:40:04] Starting 'default'...
[01:40:04] Finished 'default' after 12 μs
如此我們就能保證在執(zhí)行two
之前,one
的異步操作也執(zhí)行完畢了。
下面我們介紹另外兩種方式。
第二種方式:定義任務(wù)時返回一個流對象。
比如,
gulp.task('one', function() {
return gulp.src('client/**/*.js')
.pipe(minify())
.pipe(gulp.dest('build'));
});
gulp.task('two', ['one'], function() {
console.log('two is done.');
});
gulp.task('default', ['one', 'two']);
第三種方式:返回一個promise對象。
比如,
var Q = require('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('default', ['one', 'two']);
總結(jié),
gulp.src
相關(guān)的操作。gulp.src
前面說過gulp的工作機(jī)制是基于Node.js中的流。這里所說的流并不是Node.js原始的流對象,而是一種虛擬文件流對象(Vinyl files),這個虛擬文件對象中存儲著原始文件的路徑、文件名、內(nèi)容等信息。我們不關(guān)注這個,后面再講解寫gulp插件時會再次闡述這塊內(nèi)容。
所以,簡單來說gulp.src
就是用來獲取你需要操作的文件的。它的用法如下,
gulp.src(globs[, options])
參數(shù) | 類型 | 是否可選 | 說明 |
---|---|---|---|
globs |
String, Array | 必選 | 文件匹配模式 |
options |
Object | 可選 | 匹配選項(xiàng) |
gulp.src
內(nèi)部使用node-glob模塊來實(shí)現(xiàn)文件匹配。node-glob是一種與正則表達(dá)式類似的匹配范式。
globs
參數(shù)也可以直接是用一個文件地址,還可以是一個包含多個匹配模式的數(shù)組。而options
參數(shù)是用來輔助匹配的,它的某些參數(shù)設(shè)置將會影響虛擬文件流的獲取。
node-glob中有如下幾種匹配規(guī)則,
匹配范式 | 用法 |
---|---|
* |
匹配0個或多個字符,但不會匹配路徑分隔符,除非路徑分隔符出現(xiàn)在末尾 |
** |
匹配0個或多個目錄及其子目錄,需要單獨(dú)出現(xiàn)。如果出現(xiàn)在末尾,也能匹配文件 |
? |
匹配一個字符,不會匹配路徑分隔符 |
[...] |
匹配方括號中出現(xiàn)的字符中的任意一個,當(dāng)方括號中第一個字符為^或!時,表示取反 |
!(p1,p2,p3) |
匹配任何與括號中給定的任一模式都不匹配的 |
?(p1,p2,p3) |
匹配括號中給定的任一模式0次或1次 |
+(p1,p2,p3) |
匹配括號中給定的任一模式至少1次 |
*(p1,p2,p3) |
匹配括號中給定的任一模式0次或多次 |
@(p1,p2,p3) |
匹配括號中給定的任一模式1次 |
{p1,p2} |
以展開模式進(jìn)行匹配 |
備注:
(p1,p2,p3)
中的,
其實(shí)是|
。如果寫成p1|p2|p3
,將會導(dǎo)致markdown解析錯誤。暫時沒找到解決方案,望知道的大神告知????
關(guān)于node-glob更多的用法,請參閱node-glob的相關(guān)文檔。不過基本上上面的幾種用法將會覆蓋gulp.src
90%以上的場景。
下面是一些更詳細(xì)的示例,
*
能匹配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
,因?yàn)橹挥袉?code>**單獨(dú)出現(xiàn)才能匹配多級目錄?.js
能匹配a.js
,b.js
,c.js
a??
能匹配a.b
,abc
,但不能匹配ab/
,因?yàn)樗粫ヅ渎窂椒指舴?/li>
[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
a{b,c}d.js
能匹配abd.js
,acd.js
,不能匹配abcd.js
我們還可以將多個匹配模式組合成一個數(shù)組傳遞給gulp.src
,比如
gulp.src(['js/*.js', 'css/*.js', 'views/*.html']);
在使用這一方式時,我們還可以添加一個反模式用來在之前的匹配結(jié)果中排除某些匹配項(xiàng),比如
gulp.src(['js/*.js', '!js/jquery.js']);
因?yàn)榉茨J绞窃谥暗钠ヅ浣Y(jié)果做排除的,所以反模式不能放在數(shù)組的第一個位置,比如下面這種方式是不正確用法,
gulp.src(['!js/jquery.js', 'js/*.js']); // 反模式位于匹配模式數(shù)組的第一個位置,此時將起不到排除作用
我們可以通過options
給gulp.src
傳遞匹配參數(shù)。options
除了node-glob原有的一些參數(shù)之外,還有一些額外的參數(shù)。
參數(shù) | 默認(rèn)值 | 說明 |
---|---|---|
options.buffer |
true |
gulp的虛擬文件對象默認(rèn)以buffer的形式返回文件內(nèi)容。若此項(xiàng)為false ,則以stream的形式返回文件內(nèi)容 |
options.read |
true |
gulp默認(rèn)是會進(jìn)行讀取文件內(nèi)容操作的。若此項(xiàng)被設(shè)置為false 則gulp則不會去讀取文件 |
options.base |
模式匹配中直到出現(xiàn)通配符之前的字符串 | 這個參數(shù)跟gulp.dest 的最終路徑息息相關(guān) |
這些參數(shù)中,唯一值得一提就是options.base
。具體請接著往下看。
gulp.dest
gulp.dest
簡單來說就是用來寫文件的。其用法如下,
gulp.dest(path[, options])
參數(shù) | 類型 | 是否可選 | 說明 |
---|---|---|---|
path |
String | 必選 | 寫入文件的路徑 |
options |
Object | 可選 | 寫入文件時的參數(shù) |
這里唯一值得一提就是寫入文件的路徑。這里的寫入路徑是gulp結(jié)合gulp.src
中的options.base
參數(shù)來計算的,我們傳入的path
參數(shù)其實(shí)只是最終生成文件路徑的文件夾。讓我們來看一個例子,
gulp.src('js/jquery.js')
.pipe(gulp.dest('dist/build.js'));
上面的代碼最終寫的文件路徑是dist/build.js/jquery.js
,而不是dist/build.js
。
其實(shí),gulp.dest
在寫文件時,其步驟可以簡單抽象如下,
gulp.src
得到的文件路徑分解為base + matched的形式。而base是匹配模式中開始出現(xiàn)通配符之前的路徑,matched是開始出現(xiàn)通配符之后的路徑。如果匹配模式中沒有通配符,則matched為文件名。所以示例中的base為js/
,matched為jquery.js
。gulp.dest
的規(guī)則是使用寫入路徑參數(shù)替換base,再加上matched。即dest-path
+ matched
。所以示例中最終的路徑為dist/build.js/jquery.js
。gulp.dest
最終得到寫入路徑不存在,則會自動生成相應(yīng)的路徑。再來看一個例子,
// 假設(shè)要匹配的文件路徑為src/public/js/jquery.js
gulp.src('src/public/**/*.js') // base: src/public/ matched: js/jquery.js
.pipe(gulp.dest('dist/build')); // result: dist/build/js/jquery.js
gulp.src('src/public/**/*.js', {base: 'src'}) // base: src/ matched: public/js/jquery.js
.pipe(gulp.dest('dist')) // dist/public/js/jquery.js
注意,
options.base
時,將導(dǎo)致matched也會跟著發(fā)生變化。options.base
必須是原始base字符串的某個開始字串。比如示例中原始base串是src/public
,那么手動設(shè)置的base只能是src
或者src/public
,而不能是其他的,否則將不能正確寫入文件。參數(shù) | 默認(rèn)值 | 說明 |
---|---|---|
options.cwd |
process.cwd() |
輸出目錄的cwd參數(shù),只在所給的輸出目錄是相對路徑時候有效。 |
options.mode |
0777 | 八進(jìn)制權(quán)限字符,用以定義所有在輸出目錄中所創(chuàng)建的目錄的權(quán)限。 |
寫入?yún)?shù)一般都不用自定義。
gulp.watch
gulp.watch
用來監(jiān)視文件的變化,當(dāng)文件發(fā)生變化后,我們可以利用它來執(zhí)行相應(yīng)的任務(wù)。其內(nèi)部是基于gaze實(shí)現(xiàn)的。
其用法如下,
gulp.watch(glob[, opts], tasks)
或者
gulp.watch(glob[, opts], fn)
參數(shù) | 類型 | 是否可選 | 說明 |
---|---|---|---|
glob |
String, Array | 必須 | 同gulp.src |
opts |
Object | 可選 | - |
tasks |
Array | 必須 | 為文件變化后要執(zhí)行的任務(wù),為一個數(shù)組 |
fn |
Function | 必須 | 文件變化后需要執(zhí)行的回調(diào)函數(shù),函數(shù)參數(shù)為一個對象 |
來看個例子,
gulp.task('uglify',function(){
//do something
});
gulp.task('reload',function(){
//do something
});
gulp.watch('js/**/*.js', ['uglify','reload']);
使用fn
的例子,
gulp.watch('js/**/*.js', function(ev){
console.log(ev.type); //變化類型 added為新增,deleted為刪除,changed為改變
console.log(ev.path); //變化的文件的路徑
});
更多建議: