gulp備忘錄(一):API指南

2018-06-09 17:30 更新

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.jsondevDependencies依賴中。

至于為何全局安裝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的使用就是這樣的模式。

APIs說明

gulp的API非常簡潔,常用的API就4個,他們是gulp.task,gulp.src,gulp.dest,gulp.watch。

gulp.task

gulp.task用于定義任務(wù)。其內(nèi)部使用的是一個名為Orchestrator任務(wù)調(diào)度庫。它的用法很簡單,

1
gulp.task(name[, deps], fn)
參數(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']);

如果我們的onetwo這兩個任務(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ù)one01: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é),

  • callback和返回promise的方式更具普適性,而返回stream的方式更加合適gulp.src相關(guān)的操作。
  • callback方式在某些場景下可能更加靈活。比如任務(wù)中摻雜了一些業(yè)務(wù)邏輯的時候。

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.src90%以上的場景。

下面是一些更詳細(xì)的示例,

  • *能匹配a.js,x.yabc,abc/,但不能匹配a/b.js
  • *.*能匹配a.js,style.cssa.b,x.y
  • */*/*.js能匹配a/b/c.jsx/y/z.js,不能匹配a/b.js,a/b/c/d.js
  • **能匹配abca/b.js,a/b/c.js,x/y/z,x/y/z/a.b,能用來匹配所有的目錄和文件
  • **/*.js能匹配foo.js,a/foo.jsa/b/foo.js,a/b/c/foo.js
  • a/**/z能匹配a/za/b/z,a/b/c/za/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.jsc.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.jsy.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ù)組的第一個位置,此時將起不到排除作用

匹配參數(shù)

我們可以通過optionsgulp.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在寫文件時,其步驟可以簡單抽象如下,

  1. gulp.src得到的文件路徑分解為base + matched的形式。而base是匹配模式中開始出現(xiàn)通配符之前的路徑,matched是開始出現(xiàn)通配符之后的路徑。如果匹配模式中沒有通配符,則matched為文件名。所以示例中的base為js/,matched為jquery.js。
  2. gulp.dest的規(guī)則是使用寫入路徑參數(shù)替換base,再加上matched。即dest-path + matched。所以示例中最終的路徑為dist/build.js/jquery.js。
  3. 如果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

注意,

  • 當(dāng)手動配置options.base時,將導(dǎo)致matched也會跟著發(fā)生變化。
  • 手動設(shè)置的options.base必須是原始base字符串的某個開始字串。比如示例中原始base串是src/public,那么手動設(shè)置的base只能是src或者src/public,而不能是其他的,否則將不能正確寫入文件。

寫入?yún)?shù)

參數(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); //變化的文件的路徑
});

參考列表


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號