Open WickyNilliams opened 2 years ago
Thanks for putting this all in one place @WickyNilliams! I'm skeptical that interop with Lit is relevant, if anything it would be a nice-to-have. Granted it spurred the conversation and they have good ideas, I just don't think it should be the guiding principle here. One of the things that I always loved about Stencil was that they take the good parts from other frameworks and make them work together (ie: TSX from React, Decorators from Angular) in this case I think something like a @Controller
decorator would be an ergonomic way to handle them.
it's not interop with lit so much as interop with framework agnostic controllers. as you can see in my example, i used the lit-labs/task
package instead of reinventing the wheel. there's a lot of power in that.
and subjectively, I also think it's a lot cleaner to keep logic out of render
take the good parts from other frameworks
controllers are a good part. i have experience with both stencil and lit, and i can attest to them being good in lit, and sorely lacking in stencil
Thanks, FWIW, I do see the value in controllers/mixins and I think it's obvious that some semblance of them would be nice to have in Stencil for reasons you've mentioned. I just want to make sure we're laser focused on "what's good for Stencil"
@WickyNilliams am I crazy or is this all we need?
https://gist.github.com/snaptopixel/82bc6b6e35c6a4a4c4127941242a7039#file-component-tsx-L94-L140
Pretty much yeah. Though there is value in it being officially/natively supported.
You made me realise i missed the updateComplete
promise. But that's also something that stencil should support imo, especially as everything is async in stencil anyway. It's very useful to be able to await next render complete before continuing with some logic, rather than having to juggle flags in lifecycles
this kind of thing is exactly what i meant in the original post when i said:
At best you can do some manipulation of the component instance in a helper function, but this always feels fragile.
host.connectedCallback = () => {
this.controllers.map((ctrl) => ctrl.hostConnected?.())
connectedCallback?.apply(host)
}
🙂
feelsFragile !== isFragile
though amirite, also I'm unclear on how the updateComplete
promise is supposed to work...
Of course, but I would say monkey patching is almost always fragile! Better to have a well defined mechanism for extension be that mixins or controller or whatever
👋 Hey there
I think controller support for Stencil would be a boon to the library.
I've implemented a mixin which adds support to an arbitrary Element class: https://apolloelements.dev/api/libraries/mixins/controller-host-mixin/, as well as useController
hooks for atomico and haunted, and similar bridges for FAST and hybrids, so this isn't about "interop with Lit", like OP mentioned above.
I hope taking a look at that can give your some inspiration.
Adding my support for this too. Reactive Controllers offer an elegant pattern for code reuse in custom elements. Although they're somewhat new, we're already seeing amazing examples of what can be done with them without cluttering up our component's code base.
To demonstrate a real world example, I built a component-level localization library that uses the Reactive Controller pattern and I'd love to be able to share it with Stencil users as well.
We implemented this same type of thing (we called behaviors) but having to manually implement all the lifecycle methods to call the behaviors' methods manually is cumbersome. Almost every component in our library has this in it:
connectedCallback() {
this.behaviors.connected();
}
componentWillLoad() {
this.behaviors.willLoad();
}
componentDidLoad() {
this.behaviors.didLoad();
}
componentDidRender() {
this.behaviors.didRender();
}
disconnectedCallback() {
this.behaviors.disconnected();
}
...
We tinkered with the idea of adding a decorator to automatically do this but Stencil doesn't let us use our own decorator on the component class or constructor. And we found that the whole concept of componentWillLoad (and other lifecycle methods ) seem to be treeshaken out during the build if they aren't explicitly called in at least one component. So doing this in an "official way" would really appeal to us.
Prerequisites
Describe the Feature Request
Lit has added support for what it calls "Reactive controllers" in its latest major release. These are plain JS classes which conform to a known interface, and are able to hook into a component's lifecycle.
They are generic enough that they can be used/adapted to most/all frameworks, as they are not dependent on Lit at all (aside from interface definition, but that could be externalised). Therefore stencil could add support for these by implementing
ReactiveControllerHost
interface.This could be considered a community standard, and would allow for sharing of functionality between stencil and lit (and any other frameworks that opt-into the standard), perhaps creating an ecosystem for pieces of reusable behaviour
Controllers are a mechanism for extracting both behaviour and state from a component in a reusable way.
Relevant links:
Describe the Use Case
There are currently no ways "official" ways to share functionality/logic/behaviour between components. At best you can do some manipulation of the component instance in a helper function, but this always feels fragile.
Reactive controllers offer a way to express a "has-a" relationship, and are composable. You can extract common behaviour and state for reuse between components and even frameworks.
Describe Preferred Solution
Stencil's base component class should implement ReactiveControllerHost. This could be as simple as:
Just this would be enough to satisfy the support of reactive controllers.
Describe Alternatives
On slack, @snaptopixel described an alternative approach based around functional components: https://gist.github.com/snaptopixel/9dd86455a5791b65e9a0c0e576c097b6
However, this is entirely custom and stencil-specific, so would not be interoperable and is not framework agnostic.
Related Code
See the following gist: https://gist.github.com/WickyNilliams/79ee85ea370506ac6b16de1920f48e5e
This demonstrates the use of a number of custom controllers integrated into an example stencil component.
The
MouseController
is lifted verbatim from the Lit docs.Perhaps, more interesting is that is uses the
Task
controller published by the lit team, as an example of how functionality can be shared across frameworks!Additional Information
No response