1. 程式人生 > >Gulp.js深入講解

Gulp.js深入講解

赤溫(開發工程師)說:這東西(Gulp.js與Grunt)差距不大,社群才是王道!

英布(高階前端開發工程師)說:一個是以配置的方式,一個是以程式設計的方式,感覺各有千秋吧,複雜流程還是使用程式設計的方式比較好,簡單的那就隨便了。

誠冉(前端開發工程師)說:曾經苦於將grunt的各個工具黏合在一起的過程,grunt這個東西生出來就不是給小專案用的,必須是一定規模的並且持續更新的專案,才捨得花這麼大力氣來“製造工具”。一點也不誇張,雖然grunt的元件越來越多,但概念就像import一樣,稱不上是框架,頂多是一種合作機制。Nodejs的世界裡,好像不跟stream和pipe沾點邊的概念都不好用似的,現在寫Web服務框架,基本都跟connect一個風格,req和res一層一層的流經各個handler。剛一看到glup下意識的覺得有點俗氣了,但是想想Unix的哲學,標準化的輸入輸出,不需要過多的元資訊描述,簡單實用,元件連線起來產生的巨大威力,任何人都無法拒絕!

獵隼(資深前端開發工程師)說:基於node.js的工具有一點讓人深惡痛絕:會建立一個資料夾,而且裡邊的檔案有大量的冗餘。這對專案檔案是一種汙染,很有可能讓一些不明真相的工具把這些程式碼也一起搜尋了,比如一些查詢替換程式。比較喜歡ruby的管理方式,需要的包在本機只需要安裝一次,而且不會像node那樣建立一個資料夾

Gulp.js處理構建任務如下圖:

B0B77QN

Grunt處理任務:

oeCGJUS

理解Gulp.js的核心設計

streaming很容易聯想到河流,河流有哪些特徵?流動、源源不斷、具有方向、有起點、有終點,而這些特徵也是Gulp.js構建程式碼的特點。

Gulp.js官網上的簡介是The streaming build system

,核心的詞是streaming(流動式),如何理解這個詞呢?《Gulp.js—比Grunt更易用的前端構建工具》,明河講到Gulp.js的精髓在於對Node.js中Streams API的利用,所以想要理解Gulp.js,我們必須理解Streams,streaming其實就是Streams的設計思想。

流(stream)的概念來自unix,核心思想是do one thing well,一個大工程系統應該是由各個小且獨立的管子連線而成,如誠冉同學所說:Unix的哲學,標準化的輸入輸出,不需要過多的元資訊描述,簡單實用,元件連線起來產生的巨大威力,任何人都無法拒絕!,如下圖:

gulp.js

推薦下《stream-handbook》

,非常詳細地說明了如何使用Streams,也提供了很多Streams的第三方模組供參考。

我們以經典的Nodejs讀取檔案邏輯來說明Streams與傳統方式的差異。

使用fs模組讀取一個json檔案

傳統的方式:

var dataJson, fs;
fs =require('fs');
dataJson ='./gallery-db/component-info.json';
exports.all =function(req, res){
    fs.readFile(dataJson,function(err, data){if(err){
            console.log(err);}else{
            res.end(data);}});};

fs.readFile()是將檔案全部讀取放進記憶體,然後觸發回撥,這種方式存在二方面的瓶頸。

1.讀取大檔案時容易造成記憶體洩漏

比如一個上傳視屏的服務,面對少者二三百M,多者七八G的檔案,明顯這種方式不可行,所以我們需要分斷上傳,如果使用傳統方式,就會發現非常蛋疼,估計沒人想這麼幹….

2.深惡痛絕的回撥問題

這是我自己寫NodeJs程式碼最深惡痛絕的地方,在提供大的服務時,比如讀取4-5個檔案,回撥層層巢狀,可讀性和維護性非常大,噁心指數陡增。

而且每次你都要對err進行處理:

