编写插件
如果你计划创建自己的 Gulp 插件,阅读完整的文档将节省你的时间。
它做什么
流式处理文件对象
gulp 插件总是返回一个对象模式的流,该流执行以下操作:
- 接收 vinyl 文件对象
- 输出 vinyl 文件对象(通过
transform.push()
和/或插件的回调函数)
这些被称为转换流(有时也称为通过流)。转换流是既可读又可写的流;它们在对象通过时对其进行操作。
所有 gulp 插件本质上可归结为以下内容:
var Transform = require('stream').Transform;
module.exports = function() {
// 猴子补丁 Transform 或创建你自己的子类,
// 实现 `_transform()` 和可选的 `_flush()`
var transformStream = new Transform({objectMode: true});
/**
* @param {Buffer|string} file
* @param {string=} encoding - 如果文件包含 Buffer,则忽略
* @param {function(Error, object)} callback - 当你完成处理所提供的块时,
* 调用此函数(可选带有错误参数和数据)。
*/
transformStream._transform = function(file, encoding, callback) {
var error = null,
output = doSomethingWithTheFile(file);
callback(error, output);
};
return transformStream;
};
或者,你可以将你的转换和刷新函数传递给 Transform
构造函数,甚至使用 ES6 类扩展 Transform
,如 Node.js 文档所述。然而,许多插件更喜欢使用 through2 模块来简化他们的代码:
var through = require('through2'); // npm install --save through2
module.exports = function() {
return through.obj(function(file, encoding, callback) {
callback(null, doSomethingWithTheFile(file));
});
};
从 through()
返回的流(以及转换函数内的 this
)是 Transform 类的实例,该类扩展了 Duplex,Readable(并寄生式地从 Writable 继承)并最终继承 Stream。如果你需要解析额外的选项,可以直接调用 through()
函数:
return through({objectMode: true /* 其他选项... */}, function(file, encoding, callback) { ...
支持的选项包括:
- highWaterMark(默认为 16)
- defaultEncoding(默认为 'utf8')
- encoding - 'utf8', 'base64', 'utf16le', 'ucs2' 等。
如果指定,将为流附加一个 StringDecoder
decoder
。 - readable
boolean
- writable
boolean
- allowHalfOpen
boolean
如果设置为 false,则当可写端结束时,流将自动结束可读端,反之亦然。
修改文件内容
你传递给 through.obj()
的函数参数是一个 _transform 函数,它将对输入 file
进行操作。如果你需要在流的末尾发出更多数据,你还可以提供一个可选的 _flush 函数。
在你的转换函数中,调用 this.push(file)
0 次或多次,以传递转换/克隆的文件。如果你将所有输出提供给 callback()
函数,则不需要调用 this.push(file)
。
只有当当前文件(流/缓冲区)完全消耗时,才调用 callback
函数。如果遇到错误,将其作为回调的第一个参数传递,否则将其设置为 null。如果你已将所有输出数据传递给 this.push()
,则可以省略回调的第二个参数。
通常,gulp 插件会更新 file.contents
,然后选择:
- 调用
callback(null, file)
或 - 调用一次
this.push(file)
如果插件从单个输入文件创建多个文件,它会多次调用 this.push()
- 例如:
module.exports = function() {
/**
* @this {Transform}
*/
var transform = function(file, encoding, callback) {
var files = splitFile(file);
this.push(files[0]);
this.push(files[1]);
callback();
};
return through.obj(transform);
};
gulp-unzip 插件提供了一个很好的例子,展示了多次调用 push()
。它还在 Vinyl 转换函数内使用带有 _flush()
函数的块转换流。
Vinyl 文件的 contents 属性可以有 3 种可能的形式:
下面提供了一个简单的示例,展示如何检测和处理每种形式,有关每种方法的更详细说明,请点击上面的链接。
var PluginError = require('plugin-error');
// 常量
var PLUGIN_NAME = 'gulp-example';
module.exports = function() {
return through.obj(function(file, encoding, callback) {
if (file.isNull()) {
// 无需处理
return callback(null, file);
}
if (file.isStream()) {
// file.contents 是一个 Stream - https://nodejs.org/api/stream.html
this.emit('error', new PluginError(PLUGIN_NAME, 'Streams not supported!'));
// 或者,如果你可以处理流:
//file.contents = file.contents.pipe(...
//return callback(null, file);
} else if (file.isBuffer()) {
// file.contents 是一个 Buffer - https://nodejs.org/api/buffer.html
this.emit('error', new PluginError(PLUGIN_NAME, 'Buffers not supported!'));
// 或者,如果你可以处理缓冲区:
//file.contents = ...
//return callback(null, file);
}
});
};
注意:当查看其他 gulp 插件(以及上面的示例)的代码时,你可能会注意到转换函数将返回回调的结果:
return callback(null, file);
...不要被混淆 - gulp 会忽略你的转换函数的任何返回值。上面的代码只是以下形式的简写:
if (someCondition) {
callback(null, file);
return;
}
// 进一步执行...
有用的资源
示例插件
关于流
如果你不熟悉流,你需要阅读以下内容:
- https://www.freecodecamp.org/news/node-js-streams-everything-you-need-to-know-c9141306be93/
- https://nodejs.org/api/stream.html
其他不是通过流进行文件操作但为与 gulp 一起使用而制作的库在 npm 上用 gulpfriendly 关键字标记。