Comparing version 0.1.0 to 0.1.1
@@ -24,42 +24,71 @@ 'use strict'; | ||
const mix = exports.mix = superClass => new MixinBuilder(superClass); | ||
const _originalMixin = exports._originalMixin = Symbol('_originalMixin'); | ||
class MixinBuilder { | ||
constructor(superclass) { | ||
this.superclass = superclass; | ||
const wrap = exports.wrap = (mixin, wrapper) => { | ||
Object.setPrototypeOf(wrapper, mixin); | ||
if (!mixin[_originalMixin]) { | ||
mixin[_originalMixin] = mixin; | ||
} | ||
return wrapper; | ||
}; | ||
with() { | ||
let mixins = Array.prototype.slice.call(arguments); | ||
return mixins.reduce((c, mixin) => { | ||
let applicationRef = mixin[_cachedApplicationRef]; | ||
const Cached = exports.Cached = mixin => wrap(mixin, superclass => { | ||
// Get or create a symbol used to look up a previous application of mixin | ||
// to the class. This symbol is unique per mixin definition, so a class will have N | ||
// applicationRefs if it has had N mixins applied to it. A mixin will have | ||
// exactly one _cachedApplicationRef used to store its applications. | ||
let applicationRef = mixin[_cachedApplicationRef]; | ||
if (!applicationRef) { | ||
applicationRef = mixin[_cachedApplicationRef] = Symbol(mixin.name); | ||
} | ||
// Look up an existing application of `mixin` to `c`, return it if found. | ||
if (superclass.hasOwnProperty(applicationRef)) { | ||
return superclass[applicationRef]; | ||
} | ||
// Apply the mixin | ||
let application = mixin(superclass); | ||
// Cache the mixin application on the superclass | ||
superclass[applicationRef] = application; | ||
return application; | ||
}); | ||
if (!applicationRef) { | ||
applicationRef = mixin[_cachedApplicationRef] = Symbol(mixin.name); | ||
const HasInstance = exports.HasInstance = mixin => { | ||
if (Symbol.hasInstance && !mixin.hasOwnProperty(Symbol.hasInstance)) { | ||
Object.defineProperty(mixin, Symbol.hasInstance, { | ||
value: function (o) { | ||
const originalMixin = this[_originalMixin]; | ||
while (o != null) { | ||
if (o.hasOwnProperty(_mixinRef) && o[_mixinRef] === originalMixin) { | ||
return true; | ||
} | ||
o = Object.getPrototypeOf(o); | ||
} | ||
return false; | ||
} | ||
}); | ||
} | ||
return mixin; | ||
}; | ||
if (c.hasOwnProperty(applicationRef)) { | ||
return c[applicationRef]; | ||
} | ||
const BareMixin = exports.BareMixin = mixin => wrap(mixin, superclass => { | ||
// Apply the mixin | ||
let application = mixin(superclass); | ||
let application = mixin(c); | ||
application.prototype[_mixinRef] = mixin; | ||
c[applicationRef] = application; | ||
// Attach a reference from mixin applition to wrapped mixin for RTTI | ||
// mixin[@@hasInstance] should use this. | ||
application.prototype[_mixinRef] = mixin[_originalMixin]; | ||
return application; | ||
}); | ||
if (Symbol.hasInstance && !mixin.hasOwnProperty(Symbol.hasInstance)) { | ||
mixin[Symbol.hasInstance] = function (o) { | ||
do { | ||
if (o.hasOwnProperty(_mixinRef) && o[_mixinRef] === this) { | ||
return true; | ||
} | ||
const Mixin = exports.Mixin = mixin => Cached(HasInstance(BareMixin(mixin))); | ||
o = Object.getPrototypeOf(o); | ||
} while (o !== Object); | ||
const mix = exports.mix = superClass => new MixinBuilder(superClass); | ||
return false; | ||
}; | ||
} | ||
class MixinBuilder { | ||
constructor(superclass) { | ||
this.superclass = superclass; | ||
} | ||
return application; | ||
}, this.superclass); | ||
with() { | ||
return Array.from(arguments).reduce((c, m) => m(c), this.superclass); | ||
} | ||
@@ -66,0 +95,0 @@ |
{ | ||
"name": "mixwith", | ||
"version": "0.1.0", | ||
"version": "0.1.1", | ||
"description": "A simple, powerful mixin applier for JavaScript classes", | ||
@@ -5,0 +5,0 @@ "main": "mixwith.js", |
@@ -7,3 +7,3 @@ # mixwith.js | ||
`mixwith` differs from other mixin approaches because it does not copy properties object to another. Instead, `mixwith` works with "subclass factories" which create a new class that extends a superclass with the mixin - this is called a _mixin_ _application_. | ||
`mixwith` differs from other mixin approaches because it does not copy properties from one object to another. Instead, `mixwith` works with "subclass factories" which create a new class that extends a superclass with the mixin - this is called a _mixin_ _application_. | ||
@@ -10,0 +10,0 @@ Subclass factory style mixins take advantage of two awesome features of ES6 classes: _class_ _expressions_, and expressions in the `extends` clause of a class declaration. |
@@ -5,62 +5,96 @@ 'use strict'; | ||
export const _mixinRef = Symbol('_mixinRef'); | ||
export const _originalMixin = Symbol('_originalMixin'); | ||
export const mix = (superClass) => new MixinBuilder(superClass); | ||
/** | ||
* Sets the prototype of mixin to wrapper so that properties set on mixin are | ||
* inherited by the wrapper. | ||
* | ||
* This is needed in order to implement @@hasInstance as a decorator function. | ||
*/ | ||
export const wrap = (mixin, wrapper) => { | ||
Object.setPrototypeOf(wrapper, mixin); | ||
if (!mixin[_originalMixin]) { | ||
mixin[_originalMixin] = mixin; | ||
} | ||
return wrapper; | ||
}; | ||
class MixinBuilder { | ||
constructor(superclass) { | ||
this.superclass = superclass; | ||
/** | ||
* Decorates mixin so that it caches its applications. When applied multiple | ||
* times to the same superclass, mixin will only create one subclass and | ||
* memoize it. | ||
*/ | ||
export const Cached = (mixin) => wrap(mixin, (superclass) => { | ||
// Get or create a symbol used to look up a previous application of mixin | ||
// to the class. This symbol is unique per mixin definition, so a class will have N | ||
// applicationRefs if it has had N mixins applied to it. A mixin will have | ||
// exactly one _cachedApplicationRef used to store its applications. | ||
let applicationRef = mixin[_cachedApplicationRef]; | ||
if (!applicationRef) { | ||
applicationRef = mixin[_cachedApplicationRef] = Symbol(mixin.name); | ||
} | ||
// Look up an existing application of `mixin` to `c`, return it if found. | ||
if (superclass.hasOwnProperty(applicationRef)) { | ||
return superclass[applicationRef]; | ||
} | ||
// Apply the mixin | ||
let application = mixin(superclass); | ||
// Cache the mixin application on the superclass | ||
superclass[applicationRef] = application; | ||
return application; | ||
}); | ||
with() { | ||
let mixins = Array.prototype.slice.call(arguments); | ||
return mixins.reduce((c, mixin) => { | ||
// Get or create a Symbol used to look up a previous application of mixin | ||
// to the class. This symbol is unique per mixin, so a class will have N | ||
// applicationRefs if it has had N mixins applied to it. A mixin will have | ||
// exactly one _cachedApplicationRef use to store its applications. | ||
let applicationRef = mixin[_cachedApplicationRef]; | ||
if (!applicationRef) { | ||
applicationRef = mixin[_cachedApplicationRef] = Symbol(mixin.name); | ||
/** | ||
* Adds @@hasInstance (ES2015 instanceof support) to mixin. | ||
* Note: @@hasInstance is not supported in any browsers yet. | ||
*/ | ||
export const HasInstance = (mixin) => { | ||
if (Symbol.hasInstance && !mixin.hasOwnProperty(Symbol.hasInstance)) { | ||
Object.defineProperty(mixin, Symbol.hasInstance, { | ||
value: function(o) { | ||
const originalMixin = this[_originalMixin]; | ||
while (o != null) { | ||
if (o.hasOwnProperty(_mixinRef) && o[_mixinRef] === originalMixin) { | ||
return true; | ||
} | ||
o = Object.getPrototypeOf(o); | ||
} | ||
return false; | ||
} | ||
// Look up an existing application of `mixin` to `c`, return it if found. | ||
if (c.hasOwnProperty(applicationRef)) { | ||
return c[applicationRef]; | ||
} | ||
}); | ||
} | ||
return mixin; | ||
}; | ||
// Apply the mixin | ||
let application = mixin(c); | ||
/** | ||
* A basic mixin decorator that sets up a reference from mixin applications | ||
* to the mixin defintion for use by other mixin decorators. | ||
*/ | ||
export const BareMixin = (mixin) => wrap(mixin, (superclass) => { | ||
// Apply the mixin | ||
let application = mixin(superclass); | ||
// Attach a reference from mixin applition to mixin for RTTI | ||
// mixin[@@hasInstance] should use this | ||
application.prototype[_mixinRef] = mixin; | ||
// Attach a reference from mixin applition to wrapped mixin for RTTI | ||
// mixin[@@hasInstance] should use this. | ||
application.prototype[_mixinRef] = mixin[_originalMixin]; | ||
return application; | ||
}); | ||
// Cache the mixin application on superclass c | ||
c[applicationRef] = application; | ||
/** | ||
* Decorates a mixin function to add application caching and instanceof | ||
* support. | ||
*/ | ||
export const Mixin = (mixin) => Cached(HasInstance(BareMixin(mixin))); | ||
// Patch in instanceof support to mixin | ||
// not supported in any browsers yet? | ||
if (Symbol.hasInstance && !mixin.hasOwnProperty(Symbol.hasInstance)) { | ||
mixin[Symbol.hasInstance] = function(o) { | ||
do { | ||
if (o.hasOwnProperty(_mixinRef) && o[_mixinRef] === this) { | ||
return true; | ||
} | ||
o = Object.getPrototypeOf(o); | ||
} while (o !== Object) | ||
return false; | ||
} | ||
} | ||
export const mix = (superClass) => new MixinBuilder(superClass); | ||
return application; | ||
}, this.superclass); | ||
class MixinBuilder { | ||
constructor(superclass) { | ||
this.superclass = superclass; | ||
} | ||
with() { | ||
return Array.from(arguments).reduce((c, m) => m(c), this.superclass); | ||
} | ||
} | ||
// function extend(subclass, superclass) { | ||
// var prototype = Object.create(superclass.prototype); | ||
// prototype.constructor = subclass; | ||
// return prototype; | ||
// } |
'use strict'; | ||
import {mix, _mixinRef} from '../mixwith'; | ||
import {mix, _mixinRef, Mixin, _originalMixin} from '../mixwith'; | ||
import {assert} from 'chai'; | ||
let A = (superclass) => class A extends superclass { | ||
// Enable the @@hasInstance patch in HasInstance | ||
const originalHasInstance = Symbol.hasInstance; | ||
Symbol.hasInstance = Symbol.hasInstance || Symbol('hasInstance'); | ||
let A = Mixin((superclass) => class A extends superclass { | ||
foo() { | ||
@@ -19,11 +23,11 @@ return ['A.foo']; | ||
}; | ||
}); | ||
let B = (superclass) => class extends superclass { | ||
let B = Mixin((superclass) => class extends superclass { | ||
bar() { | ||
console.log('B.bar'); | ||
} | ||
}; | ||
}); | ||
let C = (superclass) => class extends superclass { | ||
let C = Mixin((superclass) => class extends superclass { | ||
constructor() { | ||
@@ -37,3 +41,3 @@ console.log('C.constructor', arguments); | ||
} | ||
} | ||
}); | ||
@@ -115,11 +119,12 @@ class D { | ||
assert.isTrue(o.__proto__.__proto__.hasOwnProperty(_mixinRef)); | ||
assert.equal(o.__proto__.__proto__[_mixinRef], A); | ||
assert.equal(o.__proto__.__proto__[_mixinRef], A[_originalMixin]); | ||
}); | ||
if (Symbol.hasInstance) { | ||
test('subclasses implement mixins', () => { | ||
let o = new DwithA(); | ||
test('subclasses implement mixins', () => { | ||
let o = new DwithA(); | ||
assert.isTrue(A[Symbol.hasInstance](o)); | ||
if (originalHasInstance) { | ||
assert.instanceOf(o, A); | ||
}); | ||
} | ||
} | ||
}); | ||
@@ -126,0 +131,0 @@ test('methods on mixin are present', () => { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
26753
317