lion/packages/core/src/dedupeMixin.js
Thomas Allmer ec8da8f12c feat: release inital public lion version
Co-authored-by: Mikhail Bashkirov <mikhail.bashkirov@ing.com>
Co-authored-by: Thijs Louisse <thijs.louisse@ing.com>
Co-authored-by: Joren Broekema <joren.broekema@ing.com>
Co-authored-by: Gerjan van Geest <gerjan.van.geest@ing.com>
Co-authored-by: Erik Kroes <erik.kroes@ing.com>
Co-authored-by: Lars den Bakker <lars.den.bakker@ing.com>
2019-04-26 10:37:57 +02:00

87 lines
2.9 KiB
JavaScript

const appliedClassMixins = new WeakMap();
/**
* @param {Object} obj
* @returns {Array}
*/
function getPrototypeChain(obj) {
const chain = [];
let proto = obj;
while (proto) {
chain.push(proto);
proto = Object.getPrototypeOf(proto);
}
return chain;
}
/**
* @param {function()} mixin
* @param {function()} superClass
* @returns {boolean}
*/
function wasApplied(mixin, superClass) {
const classes = getPrototypeChain(superClass);
return classes.reduce((res, klass) => res || appliedClassMixins.get(klass) === mixin, false);
}
/**
* # dedupeMixin
*
* Imagine you are developing web components and creating ES classes for
* Custom Elements. You have two generic mixins (let's say `M1` and `M2`) which
* require independently the same even more generic mixin (`BaseMixin`).
* `M1` and `M2` can be used independently, that means they have to inherit
* from `BaseMixin` also independently. But they can be also used in combination.
* Sometimes `M1` and `M2` are used in the same component and can mess up the
* inheritance chain if `BaseMixin` is applied twice. In other words, this may happen
* to the protoype chain `... -> M2 -> BaseMixin -> M1 -> BaseMixin -> ...`.
*
* An example of this may be a `LocalizeMixin` used across different components and mixins.
* Some mixins may need it and many components need it too and can not rely on other mixins
* to have it by default, so must inherit from it independently.
*
* The more generic the mixin is, the higher the chance of being applied more than once.
* As a mixin author you can't control how it is used, and can't always predict it.
* So as a safety measure it is always recommended to create deduping mixins.
*
* @param {function()} mixin
* @returns {function()}
*
* @example
* // makes a conventional ES mixin deduping
* const BaseMixin = dedupeMixin((superClass) => {
* return class extends superClass { ... };
* });
*
* // inherits from BaseMixin
* const M1 = dedupeMixin((superClass) => {
* return class extends BaseMixin(superClass) { ... };
* });
*
* // inherits from BaseMixin
* const M2 = dedupeMixin((superClass) => {
* return class extends BaseMixin(superClass) { ... };
* });
*
* // component inherits from M1
* // MyCustomElement -> M1 -> BaseMixin -> BaseCustomElement;
* class MyCustomElement extends M1(BaseCustomElement) { ... }
*
* // component inherits from M2
* // MyCustomElement -> M2 -> BaseMixin -> BaseCustomElement;
* class MyCustomElement extends M2(BaseCustomElement) { ... }
*
* // component inherits from both M1 and M2
* // MyCustomElement -> M2 -> M1 -> BaseMixin -> BaseCustomElement;
* class MyCustomElement extends M2(M1(BaseCustomElement)) { ... }
*/
export function dedupeMixin(mixin) {
return superClass => {
if (wasApplied(mixin, superClass)) {
return superClass;
}
const mixedClass = mixin(superClass);
appliedClassMixins.set(mixedClass, mixin);
return mixedClass;
};
}