In response to our last post on building components from a loose collection of mixins, a helpful commenter referred us to another mixin library he had released called mixwith.js. That library treats mixins more as a pattern than a thing. In that pattern, a mixin is simply a function that takes a base class and returns a subclass of that base class with the desired new features.
We were intrigued by this approach. As we've blogged about before, we're really only interested in coding approaches that can be shared with other people. This functional approach would allow us to lower the barrier to adopting a given mixin. As much as we like the Composable class discussed in that earlier post, using mixins that way requires adoption of that class. It's not quite a framework — it's more of a kernel for a framework — but it's still a bit of shared library code that must be included to use that style of mixin.
Here's an example of a mixin class using that Composable approach. This creates a subclass of HTMLElement that incorporates a TemplateStamping mixin. That mixin will take care of stamping a `template` property into a Shadow DOM shadow tree in the element's `createdCallback`.
import Composable from 'Composable'
import TemplateStamping from 'TemplateStamping';
class MyElement extends Composable.compose(HTMLElement, TemplateStamping) {
get template() {
return `Hello, world.`;
}
}
That's pretty clean — but notice that we had to `import` two things: the Composable helper class, and the TemplateStamping mixin class.
The functional approach implements the mixin as a function that applies the desired functionality. The mixin is self-applying, so we don't need a helper like Composable above. The example becomes:
import TemplateStamping from 'TemplateStamping';
class MyElement extends TemplateStamping(HTMLElement) {
get template() {
return `Hello, world.`;
}
}
That's even cleaner. At this point, we don't even really have a framework per se. Instead we have a convention for building components from mixin functions. The nice thing about that is that such a mixin could conceivably be used with custom elements created by other frameworks. Interoperability isn't guaranteed, but the possibility exists.
We like this so much that we've changed out nascent core-component-mixins project to use mixin functions. Because there's so little involved in adopting this sort of mixin, there's a greater chance it will find use, even among projects that write (or claim to write) web components in plain javascript. Again, that should accelerate adoption.
The most significant cost we discussed in making this change is that a mixin author needs to write their mixin methods and properties to allow for composition with a base class. The Composable class had provided automatic composition of methods and properties along the prototype chain according to a set of rules. In a mixin function, that work needs to be done manually by the mixin author.
We've identified a series of composition rules that capture our thinking on how best to write a mixin function that can safely applied to arbitrary base classes. The rules are straightforward, but do need to be learned and applied. That said, only the authors of a mixin need to understand those, and that's a relatively small set of people.
Most people will just need to know how to use a mixin — something that's now as easy as calling a function.