如果你打算自己写一个 Gulp 插件,为了节约你的时间,你可以先完整地阅读下这个文档。
- 导览 (a MUST read)
- 使用 buffers
- 使用 streams 来处理
- 测试
### 流式处理文件对象(Streaming file objects)
gulp 插件总是返回一个 object mode 形式的 stream 来做这些事情:
- 接收 vinyl File 对象
- 输出 vinyl File 对象 (通过
transform.push()
以及/或者插件的回调函数)
这通常被叫做 transform streams (有时候也叫做 through streams)。 transform streams 是双工可读又可写的,它会对传给它的对象做一些转换的操作。
所有的插件本质上都可以归结成如此:
var Transform = require('transform');
module.exports = function() {
// 加 Monkey patch 或者创建你自己的子类
// 实现 `_transform()` 和可选的 `_flush()`
var transformStream = new Transform({objectMode: true});
/**
* @param {Buffer|string} 文件
* @param {string=} 编码 - 如果文件包含 Buffer 则忽略
* @param {function(Error, object)} 回调 - 当对传递进来的 chunk 处理完成后执行(可选的一个错误参数和数据)
*/
transformStream._transform = function(file, encoding, callback) {
var error = null,
output = doSomethingWithTheFile(file);
callback(error, output);
};
return transformStream;
};
很多插件使用 through2 模块来简化代码:
var through = require('through2'); // npm install --save through2
module.exports = function() {
return through.obj(function(file, encoding, callback) {
callback(null, doSomethingWithTheFile(file));
});
};
through()
所返回的 stream (以及你 transform 函数中的 this
) 都是 Transform 类的实例,它继承自 Duplex,
Readable
(并寄生自 Writable) 最终继承 Stream.
如果你需要处理额外的参数,你可以直接调用 through()
函数:
return through({objectMode: true /* other options... */}, function(file, encoding, callback) { ...
支持的参数包括:
- highWaterMark (defaults to 16)
- defaultEncoding (defaults to 'utf8')
- encoding - 'utf8', 'base64', 'utf16le', 'ucs2' etc.
指定后, 一个 StringDecoder
decoder
会被加到这个 stream。 - readable {boolean}
- writable {boolean}
- allowHalfOpen {boolean} 如果为 false,那么 stream 会在 sriteable 结束的时候自动结束 readable,反之亦然。
传递给 through.obj()
的参数是一个 _transform
函数,它将对输入的 file
做一些操作,你可能也将提供一个可选 _flush
函数,如果你需要在结束时候发送更多的数据的话。
从 transform 函数内部调用 this.push(file)
0 次或者多次来传递 transformed/cloned 的文件.
如果你把所有输出提供给 callback()
那么,你不需要调用 this.push(file)
。
只在当前文件(stream/buffer) 被完全的消费后才调用 callback
函数
如果发生了一个错误,则将它以第一个参数的形式传递给回调函数,否则,将第一个参数设置为 null。
如果你已经将所有输出的内容传递给了 this.push()
那么,你可以省略回调函数的第二个参数。
通常,一个 gulp 插件会更新 file.contents
然后,选择以下一个:
- call
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()
。它同时也使用了一个 chunk transform stream 和 在 Vinyl transform 函数中. _flush()
函数
Vinyl 文件可以通过三种不同形式来访问文件内容:
以下是一个简单的例子来展示如何检测和处理每一种形式,更多的细节请参考上面的链接。
var PluginError = require('gulp-util').PluginError;
// 常量声明
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);
}
});
};
注意:当你查看其他的插件的代码时候(以及上面的例子),你会注意到, transform 函数会返回 callback:
return callback(null, file);
...不要被困扰 - gulp 会忽略所有 transform 函数返回的结果,上面的例子只是以下代码的简写方式:
if (someCondition) {
callback(null, file);
return;
}
// 进一步执行...
如果你不熟悉 stream,你可以阅读这些来
其他的一些为 gulp 创建的和使用的,但又并非通过 stream 去处理的库,在 npm 上都会被打上 gulpfriendly 标签。