原文連接:http://mrzhang123.github.io/2016/09/07/gulpUse/
項目連接:https://github.com/MrZhang123/Web_Project_Build/tree/master/gulpcss
上個月月底在公司提出關於先後端分離的想法,而且開始研究關於先後端分離,前端工程化,模塊化的一些東西,上週開始我準備本身開始寫基於Gulp流的前端工程文件,這兩天有時間,着手開始實現這個想法,可是寫的過程當中,遇到了一些問題,正是由於這些問題的解決讓我對Gulp的流式處理有了更深的理解,寫下這篇文章,分享一下這倆天我在寫Gulp的時候學到的一些東西。html
首先Gulp是基於Nodejs的,因此安裝Nodejs是前提,Node能夠說是前端神器,基於Node有各類各樣的工具,正是由於這些工具讓咱們很是方便的構建前端工程。前端
我本身通常不喜歡在C盤狀太多與系統無關的東西,而經過Node自帶的npm安裝的插件默認在C盤,可是我將Node安裝到D盤後,想讓插件就安裝在Nodejs的主目錄下,怎麼辦呢?node
在Node主目錄下新建"node_global"及"node_cache"兩個文件夾git
啓動cmd,輸入github
//後面的設置目錄根據你的目錄結構自行更改 npm config set prefix "D:\Program\nodejs\node_global" npm config set cache "D:\Program\nodejs\node_cache"
關閉cmd,打開系統對話框,「個人電腦」右鍵「屬性」-「高級系統設置」-「高級」-「環境變量」。chrome
進入環境變量對話框,在系統變量下新建"NODE_PATH",輸入"D:Programnodejsnode_globalnode_module"。 因爲改變了module的默認地址,因此上面的用戶變量都要跟着改變一下(用戶變量"PATH"修改成"D:Programnodejsnode_global"),要不使用module的時候會致使輸入命令出現「xxx不是內部或外部命令,也不是可運行的程序或批處理文件」這個錯誤。npm
通過這四步的設置就可讓安裝的Node插件放在Nodejs的主目錄了。json
//全局安裝Gulp npm install -g gulp //在項目中安裝Gulp npm install --save-dev gulp
運行gulp -v
,若是不報錯,表示安裝成功gulp
而後在命令行運行
npm init
讓項目生產package.json
文件
衆所周知,在開發工程中有開發和上線兩個過程,在開發中,咱們通常須要自動刷新以及實時編譯,可是若是上線,咱們就須要考慮不少優化的東西,好比文件編譯壓縮,靜態資源放緩存處理等等問題,我本身搭的這個工程只涉及到文件編譯壓縮,實時刷新,靜態資源放緩存這三個基本的流程。
在項目的目錄結構以下
-------------------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
在工程中準備使用scss做爲css的預編譯,因此須要利用gulp對scss進行編譯,因此首先安裝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文件夾下 });
這裏簡單介紹下gulp的兩個api:
gulp.src()輸入符合所提供的匹配模式或者匹配模式的數組的文件。將返回一個stream或者能夠被piped到別的插件中。讀文件
gulp.dest()能被pipe進來,而且將會寫文件。並從新輸出(emits)全部數據,所以能夠將它pipe到多個文件夾,若是文件夾不存在則將會自動建立。寫文件
實現實時刷新的工具備不少,我本身使用browser-sync,這個工具的功能很是強大,想了解它更多的用法能夠查看官網:http://www.browsersync.cn/。
首先咱們在項目中安裝該模塊
npm install --save-dev browser-sync
根據官網的browser-sync與gulp的配置,獲得以下配置:
const browserSync = require('browser-sync').create(); //實時刷新 const reload = browserSync.reload; gulp.task('dev',['scss:dev'],function () { browserSync.init({ server:{ baseDir:'./' //設置服務器的根目錄 }, logLevel: "debug", logPrefix:"dev", browser:'chrome', notify:false //開啓靜默模式 }); //使用gulp的監聽功能,實現編譯修改事後的文件 gulp.watch('src/scss/*.scss',['scss:dev']); gulp.watch(('*.html')).on('change',reload); });
這樣,一個簡單的gulp開發流程就出來了,僅僅只是一個編譯scss和一個實時刷新。
打包上線,咱們更多的是考慮,靜態資源防緩存,優化。對css,js的壓縮,對圖片的處理,我寫的這個簡單的流程中並無涉及對圖片的處理,因此這裏僅針對css,js,html處理。
壓縮css咱們使用gulp-sass就能夠,由於它在編譯scss的時候有一個配置選項能夠直接輸出被壓縮的css。壓縮js我使用了gulp-uglify,靜態資源防緩存使用gulp-rev和gulp-rev-collector。
//scss編譯 gulp.task('css',()=> { gulp.src('src/scss/*.scss') .pipe(sass({ outputStyle: 'compressed' //編譯並輸出壓縮過的文件 })) .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')); });
其中gulp-rev是爲css文件名添加哈希值,而rev.manifest()會生成一個json文件,這個json文件中記錄了原文件名和添加哈希值後的文件名的一個對應關係,這個對應關係在最後對應替換html的引用的時候會用到。
生成的json文件以下:
{ "index.css": "index-9dcc24fe2e.css" }
因爲給文件添加了哈希值,因此每次編譯出來的css和js都是不同的,這會致使有不少冗餘文件,因此咱們能夠每次在生成文件以前,先將原來的文件所有清空。
gulp中也有作這個工做的插件---gulp-clean,所以咱們能夠在編譯壓縮添加哈希值以前先將原文將清空。
const clean = require('gulp-clean'); //清空文件夾裏全部的文件 //每次打包時先清空原有的文件夾 gulp.task('clean', ()=> { gulp.src(['dist', 'rev'], {read: false}) //這裏設置的dist表示刪除dist文件夾及其下全部文件 .pipe(clean()); });
前面提到的gulp-rev實現了給文件名添加哈希編碼,可是在打包完成後如何讓原來未添加哈希值的引用自動變爲已經添加哈希值的引用,這裏用到gulp-rev的一個插件gulp-rev-collector,配置以下:
//將處理過的css,js引入html gulp.task('reCollector',()=>{ gulp.src(['rev/**/*.json','src/*.html']) .pipe(reCollector({ replaceReved: true, //模板中已經被替換的文件是否還能再被替換,默認是false dirReplacements: { //標識目錄替換的集合, 由於gulp-rev建立的manifest文件不包含任何目錄信息, 'css/': '/dist/css/', 'js/': '/dist/js/' } })) .pipe(gulp.dest('dist')) });
在我本身寫的時候,出現這個問題,運行完成該任務後,html中的css和js引用並無發生變化,網上搜了半天,才知道是因爲本身用了gulp-rename插件,而後將文件名都添加了.min(至於爲何添加,僅僅是由於是壓縮過的,應該寫個)而在本身寫的html裏面引用的文件並無.min,因爲gulp-rev-collector在替換的時候根據生成的json文件替換,在json中,文件都有了.min而在html中沒有,因此沒法匹配,天然也就不能實現替換了,因此在替換的時候必定要注意gulp-rev生成的json文件中的css,js與html中的引用的同樣,不然沒法實現替換。
<font color="red">在gulp-rev-collector的api中有一個revSuffix,這個看起來能夠實現相似於gulp-rename的功能,可是不知道該怎麼用,你們若是知道的話請告訴我...</font>
完成上面幾個步驟後咱們將全部任務串起來,讓其能夠一條命令而後所有執行
gulp.task('build',['clean', 'css', 'js', 'reCollector']);
本覺得到這裏,就算是寫完了,運行,完美,打包生成文件,再運行一次,報錯了!!!!
[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)
提示我找不到這個文件,這讓我很鬱悶啊,而後我分開執行,很ok,能夠肯定是執行順序有問題,極可能在沒有清理完成就執行後面了,查了gulp的官網文檔才知道自己gulp的pipe是一個一個任務進行的,是同步的,可是每一個task之間是不一樣步的,是一塊兒進行的,這也驗證了個人猜測,因此在網上找如何解決這個問題,找到一個叫run-sequence的npm插件,配置文件以下:
//進行打包上線 gulp.task('build', ()=> { runSequence('clean', ['css', 'js'], 'reCollector'); });
本覺得運行就ok,結果,仍是報錯,這裏就涉及到對gulp的另外一個理解
在用這個插件讓任務有序進行後,我想進一步直觀的看到它對任務的序列化,本身寫了一個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'); });
可是這裏就出現問題了,runSequence無論用了,找插件的說明和gulp官方文檔,原來異步任務,像setTimeout,readFile等,須要添加一個callback的執行,這裏的callback()就會返回一個promise的resolve(),告訴後面的任務,當前任務已經完成,後面能夠繼續執行了,因此在task a裏面執行callback。
gulp.task('a',function(cb){ setTimeout(function () { console.log(1); cb(); },30); });
那爲何前面寫的那些任務不須要添加一個callback呢?因爲gulp的pipe流讓每個task中的小任務(每個pipe)順序執行,從而整個pipe流是同步的,並非異步任務,因此並不須要手動讓其返回promise,run-sequence會自動幫咱們管理。
至此,咱們就完成了一個簡易的基於gulp的前端工程的搭建,不少東西確實,想着並不難,作起來會出現各類各樣意想不到的問題,gulp很早就知道,都是單個任務在寫,而後用哪一個執行哪一個命令,直到本身寫完這個這個簡單的工程,纔對gulp有了更深刻的理解。