跳到主要内容

编写插件

如果你计划创建自己的 Gulp 插件,阅读完整的文档将节省你的时间。

它做什么

流式处理文件对象

gulp 插件总是返回一个对象模式的流,该流执行以下操作:

  1. 接收 vinyl 文件对象
  2. 输出 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 类的实例,该类扩展了 DuplexReadable(并寄生式地从 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 种可能的形式:

  • 缓冲区
  • 空(null)- 对于不需要内容的情况(如 rimraf、clean)很有用。

下面提供了一个简单的示例,展示如何检测和处理每种形式,有关每种方法的更详细说明,请点击上面的链接。

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;
}
// 进一步执行...

有用的资源

示例插件

关于流

如果你不熟悉流,你需要阅读以下内容:

其他不是通过流进行文件操作但为与 gulp 一起使用而制作的库在 npm 上用 gulpfriendly 关键字标记。