一些關於Gulp和NodeJS Stream的理解

    近期學習Gulp和Browserify,按照網上的教程能夠實現二者的整合,可是總存在各類疑惑,例如,二者爲何能夠整合、爲何須要一些另外的模塊的輔助才能整合、以及對於NodeJS的Stream API自己的疑惑。只有弄清楚其中的原理,才能本身靈活地運用Gulp來整合一些有用的工具,也能享受Stream API的靈活性。node

內容:

  • Stream API 簡介
  • Gulp如何使用Stream API
  • 一些Gulp相關或Stream相關的工具

Stream API 簡介

NodeJS提供了Stream API來實現基於流的IO。要注意的是,NodeJS提供的Stream API只是一套API,咱們實際使用的是實現了這套API的模塊,例如Socket,或者根據本身的須要實現本身的處理流的模模塊。Stream最靈活的使用方式,應該就是pipe了,pipe能夠將多個流拼接起來,組成一個流水線(pipeline),數據從pipeline的寫入端流入,在組成pipeline的stream中以此對數據進行處理,最後從pipeline的讀取端流出。webpack

流的主要類型:

NodeJS的流主要有:Readable Stream, Writable Stream, Duplex Stream, Transform Stream 幾種子類型。顧名思義,這四種流的特性以下:git

  • Readable Stream: 提供從流中讀取數據的API
  • Writable Stream: 提供往流中寫入數據的API
  • Duplex Stream: 同時是Readable Stream又是Writable Stream
  • Transform Stream: 是一個提供了transform方法的Duplex Stream,數據經過其Writable Stream的API寫入流中,數據通過transform方法處理,處理的結果能夠經過Readable Stream的API讀取
  • 另外可能還會看到Pass Through這種流,其實只是表明一種不做爲的Transform Stream,直接將輸入的數據原樣輸出

流的模式(即流中數據的類型)

NodeJS的流有兩種模式(Mode),其實就是根據流中傳遞(讀取,寫入)的數據的類型來區分,分別是:github

  • Object Mode:對象模式,即流中傳遞的是任意類型的JavaScript對象(null除外,由於null在流中有特殊的做用,下面會講到)
  • Buffering Mode:Buffer模式,即流中傳遞的是Buffer,咱們看API文檔看到的chunk參數,在該模式下就是一個NodeJS的Buffer類型的對象,咱們能夠理解爲這種模式下傳遞的是裸(raw)的二進制數據

通常狀況下流須要在建立的時候指定其模式,一旦建立,則再也不修改其模式,可是能夠經過拼接轉換流來進行模式轉換,並獲得一個新的流,下面會講到。web

null對象在流中的做用

NodeJS的Stream API規定:gulp

  • 往Writable Stream中寫入null,表明數據源的數據已經傳遞完了,不會再有數據寫入,若在以後繼續寫入數據將產生錯誤
  • 從Readable Stream中讀取到null,表明全部數據已經讀取完畢,流中不會再有可讀取的數據

流的拼接(pipe)

NodeJS的Readable Stream API提供了pipe方法用於流的拼接,pipe方法接受一個輸出流(Writable Stream)對象做爲參數,同時返回該輸出流的引用,若是,該做爲參數的輸出流是一個Duplex Stream,也即返回值同時也是一個Readable Stream,則能夠用這種方式拼接:streamA.pipe(streamB).pipe(streamC)....
拼接的起點,即第一個流,能夠是隻讀流(即不可寫的Readable Stream);拼接的終點,即最後一個流,能夠是純輸出流(即不可讀的Writable Stream);位於中間的流必須是Duplex Stream。api

流水線(pipeline)

不少工具中有pipeline的概念,其實就是將多個流,經過pipe進行拼接,獲得一個有序的流序列,數據從一端寫入,依次進入每個流(一般是transform stream)中進行處理,並從最後一個流輸出。
流能夠是Object Mode和Buffering Mode兩種模式中的一種,不一樣模式的流能夠經過一些transform stream進行模式的轉換。框架

本文並非要講怎麼去實現一個Stream,只是闡述一些概念,以便理解,真正要實現一個Stream還要詳細閱讀Stream API的定義和規範,瞭解上述的概念以後會對本身實現一個Stream有所幫助。異步

Gulp如何使用Stream API