if(err){
    console.log(err);}else{//...}

程式碼囉嗦,不夠簡練。koa已經解決了這個問題,NodeJs的發展史就是跟回撥鬥爭史啊。

流的方式:

var dataJson, fs;
fs =require('fs');
dataJson ='./gallery-db/component-info.json';
exports.all =function(req, res){var stream;
  stream = fs.createReadStream(dataJson);
  stream.on('error',function(err){return console.log(err);});
  stream.on('data',function(chunk){return console.log('got %d bytes of data', chunk.length);});return stream.pipe(res);};

fs.createReadStream()建立一個readable streams(只讀的流),請看《stream-handbook》中的readable streams部分,readable streams是有狀態的,比如可以監聽其data事件,讀取資料後會觸發,比如error事件,讀取資料失敗後會觸發。

stream.pipe(res)pipe方法是流物件的核心方法,這句程式碼可以理解為,res(結果集,實際上是一個writeable streams,寫流)物件接收從stream而來的資料,並予以處理輸出。所以Gulp.js中的pipe()方法並不是gulp的方法,而是流物件的方法。pipe()返回的是res的返回的物件。

上述程式碼會把component-info.json的內容打印出來。

流的種類有:readable streams、writeable streams(比如Gulp.js中的gulp.dest(‘./build’)f方法)、transform(明河很不喜歡這個詞,詞不達意,稱之為through更為合適,表示這是讀/寫流,後面會詳細講解)streams,duplex streams(duplex可以理解為迴圈的流,比如伺服器端和客戶端的實時通訊,由於與Gulp.js無關,不展開講)。

如何開發個Gulp.js外掛?

所有的Gulp.js外掛基本都是through(後面不再使用transform這個詞) streams,即是消費者(接收gulp.src() 傳遞出來的資料,然後進行處理加工處理),又是生產者(將加工後的資料傳遞出去),我們以gulp-clean為例(這個外掛用於清理目錄和檔案)。

先看下gulp-clean的API用法:

var gulp =require('gulp');var clean =require('gulp-clean');
gulp.task('default',function(){
    gulp.src('app/tmp').pipe(clean());});

非常簡單的程式碼,目的是殘忍地將tmp目錄幹掉。

gulp-clean的原始碼傳送門

引入依賴模組

var rimraf =require('rimraf');var es =require('event-stream');var gutil =require('gulp-util');var path =require('path');

rimraf 模組用於刪除目錄。

gulp-clean外掛使用了個through streams的包裝模組:event-stream,用於簡化streams呼叫的api,明河推薦使用through2,會更好用。

path 模組就不說了,大家太熟悉了。

定義供外部呼叫外掛方法

module.exports =function(options){return es.map(function(file, cb){//具體業務邏輯});};

es.map()會建立一個through streams,我們只要關注寫業務邏輯就好,不用關注如何建立streams。

file為上游讀流產生的檔案資料,cb是核心方法用於向下遊傳遞處理後的資料。

return cb(null, file);

具體業務程式碼

// 獲取檔案絕對路徑var filepath = file.path;var cwd = file.cwd;var relative = path.relative(cwd, filepath);// 防止路徑錯誤if(!(relative.substr(0,2)==='..')&& relative !==''||(options ?(options.force &&typeof options.force ==='boolean'):false)){//刪除目錄
  rimraf(filepath,function(error){if(!error){return cb(null, file);}else{return cb(newError('Unable to delete "'+ filepath +'" file ('+ error.message +').'), file);}});//列印錯誤訊息}elseif(relative ===''){
  gutil.log('gulp-clean: Cannot delete current working directory. ('+ filepath +')');return cb(null, file);}//列印錯誤訊息else{
  gutil.log('gulp-clean: Cannot delete files outside the current working directory. ('+ filepath +')');return cb(null, file);}

程式碼非常簡單,獲取檔案(目錄)路徑,呼叫rimraf()刪除之即可。

一個簡單的Gulp.js外掛就完成了,so easy!

總結

Gulp.js的使用和外掛的開發都很簡單,當然裡面還有很多細節,明河就不展開講了,請看Gulp.js的官方文件。