對比webpack,你更應(yīng)該先掌握gulp【10分鐘教你徹底掌握gulp】
前言
可能很多人對于gulp都相對陌生,特別是vue,react出現(xiàn)以后,漸漸淡出了做業(yè)務(wù)前端人員的視野,14到16年的時候應(yīng)該是它最巔峰的時候,真正的是出道即巔峰,取代了當時最火的grunt成為了前端構(gòu)建的主流工具,就連某度都忍不住來瓜分一下流量,出了個fis(不過按照爛尾的慣例來看,基本會屬于后繼無人的狀態(tài),所以沒有真正去用在生產(chǎn)項目中過),而且當時webpack雖然已經(jīng)出現(xiàn),但完全不能跟gulp抗衡,直到vue,react等spa項目出現(xiàn),才讓webpack取而代之,gulp也逐漸退出幕前,轉(zhuǎn)戰(zhàn)幕后,去做了它更擅長的事情:前端開發(fā)流程規(guī)范管理。
現(xiàn)在我們在各種組件庫,像antd,element-ui,vant等比較人們的組件庫,或者其他一些前端工程中都能看到它的身影,只不過它不再介入到業(yè)務(wù)的實際生產(chǎn)開發(fā)中了,所以對業(yè)務(wù)開發(fā)人員來說是不太能感知到它的存在了。
gulp和webpack的區(qū)別
首先,可能很多人面試過程中都會被問到這個問題。我說一說自己的理解:
| gulp | webpack |
|---|---|
| 強調(diào)的是規(guī)范前端開發(fā)的流程 | 是一個前端模塊化方案 |
| 是一個基于流的自動化構(gòu)建工具,不包括模塊化的功能,通過配置一系列的task,例如文件壓縮合并、雪碧圖、啟動server、版本控制等,然后定義執(zhí)行順序來讓gulp執(zhí)行task,從而構(gòu)建前端項目的流程 | 是一個自動化模塊打包工具,把開發(fā)中的所有資源(圖片、js文件、css文件等)都看成模塊,通過loader(加載器)和plugins(插件)對資源進行處理,劃分成不同的模塊,需要哪個加載哪個,實現(xiàn)按需加載的功能,入口引入的更多是js文件 |
在webpack剛面世的時候,webpack在gulp中也有一個插件(gulp-webpack)作為使其可以作為gulp?一個子任務(wù)來執(zhí)行。只不過當時還是JQuery的時代,功能基本重復(fù),真正使用webpack的還是很少,所以react等spa框架的出現(xiàn)讓webpack迅速躥紅。
gulp的核心api
task, series, parallel, src, pipe, dest, on, watch
task: 創(chuàng)建一個任務(wù) series:順序執(zhí)行多個任務(wù) prallel:并行執(zhí)行多個任務(wù) src:讀取數(shù)據(jù)源轉(zhuǎn)換成stream pipe:管道-可以在中間對數(shù)據(jù)流進行處理 dest:輸出數(shù)據(jù)流到目標路徑 on:事件監(jiān)聽 watch:數(shù)據(jù)源監(jiān)聽
這些api在demo中都有用一個例子串起來講解使用
其他的基本很少會用到了,這里就不多復(fù)述,網(wǎng)上的很多文章,還有官方的api都有詳細的,但在實際的開發(fā)中我基本很少用到,可能是使用的場景過于簡單吧
本文就用一個實際的例子把這幾個api全部串聯(lián)起來,我將實現(xiàn)一個這樣的功能:

