寫在前面
gulp使用了有一段時間,公司好幾個項目自動化就是用它構建的。不過對這個簡單粗暴的工具我常常是又愛又怕。啥意思呢?大牛們寫的gulp任務我看得懂,也能依樣畫葫蘆運用到自己的項目,但是如果撇開doc,讓我自己寫我還真寫不好。紙上得來終覺淺啊,考驗對一門技術的掌握程度,光看得懂、會套用還是停留在淺層,在理解的基礎上自己能寫出漂亮又高效的代碼那纔是真正掌握了。
詳解gulp的API
要運行gulp任務,只需切換到存放gulpfile.js文件的目錄(windows平臺請使用cmd或者Power Shell等工具),然後在命令行中執行gulp命令就行了,gulp後面可以加上要執行的任務名,例如gulp task1,如果沒有指定任務名,則會執行任務名爲default的默認任務。
使用gulp,僅需知道4個API即可:gulp.task(),gulp.src(),gulp.dest(),gulp.watch(),所以很容易就能掌握,但有幾個地方需理解透徹才行。往下看…
gulp.src()
獲取文件流。注意這個流裏的內容不是原始的文件流,而是一個虛擬文件對象流(Vinyl files),這個虛擬文件對象中存儲着原始文件的路徑、文件名、內容等信息,基本語法爲:
gulp.src([globs],options)
globs參數是文件匹配模式(類似正則表達式),用來匹配文件路徑(包括文件名),當然這裏也可以直接指定某個具體的文件路徑。當有多個匹配模式時,該參數可以爲一個數組。
options爲可選參數。通常情況下我們不需要用到。
下面重點介紹gulp常用的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()
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); //變化的文件的路徑
});
常用的gulp插件
自動加載插件
使用 gulp-load-plugins
安裝:npm install --save-dev gulp-load-plugins
要使用gulp的插件,首先得用require
來把插件加載進來,如果我們要使用的插件非常多,那我們的gulpfile.js
文件開頭可能就會是這個樣子的:
var gulp = require('gulp'),
//一些gulp插件,abcd這些命名只是用來舉個例子
a = require('gulp-a'),
b = require('gulp-b'),
c = require('gulp-c'),
d = require('gulp-d'),
e = require('gulp-e'),
f = require('gulp-f'),
g = require('gulp-g'),
//更多的插件...
z = require('gulp-z');
雖然這沒什麼問題,但會使我們的gulpfile.js
文件變得很冗長,看上去不那麼舒服。gulp-load-plugins
插件正是用來解決這個問題。
gulp-load-plugins
這個插件能自動幫你加載package.json
文件裏的gulp插件。例如假設你的package.json
文件裏的依賴是這樣的:
{
"devDependencies": {
"gulp": "~3.6.0",
"gulp-rename": "~1.2.0",
"gulp-ruby-sass": "~0.4.3",
"gulp-load-plugins": "~0.5.1"
}
}
然後我們可以在gulpfile.js
中使用gulp-load-plugins
來幫我們加載插件:
var gulp = require('gulp');
//加載gulp-load-plugins插件,並馬上運行它
var plugins = require('gulp-load-plugins')();
然後我們要使用gulp-rename
和gulp-ruby-sass
這兩個插件的時候,就可以使用plugins.rename
和plugins.rubySass
來代替了,也就是原始插件名去掉gulp-
前綴,之後再轉換爲駝峯命名。
實質上gulp-load-plugins
是爲我們做了如下的轉換:
plugins.rename = require('gulp-rename');
plugins.rubySass = require('gulp-ruby-sass');
gulp-load-plugins
並不會一開始就加載所有package.json
裏的gulp插件,而是在我們需要用到某個插件的時候,纔去加載那個插件。最後要提醒的一點是,因爲gulp-load-plugins
是通過你的package.json
文件來加載插件的,所以必須要保證你需要自動加載的插件已經寫入到了package.json
文件裏,並且這些插件都是已經安裝好了的。
重命名
使用 gulp-rename
安裝:npm install --save-dev gulp-rename
用來重命名文件流中的文件。用gulp.dest()
方法寫入文件時,文件名使用的是文件流中的文件名,如果要想改變文件名,那可以在之前用gulp-rename
插件來改變文件流中的文件名。
var gulp = require('gulp'),
rename = require('gulp-rename'),
uglify = require("gulp-uglify");
gulp.task('rename', function () {
gulp.src('js/jquery.js')
.pipe(uglify()) //壓縮
.pipe(rename('jquery.min.js')) //會將jquery.js重命名爲jquery.min.js
.pipe(gulp.dest('js'));
//關於gulp-rename的更多強大的用法請參考https://www.npmjs.com/package/gulp-rename
});
js文件壓縮
使用 gulp-uglify
安裝:npm install --save-dev gulp-uglify
用來壓縮js文件,使用的是uglify
引擎
var gulp = require('gulp'),
uglify = require("gulp-uglify");
gulp.task('minify-js', function () {
gulp.src('js/*.js') // 要壓縮的js文件
.pipe(uglify()) //使用uglify進行壓縮,更多配置請參考:
.pipe(gulp.dest('dist/js')); //壓縮後的路徑
});
js模塊化構建
使用 gulp-browserify
安裝:npm install --save-dev gulp-browserify
前端js代碼的模塊化一直是前端工程師之痛,因爲JS在ES6之前不支持 import
或者 require
這類的模塊加載能力,需要藉助 一些工具,比如 requireJS
、seaJS
等。但是 有了 browserify
,js代碼代碼只要編譯之後,你可以大膽地 使用require()
,就像在node
環境裏一樣,而且它既支持commonJS
規範,也支持非commonJS
規範,但是爲了提高準確率,還是自覺遵守commonJS
規範吧!
var gulp = require('gulp');
var browserify = require('gulp-browserify');
// 處理javascript
gulp.task('scripts', ['clean'], function() {
return gulp.src('src/js/**/*.js')
.pipe(browserify())
.pipe(gulp.dest(JS_DEST));
});
css文件壓縮
使用 gulp-minify-css
安裝:npm install --save-dev gulp-minify-css
要壓縮css文件時可以使用該插件
var gulp = require('gulp'),
minifyCss = require("gulp-minify-css");
gulp.task('minify-css', function () {
gulp.src('css/*.css') // 要壓縮的css文件
.pipe(minifyCss()) //壓縮css
.pipe(gulp.dest('dist/css'));
});
html文件壓縮
使用 gulp-minify-html
安裝:npm install --save-dev gulp-minify-html
用來壓縮html文件
var gulp = require('gulp'),
minifyHtml = require("gulp-minify-html");
gulp.task('minify-html', function () {
gulp.src('html/*.html') // 要壓縮的html文件
.pipe(minifyHtml()) //壓縮
.pipe(gulp.dest('dist/html'));
});
js代碼檢查
使用 gulp-jshint
安裝:npm install --save-dev gulp-jshint
用來檢查js代碼
var gulp = require('gulp'),
jshint = require("gulp-jshint");
gulp.task('jsLint', function () {
gulp.src('js/*.js')
.pipe(jshint())
.pipe(jshint.reporter()); // 輸出檢查結果
});
文件合併
使用 gulp-concat
安裝:npm install --save-dev gulp-concat
用來把多個文件合併爲一個文件,我們可以用它來合併js或css文件等,這樣就能減少頁面的http請求數了
var gulp = require('gulp'),
concat = require("gulp-concat");
gulp.task('concat', function () {
gulp.src('js/*.js') //要合併的文件
.pipe(concat('all.js')) // 合併匹配到的js文件並命名爲 "all.js"
.pipe(gulp.dest('dist/js'));
});
less和sass的編譯
less使用 gulp-less
安裝:npm install --save-dev gulp-less
var gulp = require('gulp'),
less = require("gulp-less");
gulp.task('compile-less', function () {
gulp.src('less/*.less')
.pipe(less())
.pipe(gulp.dest('dist/css'));
});
sass使用 gulp-sass
安裝:npm install --save-dev gulp-sass
var gulp = require('gulp'),
sass = require("gulp-sass");
gulp.task('compile-sass', function () {
gulp.src('sass/*.sass')
.pipe(sass())
.pipe(gulp.dest('dist/css'));
});
圖片壓縮
可以使用 gulp-imagemin
插件來壓縮jpg、png、gif等圖片。
安裝:npm install --save-dev gulp-imagemin
var gulp = require('gulp');
var imagemin = require('gulp-imagemin');
var pngquant = require('imagemin-pngquant'); //png圖片壓縮插件
var cache = require('gulp-cache'); //只壓縮修改的圖片,沒有修改的圖片直接從緩存文件讀取
gulp.task('default', function () {
return gulp.src('src/images/*')
.pipe(cache(imagemin({
optimizationLevel: 5, //類型:Number 默認:3 取值範圍:0-7(優化等級)
progressive: true, //類型:Boolean 默認:false 無損壓縮jpg圖片
interlaced: true, //類型:Boolean 默認:false 隔行掃描gif進行渲染
multipass: true, //類型:Boolean 默認:false 多次優化svg直到完全優化
svgoPlugins: [{removeViewBox: false}], //不要移除svg的viewbox屬性
use: [pngquant()] //使用pngquant來壓縮png圖片
})))
.pipe(gulp.dest('dist'));
});
gulp-imagemin
的使用比較複雜一點,而且它本身也有很多插件,建議去它的項目主頁看看文檔
自動刷新
使用gulp-livereload
插件,安裝: npm install --save-dev gulp-livereload
當代碼變化時,它可以幫我們自動刷新頁面
該插件最好配合谷歌瀏覽器來使用,且要安裝 livereload chrome extension
擴展插件,不能下載的請自行FQ。
var gulp = require('gulp'),
less = require('gulp-less'),
livereload = require('gulp-livereload');
gulp.task('less', function() {
gulp.src('less/*.less')
.pipe(less())
.pipe(gulp.dest('css'))
.pipe(livereload());
});
gulp.task('watch', function() {
livereload.listen(); //要在這裏調用listen()方法
gulp.watch('less/*.less', ['less']);
});
清理編譯環境
使用 gulp-clean
安裝 npm install gulp-clean --save-dev
提交代碼之前我們需要把本地的編譯環境清理乾淨。
var clean = require('gulp-clean');
gulp.task('clean',function(){
return gulp.src(['dist/assets/css','dist/assets/js','dist/assets/img'],{read: false}).pipe(clean());
});
我們可以傳入一個目錄(或檔案)陣列到gulp.src()
。因爲我們不需要讀取已經被刪除的檔案,我們可以加入read:false
選項來防止gulp讀取檔案內容讓它快一些。
我的gulpfile.js
下面的是我花了一下午研究出來的gulp前端自動化管理的最佳實踐的gulpfile.js這個文件的代碼,想看整體代碼的請戳這裏 gulp-best-practice
'use strict';
var gulp = require('gulp'), // gulp核心模塊
DEST = 'build', // 編譯目錄
CSS_DEST = 'build/css', // css編譯目錄
JS_DEST = 'build/js', // js編譯目錄
IMG_DEST = 'build/img', // img編譯目錄
HTML_DEST = 'build/html', // html編譯目錄
WEB_PORT = 9000, // 服務器監聽的端口
$ = require('gulp-load-plugins')(); // gulp插件加載模塊
/**
* --------------------------------------
* 注意:下面註釋的這些代碼可以刪除,插件用$加載
* 但是用這種方式加載的插件必須在 package.json裏
*/
// less = require('gulp-less'), // less與編譯模塊
// autoprefixer = require('gulp-autoprefixer'), // 瀏覽器前綴自動補全
// minifyCss = require('gulp-minify-css'), // 壓縮css
// minifyHtml = require("gulp-minify-html"), // 壓縮html
// jshint = require('gulp-jshint'), // js語法校驗
// browserify = require('gulp-browserify'), // js模塊化構建工具
// uglify = require('gulp-uglify'), // 壓縮js
// imagemin = require('gulp-imagemin'), // 壓縮圖片
// rename = require('gulp-rename'), // 文件重命名
// clean = require('gulp-clean'), // 文件清理
// notify = require('gulp-notify'), // 消息通知
// cache = require('gulp-cache'), // 緩存
// sequence = require('gulp-sequence'), // gulp任務執行隊列
// connect = require('gulp-connect'), // node本地服務器
// livereload = require('gulp-livereload'); // 瀏覽器即時刷新
//---------------------------------------
// 處理less
gulp.task('styles', function() {
return gulp.src('src/less/**/*.less')
.pipe($.less())
.pipe($.autoprefixer('last 2 version','safari 5','ie 8','ie 9','opera 12.1','ios 6','android 4'))
.pipe(gulp.dest(CSS_DEST))
.pipe($.rename({
suffix: '.min'
}))
.pipe($.minifyCss())
.pipe(gulp.dest(CSS_DEST))
.pipe($.livereload())
.pipe($.notify({
message: 'Styles task complete'
}));
});
// 處理javascript
gulp.task('scripts', function() {
return gulp.src('src/js/**/*.js')
.pipe($.jshint('.jshintrc'))
.pipe($.jshint.reporter('default'))
.pipe($.browserify())
.pipe(gulp.dest(JS_DEST))
.pipe($.rename({
suffix: '.min'
}))
.pipe($.uglify())
.pipe(gulp.dest(JS_DEST))
.pipe($.livereload())
.pipe($.notify({
message: 'Scripts task complete'
}));
});
// 處理圖片
gulp.task('images', function() {
return gulp.src('src/img/**/*')
.pipe($.cache($.imagemin({
optimizationLevel: 3,
progressive: true,
interlaced: true
})))
.pipe(gulp.dest(IMG_DEST))
.pipe($.livereload())
.pipe($.notify({
message: 'Images task complete'
}))
});
// 處理html
gulp.task('htmls', function() {
return gulp.src('src/html/**/*.html')
.pipe($.rename({
suffix: '.min'
}))
.pipe($.minifyHtml())
.pipe(gulp.dest(HTML_DEST))
.pipe($.livereload())
.pipe($.notify({
message: 'Htmls task complete'
}))
});
// 清理build目錄
gulp.task('clean', function() {
return gulp.src([HTML_DEST,JS_DEST,CSS_DEST,IMG_DEST], {
read: false
})
.pipe($.clean())
.pipe($.notify({
message: 'Clean task complete'
}));
});
// 設置服務器
gulp.task('http', function() {
$.connect.server({
root: DEST,
port: WEB_PORT,
livereload: true
});
});
// 監聽文件變化
gulp.task('watch', function() {
// 監聽livereload
$.livereload.listen();
// 監聽less
gulp.watch('src/less/**/*.less', ['styles']);
// 監聽js
gulp.watch('src/js/**/*.js', ['scripts']);
// 監聽圖片
gulp.watch('src/img/**/*', ['images']);
// 監聽html
gulp.watch('src/html/**/*.html', ['htmls']);
});
// build任務
gulp.task('build', function(cb){
$.sequence('clean',['styles','scripts','images','htmls','watch'])(cb)
});
// 主任務
gulp.task('main', function(cb){
$.sequence('build', ['http'])(cb)
});
// 默認任務
gulp.task('default',['main']);
重構gulpfile.js
重構迭代1: 拆分任務
最先是按照 https://github.com/gulpjs/gulp/blob/master/docs/recipes/split-tasks-ac… 此文檔中的架構進行迭代的。
文件結構
gulpfile.js
tasks/
├── xxxx.js
├── xxxx.js
└── image.js
image.js
var imagemin = require('gulp-imagemin');
gulp.task('img', function() {
return gulp.src('./images/**/*.*')
.pipe(imagemin({
optimizationLevel: 2,
progressive: true
}))
.pipe(gulp.dest('./imagemini'))
});
gulpfile.js
var requireDir = require('require-dir')
, tasks = requireDir('./tasks');
這種文件架構讓任務按照類型分成子任務放在單獨的文件中,頓時感覺乾淨了很多,這時可以自由的添加子任務,而不用管 gulpfile.js, 此時子任務好比插件,需要就添加,沒用就刪除,相當方便。
重構迭代2: 避免模塊和插件重複依賴
隨着時間的推移,發現這種組織架構還是有些不方便,不方便在哪裏呢,每一個任務文件中,我都要寫 var xxx = require(‘xxx’), 如果你是用上面的架構,任務多的時候,估計也會抓狂,因爲你會發現 插件和模塊依賴被重複的引入進來,這樣就提高了成本。
我不想在子任務文件中重複的引入 插件或模塊依賴,有沒有上面好方法,stackoverflow 是個好老師,老師告知:
- 使用 gulp-load-plugins 插件
- 地址:https://www.npmjs.com/package/gulp-load-plugins
- 把子任務封裝成模塊
代碼結構
gulpfile.js
tasks/
├── xxxx.js
├── xxxx.js
└── image.js
gulpfile.js
var gulp = require('gulp'),
gulpLoadPlugins = require('gulp-load-plugins');
// 這裏請查看文檔
gulpLoadPlugins.imagemin = require('gulp-imagemin');
require('./tasks/image')(gulp, gulpLoadPlugins);
image.js
module.exports = function (gulp, Plugin) {
gulp.task('img', function() {
return gulp.src('./images/**/*.*')
.pipe(Plugin.imagemin({
optimizationLevel: 2,
progressive: true
}))
.pipe(gulp.dest('./imagemini'))
});
};
運行任務 一切正常,此時一個文件測試已經ok。
但是 ./tasks 下面是有很多的子任務,所以需要一個迭代加載,修改 gulpfile.js 如下
var gulp = require('gulp')
, gulpLoadPlugins = require('gulp-load-plugins')
// 這裏獲取子任務文件列表 使用了 fs 模塊
, gulpTaskList = require('fs').readdirSync('./tasks/')
;
// 這裏請查看文檔
gulpLoadPlugins.imagemin = require('gulp-imagemin');
gulpTaskList.forEach(function(taskfile) {
require('./tasks/' + taskfile)(gulp, gulpLoadPlugins);
});
這一次迭代避免了重複依賴的問題,但是你會發現,所有的依賴都聲明在 gulpTaskList 命名空間下,如果你依賴很多插件或模塊,gulpfile.js 也是相當長,魚和熊掌不可兼得,在現在情況下,只能尋找最佳的解決方案。
重構迭代3: 參數配置全局化
其實第二部迭代之後,就可以滿足大部分需求,但還是有小夥伴抱怨,有些子任務有相同的參數,能不能抽取出來,放到一個單獨的文件中,so 繼續翻閱文檔。
參考文檔 https://github.com/gulpjs/gulp/blob/master/docs/recipes/using-external…
代碼結構
gulpfile.js
gulp
├── config.json
├── tasks/
├── xxxx.js
├── xxxx.js
└── image.js
注意:文件夾層次變了
config.json
{
"pnglevel": 2
}
gulpfile.js
var gulp = require('gulp')
, config = require('./gulp/config.json');
, gulpLoadPlugins = require('gulp-load-plugins')
, gulpTaskList = require('fs').readdirSync('./gulp/tasks/')
;
gulpLoadPlugins.imagemin = require('gulp-imagemin');
gulpTaskList.forEach(function(taskfile) {
require('./gulp/tasks/' + taskfile)(gulp, gulpLoadPlugins, config);
});
image.js
module.exports = function (gulp, Plugin, config) {
gulp.task('img', function() {
return gulp.src('./images/**/*.*')
.pipe(Plugin.imagemin({
optimizationLevel: config.pnglevel,
progressive: true
}))
.pipe(gulp.dest('./imagemini'))
});
};
此次迭代結束後,我把子任務中通用的配置都寫到 ./gulp/config.json
中,全局配置。
重構迭代4: 參數配置模塊化
此次迭代緊跟迭代3,json 不夠完美,不想每次去寫 “”, 這裏我把配置文件封裝成一個模塊
即迭代3 中的 config.json
變成了 config.js
config.js
module.exports = function () {
var config = {
pnglevel: 2
};
return config;
};
gulpfile.js 加載
var config = require('./gulp/gulp.config')();
其他不變,當封裝成一個模塊的時候,你就發現好處多多了,可以在模塊中添加函數,你也可以把配置拆分,根據你的業務需要,自由調整。通過4步的迭代,整個代碼組織架構就清晰多了。
注意: 子任務中注意文件夾的層次,子任務中的文件夾是以 gulpfile.js 爲基準,因爲 gulpfile.js 把子任務都包含進來了。