一些关于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都读取完,有兴趣能够欣赏如下它的源码

以上都是我的学习总结出来的内容,理解和表述的专业性可能不强,做为我的笔记,也但愿能帮助到有须要的人。

相关文章
相关标签/搜索