全局安裝gulp
$?npm?i?gulp?-g
項目根目錄新建gulpfile.js文件
文件頭引入模塊
//?gulpfile.js
const?gulp?=?require("gulp");
/**
?*?合并文件插件
?*?gulp的插件很多,有4000多個,足夠滿足大家日常的各種需求,而且插件寫起來也超級簡單
?*/
const?concat?=?require("gulp-concat");
const?through2?=?require("through2");
創(chuàng)建合并文件任務(wù)
新建合并任務(wù),讀取20201108目錄下所有txt文件,合并為20201108.txt文件并存儲在demo文件夾下
//?task?為創(chuàng)建gulp子任務(wù)
gulp.task('concat',?()?=>?{
??return?gulp.src('./20201108/*.txt')?//?src:?讀取文件轉(zhuǎn)化為可讀流,參數(shù)可以是文件通配符匹配
???????.pipe(gulpConcat('20201108.txt'))?//?pipe:管道,把gulp的執(zhí)行步驟一步步串聯(lián)起來,也是gulp的核心
???????.pipe(dest('./demo/'))?// dest:存放文件
????.on('end',?()?=>?{?//?事件監(jiān)聽
????console.log('concat:?文件合并完成');
????})
})
創(chuàng)建文件去除空行任務(wù)
因為是需要順序執(zhí)行子任務(wù),所以用的series,如果是需要并行執(zhí)行的話用parallel
代碼中的through2主要是用來做文件流轉(zhuǎn)換過濾,寫gulp插件必備,下一節(jié)會大概的介紹一下
gulp.task('format',?gulp.series('concat',?()?=>?{
??return?gulp.src('./demo/20201108.txt')
????.pipe(through2.obj(function?(file,?encoding,?cb)?{?// through2:文件流轉(zhuǎn)換,寫gulp插件必備,下面會大概的介紹一下
????let?contents?=?file.contents.toString();
????contents?=?contents
?????.replace(/(\n[\s\t]*\r*\n)/g,?"\n")
?????.replace(/^[\n\r\n\t]*|[\n\r\n\t]*$/g,?"");?//?去除空行
????let?lines?=?contents.split(/\n/g);
????totalLine?=?lines.length;
????contents?=?lines.join("\n");
????file.contents?=?Buffer.from(contents);
????this.push(file);
????cb();
???}))
???.pipe(dest('./demo/'))
???.on('end',?()?=>?{
????console.log('format:?去除空行完成');
???})
}))
創(chuàng)建監(jiān)聽任務(wù)
當20201108文件夾下的文件有寫入操作時,去執(zhí)行format任務(wù),format任務(wù)又依賴concat任務(wù)執(zhí)行
gulp.task('watch',?()?=>?{
??//?因為是需要順序執(zhí)行子任務(wù),所以用的concat,如果是需要并行執(zhí)行的話用parallel
??gulp.watch('./20201108/*.txt',?gulp.series('format',?(cb)?=>?{
??cb();
??})).on('change',?()?=>?{?//?更多事件監(jiān)聽可以查看官方文檔
??console.log('watch:?文件被改變');
?})
})
在項目目錄下執(zhí)行
以上幾步的代碼合并到一個gulpfile.js文件中即可運行
#?監(jiān)控20201108文件夾下所有文件變化,則執(zhí)行format子任務(wù)
$?gulp?watch
下圖為命令行中輸入日志

看了上面的demo可能大家會對through2比較好奇吧,接下來會大概介紹一下
gulp插件機制
我們先提一提gulp的機制,gulp內(nèi)部的實現(xiàn)很簡單,用了三個sdk實現(xiàn)undertaker,vinyl-fs, glob-watcher
undertaker: 主要用來實現(xiàn)gulp的子任務(wù)流程管理
vinyl-fs:
.src接口可以匹配一個文件通配符,將匹配到的文件轉(zhuǎn)為Vinyl Stream(流),gulp理念就是萬物皆可流glob-watcher: 也就是去實現(xiàn)
gulp.watch功能,監(jiān)控文件流變化
核心就是把文件轉(zhuǎn)換成Stream流,然后對Stream進行操作。
所以gulp采用pipe(管道)的概念,意味著順著管道流淌,然后我們對于gulp的插件,也很好理解了,就是在管道中間有個過濾站,對流進行過濾處理,這就用到了上面提到的through2,這個插件主要的作用也是對流文件進行處理,類似的插件還有map-stream等,不過gulp的主流的插件都是基于through2編寫的.
例如上面的例子(文件去除空行任務(wù)),單獨封裝一下,使用的時候就是一個簡單的插件
//?gulp-file-format.js
module.exports?=?()?=>?{
??return?through2.obj(function?(file,?encoding,?cb)?{
?let?contents?=?file.contents.toString();
?contents?=?contents
??.replace(/(\n[\s\t]*\r*\n)/g,?"\n")
??.replace(/^[\n\r\n\t]*|[\n\r\n\t]*$/g,?"");?//?去除空行
?let?lines?=?contents.split(/\n/g);
?totalLine?=?lines.length;
?contents?=?lines.join("\n");
?file.contents?=?Buffer.from(contents);
?this.push(file);
?cb();
??})
}
替換文件去除空行任務(wù)
const?gulpFormact?=?require('gulp-file-format.js');
gulp.task('format',?gulp.series('concat',?()?=>?{
??return?gulp.src('./demo/20201108.txt')
????.pipe(gulpFormact())
????.pipe(dest('./demo/'))
????.on('end',?()?=>?{
????console.log('format:?去除空行完成');
????})
}))
這就是一個很簡單的gulp插件了,是不是很簡單,比webpack的插件簡單多了
下面講一個日常中對于重復(fù)工作提效寫的一個腳本,講講思路,讓大家對gulp的使用場景有個更深的理解。
實際應(yīng)用案例思路拆解-支付中間頁改版后數(shù)據(jù)統(tǒng)計
由于實際的代碼涉及到一些敏感數(shù)據(jù),所以這個段落只是講一下解決這個實際問題的思路拆解,怎么去用gulp完成想要的結(jié)果,不貼詳細的代碼了。
例如,作者最近做了一個支付中間頁的改版
我需要統(tǒng)計從這個支付中間頁轉(zhuǎn)化的用戶產(chǎn)生了多少收入,人工流程如下:

把以上幾個步驟拆解成gulp的任務(wù),用gulp的任務(wù)機制管理起來,每一個任務(wù)可以單獨執(zhí)行,又可以統(tǒng)一執(zhí)行
export:下載用戶uid
//?導(dǎo)出uid表
gulp.task('export',?()?=>?{})
concat:合并文件并去重
//?對excel文件進行合并去重
gulp.task('cocat',?()?=>?{})
money:循環(huán)uid,遠程請求接口,拿到支付金額
//?獲取每一個uid的支付金額
gulp.task('money',?()?=>?{})
total: 匯總數(shù)據(jù),生成匯總excel表格并輸出
//?數(shù)據(jù)匯總
gulp.task('total',?gulp.series('export',?'concat',?'money',?(cb)?=>?{
?//...
}))
執(zhí)行命令
$?gulp?total
以上任務(wù)都可以獨立執(zhí)行,也可以合并執(zhí)行
更復(fù)雜的應(yīng)用場景-轉(zhuǎn)轉(zhuǎn)sdk生成命令工具
更復(fù)雜的應(yīng)用場景可以查看我們之前產(chǎn)出的一套sdk命令生成工具:commander-tools,現(xiàn)已在github開源,在轉(zhuǎn)轉(zhuǎn)支撐團隊的維護下功能越來強大,主要實現(xiàn)以下命令:
{
??"scripts":?{
????"lint":?"commander-tools?run?lint",?//?校驗
????"fix":?"commander-tools?run?lint?--fix",?//?修復(fù)
????"staged":?"commander-tools?run?lint?--staged",
????"staged-fix":?"commander-tools?run?lint?--staged?--fix",
????"dev":?"commander-tools?run?dev",?//?啟動本地調(diào)試服務(wù)
????"compile":?"commander-tools?run?compile",?//?編譯
????"dist":?"commander-tools?run?dist",?//?外鏈打包
????"analyz":?"commander-tools?run?dist?--analyz",?//?代碼分析
????"build":?"commander-tools?run?build",
????"pub":?"commander-tools?run?pub",?//?發(fā)布正式版
????"pub-beta":?"commander-tools?run?pub-beta",?//?發(fā)布beta版本
????"unpub":?"commander-tools?run?unpub",?//?卸載版本
????"doc":?"commander-tools?run?doc",?//?預(yù)覽文檔
????"build-doc":?"commander-tools?run?build-doc",?//?生成文檔
????"doc-upload":?"commander-tools?run?doc-upload"?//?文檔上傳ftp
??}
}
例如:一個上傳注釋文檔的功能
$?npm?run?doc-upload
/**
*?上傳文檔
**/
const?chalk?=?require('chalk')
const?ftp?=?require('vinyl-ftp')
gulp.task('doc-upload',?gulp.series('build-doc',?done?=>?{
console.log(chalk.green('running?doc-upload'))
if?(!ftpConfig)?{
??console.log(chalk.red('請配置?ftp.config.js'))
??process.exit(1)
}?else?{
??const?businessLine?=?getBusinessLine(program)
??const?{?name?}?=?packageJson
??const?conn?=?ftp.create({
????parallel:?10,
????log:?fancyLog,
????...ftpConfig
??})
??const?pipe?=?gulp
????.src(`${cwd}/${program.docsDirName?||?'docs'}/**/*`)
????.pipe(conn.dest(`/${businessLine}/${name}/`))
????.on('end',?()?=>?{
??????console.log(chalk.green('Success:?文檔上傳成功'))
??????ftp.docUrl?&&?open(ftp.docUrl)
????})
??return?pipe
}
}))
以上命令保證轉(zhuǎn)轉(zhuǎn)的所有sdk都能實現(xiàn)按需加載,并且規(guī)范化輸出
結(jié)語
如果只是想用一個很簡單的小功能,不用寫繁瑣的node腳本,不用去配置復(fù)雜的webpack,gulp不超過10行代碼就能幫你搞定,它豐富的插件生態(tài)基本能滿足你所有的功能需求,簡直就是提升開發(fā)效率的利器。
參考資料
gulp官網(wǎng) gulp插件集合 commander-tools 文件通配符

