If you plan to create your own Gulp plugin, you will save time by reading the full documentation.
- Guidelines (a MUST read)
- Using buffers
- Dealing with streams
- Testing
A gulp plugin always returns a stream in object mode that does the following:
- Takes in vinyl File objects
- Outputs vinyl File objects (via
transform.push()
and/or the plugin's callback function)
These are known as transform streams (also sometimes called through streams). Transform streams are streams that are readable and writable; they manipulate objects as they're being passed through.
All gulp plugins essentially boil down to this:
var Transform = require('stream').Transform;
module.exports = function() {
// Monkey patch Transform or create your own subclass,
// implementing `_transform()` and optionally `_flush()`
var transformStream = new Transform({objectMode: true});
/**
* @param {Buffer|string} file
* @param {string=} encoding - ignored if file contains a Buffer
* @param {function(Error, object)} callback - Call this function (optionally with an
* error argument and data) when you are done processing the supplied chunk.
*/
transformStream._transform = function(file, encoding, callback) {
var error = null,
output = doSomethingWithTheFile(file);
callback(error, output);
};
return transformStream;
};
Alternatively you could pass your transform and flush functions to the Transform
constructor or even extend Transform
with ES6 classes, as described by the Node.js docs. However, many plugins prefer to use the through2 module to simplify their code:
var through = require('through2'); // npm install --save through2
module.exports = function() {
return through.obj(function(file, encoding, callback) {
callback(null, doSomethingWithTheFile(file));
});
};
The stream returned from through()
(and this
within your transform function) is an instance of the Transform
class, which extends Duplex,
Readable
(and parasitically from Writable) and ultimately Stream.
If you need to parse additional options, you can call the through()
function directly:
return through({objectMode: true /* other options... */}, function(file, encoding, callback) { ...
Supported options include:
- highWaterMark (defaults to 16)
- defaultEncoding (defaults to 'utf8')
- encoding - 'utf8', 'base64', 'utf16le', 'ucs2' etc.
If specified, a StringDecoder
decoder
will be attached to the stream. - readable {boolean}
- writable {boolean}
- allowHalfOpen {boolean} If set to false, then the stream will automatically end the readable side when the writable side ends and vice versa.
The function parameter that you pass to through.obj()
is a _transform
function which will operate on the input file
. You may also provide an optional _flush
function if you need to emit a bit more data at the end of the stream.
From within your transform function call this.push(file)
0 or more times to pass along transformed/cloned files.
You don't need to call this.push(file)
if you provide all output to the callback()
function.
Call the callback
function only when the current file (stream/buffer) is completely consumed.
If an error is encountered, pass it as the first argument to the callback, otherwise set it to null.
If you have passed all output data to this.push()
you can omit the second argument to the callback.
Generally, a gulp plugin would update file.contents
and then choose to either:
- call
callback(null, file)
or - make one call to
this.push(file)
If a plugin creates multiple files from a single input file, it would make multiple calls to this.push()
- eg:
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);
};
The gulp-unzip plugin provides a good example of making
multiple calls to push()
. It also uses a chunk transform stream with a _flush()
function within the Vinyl transform function.
Vinyl files can have 3 possible forms for the contents attribute:
A simple example showing how to detect & handle each form is provided below, for a more detailed explanation of each approach follow the links above.
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 - https://nodejs.org/api/stream.html
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 - https://nodejs.org/api/buffer.html
this.emit('error', new PluginError(PLUGIN_NAME, 'Buffers not supported!'));
// or, if you can handle Buffers:
//file.contents = ...
//return callback(null, file);
}
});
};
Note: When looking through the code of other gulp plugins (and the example above), you may notice that the transform functions will return the result of the callback:
return callback(null, file);
...don't be confused - gulp ignores any return value of your transform function. The code above is simply a short-hand form of:
if (someCondition) {
callback(null, file);
return;
}
// further execution...
If you're unfamiliar with streams, you will need to read up on them:
Other libraries that are not file manipulating through streams but are made for use with gulp are tagged with the gulpfriendly keyword on npm.