上一節講了NodeJS的Stream API的基本概念,如今咱們講如下Gulp是如何利用Stream API的。
Gulp是一個基於流的構建工具,所以十分靈活,其API也十分簡單,就4個方法 src, dest, task, watch,分別用於輸入數據,輸出數據,定義任務,監控文件的變化並執行指定的任務。
Gulp自己只負責初始輸入和最終輸出,並提供了一個框架來管理任務,其實就連輸入輸出均可以不用Gulp來完成,這時候它就純粹至關於一個任務管理的角色。
在輸入輸出之間的各類具體任務都是經過第三方或者用戶自定義的流處理工具來完成的。
咱們會想,Gulp什麼都不作,爲何咱們還要用它,要用插件或者本身寫的話,還不如用功能豐富的webpack?其實Gulp比webpack靈活的地方在於用戶定義的Gulp任務自己就是跑在NodeJS上的JavaScript程序,跟普通的程序沒什麼兩樣,所以及其靈活;而Webpack內置的功能很豐富,用戶經過配置文件來指定其構建行爲,可是太多既定的規則和內置的功能,雖然可經過loader和plugin進行擴展,可是這些東西都是webpack特有的,也就是說這些擴展都被打上了‘webpack專用’的記號。
其實,Gulp的靈活性除了體如今使用Gulp其實就是在寫普通JavaScript程序這個事實以外,還體如今其能夠直接使用現有的工具來完成任務,例如Browserify等,而不須要特意爲這些工具開發」Gulp專用「的版本。Gulp的這個特性得益於其設計的虛擬文件格式與流的結合。
Gulp使用了Vinyl這種虛擬文件格式(github上的gulpjs/vinyl模塊),來用於其輸入輸出。Vinyl抽象了大多數文件系統中文件的屬性字段,例如文件名、路徑和修改日期等等;同時,Vinyl還將文件的內容抽象成了Buffer或者Stream。抽象的好處就是讓底層實際文件格式之間的差別,對上層透明,也就是說Gulp只認識Vinyl這種文件格式,咱們只要經過一些Adapter將其餘形式的文件(甚至不須要是真的文件,可能只是一個數據流或者一個Buffer)轉換成Vinyl格式,即可以被Gulp處理。理論上,對於任意現有的第三方工具,咱們只要Vinyl格式轉換成其能夠處理的格式,並將其輸出轉換成Vinyl格式,即可以在Gulp中使用,而因爲格式轉換的工做能夠交給獨立的轉換模塊來完成,因此咱們能夠不加修改就在Gulp中使用豐富的第三方工具來完成咱們的任務。
Gulp實際處理的是流,準確地講,是Object Mode的流,並且Object的類型是Vinyl。
在github中,咱們能夠看到一些Gulp專用工具的項目已經被廢棄,例如gulp-browserify,而推薦直接使用非Gulp專用版本的工具,例如node-browserify。開發和維護Gulp專用版本的工具費時費力,並且常常會出現落後於獨立工具版本的狀況,例如工具A已經到了3.0版本,可是Gulp專用的gulp-A中使用的A工具才2.5,跟不上主流。
下一節會介紹一些轉換Vinyl格式的工具。工具

一些Gulp相關或Stream相關的工具

  • vinyl-fs: 用於讀取指定路徑的文件並封裝成vinyl格式的對象流,或者將vinyl對象流寫入文件系統指定路徑,該工具該支持glob;該工具實際上是gulp的src和dest的底層實現
  • vinyl-buffer: 讀取一個vinyl對象流,並將流中的vinyl對象的內容(即contents屬性,該工具主要是針對contents爲Stream的對象,對於contents爲Buffer類型的,則原樣輸出)所有讀取並封裝到一個Buffer中,返回一個相同的vinyl對象,可是將其contents換成封裝好的Buffer
  • vinyl-source-stream: 用於將一個Stream封裝成一個vinyl對象,即建立一個vinyl對象,給它指定一個文件名,並將其contents設置爲該Stream;須要注意的是,因爲要封裝的流自己只是一個流,並非一個文件,因此這裏指定的文件名是由用戶隨意指定的,能夠說是假的文件名,可是這個假的文件名也能夠被下游的輸出流利用,例如使用該vinyl對象的文件名和路徑將文件寫到實際的文件系統中
  • bl(buffer-list): 用於從一個輸入流中讀取全部buffer直到再也不有新的數據,並按順序拼接成單一個Buffer,並經過回調交給調用者;目前的vinyl-buffer的實現就是用了這個工具
  • through/through2: 用於方便的建立一個transform stream,只須要指定要建立的流的模式和transform方法,就能夠獲得一個可用的轉換流,而不須要本身去實現繁瑣的流的讀寫控制,以及因爲NodeJS歷史緣由形成的各類兼容性問題,許多流相關的工具都是基於該模塊完成的
  • concat-stream: 用於拼接多個stream,與pumpify相似,可用於建立pipeline
  • pumpify: 同上
  • ordered-read-streams: 用於讀取多個指定順序的Readable Stream的內容,並按順序傳遞到給用戶,因爲多個流自己的讀取是異步的,因此不容易作到這一點,咱們能夠選擇將全部的stream的內容讀取完,而後在排個序交給下游,可是這個工具很巧妙地讓數據能夠儘快地流入下游而不須要等待全部stream都讀取完,有興趣能夠欣賞如下它的源碼

以上都是我的學習總結出來的內容,理解和表述的專業性可能不強,做爲我的筆記,也但願能幫助到有須要的人。

相關文章
相關標籤/搜索