基于Gulp的簡(jiǎn)易前端自動(dòng)化工程搭建
上個(gè)月月底在公司提出關(guān)于前后端分離的想法,并且開始研究關(guān)于前后端分離,前端工程化,模塊化的一些東西,上周開始我準(zhǔn)備自己開始寫基于Gulp流的前端工程文件,這兩天有時(shí)間,著手開始實(shí)現(xiàn)這個(gè)想法,但是寫的過(guò)程中,遇到了一些問(wèn)題,正是因?yàn)檫@些問(wèn)題的解決讓我對(duì)Gulp的流式處理有了更深的理解,寫下這篇文章,分享一下這倆天我在寫Gulp的時(shí)候?qū)W到的一些東西。
準(zhǔn)備工作
安裝Node
首先Gulp是基于Nodejs的,所以安裝Nodejs是前提,Node可以說(shuō)是前端神器,基于Node有各種各樣的工具,正是因?yàn)檫@些工具讓我們非常方便的構(gòu)建前端工程。
更改Node插件默認(rèn)安裝位置(非必需)
我自己一般不喜歡在C盤狀太多與系統(tǒng)無(wú)關(guān)的東西,而通過(guò)Node自帶的npm安裝的插件默認(rèn)在C盤,但是我將Node安裝到D盤后,想讓插件就安裝在Nodejs的主目錄下,怎么辦呢?
- 在Node主目錄下新建"node_global"及"node_cache"兩個(gè)文件夾
- 啟動(dòng)cmd,輸入
- //后面的設(shè)置目錄根據(jù)你的目錄結(jié)構(gòu)自行更改
- npm config set prefix "D:\Program\nodejs\node_global"
- npm config set cache "D:\Program\nodejs\node_cache"
經(jīng)過(guò)這四步的設(shè)置就可以讓安裝的Node插件放在Nodejs的主目錄了。
安裝Gulp
- //全局安裝Gulp
- npm install -g gulp
- //在項(xiàng)目中安裝Gulp
- npm install --save-dev gulp
運(yùn)行g(shù)ulp -v,如果不報(bào)錯(cuò),表示安裝成功
然后在命令行運(yùn)行
- npm init
讓項(xiàng)目生產(chǎn)package.json文件
搭建工程
眾所周知,在開發(fā)工程中有開發(fā)和上線兩個(gè)過(guò)程,在開發(fā)中,我們一般需要自動(dòng)刷新以及實(shí)時(shí)編譯,但是如果上線,我們就需要考慮很多優(yōu)化的東西,比如文件編譯壓縮,靜態(tài)資源放緩存處理等等問(wèn)題,我自己搭的這個(gè)工程只涉及到文件編譯壓縮,實(shí)時(shí)刷新,靜態(tài)資源放緩存這三個(gè)基本的流程。
在項(xiàng)目的目錄結(jié)構(gòu)如下
- -------------------project
- |
- |--------------dist (該文件夾為打包生成的)
- | |
- | |----------css
- | | |
- | | |------index-9dcc24fe2e.css
- | |
- | |----------js
- | | |
- | | |------index-9dcc24fe2e.js
- | |----------index.html
- |
- |--------------src
- | |
- | |----------scss
- | | |
- | | |------index.scss
- | |
- | |----------js
- | | |
- | | |------index.js
- | |
- | |----------index.html
- |--------------gulpfile.js
- |--------------package.json
開發(fā)所用流程
文件編譯
在工程中準(zhǔn)備使用scss作為css的預(yù)編譯,所以需要利用gulp對(duì)scss進(jìn)行編譯,所以首先安裝gulp-sass。
- npm install --save-dev gulp-sass
安裝完成之后,直接在gulpfile.js引用配置
- const sass = require('gulp-sass'); //scss編譯
- gulp.task('scss:dev',()=>{
- gulp.src('src/scss/*.scss')
- .pipe(sass())
- .pipe(gulp.dest('dist/css')); //將生成好的css文件放到dist/css文件夾下
- });
這里簡(jiǎn)單介紹下gulp的兩個(gè)api:
gulp.src()輸入符合所提供的匹配模式或者匹配模式的數(shù)組的文件。將返回一個(gè)stream或者可以被piped到別的插件中。讀文件
gulp.dest()能被pipe進(jìn)來(lái),并且將會(huì)寫文件。并重新輸出(emits)所有數(shù)據(jù),因此可以將它pipe到多個(gè)文件夾,如果文件夾不存在則將會(huì)自動(dòng)創(chuàng)建。寫文件
實(shí)時(shí)刷新
實(shí)現(xiàn)實(shí)時(shí)刷新的工具有很多,我自己使用browser-sync,這個(gè)工具的功能非常強(qiáng)大,想了解它更多的用法可以查看官網(wǎng):http://www.browsersync.cn/。
首先我們?cè)陧?xiàng)目中安裝該模塊
- npm install --save-dev browser-sync
根據(jù)官網(wǎng)的browser-sync與gulp的配置,得到如下配置:
- const browserSync = require('browser-sync').create(); //實(shí)時(shí)刷新
- const reload = browserSync.reload;
- gulp.task('dev',['scss:dev'],function () {
- browserSync.init({
- server:{
- baseDir:'./' //設(shè)置服務(wù)器的根目錄
- },
- logLevel: "debug",
- logPrefix:"dev",
- browser:'chrome',
- notify:false //開啟靜默模式
- });
- //使用gulp的監(jiān)聽(tīng)功能,實(shí)現(xiàn)編譯修改過(guò)后的文件
- gulp.watch('src/scss/*.scss',['scss:dev']);
- gulp.watch(('*.html')).on('change',reload);
- });
這樣,一個(gè)簡(jiǎn)單的gulp開發(fā)流程就出來(lái)了,僅僅只是一個(gè)編譯scss和一個(gè)實(shí)時(shí)刷新。
打包上線所有流程
打包上線,我們更多的是考慮,靜態(tài)資源防緩存,優(yōu)化。對(duì)css,js的壓縮,對(duì)圖片的處理,我寫的這個(gè)簡(jiǎn)單的流程中并沒(méi)有涉及對(duì)圖片的處理,所以這里僅針對(duì)css,js,html處理。
壓縮css我們使用gulp-sass就可以,因?yàn)樗诰幾gscss的時(shí)候有一個(gè)配置選項(xiàng)可以直接輸出被壓縮的css。壓縮js我使用了gulp-uglify,靜態(tài)資源防緩存使用gulp-rev和gulp-rev-collector。
對(duì)css,js的處理
- //scss編譯
- gulp.task('css',()=> {
- gulp.src('src/scss/*.scss')
- .pipe(sass({
- outputStyle: 'compressed' //編譯并輸出壓縮過(guò)的文件
- }))
- .pipe(rev()) //給css添加哈希值
- .pipe(gulp.dest('dist/css'))
- .pipe(rev.manifest()) //給添加哈希值的文件添加到清單中
- .pipe(gulp.dest('rev/css'));
- });
- //壓縮js
- gulp.task('js', ()=> {
- gulp.src('src/js/*js')
- .pipe(uglify())
- .pipe(rev()) //給js添加哈希值
- .pipe(gulp.dest('dist/js'))
- .pipe(rev.manifest()) //給添加哈希值的文件添加到清單中
- .pipe(gulp.dest('rev/js'));
- });
其中g(shù)ulp-rev是為css文件名添加哈希值,而rev.manifest()會(huì)生成一個(gè)json文件,這個(gè)json文件中記錄了原文件名和添加哈希值后的文件名的一個(gè)對(duì)應(yīng)關(guān)系,這個(gè)對(duì)應(yīng)關(guān)系在***對(duì)應(yīng)替換html的引用的時(shí)候會(huì)用到。
生成的json文件如下:
- {
- "index.css": "index-9dcc24fe2e.css"
- }
由于給文件添加了哈希值,所以每次編譯出來(lái)的css和js都是不一樣的,這會(huì)導(dǎo)致有很多冗余文件,所以我們可以每次在生成文件之前,先將原來(lái)的文件全部清空。
gulp中也有做這個(gè)工作的插件---gulp-clean,因此我們可以在編譯壓縮添加哈希值之前先將原文將清空。
清空生成的項(xiàng)目文件
- const clean = require('gulp-clean'); //清空文件夾里所有的文件
- //每次打包時(shí)先清空原有的文件夾
- gulp.task('clean', ()=> {
- gulp.src(['dist', 'rev'], {read: false}) //這里設(shè)置的dist表示刪除dist文件夾及其下所有文件
- .pipe(clean());
- });
讓添加哈希編碼的文件自動(dòng)添加到html中
前面提到的gulp-rev實(shí)現(xiàn)了給文件名添加哈希編碼,但是在打包完成后如何讓原來(lái)未添加哈希值的引用自動(dòng)變?yōu)橐呀?jīng)添加哈希值的引用,這里用到gulp-rev的一個(gè)插件gulp-rev-collector,配置如下:
- //將處理過(guò)的css,js引入html
- gulp.task('reCollector',()=>{
- gulp.src(['rev/**/*.json','src/*.html'])
- .pipe(reCollector({
- replaceReved: true, //模板中已經(jīng)被替換的文件是否還能再被替換,默認(rèn)是false
- dirReplacements: { //標(biāo)識(shí)目錄替換的集合, 因?yàn)間ulp-rev創(chuàng)建的manifest文件不包含任何目錄信息,
- 'css/': '/dist/css/',
- 'js/': '/dist/js/'
- }
- }))
- .pipe(gulp.dest('dist'))
- });
并沒(méi)有正常替換?
在我自己寫的時(shí)候,出現(xiàn)這個(gè)問(wèn)題,運(yùn)行完成該任務(wù)后,html中的css和js引用并沒(méi)有發(fā)生變化,網(wǎng)上搜了半天,才知道是由于自己用了gulp-rename插件,然后將文件名都添加了.min(至于為什么添加,僅僅是因?yàn)槭菈嚎s過(guò)的,應(yīng)該寫個(gè))而在自己寫的html里面引用的文件并沒(méi)有.min,由于gulp-rev-collector在替換的時(shí)候根據(jù)生成的json文件替換,在json中,文件都有了.min而在html中沒(méi)有,所以無(wú)法匹配,自然也就不能實(shí)現(xiàn)替換了,所以在替換的時(shí)候一定要注意gulp-rev生成的json文件中的css,js與html中的引用的一樣,否則無(wú)法實(shí)現(xiàn)替換。
<font color="red">在gulp-rev-collector的api中有一個(gè)revSuffix,這個(gè)看起來(lái)可以實(shí)現(xiàn)類似于gulp-rename的功能,但是不知道該怎么用,大家如果知道的話請(qǐng)告訴我...</font>
執(zhí)行所有任務(wù)
完成上面幾個(gè)步驟后我們將所有任務(wù)串起來(lái),讓其可以一條命令然后全部執(zhí)行
- gulp.task('build',['clean', 'css', 'js', 'reCollector']);
再次理解gulp
gulp---它的task是順序執(zhí)行嗎?
本以為到這里,就算是寫完了,運(yùn)行,***,打包生成文件,再運(yùn)行一次,報(bào)錯(cuò)了!!!!
- [19:04:57] Finished 'default' after 7.38 μs
- stream.js:74
- throw er; // Unhandled stream error in pipe.
- ^
- Error: ENOENT: no such file or directory, stat 'D:\project\dist\js\index-6045b384e6.min.js'
- at Error (native)
提示我找不到這個(gè)文件,這讓我很郁悶啊,然后我分開執(zhí)行,很ok,可以確定是執(zhí)行順序有問(wèn)題,很可能在沒(méi)有清理完成就執(zhí)行后面了,查了gulp的官網(wǎng)文檔才知道本身gulp的pipe是一個(gè)一個(gè)任務(wù)進(jìn)行的,是同步的,但是每個(gè)task之間是不同步的,是一起進(jìn)行的,這也驗(yàn)證了我的猜想,所以在網(wǎng)上找如何解決這個(gè)問(wèn)題,找到一個(gè)叫run-sequence的npm插件,配置文件如下:
- //進(jìn)行打包上線
- gulp.task('build', ()=> {
- runSequence('clean', ['css', 'js'], 'reCollector');
- });
本以為運(yùn)行就ok,結(jié)果,還是報(bào)錯(cuò),這里就涉及到對(duì)gulp的另一個(gè)理解
run-sequence插件對(duì)異步任務(wù)的處理
在用這個(gè)插件讓任務(wù)有序進(jìn)行后,我想進(jìn)一步直觀的看到它對(duì)任務(wù)的序列化,自己寫了一個(gè)demo,如下:
- gulp.task('a',function(){
- setTimeout(function () {
- console.log(1);
- },30);
- });
- gulp.task('b',function() {
- console.log(2);
- });
- gulp.task('ab',function(){
- runSequence('a','b');
- });
但是這里就出現(xiàn)問(wèn)題了,runSequence不管用了,找插件的說(shuō)明和gulp官方文檔,原來(lái)異步任務(wù),像setTimeout,readFile等,需要添加一個(gè)callback的執(zhí)行,這里的callback()就會(huì)返回一個(gè)promise的resolve(),告訴后面的任務(wù),當(dāng)前任務(wù)已經(jīng)完成,后面可以繼續(xù)執(zhí)行了,所以在task a里面執(zhí)行callback。
- gulp.task('a',function(cb){
- setTimeout(function () {
- console.log(1);
- cb();
- },30);
- });
那為什么前面寫的那些任務(wù)不需要添加一個(gè)callback呢?由于gulp的pipe流讓每一個(gè)task中的小任務(wù)(每一個(gè)pipe)順序執(zhí)行,從而整個(gè)pipe流是同步的,并不是異步任務(wù),所以并不需要手動(dòng)讓其返回promise,run-sequence會(huì)自動(dòng)幫我們管理。
總結(jié)
至此,我們就完成了一個(gè)簡(jiǎn)易的基于gulp的前端工程的搭建,很多東西確實(shí),想著并不難,做起來(lái)會(huì)出現(xiàn)各種各樣意想不到的問(wèn)題,gulp很早就知道,都是單個(gè)任務(wù)在寫,然后用哪個(gè)執(zhí)行哪個(gè)命令,直到自己寫完這個(gè)這個(gè)簡(jiǎn)單的工程,才對(duì)gulp有了更深入的理解。