视频网站建设 方案,推广渠道,前端做网站的兼职,wordpress页面不要侧边栏前面两篇#xff08;基础篇和进阶篇#xff09;主要介绍流的基本用法和原理#xff0c;本篇从应用的角度#xff0c;介绍如何使用管道进行程序设计#xff0c;主要内容包括#xff1a; 管道的概念Browserify的管道设计Gulp的管道设计两种管道设计模式比较实例所谓“管道”… 前面两篇基础篇和进阶篇主要介绍流的基本用法和原理本篇从应用的角度介绍如何使用管道进行程序设计主要内容包括 管道的概念Browserify的管道设计Gulp的管道设计两种管道设计模式比较实例所谓“管道”指的是通过a.pipe(b)的形式连接起来的多个Stream对象的组合。 假如现在有两个Transformbold和red分别可将文本流中某些关键字加粗和飘红。 可以按下面的方式对文本同时加粗和飘红 // source: 输入流
// dest: 输出目的地
source.pipe(bold).pipe(red).pipe(dest) bold.pipe(red)便可以看作一个管道输入流先后经过bold和red的变换再输出。 但如果这种加粗且飘红的功能的应用场景很广我们期望的使用方式是 // source: 输入流
// dest: 输出目的地
// pipeline: 加粗且飘红
source.pipe(pipeline).pipe(dest) 此时pipeline封装了bold.pipe(red)从逻辑上来讲也称其为管道。 其实现可简化为 var pipeline new Duplex()
var streams pipeline._streams [bold, red]// 底层写逻辑将数据写入管道的第一个Stream即bold
pipeline._write function (buf, enc, next) {streams[0].write(buf, enc, next)
}// 底层读逻辑从管道的最后一个Stream即red中读取数据
pipeline._read function () {var bufvar reads 0var r streams[streams.length - 1]// 将缓存读空while ((buf r.read()) ! null) {pipeline.push(buf)reads}if (reads 0) {// 缓存本来为空则等待新数据的到来r.once(readable, function () {pipeline._read()})}
}// 将各个Stream组合起来此处等同于bold.pipe(red)
streams.reduce(function (r, next) {r.pipe(next)return next
}) 往pipeline写数据时数据直接写入bold再流向red最后从pipeline读数据时再从red中读出。 如果需要在中间新加一个underline的Stream可以 pipeline._streams.splice(1, 0, underline)
bold.unpipe(red)
bold.pipe(underline).pipe(red) 如果要将red替换成green可以 // 删除red
pipeline._streams.pop()
bold.unpipe(red)// 添加green
pipeline._streams.push(green)
bold.pipe(green) 可见这种管道的各个环节是可以修改的。 stream-splicer对上述逻辑进行了进一步封装提供splice、push、pop等方法使得pipeline可以像数组那样被修改 var splicer require(stream-splicer)
var pipeline splicer([bold, red])
// 在中间添加underline
pipeline.splice(1, 0, underline)// 删除red
pipeline.pop()// 添加green
pipeline.push(green) labeled-stream-splicer在此基础上又添加了使用名字替代下标进行操作的功能 var splicer require(labeled-stream-splicer)
var pipeline splicer([bold, bold,red, red,
])// 在red前添加underline
pipeline.splice(red, 0, underline)// 删除bold
pipeline.splice(bold, 1) 由于pipeline本身与其各个环节一样也是一个Stream对象因此可以嵌套 var splicer require(labeled-stream-splicer)
var pipeline splicer([style, [ bold, red ],insert, [ comma ],
])pipeline.get(style) // 取得管道[bold, red].splice(1, 0, underline) // 添加underline Browserify的功能介绍可见substack/browserify-handbook其核心逻辑的实现在于管道的设计 var splicer require(labeled-stream-splicer)
var pipeline splicer.obj([// 记录输入管道的数据重建管道时直接将记录的数据写入。// 用于像watch时需要多次打包的情况record, [ this._recorder() ],// 依赖解析预处理deps, [ this._mdeps ],// 处理JSON文件json, [ this._json() ],// 删除文件前面的BOMunbom, [ this._unbom() ],// 删除文件前面的#!行unshebang, [ this._unshebang() ],// 语法检查syntax, [ this._syntax() ],// 排序以确保打包结果的稳定性sort, [ depsSort(dopts) ],// 对拥有同样内容的模块去重dedupe, [ this._dedupe() ],// 将id从文件路径转换成数字避免暴露系统路径信息label, [ this._label(opts) ],// 为每个模块触发一次dep事件emit-deps, [ this._emitDeps() ],debug, [ this._debug(opts) ],// 将模块打包pack, [ this._bpack ],// 更多自定义的处理wrap, [],
]) 每个模块用row表示定义如下 {// 模块的唯一标识id: id,// 模块对应的文件路径file: /path/to/file,// 模块内容source: ,// 模块的依赖deps: {// require(expr)expr: id,}
} 在wrap阶段前所有的阶段都处理这样的对象流且除pack外都输出这样的流。 有的补充row中的一些信息有的则对这些信息做一些变换有的只是读取和输出。 一般row中的source、deps内容都是在deps阶段解析出来的。 下面提供一个修改Browserify管道的函数。 var Transform require(stream).Transform
// 创建Transform对象
function through(write, end) {return Transform({transform: write,flush: end,})
}// b为Browserify实例
// 该插件可打印出打包时间
function log(b) {// watch时需要重新打包整个pipeline会被重建所以也要重新修改b.on(reset, reset)// 修改当前pipelinereset()function reset () {var time nullvar bytes 0b.pipeline.get(record).on(end, function () {// 以record阶段结束为起始时刻time Date.now()})// wrap是最后一个阶段在其后添加记录结束时刻的Transformb.pipeline.get(wrap).push(through(write, end))function write (buf, enc, next) {// 累计大小bytes buf.lengththis.push(buf)next()}function end () {// 打包时间var delta Date.now() - timeb.emit(time, delta)b.emit(bytes, bytes)b.emit(log, bytes bytes written ( (delta / 1000).toFixed(2) seconds))this.push(null)}}
}var fs require(fs)
var browserify require(browserify)
var b browserify(opts)
// 应用插件
b.plugin(log)
b.bundle().pipe(fs.createWriteStream(bundle.js)) 事实上这里的b.plugin(log)就是直接执行了log(b)。 在插件中可以修改b.pipeline中的任何一个环节。 因此Browserify本身只保留了必要的功能其它都由插件去实现如watchify、factor-bundle等。 除了了上述的插件机制外Browserify还有一套Transform机制即通过b.transform(transform)可以新增一些文件内容预处理的Transform。 预处理是发生在deps阶段的当模块文件内容被读出来时会经过这些Transform处理然后才做依赖解析如babelify、envify。 Gulp的核心逻辑分成两块任务调度与文件处理。 任务调度是基于orchestrator而文件处理则是基于vinyl-fs。 类似于Browserify提供的模块定义用row表示vinyl-fs也提供了文件定义vinyl对象。 Browserify的管道处理的是row流Gulp管道处理vinyl流 gulp.task(scripts, [clean], function() {// Minify and copy all JavaScript (except vendor scripts) // with sourcemaps all the way down return gulp.src(paths.scripts).pipe(sourcemaps.init()).pipe(coffee()).pipe(uglify()).pipe(concat(all.min.js)).pipe(sourcemaps.write()).pipe(gulp.dest(build/js));
}); 任务中创建的管道起始于gulp.src终止于gulp.dest中间有若干其它的Transform插件。 如果与Browserify的管道对比可以发现Browserify是确定了一条具有完整功能的管道而Gulp本身只提供了创建vinyl流和将vinyl流写入磁盘的工具管道中间经历什么全由用户决定。 这是因为任务中做什么是没有任何限制的文件处理也只是常见的情况并非一定要用gulp.src与gulp.dest。 Browserify与Gulp都借助管道的概念来实现插件机制。 Browserify定义了模块的数据结构提供了默认的管道以处理这样的数据流而插件可用来修改管道结构以定制处理行为。 Gulp虽也定义了文件的数据结构但只提供产生、消耗这种数据流的接口完全由用户通过插件去构造处理管道。 当明确具体的处理需求时可以像Browserify那样构造一个基本的处理管道以提供插件机制。 如果需要的是实现任意功能的管道可以如Gulp那样只提供数据流的抽象。 本节中实现一个针对Git仓库自动生成changelog的工具完整代码见ezchangelog。 ezchangelog的输入为git log生成的文本流输出默认为markdown格式的文本流但可以修改为任意的自定义格式。 输入示意 commit 9c5829ce45567bedccda9beb7f5de17574ea9437
Author: zoubin zoubin04gmail.com
Date: Sat Nov 7 18:42:35 2015 0800CHANGELOGcommit 3bf9055b732cc23a9c14f295ff91f48aed5ef31a
Author: zoubin zoubin04gmail.com
Date: Sat Nov 7 18:41:37 2015 08004.0.3commit 87abe8e12374079f73fc85c432604642059806ae
Author: zoubin zoubin04gmail.com
Date: Sat Nov 7 18:41:32 2015 0800fix readmeadd more tests 输出示意 * [[9c5829c](https://github.com/zoubin/ezchangelog/commit/9c5829c)] CHANGELOG## [v4.0.3](https://github.com/zoubin/ezchangelog/commit/3bf9055) (2015-11-07)* [[87abe8e](https://github.com/zoubin/ezchangelog/commit/87abe8e)] fix readmeadd more tests 其实需要的是这样一个pipeline source.pipe(pipeline).pipe(dest) 可以分为两个阶段 * parse从输入文本流中解析出commit信息 * format: 将commit流变换为文本流 默认的情况下要想得到示例中的markdown需要解析出每个commit的sha1、日期、消息、是否为tag。 定义commit的格式如下 {commit: {// commit sha1long: 3bf9055b732cc23a9c14f295ff91f48aed5ef31a,short: 3bf9055,},committer: {// commit datedate: new Date(Sat Nov 7 18:41:37 2015 0800),},// raw message linesmessages: [, 4.0.3, ],// raw headers before the messagesheaders: [[Author, zoubin zoubin04gmail.com],[Date, Sat Nov 7 18:41:37 2015 0800],],// the first non-empty message linesubject: 4.0.3,// other message linesbody: ,// git tagtag: v4.0.3,// link to the commit. opts.baseUrl should be specified.url: https://github.com/zoubin/ezchangelog/commit/3bf9055,
} 于是有 var splicer require(labeled-stream-splicer)
pipeline splicer.obj([parse, [// 按行分隔split, split(),// 生成commit对象解析出sha1和日期commit, commit(),// 解析出tagtag, tag(),// 解析出urlurl, url({ baseUrl: opts.baseUrl }),],format, [// 将commit组合成markdown文本markdownify, markdownify(),],
]) 至此基本功能已经实现。 现在将其封装并提供插件机制。 function Changelog(opts) {opts opts || {}this._options opts// 创建pipelinethis.pipeline splicer.obj([parse, [split, split(),commit, commit(),tag, tag(),url, url({ baseUrl: opts.baseUrl }),],format, [markdownify, markdownify(),],])// 应用插件;[].concat(opts.plugin).filter(Boolean).forEach(function (p) {this.plugin(p)}, this)
}Changelog.prototype.plugin function (p, opts) {if (Array.isArray(p)) {opts p[1]p p[0]}// 执行插件函数修改pipelinep(this, opts)return this
} 上面的实现提供了两种方式来应用插件。 一种是通过配置传入另一种是创建实例后再调用plugin方法本质一样。 为了使用方便还可以简单封装一下。 function changelog(opts) {return new Changelog(opts).pipeline
} 这样就可以如下方式使用 source.pipe(changelog()).pipe(dest) 这个已经非常接近我们的预期了。 现在来开发一个插件修改默认的渲染方式。 var through require(through2)function customFormatter(c) {// c是Changelog实例// 添加解析author的transformc.pipeline.get(parse).push(through.obj(function (ci, enc, next) {// parse the author name from: zoubin zoubin04gmail.comci.committer.author ci.headers[0][1].split(/\s/)[0]next(null, ci)}))// 替换原有的渲染c.pipeline.get(format).splice(markdownify, 1, through.obj(function (ci, enc, next) {var sha1 ci.commit.shortsha1 [ sha1 ]( c._options.baseUrl sha1 )var date ci.committer.date.toISOString().slice(0, 10)next(null, * sha1 date ci.committer.author \n)}))
}source.pipe(changelog({baseUrl: https://github.com/zoubin/ezchangelog/commit/,plugin: [customFormatter],})).pipe(dest) 同样的输入输出将会是 * [9c5829c](https://github.com/zoubin/ezchangelog/commit/9c5829c) 2015-11-07 zoubin
* [3bf9055](https://github.com/zoubin/ezchangelog/commit/3bf9055) 2015-11-07 zoubin
* [87abe8e](https://github.com/zoubin/ezchangelog/commit/87abe8e) 2015-11-07 zoubin 可以看出通过创建可修改的管道ezchangelog保持了本身逻辑的单一性同时又提供了强大的自定义空间。 GitHubsubstack/browserify-handbookGitHubzoubin/streamify-your-node-program