fix(core): use dedupeMixin @open-wc

This commit is contained in:
Thijs Louisse 2020-03-05 15:47:47 +01:00 committed by Thomas Allmer
parent f810a30103
commit d1beffbff5
9 changed files with 10 additions and 171 deletions

View file

@ -51,8 +51,10 @@ export { styleMap } from 'lit-html/directives/style-map.js';
export { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
export { until } from 'lit-html/directives/until.js';
export { render as renderShady } from 'lit-html/lib/shady-render.js';
// open-wc
export { ScopedElementsMixin, getScopedTagName } from '@open-wc/scoped-elements';
export { dedupeMixin } from '@open-wc/dedupe-mixin';
// ours
export { dedupeMixin } from './src/dedupeMixin.js';
export { DelegateMixin } from './src/DelegateMixin.js';
export { DisabledMixin } from './src/DisabledMixin.js';
export { DisabledWithTabIndexMixin } from './src/DisabledWithTabIndexMixin.js';

View file

@ -33,6 +33,8 @@
"*.js"
],
"dependencies": {
"@open-wc/dedupe-mixin": "^1.2.1",
"@open-wc/scoped-elements": "^0.5.0",
"lit-element": "^2.2.1",
"lit-html": "^1.0.0"
},

View file

@ -1,6 +1,6 @@
/* eslint-disable class-methods-use-this */
import { dedupeMixin } from './dedupeMixin.js';
import { dedupeMixin } from '@open-wc/dedupe-mixin';
/**
* # DelegateMixin

View file

@ -1,4 +1,4 @@
import { dedupeMixin } from './dedupeMixin.js';
import { dedupeMixin } from '@open-wc/dedupe-mixin';
/**
* #DisabledMixin

View file

@ -1,4 +1,4 @@
import { dedupeMixin } from './dedupeMixin.js';
import { dedupeMixin } from '@open-wc/dedupe-mixin';
import { DisabledMixin } from './DisabledMixin.js';
/**

View file

@ -1,7 +1,6 @@
/* eslint-disable class-methods-use-this */
import { dedupeMixin } from './dedupeMixin.js';
import { dedupeMixin } from '@open-wc/dedupe-mixin';
/**
* # SlotMixin
* `SlotMixin`, when attached to the DOM it creates content for defined slots in the Light DOM.

View file

@ -1,5 +1,5 @@
/* global ShadyCSS */
import { dedupeMixin } from './dedupeMixin.js';
import { dedupeMixin } from '@open-wc/dedupe-mixin';
export const UpdateStylesMixin = dedupeMixin(
superclass =>

View file

@ -1,87 +0,0 @@
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;
};
}

View file

@ -1,77 +0,0 @@
import { expect } from '@open-wc/testing';
import { dedupeMixin } from '../src/dedupeMixin.js';
describe('dedupeMixin', () => {
function createMixin(name) {
return superClass =>
class MixinClass extends superClass {
getMixinNames() {
const superName = super.getMixinNames ? ` ${super.getMixinNames()}` : '';
return `${name}${superName}`;
}
};
}
function createMixins(...names) {
return names.map(name => createMixin(name));
}
it('dedupes mixins', () => {
const [Mixin1, Mixin2, Mixin3] = createMixins('Mixin1', 'Mixin2', 'Mixin3');
const DedupingMixin3 = dedupeMixin(Mixin3);
class BaseClass {}
class MixedClass1 extends DedupingMixin3(Mixin1(Mixin2(DedupingMixin3(BaseClass)))) {}
const MixedClass2 = DedupingMixin3(Mixin1(Mixin2(DedupingMixin3(BaseClass))));
const myObj1 = new MixedClass1();
const myObj2 = new MixedClass2();
expect(myObj1.getMixinNames()).to.equal('Mixin1 Mixin2 Mixin3');
expect(myObj2.getMixinNames()).to.equal('Mixin1 Mixin2 Mixin3');
});
it('dedupes mixins only explicitely', () => {
const [Mixin1, Mixin2, Mixin3] = createMixins('Mixin1', 'Mixin2', 'Mixin3');
const DedupingMixin3 = dedupeMixin(Mixin3);
class BaseClass {}
class MixedClass1 extends Mixin3(Mixin1(Mixin2(DedupingMixin3(BaseClass)))) {}
class MixedClass2 extends DedupingMixin3(Mixin1(Mixin2(Mixin3(BaseClass)))) {}
const myObj1 = new MixedClass1();
const myObj2 = new MixedClass2();
expect(myObj1.getMixinNames()).to.equal('Mixin3 Mixin1 Mixin2 Mixin3');
expect(myObj2.getMixinNames()).to.equal('Mixin3 Mixin1 Mixin2 Mixin3');
});
it('dedupes mixins applied on inherited base classes', () => {
const [Mixin1, Mixin2, Mixin3] = createMixins('Mixin1', 'Mixin2', 'Mixin3');
const DedupingMixin3 = dedupeMixin(Mixin3);
class BaseClass {}
class BaseMixedClass extends Mixin1(Mixin2(DedupingMixin3(BaseClass))) {}
class ExtendedMixedClass extends DedupingMixin3(BaseMixedClass) {}
const myObj = new ExtendedMixedClass();
expect(myObj.getMixinNames()).to.equal('Mixin1 Mixin2 Mixin3');
});
it('dedupes mixins applied via other mixins', () => {
const [Mixin1, Mixin2, Mixin3] = createMixins('Mixin1', 'Mixin2', 'Mixin3');
const DedupingMixin1 = dedupeMixin(Mixin1);
const DedupingMixin2 = dedupeMixin(Mixin2);
const DedupingMixin3 = dedupeMixin(Mixin3);
const Mixin123 = superClass => DedupingMixin1(DedupingMixin2(DedupingMixin3(superClass)));
const Mixin312 = superClass => DedupingMixin3(DedupingMixin1(DedupingMixin2(superClass)));
class BaseClass {}
class MixedClass extends Mixin312(Mixin123(BaseClass)) {}
const myObj = new MixedClass();
expect(myObj.getMixinNames()).to.equal('Mixin1 Mixin2 Mixin3');
});
// // ToDO: check with polymer3 mixin once we are on npm
// it('works with mixins deduped by Polymer', () => {
// const [Mixin1, Mixin2] = createMixins('Mixin1', 'Mixin2');
// const DedMixin1 = dedupeMixin(Mixin1);
// const PolMixin2 = dedupingMixin(Mixin2);
// class BaseClass {}
// class MixedClass extends DedMixin1(PolMixin2(DedMixin1(PolMixin2(BaseClass)))) {}
// const myObj = new MixedClass();
// expect(myObj.getMixinNames()).to.equal('Mixin1 Mixin2');
// });
});