尧图精选

Gulp 插件开发实战:基于 Buffer 处理 file.contents(以 gulp-prefixer 为例)

🕒 发布时间:2026/9/19 17:56:28 📁 来源:尧图网络
Gulp 插件开发实战基于 Buffer 处理 file.contents以 gulp-prefixer 为例【免费下载链接】gulpA toolkit to automate enhance your workflow项目地址: https://gitcode.com/gh_mirrors/gu/gulp本文是 Writing a Plugin 系列指南中的「Using buffers」专题。如果你要编写的插件依赖某个以 Buffer 为输入输出的库如压缩、编译、替换文本类库那么你的插件大概率会直接操作file.contents这个 Buffer。本文以「给每个文件开头添加前缀文本」的gulp-prefixer插件为例从完整源码、逐行讲解、错误处理到单元测试带你掌握基于 Buffer 的 Gulp 插件开发全流程并补充 Gulp 源码中src()的buffer选项与测试用例作为底层佐证。背景Vinyl 文件对象的 contents 属性Gulp 插件本质上是返回 transform stream。file.contents一共有三种可能的形式插件必须针对每一种形态做出明确处理完整示例见 Writing a Plugin READMEfile.contents形态检测方法典型场景Bufferfile.isBuffer()默认模式文件内容以内存 Buffer 形式存在Streamfile.isStream()src()以流模式读取时的内容形态空nullfile.isNull()read: false时读取或用于 rimraf、clean 等无需内容的场景基于 Buffer 的插件只处理isBuffer()分支同时必须对其他两种形态给出明确行为忽略、透传或报错。基于 Buffer 的插件完整实现gulp-prefixer如果你的插件依赖的库只接受 Buffer那么把插件建立在对file.contents这个 Buffer 的操作上是最自然的选择。下面实现一个给每个文件开头追加前缀文本的插件var through require(through2); var PluginError require(plugin-error); // consts const PLUGIN_NAME gulp-prefixer; // plugin level function (dealing with files) function gulpPrefixer(prefixText) { if (!prefixText) { throw new PluginError(PLUGIN_NAME, Missing prefix text!); } prefixText Buffer.from(prefixText); // allocate ahead of time // creating a stream through which each file will pass var stream through.obj(function(file, enc, cb) { if (file.isStream()) { this.emit(error, new PluginError(PLUGIN_NAME, Streams are not supported!)); return cb(); } if (file.isBuffer()) { file.contents Buffer.concat([prefixText, file.contents]); } // make sure the file goes through the next gulp plugin this.push(file); // tell the stream engine that we are done with this file cb(); }); // returning the file stream return stream; }; // exporting the plugin main function module.exports gulpPrefixer;逐行拆解这段代码做了什么依赖与常量through2是对 Node 内置 transform stream 的薄封装through.obj()直接创建对象模式objectMode的转换流plugin-error用于生成带插件名前缀的错误对象。参数校验if (!prefixText) throw new PluginError(...)。注意这里抛错发生在流外部创建流之前符合 插件开发指南 的规则——流内部的错误应当以error事件形式emit而流外部的配置类错误可以直接throw。预分配前缀 BufferprefixText Buffer.from(prefixText)。在转换函数外预先将字符串转为 Buffer避免每个文件都重复转换Buffer.from()是当前 Node.js 推荐写法原文档示例使用的new Buffer()为历史 API已标记废弃。转换函数through.obj(function(file, enc, cb) {...})中的函数即_transform对每个进入的文件调用一次。Stream 分支防御file.isStream()时this.emit(error, ...)并cb()结束当前文件明确告知调用方「当前插件不支持流」。核心操作file.contents Buffer.concat([prefixText, file.contents])将前缀与原有内容拼接为新的 Buffer 并写回。继续传递this.push(file)把处理完的文件推给下游插件cb()通知流引擎本文件处理完毕。一个需要注意的细节文档与社区插件中常见return callback(null, file);这种写法其返回值其实会被 Gulp 忽略——它只是「调用回调后提前返回」的简写形式详见 Writing a Plugin README等价于if (someCondition) { callback(null, file); return; } // further execution...不要被这种简写迷惑Gulp 不读取转换函数的返回值。在 Gulp 任务中使用该插件上述插件可直接接入 Gulp 管道var gulp require(gulp); var gulpPrefixer require(gulp-prefixer); gulp.src(files/**/*.js) .pipe(gulpPrefixer(prepended string)) .pipe(gulp.dest(modified-files));gulp.src()默认以 Buffer 模式读取文件内容buffer: true此时进入gulpPrefixer的每个文件isBuffer()都为 true前缀拼接逻辑生效。从 Gulp 源码看 buffer 选项何时会遇到 Streamsrc()的行为决定了你的插件会收到什么形态的contents。在 src() API 文档 中选项类型默认值说明bufferboolean / functiontrue为 true 时文件内容缓冲进内存为 false 时 Vinyl 对象的contents是一个暂停的pausedstream。注很多插件没有实现流式内容的支持在仓库测试 test/src.js 中可以印证这一行为it(should return a input stream with contents as stream when buffer is false, function(done) { var stream gulp.src(./fixtures/*.coffee, { buffer: false, cwd: __dirname }); stream.on(error, done); stream.on(data, function(file) { expect(file).toBeDefined(); expect(file.path).toBeDefined(); expect(file.contents).toBeDefined(); var buf ; file.contents.on(data, function(d) { buf d; }); file.contents.on(end, function() { expect(buf).toEqual(this is a test); done(); }); ... }); });也就是说当用户在任务里写成gulp.src(files/**/*.js, { buffer: false })或传入{ buffer: false }的函数形式时你的插件就会收到isStream() true的文件。基于 Buffer 的插件必须对此做好准备——这正是下面一节要解决的问题。处理 Stream 形态报错还是支持很遗憾前面给出的 Buffer 版插件在gulp.src的非缓冲流式模式下会出错。建议尽可能兼容 Stream。处理策略有两种明确报错Buffer 插件的默认策略检测到file.isStream()时 emit 一个带插件名的PluginError如本文示例所示。这比静默产出错误结果要好得多。真正支持流把file.contents通过.pipe()接到自己的转换流上例如在流上stream.write(prefixText)后再pipe实现前缀写入。完整的流式版本插件实现支持 Buffer、Stream、null 三种形态的gulp-prefixer见 Dealing with streams。同时插件开发指南 明确提醒不要把 stream 强行缓冲成 Buffer 来迁就自己的插件——这会导致糟糕的事情发生内存开销与背压问题。如果确实只支持 Buffer就在isStream()分支明确 emit 错误。一个完整的健壮版骨架处理所有三种形态参照 Writing a Plugin README一个兼顾三种形态的插件骨架如下var PluginError require(plugin-error); // consts var PLUGIN_NAME gulp-example; module.exports function() { return through.obj(function(file, encoding, callback) { if (file.isNull()) { // nothing to do return callback(null, file); } if (file.isStream()) { // file.contents is a Stream this.emit(error, new PluginError(PLUGIN_NAME, Streams not supported!)); // or, if you can handle Streams: //file.contents file.contents.pipe(... //return callback(null, file); } else if (file.isBuffer()) { // file.contents is a Buffer //file.contents ... //return callback(null, file); } }); };要点isNull()时直接callback(null, file)透传不做任何处理isStream()且不支持时this.emit(error, new PluginError(PLUGIN_NAME, Streams not supported!))isBuffer()时执行基于 Buffer 的转换逻辑错误信息务必带上插件名前缀如gulp-replace: Cannot do regexp replace on a stream这是 指南 的硬性要求。为 Buffer 模式插件编写单元测试测试 Buffer 模式的插件非常简单甚至不需要引入 Gulp 本身。在 Testing 中有完整的 mocha vinyl 测试范式直接用vinyl构造一个contents为 Buffer 的假文件写入插件流并断言输出var assert require(assert); var es require(event-stream); var File require(vinyl); var prefixer require(../); describe(gulp-prefixer, function() { describe(in buffer mode, function() { it(should prepend text, function(done) { // create the fake file var fakeFile new File({ contents: Buffer.from(abufferwiththiscontent) }); // Create a prefixer plugin stream var myPrefixer prefixer(prependthis); // write the fake file to it myPrefixer.write(fakeFile); // wait for the file to come back out myPrefixer.once(data, function(file) { // make sure it came out the same way it went in assert(file.isBuffer()); // check the contents assert.equal(file.contents.toString(utf8), prependthisabufferwiththiscontent); done(); }); }); }); });同时建议补一个流模式用例验证isStream()分支的报错行为构造contents: es.readArray([...])的假文件断言file.isStream()为真确保非缓冲输入下插件行为符合预期具体写法可参考 Testing 的流模式小节。实践建议小结插件 API 应当是一个「返回 stream 的函数」需要传参就作为函数参数不要在任何地方require(gulp)作为插件依赖详见 指南。在package.json的 keywords 中加入gulpplugin便于被插件检索到。基于 Buffer 的插件务必显式处理isStream()分支要么 emit 带插件名的PluginError要么真正实现流支持参考 Dealing with streams。测试是唯一的质量保证手段不需要 Gulp用vinyl构造假文件即可覆盖 Buffer / Stream / null 三种形态。若你的插件同时需要处理 Buffer 与 Stream且转换逻辑以「读入内容、产出内容」为粒度可关注bufferstreams这类辅助模块来统一两种形态的处理相关资源罗列于 Writing a Plugin README。【免费下载链接】gulpA toolkit to automate enhance your workflow项目地址: https://gitcode.com/gh_mirrors/gu/gulp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →