gulp 簡介
gulp是一個基于Nodejs的自動化任務(wù)運行器冈在,能自動化地完成javascript/coffee/sass/less/html/image/css
等文件的的測試艾凯、檢查灾票、合并、壓縮簇爆、格式化在孝、瀏覽器自動刷新崭篡、部署文件生成,并監(jiān)聽文件在改動后重復(fù)指定的這些步驟浸须。
在實現(xiàn)上惨寿,它借鑒了Unix操作系統(tǒng)的管道(pipe)思想,前一級的輸出删窒,直接變成后一級的輸入裂垦,使得在操作上非常簡單。
gulp的核心設(shè)計
核心詞是streaming(流動式)
肌索。Gulpjs的精髓在于對Nodejs中Stream API 的利用蕉拢。
流(stream)的概念來自Unix
,核心思想是do one thing well
。一個大工程系統(tǒng)應(yīng)該由各個小且獨立的管子連接而成诚亚。
我們以經(jīng)典的Nodejs
讀取文件邏輯來說明stream
和傳統(tǒng)方式的差異晕换。使用fs
模塊讀取一個json
文件,傳統(tǒng)的方式代碼如下
var dataJson, fs;
fs = require('fs');
dataJson = 'public/json/test.json';
exports.all = function(req, res) {
fs.readFile(dataJson,function(err, data){
if (err) {
console.log(err);
} else {
res.end(data);
}
});
};
fs.readFile()是將文件全部讀進內(nèi)存亡电,然后觸發(fā)回調(diào)届巩。有兩方面的瓶頸。
- 讀取大文件時容易造成內(nèi)存泄露
- 深惡痛絕的回調(diào)大坑
下面我們看看使用流的方式
var fs = require('fs');
var readStream = fs.createReadStream('data.json');
var writeStream = fs.createWriteStream('data1.json');
readStream.on('data', function(chunk) { // 當有數(shù)據(jù)流出時份乒,寫入數(shù)據(jù)
if (writeStream.write(chunk) === false) { // 如果沒有寫完恕汇,暫停讀取流
readStream.pause();
}
});
writeStream.on('drain', function() { // 寫完后,繼續(xù)讀取
readStream.resume();
});
readStream.on('end', function() { // 當沒有數(shù)據(jù)時或辖,關(guān)閉數(shù)據(jù)流
writeStream.end();
});
或者直接使用
fs.createReadStream('/path/to/source').pipe(fs.createWriteStream('/path/to/dest'));
先創(chuàng)建一個有狀態(tài)的只讀的流瘾英,然后調(diào)用stream.pipe(res)。pipe方法是stream的核心方法颂暇。這句話的代碼可以理解為res對象接收從stream來的數(shù)據(jù),并予以處理輸出缺谴。所以gulp
的pipe()
并不是gulp
的方法,而是流對象的方法耳鸯。切記:pipe()返回的res的返回的對象湿蛔。
- 參考如下文章 NODE_API
gulp的安裝與使用
-
全局安裝
npm install gulp -g
-
切換到項目目錄執(zhí)行npm初始化,生成
pakeage.json
npm init
-
開發(fā)依賴安裝
npm install gulp --save-dev //項目內(nèi)集成gulp npm install gulp-name --save-dev//項目內(nèi)集成gulp第三方插件
-
項目根目錄下創(chuàng)建配置文件
gulpfile.js
-
在gulpfile.js中引用gulp
var gulp = require('gulp') var name = require('gulp-name') //插件調(diào)用
-
在gulpfile.js中配置gulp任務(wù)
gulp.task('task_name', function () { return gulp.src('file source path') .pipe(...) .pipe(...) // till the end .pipe(...); });
gulp task_name
可以來執(zhí)行不同的任務(wù)膀曾。這個task_name可以是自定義的,也可以是默認的任務(wù)(task_name為‘default’),默認任務(wù)執(zhí)行的時候阳啥,可以不用拼上task_name ,直接使用gulp來執(zhí)行
//例子1(默認):
gulp.task('default',function() {
console.log('我是default')
});
//運行結(jié)果:
$ gulp
[21:54:33] Using gulpfile ~/Desktop/nodeJS/gulp/gulpfile.js
[21:54:33] Starting 'default'...
我是default
[21:54:33] Finished 'default' after 120 μs
//例子2(自定義)
gulp.task('task1',function () {
console.log('我是task1')
})
//運行結(jié)果:
$ gulp task1 //后面跟上自定義的任務(wù)名稱
[21:58:00] Using gulpfile ~/Desktop/nodeJS/gulp/gulpfile.js
[21:58:00] Starting 'task1'...
我是task1
[21:58:00] Finished 'task1' after 121 μs
//例子3(復(fù)合)
var gulp = require('gulp')
gulp.task('task', function(){
console.log('hello,i am task')
})
gulp.task('task2', function(){
console.log('hello,i am task2')
})
gulp.task('task3', function(){
console.log('hello,i am task3')
})
gulp.task('task4', function(){
console.log('hello,i am task4')
})
gulp.task('task5', function(){
console.log('hello,i am task5')
})
gulp.task('default', ['task','task2','task3','task4','task5'], function(){
console.log('hello,i am default')
})
//運行結(jié)果:
D:\test\gulpTest>gulp //先按順序執(zhí)行數(shù)組內(nèi)依賴項添谊,后執(zhí)行默認
[10:52:56] Using gulpfile D:\test\gulpTest\gulpfile.js
[10:52:56] Starting 'task'...
hello,i am task
[10:52:56] Finished 'task' after 140 μs
[10:52:56] Starting 'task2'...
hello,i am task2
[10:52:56] Finished 'task2' after 59 μs
[10:52:56] Starting 'task3'...
hello,i am task3
[10:52:56] Finished 'task3' after 52 μs
[10:52:56] Starting 'task4'...
hello,i am task4
[10:52:56] Finished 'task4' after 56 μs
[10:52:56] Starting 'task5'...
hello,i am task5
[10:52:56] Finished 'task5' after 54 μs
[10:52:56] Starting 'default'...
hello,i am default
[10:52:56] Finished 'default' after 55 μs
gulp的核心API
gulp.task(taskName, deps, callback)
name:任務(wù)名稱,不能包含空格;
deps: 依賴任務(wù)察迟,依賴任務(wù)的執(zhí)行順序按照deps中聲明順序斩狱,先于taksName執(zhí)行;
callback,指定任務(wù)要執(zhí)行的一些操作扎瓶,支持異步執(zhí)行所踊。
下面提供幾個特殊用法
-
接受一個callback
// 在 shell 中執(zhí)行一個命令 var exec = require('child_process').exec; gulp.task('jekyll', function(cb) { // 編譯 Jekyll exec('jekyll build', function(err) { if (err) return cb(err); // 返回 error cb(); // 完成 task }); });
-
返回一個stream
gulp.task('somename', function() { var stream = gulp.src('client/**/*.js') .pipe(minify()) .pipe(gulp.dest('build')); return stream; });
-
返回一個promise
var Q = require('q'); gulp.task('somename', function() { var deferred = Q.defer(); // 執(zhí)行異步的操作 setTimeout(function() { deferred.resolve(); }, 1); return deferred.promise; });
gulp.src(globs,options)
該函數(shù)通過一定的匹配模式,用來取出待處理的源文件對象
globs作為需要處理的源文件匹配符路徑
src/a.js
:指定具體文件概荷;*
:匹配所有文件 例:src/*.js
(包含src下的所有js文件)秕岛;**
:匹配0個或多個子文件夾 例:src/**/*.js
(包含src的0個或多個子文件夾下的js文件);{}
:匹配多個屬性 例:src/{a,b}.js
(包含a.js和b.js文件)src/*.{jpg,png,gif}
(src下的所有jpg/png/gif文件)乍赫;-
“!”:排除文件 例:
!src/a.js
(不包含src下的a.js文件)瓣蛀;gulp.src(['src/js/*.js','!src/js/test.js']) .pipe(gulp.dest('dist'))
options包括如下內(nèi)容
options.buffer
:boolean
,默認true
,設(shè)置為false
將返回file.content
的流并且不緩存文件雷厂,處理大文件很好用options.read
:boolean
惋增,默認true
,是否讀取文件-
options.base
: 設(shè)置輸出路徑以某個路徑的某個組成部分為基礎(chǔ)向后拼接gulp.src('client/js/**/*.js') .pipe(minify()) .pipe(gulp.dest('build')); // 寫入 'build/somedir/somefile.js' gulp.src('client/js/**/*.js', { base: 'client' }) .pipe(minify()) .pipe(gulp.dest('build')); // 寫入 'build/js/somedir/somefile.js'
dest()
該函數(shù)用來設(shè)置目標流的輸出,一個流可以被多次輸出改鲫。如果目標文件夾不存在诈皿,則創(chuàng)建之。文件被寫入的路徑是以所給的相對路徑根據(jù)所給的目標目錄計算而來像棘。類似的稽亏,相對路徑也可以根據(jù)所給的 base
來計算
gulp.task('sass', function(){
return gulp.src('public/sass/*.scss')
.pipe(concat('style1.js'))
.pipe(gulp.dest('public/sass/'))//目錄下生成style1.js
.pipe(sass(
{'sourcemap=none': true}
))
.pipe(concat('style.css'))
.pipe(gulp.dest('public/sass/'))//目錄下生成style.css
});
pipe()
該函數(shù)使用類似管道的原理,將上一個函數(shù)的輸出傳遞到下一個函數(shù)的輸入
watch()
該函數(shù)用來監(jiān)聽源文件的任何改動缕题。每當更新監(jiān)聽文件時截歉,回調(diào)函數(shù)會自動執(zhí)行。
注意:別忘記將watch任務(wù)放置到default任務(wù)中
gulp.task('watch',function(){
gulp.watch('public/sass/*.scss',['sass'],function(event){
console.log(event.type);//added或deleted或changed
console.log(event.paht);//變化的路徑
});
});
gulp.task('default', ['sass','watch']);
run()
該函數(shù)能夠盡可能的并行運行多個任務(wù)烟零,并且可能不會按照指定的執(zhí)行順序
gulp.task('end',function(){
gulp.run('task1','task3','task2');
});
gulp 工作流
下面展示一下實際項目中瘪松,gulp的前端自動化流程。
配置gulpfile.js中的目錄
我們新建一個文件锨阿,名為gulpfileConfig.js
來管理靜態(tài)資源的目錄
var src = 'public';//默認目錄文件夾
module.exports = {
sass: {
src : src + '/sass/*.scss',
dest : src + '/css/'
},
css: {
src : src + '/css/*.css',
dest:'dist/css'
},
js: {
src : src + '/js/*.js',
dest: 'dist/js'
},
images: {
src : src + '/images/**/*',
dest : 'dist/images'
},
zip: {
src : './**/*',
dest : './release/'
}
};
然后宵睦,在gulpfile.js中引入,并使用
var gulpConfig = require('./gulpfileConfig');
var cssConfig = gulpConfig.css;
gulp.src(cssConfig.src)
下文中默認使用
gulpfileConfig.js
中的配置項
根據(jù)參數(shù)配置構(gòu)建平臺
gulp.task('set-platform', function() {
console.log('當前構(gòu)建平臺:' + gulp.env.platform);//當前構(gòu)建平臺:web
gulp.env.platform = gulp.env.platform || 'web';
// 根據(jù)傳進來的平臺參數(shù)墅诡,設(shè)置項目public/目錄下的系統(tǒng)icon
gulp.src('./public/' + gulp.env.platform + '.ico')
.pipe(rename('favicon.ico'))
.pipe(gulp.dest('./public'));
// 根據(jù)傳進來的平臺參數(shù)壳嚎,設(shè)置平臺相關(guān)的主scss文件(包含對應(yīng)平臺的主色調(diào))
gulp.src('./public/sass/mixins/base-' + gulp.env.platform + '.scss')
.pipe(rename('base.scss'))
.pipe(gulp.dest('./public/sass/mixins'));
// 根據(jù)傳進來的平臺參數(shù),設(shè)置對應(yīng)平臺的配置文件
return gulp.src('./config/' + gulp.env.platform + '.js')
.pipe(rename('index.js'))
.pipe(gulp.dest('./config'));
});
//構(gòu)建入口,傳入特定的參數(shù)
gulp publish --platform web
從上文可以看出烟馅,我們可以通過gulp在構(gòu)建開始時通過不同的參數(shù)说庭,給項目變更成對應(yīng)的文件內(nèi)容,是不是很厲害焙糟?
gulp && clean
清理構(gòu)建生成的文件夾口渔。一般在構(gòu)建開始時清理掉上一次生成的歷史文件。
var clean = require('gulp-clean');
gulp.task('clean-dist', function(){
return gulp.src('dist')
.pipe(clean());
});
gulp && sass
編譯sass穿撮。一般都是將編譯生成好的CSS文件輸出到項目CSS目錄,用于下一步進行CSS的自動化操縱痪欲。
var sass = require('gulp-ruby-sass');
var sassConfig = gulpConfig.sass;
gulp.task('sass', function () {
return sass(sassConfig.src)
.pipe(gulp.dest(sassConfig.dest))
});
gulp && css
對目標文件夾內(nèi)的css
文件悦穿,進行壓縮,添加md5后綴的操作业踢,同時生成映射文件栗柒,并輸出到指定文件夾內(nèi)。這個映射文件知举,會自動替換掉html
文件的頭文件中瞬沦,引用的這個加了md5
后綴的css
文件。
var minifycss = require('gulp-minify-css'); //壓縮CSS
var rev = require('gulp-rev');//對文件名加MD5后綴
gulp.task('publish-css',function(){
return gulp.src(cssConfig.src)
.pipe(minifycss())
.pipe(rev())
.pipe(gulp.dest(cssConfig.dest)) //輸出到文件本地
.pipe(rev.manifest())
.pipe(gulp.dest(cssConfig.dest));
});
加md5
后綴的原因是為了解決瀏覽器緩存的問題:希望瀏覽器能夠緩存資源文件雇锡,但是有希望當文件內(nèi)容變化了的時候逛钻,瀏覽器能夠自動替換老文件。怎么讓瀏覽器檢測到文件的變化呢锰提?簡單曙痘,對文件大小進行md5,生成的隨機串拼到文件后就行啦立肘,這樣文件內(nèi)容如果不變的話边坤,瀏覽器依舊緩存;如果文件有變動谅年,md5值發(fā)生改變茧痒,文件名變化,瀏覽器就引用新的文件內(nèi)容融蹂。
gulp && js
類似于css的自動化操作旺订,對js文件進行錯誤檢查并輸出、混淆殿较、生成md5后綴耸峭、生成sourceMap文件并輸出
var sourcemaps = require('gulp-sourcemaps');
var uglify = require('gulp-uglify');//壓縮js
var jsConfig =gulpConfig.js;
var jshint = require('gulp-jshint');//js 代碼檢查
var rev = require('gulp-rev');
var revCollector = require('gulp-rev-collector');
function jsProcess(toAddComment){
return gulp.src(jsConfig.src)
.pipe(jshint('.jshintrc'))//錯誤檢查
.pipe(jshint.reporter('default'))//對錯誤進行輸出
.pipe(sourcemaps.init())
.pipe(uglify())
.pipe(rev())
.pipe(sourcemaps.write('/maps',{addComment: toAddComment,sourceMappingURLPrefix: '/js'}))
.pipe(gulp.dest(jsConfig.dest))
.pipe(rev.manifest())
.pipe(gulp.dest(jsConfig.dest));
}
// 用于添加map標記(本地開發(fā)使用)
gulp.task('publish-js-addMap', function (){
return jsProcess(true);
});
// 不添加map標記(線上版本)
gulp.task('publish-js', function (){
return jsProcess(false);
});
注1
使用rev()
這個工具方法,會對某個文件淋纲,比如ajax.js
進行md5
加密劳闹,在文件名后面拼上md5
串,變成ajax-fba6bf63c7.js
,而rev.manifest()
則會在rev-manifest
文件里,記錄這組對應(yīng)關(guān)系本涕,
{
"ajax.js": "ajax-fba6bf63c7.js"
}
_
注2
sourcemaps.write('/maps',{addComment: toAddComment,sourceMappingURLPrefix: '/js'})
這句代碼业汰,會給每個加密的js文件生成用于解密的map文件,同時在文件末尾標注map文件的位置
var ajax={init:function(){return window.ActiveXObject?new ActiveXObject("...
//# sourceMappingURL=/js/maps/ajax-fba6bf63c7.js.map
gulp && image
對所有的圖片進行md5菩颖,生成映射文件
gulp.task('publish-image',function(){
return gulp.src('public/images/**/*.{jpg,png,gif}')
.pipe(rev())
.pipe(gulp.dest('dist/images'))
.pipe(rev.manifest())
.pipe(gulp.dest('dist/images'));
});
gulp && html
將靜態(tài)文件里的所有文件引用样漆,根據(jù)上文中生成好的映射文件,都替換成md5后綴的格式晦闰。
gulp.task('publish-view', function () {
return gulp.src(['dist/**/*.json','views/**/*.html'])
.pipe(revCollector({
replaceReved:true
}))
.pipe(gulp.dest('dist/views'));
});
比如
<script src="/js/format.js"></script>
替換成
<script src="/js/format-f02584610e.js"></script>
gulp && css->image
替換css文件中引用的圖片名為md5后綴形式放祟。
gulp.task('replace-image-inCss', function() {
return gulp.src(['dist/images/*.json','dist/css/*.css'])
.pipe(revCollector({
replaceReved:true
}))
.pipe(gulp.dest('dist/css'));
});
gulp && copy
文件復(fù)制操作
gulp.task('copy-other-files', function () {
return gulp.src('public/favicon.ico')
.pipe(gulp.dest('dist'));
});
gulp && browerify
編譯reactjs
var gulp = require('gulp')
var browserify = require('browserify')
var reactify = require('reactify')
var source = require('vinyl-source-stream')
var streamify = require('gulp-streamify')
gulp.task('browserify',function(){
browserify('./public/js/react_main_components.js')
.transform(reactify)
.bundle()
.pipe(source('productList.js'))
.pipe(streamify(uglify().on('error', gutil.log)))
.pipe(gulp.dest('./public/js/reactCompoments/dist'))
});
gulp && zip
var zip = require('gulp-zip');
var zipConfig = gulpConfig.zip;
gulp.task('zip', function() {
// 打包時,排除掉上次生成過的release里的壓縮包
return gulp.src([zipConfig.src,'!./release/*.*'])
.pipe(zip('demo.zip'))
.pipe(gulp.dest(zipConfig.dest))
});
最后
合并工作流呻右,串行執(zhí)行各類任務(wù)跪妥。根據(jù)不同需求,執(zhí)行不同的構(gòu)建流
// gulp publish --platform jinhui或jinfeng
gulp.task('publish',function(callback){
runSequence('set-platform', 'clean-dist','sass',['publish-js', 'publish-css'],'publish-image','publish-view','replace-image-inCss','copy-other-files','zip',callback);
});
gulp.task('publish-addMap',function(callback){
runSequence('set-platform', 'clean-dist','sass',['publish-js-addMap', 'publish-css'],'publish-image','publish-view','replace-image-inCss','copy-other-files','zip',callback);
});
-
附錄:常見的jshintrc文件示例
{ //循環(huán)或者條件語句必須使用花括號包圍 "curly":false, //強制使用三等號 "eqeqeq":false, //禁止重寫原生對象的原型眉撵,比如 Array , Date "freeze":false, //代碼縮進 "indent":false, //禁止單引號雙引號混用 "quotmark":false, //變量未使用 "unused":false, //嚴格模式 "strict":false, //最大嵌套深度 "maxdepth": 10, //最多參數(shù)個數(shù) "maxparams": 10, //復(fù)雜度檢測 "maxcomplexity":false, //最大行數(shù)檢測 "maxlen": 1500, // 禁止定義之前使用變量落塑,忽略 function 函數(shù)聲明 "latedef":false, // 構(gòu)造器函數(shù)首字母大寫 "newcap":false, //禁止使用 arguments.caller 和 arguments.callee 纽疟,未來ECM5會被棄用 "noarg":false, //變量未定義 "undef":false, // 兼容低級瀏覽器 IE 6/7/8/9 "es3":false, // 控制“缺少分號”的警告 "boss":false }