前端打包工具有很多款,從早期雅虎的Ant到現(xiàn)在的Webpack,期間的迭代變化的差別之大,如同Java到JavaScript,除了名字有點(diǎn)一樣,幾乎就是兩門完全不同的語(yǔ)言。公司項(xiàng)目主要用Gulp和Webpack,本篇先整理一下Gulp。
如果你用過(guò)Grunt,可能覺(jué)得Gulp有點(diǎn)莫名其妙,Grunt前輩社區(qū)完善,插件成千上萬(wàn),弄出個(gè)Gulp干什么?我個(gè)人感覺(jué)在未來(lái)的前端開(kāi)發(fā)中一切都將是JS,因此即便是配置工具,Gulp用JS代碼實(shí)現(xiàn)的策略優(yōu)于Grunt的半JS半配置的策略。而且Gulp用流的方式進(jìn)行文件處理,通過(guò)管道將多個(gè)任務(wù)和操作連接在一起,全程只有一個(gè)IO過(guò)程,最后的結(jié)果直接寫入硬盤。因此打包流程更加清晰,沒(méi)有中間臨時(shí)文件,速度更快。注意,不要輕視“速度更快”這個(gè)評(píng)價(jià),打包的時(shí)間越短越好,快1秒也能提高前端工程師的效率,做前端的都懂的…
- 安裝和執(zhí)行
- 插件引入
- task
- 數(shù)據(jù)流
- watch
安裝和執(zhí)行
安裝很簡(jiǎn)單:
npm install --global gulp
安裝之后,在工程目錄下新建一個(gè)名為gulpfile.js的文件:
var gulp = require('gulp');
gulp.task('default', function() {
……
});
這樣在目錄下執(zhí)行g(shù)ulp就能自動(dòng)執(zhí)行上面定義的default方法。
插件引入
作為一款流程的打包工具,社區(qū)豐富的插件庫(kù)是必不可少的。除了var gulp=require(‘gulp’);是一定要引入的外,其他的插件根據(jù)需要自行引入。例如gulpfile.js的頭部可能是這樣的:
var gulp = require('gulp');
var del = require('del');
var less = require('gulp-less');
var base64 = require('gulp-base64');
var tpl2mod = require('gulp-tpl2mod');
var extReplace = require('gulp-ext-replace');
從上面代碼就能看出,Gulp完全遵守CommonJS的規(guī)范,無(wú)論是前端頁(yè)面工程師,還是后端Node工程師都可以零基礎(chǔ)立馬上手。這可能也是Gulp能取代Grunt的一個(gè)原因。
task
引入插件后,就需要建立一個(gè)個(gè)子任務(wù)來(lái)執(zhí)行插件。task函數(shù)的原型:
gulp.task(name[, deps], fn)
第一個(gè)參數(shù)是task名。第二個(gè)參數(shù)可選,是個(gè)任務(wù)列表array,這些任務(wù)會(huì)在運(yùn)行當(dāng)前任務(wù)之前運(yùn)行,例如:
gulp.task('default', ['css', 'image', 'ejs', 'babel']);
需要注意的是,你需要確保array里的任務(wù)都在當(dāng)前任務(wù)開(kāi)始前被運(yùn)行完畢。如果array里的任務(wù)是同步方法,那就沒(méi)沒(méi)什么好擔(dān)心的,Gulp會(huì)確保順序依次執(zhí)行。但如果array里的任務(wù)是異步方法,需要確保是否使用了一個(gè)callback,或返回一個(gè)promise或stream。
第三個(gè)參數(shù)fn里定義了任務(wù)需要執(zhí)行的操作。
數(shù)據(jù)流
如果你熟悉Java,那么IO數(shù)據(jù)流這個(gè)策略應(yīng)該非常熟了。Gulp通過(guò)串聯(lián)起來(lái)的的小函數(shù)來(lái)傳遞數(shù)據(jù),這些函數(shù)會(huì)對(duì)數(shù)據(jù)進(jìn)行修改,然后把修改后的數(shù)據(jù)傳遞給下一個(gè)函數(shù)。src函數(shù)的原型:
gulp.src(globs[, options])
該函數(shù)將輸出匹配globs的文件。輸出的文件可以用pipe輸入到別的插件中,形成一串?dāng)?shù)據(jù)流。
第一個(gè)參數(shù)globs,可以是string也可以是array,是一些常用的正則表達(dá)式,例如:
js/sample.js //精確匹配文件
js/*.js //匹配js目錄下的所有js文件
js/**/*.js //匹配js目錄及其子目錄下所有后綴為.js的文件
!js/sample.js //從匹配結(jié)果中去除js/sample.js文件
*.+(js|css) //匹配根目錄下所有后綴為.js或者.css的文件
//例如:匹配js目錄及其子目錄下所有js文件,但需要去除后綴為.min.js的文件
gulp.src(['js/**/*.js', '!js/**/*.min.js'])
更多高級(jí)語(yǔ)法,請(qǐng)仔細(xì)閱讀node-glob語(yǔ)法。
第二個(gè)參數(shù)options可選,除了支持node-glob和glob-stream的參數(shù)外,還支持一些Gulp的額外的參數(shù)。例如:
options.buffer:如果設(shè)為false,將會(huì)以stream方式返回file.contents而不是文件buffer的形式。這在處理一些大文件的時(shí)候?qū)?huì)很有用。
options.read:如果設(shè)為false, 那么file.contents會(huì)返回null,這樣就不會(huì)去讀取文件
options.base:string型的值,將會(huì)被加在glob之前。例如下面兩段代碼唯一的區(qū)別是,上面這段沒(méi)有options.base,而下面這段加上了options.base,導(dǎo)致結(jié)果路徑有區(qū)別:
gulp.src('client/js/**/*.js')
.pipe(minify())
.pipe(gulp.dest('build'));
//匹配client/js/somedir/somefile.js并且將base解析為client/js/,因此結(jié)果 寫入build/somedir/somefile.js
gulp.src('client/js/**/*.js', { base: 'client' })
.pipe(minify())
.pipe(gulp.dest('build'));
//結(jié)果寫入build/js/somedir/somefile.js
數(shù)據(jù)流的結(jié)果通過(guò)dest函數(shù)輸出,原型:
gulp.dest(path[, options])
第一個(gè)參數(shù)是文件被輸出的路徑,路徑是相對(duì)路徑,當(dāng)然相對(duì)路徑也可根據(jù)上面的options.base來(lái)計(jì)算。第二個(gè)參數(shù)options可選,具體可以參照Linux的文件系統(tǒng)操作命令:
options.cwd:默認(rèn)值process.cwd()。輸出目錄的 cwd 參數(shù),只在所給的輸出目錄是相對(duì)路徑時(shí)候有效。
options.mode:默認(rèn)值0777,用以定義所有在輸出目錄中所創(chuàng)建的目錄的權(quán)限。
看一個(gè)Babel解碼ES6的例子:
gulp.task('babel', function () {
return gulp.src('./javascript/**/*.js')
.pipe(babel({
presets: [
'es2015',
'stage-0'
],
plugins: [
'transform-runtime',
'syntax-async-functions',
'transform-class-properties',
'transform-decorators-legacy'
]
}))
.pipe(gulp.dest('./js'));
});
最后,Gulp及其插件使用的流是vinyl文件對(duì)象流(stream或buffer),和普通的文件流(chunk)還是有區(qū)別的。因此在使用Gulp時(shí),如果pipe出現(xiàn)Streaming not supported??赡苁且?yàn)閭鹘oGulp及其插件的是普通文件流(例如Node里fs.createReadStream(‘a(chǎn)pp.js’).pipe(uglify()))??梢杂?a target="_blank" rel="nofollow">vinyl-source-stream轉(zhuǎn)換工具轉(zhuǎn)一下就行了。
watch
Gulp可以監(jiān)視文件的修改,功能類似于Jade里的-w參數(shù),當(dāng)文件有變動(dòng)時(shí),執(zhí)行回調(diào)函數(shù)。Watch有兩種原型:
gulp.watch(glob[, opts], tasks)
gulp.watch(glob[, opts, cb])
第一種原型:第一個(gè)參數(shù)glob用于指定哪些文件需要被監(jiān)視,語(yǔ)法參數(shù)數(shù)據(jù)流不贅述。第二個(gè)參數(shù)opts可選,參照gaze。第三個(gè)參數(shù)tasks用于指定當(dāng)被監(jiān)視的文件有改動(dòng)時(shí),執(zhí)行哪些task。watch函數(shù)始終會(huì)返回一個(gè)EventEmitter 來(lái)發(fā)射change 事件。例如:
var watcher = gulp.watch('js/**/*.js', ['uglify','reload']);
watcher.on('change', function(event) {
console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
});
第二種原型:和第一種的區(qū)別是,第三個(gè)參數(shù)不是task數(shù)組,而是自定義回調(diào)函數(shù)?;卣{(diào)函數(shù)的參數(shù)是event對(duì)象,你可以從該參數(shù)中獲取到event.type(被監(jiān)視的文件發(fā)生的變化類型added,changed, deleted)和event.path(觸發(fā)該事件的文件的路徑)例如:
gulp.watch('js/**/*.js', function(event) {
console.log('File ' + event.path + ' was ' + event.type + ', running tasks...');
});
總結(jié)
技術(shù)總是在發(fā)展,從實(shí)際使用下來(lái)的經(jīng)驗(yàn)看,Gulp確實(shí)比Grunt更快,代碼更少更易讀。Gulp能火多久不清楚,目前來(lái)看,還是項(xiàng)目的一個(gè)比較理想的打包工具。