This prototype implements common web component features as mixins. It explores the idea that mixins can achieve the same results as a monolithic framework, while permitting more flexibility and a pay-as-you-go approach to complexity and performance.
Design goals:
- Demonstrate that it's possible to create web components entirely from mixins.
- Have each web component mixins focus on solving a single, common task. They should be well-factored. They should be able to be used on their own, or in combination.
- Introduce as few new concepts as possible. Any developer who understands the DOM API should find this architecture appealing, without having to learn many proprietary concepts (beyond mixins, see below).
- Focus on native browser support for ES6 and web components. The architecture should be useful in a production application today, but should also feel correct in a future world in which native ES6 and web components are everywhere.
> npm install
> grunt build
A foundation of this prototype is that web components can be expressed as compositions of base classes and mixins. This prototype defines a mixin as a function that takes a base class, and returns a subclass defining the new features.
let MyMixin = (base) => class MyMixin extends base {
// Mixin defines properties and methods here.
};
Such mixins should generally ensure that base class properties and methods are not broken by the mixin. In particular, if a mixin wants to add a new property or method, it should take care to also invoke the base class' property or method. To do that consistently, mixin functions should follow standardized [Composition Rules](Composition Rules.md). See that document if you are interested in creating your own mixins.
A virtue of a functional mixin is that you do not need to use any library to apply it. This increases the chance that mixins can be shared across projects. If a common extension/mixin solution can be agreed upon, frameworks sharing that solution gain a certain degree of code sharing, interoperability, and can share conceptual materials. This reduces the learning curve for dealing with any one framework.
Frameworks can still make their own decisions about which features they want to offer by virtue of which mixins they incorporate into their base classes.
Since web components often include many mixins, a helper mixin called Composable provides syntactic sugar that allows multiple mixins to be applied in a single call. Instead of defining an element like:
class MyElement extends Mixin1(Mixin2(Mixin3(Mixin4(HTMLElement)))) {
...
}
You can write:
class MyElement extends Composable(HTMLElement).compose(
Mixin1,
Mixin2,
Mixin3,
Mixin4
) {
...
}
The /src folder includes an initial set of mixins for very common web component features:
- Template stamping into a Shadow DOM tree.
- Polymer-style automatic node finding for convenient access to elements within the shadow tree.
- Marshalling attributes to properties. This includes mapping hyphenated
foo-bar
attribute references to camelCasefooBar
property names.
A sample base class, ElementBase, shows one way these mixins might be combined to create a custom element base class. This base class is not special in any way. It is essentially just a pre-packaged application of the mixins described above:
class ElementBase extends Composable(HTMLElement).compose(
TemplateStamping,
AutomaticNodeFinding,
AttributeMarshalling
) {
...
}
Another example, X-Tag, shows a hypothetical application of this strategy to the X-Tag framework. The sample base class in that example uses a different set of mixins to demonstrate that that is possible.
A Hello, world demo shows this sample ElementBase class being used to create a simple greet-element component. This can be viewed as a live demo.
More demos are available in the /demos folder.
This codebase generally assumes that class creation (e.g., with ES6 class
) is
handled separately from custom element registration with
document.registerElement()
. That said, a framework can still decide to offer
a single entry point that both defines a class and registers it. This is shown
in the xtag.register()
function of the X-Tag example.