diff --git a/README.md b/README.md
index 14db920..d4d2c61 100644
--- a/README.md
+++ b/README.md
@@ -32,6 +32,18 @@ File.isVinyl(dummy); // true
File.isVinyl(notAFile); // false
```
+### isCustomProp
+Vinyl checks if a property is not managed internally, such as `sourceMap`. This is than used in `constructor(options)` when setting, and `clone()` when copying properties.
+
+```js
+var File = require('vinyl');
+
+File.isCustomProp('sourceMap'); // true
+File.isCustomProp('path'); // false -> internal getter/setter
+```
+
+Read more in [Extending Vinyl](#extending-vinyl).
+
### constructor(options)
#### options.cwd
Type: `String`
Default: `process.cwd()`
@@ -61,6 +73,16 @@ File contents.
Type: `Buffer, Stream, or null`
Default: `null`
+#### options.{custom}
+Any other option properties will just be assigned to the new File object.
+
+```js
+var File = require('vinyl');
+
+var file = new File({foo: 'bar'});
+file.foo === 'bar'; // true
+```
+
### isBuffer()
Returns true if file.contents is a Buffer.
@@ -203,6 +225,36 @@ console.log(file.extname); // .js
console.log(file.path); // /test/file.js
```
+## Extending Vinyl
+When extending Vinyl into your own class with extra features, you need to think about a few things.
+
+When you have your own properties that are managed internally, you need to extend the static `isCustomProp` method to return `false` when one of these properties is queried.
+
+```js
+const File = require('vinyl');
+
+const builtInProps = ['foo', '_foo'];
+
+class SuperFile extends File {
+ constructor(options) {
+ super(options);
+ this._foo = 'example internal read-only value';
+ }
+
+ get foo() {
+ return this._foo;
+ }
+
+ static isCustomProp(name) {
+ return super.isCustomProp(name) && builtInProps.indexOf(name) === -1;
+ }
+}
+```
+
+This makes properties `foo` and `_foo` ignored when cloning, and when passed in options to `constructor(options)` so they don't get assigned to the new object.
+
+Same goes for `clone()`. If you have your own internal stuff that needs special handling during cloning, you should extend it to do so.
+
[npm-url]: https://npmjs.org/package/vinyl
[npm-image]: https://badge.fury.io/js/vinyl.svg
[travis-url]: https://travis-ci.org/gulpjs/vinyl
diff --git a/index.js b/index.js
index f082a0b..9bf0b4e 100644
--- a/index.js
+++ b/index.js
@@ -9,7 +9,13 @@ var inspectStream = require('./lib/inspectStream');
var Stream = require('stream');
var replaceExt = require('replace-ext');
+var builtInFields = [
+ '_contents', 'contents', 'stat', 'history', 'path', 'base', 'cwd',
+];
+
function File(file) {
+ var self = this;
+
if (!file) {
file = {};
}
@@ -28,6 +34,13 @@ function File(file) {
this.contents = file.contents || null;
this._isVinyl = true;
+
+ // Set custom properties
+ Object.keys(file).forEach(function(key) {
+ if (self.constructor.isCustomProp(key)) {
+ self[key] = file[key];
+ }
+ });
}
File.prototype.isBuffer = function() {
@@ -48,6 +61,8 @@ File.prototype.isDirectory = function() {
};
File.prototype.clone = function(opt) {
+ var self = this;
+
if (typeof opt === 'boolean') {
opt = {
deep: opt,
@@ -82,14 +97,10 @@ File.prototype.clone = function(opt) {
// Clone our custom properties
Object.keys(this).forEach(function(key) {
- // Ignore built-in fields
- if (key === '_contents' || key === 'stat' ||
- key === 'history' || key === 'path' ||
- key === 'base' || key === 'cwd') {
- return;
- }
- file[key] = opt.deep ? clone(this[key], true) : this[key];
- }, this);
+ if (self.constructor.isCustomProp(key)) {
+ file[key] = opt.deep ? clone(self[key], true) : self[key];
+ }
+ });
return file;
};
@@ -141,6 +152,10 @@ File.prototype.inspect = function() {
return '';
};
+File.isCustomProp = function(key) {
+ return builtInFields.indexOf(key) === -1;
+};
+
File.isVinyl = function(file) {
return (file && file._isVinyl === true) || false;
};
diff --git a/test/File.js b/test/File.js
index 12c7c4f..4d24729 100644
--- a/test/File.js
+++ b/test/File.js
@@ -110,6 +110,13 @@ describe('File', function() {
file.contents.should.equal(val);
done();
});
+
+ it('should set custom properties', function(done) {
+ var sourceMap = {};
+ var file = new File({ sourceMap: sourceMap });
+ file.sourceMap.should.equal(sourceMap);
+ done();
+ });
});
describe('isBuffer()', function() {