gulpjs是一個前端構建工具,與gruntjs相比,gulpjs無需寫一大堆繁雜的配置參數,API也很是簡單,學習起來很容易,並且gulpjs使用的是nodejs中stream來讀取和操做數據,其速度更快。若是你尚未使用過前端構建工具,或者以爲gruntjs太難用的話,那就嘗試一下gulp吧。css
首先確保你已經正確安裝了nodejs環境。而後以全局方式安裝gulp:html
npm install -g gulp
全局安裝gulp後,還須要在每一個要使用gulp的項目中都單獨安裝一次。把目錄切換到你的項目文件夾中,而後在命令行中執行:前端
npm install gulp
若是想在安裝的時候把gulp寫進項目package.json文件的依賴中,則能夠加上--save-dev:node
npm install --save-dev gulp
這樣就完成了gulp的安裝。至於爲何在全局安裝gulp後,還須要在項目中本地安裝一次,有興趣的能夠看下stackoverflow上有人作出的回答:why-do-we-need-to-install-gulp-globally-and-locally、what-is-the-point-of-double-install-in-gulp。大致就是爲了版本的靈活性,但若是沒理解那也沒必要太去糾結這個問題,只須要知道一般咱們是要這樣作就好了。jquery
就像gruntjs須要一個Gruntfile.js文件同樣,gulp也須要一個文件做爲它的主文件,在gulp中這個文件叫作gulpfile.js。新建一個文件名爲gulpfile.js的文件,而後放到你的項目目錄中。以後要作的事情就是在gulpfile.js文件中定義咱們的任務了。下面是一個最簡單的gulpfile.js文件內容示例,它定義了一個默認的任務。git
var gulp = require('gulp'); gulp.task('default',function(){ console.log('hello world'); });
此時咱們的目錄結構是這樣子的:github
├── gulpfile.js ├── node_modules │ └── gulp └── package.json
要運行gulp任務,只需切換到存放gulpfile.js文件的目錄(windows平臺請使用cmd或者Power Shell等工具),而後在命令行中執行gulp命令就好了,gulp後面能夠加上要執行的任務名,例如gulp task1,若是沒有指定任務名,則會執行任務名爲default的默認任務。正則表達式
使用gulp,僅需知道4個API便可:gulp.task(),gulp.src(),gulp.dest(),gulp.watch(),因此很容易就能掌握,但有幾個地方需理解透徹才行,我會在下面一一說明。爲了不出現理解誤差,建議先看一遍官方文檔。npm
在介紹這個API以前咱們首先來講一下Grunt.js和Gulp.js工做方式的一個區別。Grunt主要是以文件爲媒介來運行它的工做流的,好比在Grunt中執行完一項任務後,會把結果寫入到一個臨時文件中,而後能夠在這個臨時文件內容的基礎上執行其它任務,執行完成後又把結果寫入到臨時文件中,而後又以這個爲基礎繼續執行其它任務...就這樣反覆下去。而在Gulp中,使用的是Nodejs中的stream(流),首先獲取到須要的stream,而後能夠經過stream的pipe()方法把流導入到你想要的地方,好比Gulp的插件中,通過插件處理後的流又能夠繼續導入到其餘插件中,固然也能夠把流寫入到文件中。因此Gulp是以stream爲媒介的,它不須要頻繁的生成臨時文件,這也是Gulp的速度比Grunt快的一個緣由。再回到正題上來,gulp.src()方法正是用來獲取流的,但要注意這個流裏的內容不是原始的文件流,而是一個虛擬文件對象流(Vinyl files),這個虛擬文件對象中存儲着原始文件的路徑、文件名、內容等信息,這個咱們暫時不用去深刻理解,你只需簡單的理解能夠用這個方法來讀取你須要操做的文件就好了。其語法爲:json
gulp.src(globs[, options])
globs參數是文件匹配模式(相似正則表達式),用來匹配文件路徑(包括文件名),固然這裏也能夠直接指定某個具體的文件路徑。當有多個匹配模式時,該參數能夠爲一個數組。 options爲可選參數。一般狀況下咱們不須要用到。
下面咱們重點說說Gulp用到的glob的匹配規則以及一些文件匹配技巧。 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()所生成的文件路徑的規則,其實也能夠理解成,用咱們給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()把文件流寫入文件後,文件流仍然能夠繼續使用。
gulp.task方法用來定義任務,內部使用的是Orchestrator,其語法爲:
gulp.task(name[, deps], fn)
name 爲任務名 deps 是當前定義的任務須要依賴的其餘任務,爲一個數組。當前定義的任務會在全部依賴的任務執行完畢後纔開始執行。若是沒有依賴,則可省略這個參數 fn 爲任務函數,咱們把任務要執行的代碼都寫在裏面。該參數也是可選的。
gulp.task('mytask', ['array', 'of', 'task', 'names'], function() { //定義一個有依賴的任務 // Do something });
gulp.task()這個API沒什麼好講的,但須要知道執行多個任務時怎麼來控制任務執行的順序。 gulp中執行多個任務,能夠經過任務依賴來實現。例如我想要執行one,two,three這三個任務,那咱們就能夠定義一個空的任務,而後把那三個任務當作這個空的任務的依賴就好了:
//只要執行default任務,就至關於把one,two,three這三個任務執行了 gulp.task('default',['one','two','three']);
若是任務相互之間沒有依賴,任務會按你書寫的順序來執行,若是有依賴的話則會先執行依賴的任務。 可是若是某個任務所依賴的任務是異步的,就要注意了,gulp並不會等待那個所依賴的異步任務完成,而是會接着執行後續的任務。例如:
gulp.task('one',function(){ //one是一個異步執行的任務 setTimeout(function(){ console.log('one is done') },5000); }); //two任務雖然依賴於one任務,但並不會等到one任務中的異步操做完成後再執行 gulp.task('two',['one'],function(){ console.log('two is done'); });
上面的例子中咱們執行two任務時,會先執行one任務,但不會去等待one任務中的異步操做完成後再執行two任務,而是緊接着執行two任務。因此two任務會在one任務中的異步操做完成以前就執行了。
那若是咱們想等待異步任務中的異步操做完成後再執行後續的任務,該怎麼作呢? 有三種方法能夠實現: 第一:在異步操做完成後執行一個回調函數來通知gulp這個異步任務已經完成,這個回調函數就是任務函數的第一個參數。
gulp.task('one',function(cb){ //cb爲任務函數提供的回調,用來通知任務已經完成 //one是一個異步執行的任務 setTimeout(function(){ console.log('one is done'); cb(); //執行回調,表示這個異步任務已經完成 },5000); }); //這時two任務會在one任務中的異步操做完成後再執行 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()用來監視文件的變化,當文件發生變化後,咱們能夠利用它來執行相應的任務,例如文件壓縮等。其語法爲
gulp.watch(glob[, opts], tasks)
glob 爲要監視的文件匹配模式,規則和用法與gulp.src()方法中的glob相同。 opts 爲一個可選的配置對象,一般不須要用到 tasks 爲文件變化後要執行的任務,爲一個數組
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參數爲一個函數。每當監視的文件發生變化時,就會調用這個函數,而且會給它傳入一個對象,該對象包含了文件變化的一些信息,type屬性爲變化的類型,能夠是added,changed,deleted;path屬性爲發生變化的文件的路徑
gulp.watch('js/**/*.js', function(event){ console.log(event.type); //變化類型 added爲新增,deleted爲刪除,changed爲改變 console.log(event.path); //變化的文件的路徑 });