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>
87 lines
2.9 KiB
JavaScript
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;
|
|
};
|
|
}
|