-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[slottable-request] Initial draft #45
Conversation
For discussion:
|
There are often use cases that require a component to render UI based on data, but where the specific rendering of the data should be controllable by the user. Use cases of this include: | ||
|
||
* A data table or tree component, which manages the rendering of cells or tree nodes, but where the given look and feel of the items is customizable by the user. | ||
* A virtual list component, which optimizes rendering a list of data by only rendering a subset of user-templated items based on which array instances are visible on the screen. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This virtual list does not use this pattern: https://github.com/WICG/virtual-scroller
Worth reviewing why they came to the decision not to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The virtual-scroller effort is defunct, but even so it did seem to follow a very similar pattern where the container fires an event to tell the host to render new content: https://github.com/WICG/virtual-scroller#rangechange-event
Can you say what you think the important differences are?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's unclear to me how the event-listener is intended to provide the content. Inline examples would help clarify this for me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm of a mind to approve this into draft
state as part of our monthly gathering on Tuesday. Any opposed, feel free to chime in!
@Westbrook I have unanswered questions about this proposal that would be nice to have answered before it is merged. |
@matthewp I'd say that working through questions like yours is 100% what is meant by moving something to "draft" status:
If you are in disagreement with this being an "interesting area of investigation", we'd love to hear more about why you felt that way. Learn more about our other protocol status levels on our README. Be sure to also join our monthly WCCG meetings, managed on this calendar, or join us on Discord to further the conversation there. |
@Westbrook If that's the barrier to having something merged in this repo, then the current proposal format which focuses on how the API works is not the best way to go. If the question is whether something is "interesting area of investigation" then the format should focus mostly on goals and non-goals. I've supposed a new proposal template for this end: #47 |
export const remove = Symbol('slottable-request-remove'); | ||
``` | ||
|
||
When a component wants to render customizable content, it (1) fires a `slottable-request` event for a given slot name, and (2) renders a `<slot>` element into its shadow root corresponding to the requested slot name. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if the component is defined before the parent component has been defined? It will miss these events.
One solution here is to have an imperative API, such as a function that you can call, to cause a render to occur.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you explain who would call the imperative API, and how they would know call it?
Slotting is really a form of dependency injection, and it does get tricky when the owning scope isn't available to provide dependencies. We've dealt with this in the context protocol at an implementation level by having an "unfulfilled context request" manager at the root re-dispatch context requests from consumers to providers see discussion. We haven't updated the protocol to include that yet, but we implement it in Lit's version to pretty good effect, and I could see something like that forming a solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's unclear to me how the event-listener is intended to provide the content. Inline examples would help clarify this for me.
The `remove` symbol is provided as a sentinel value for `data` to indicate that the given content assigned to `slotName` should be removed, and should be fired when the associated `<slot>` is no longer rendered to avoid leaking light-DOM children. | ||
|
||
|
||
## Examples |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code examples here would be good.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The playground links are code examples -- were you looking for inline code snippets? They got a bit long and distracting so moved them to playground.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, outside links are fine too, but linkrot exists so there should be inline examples to view, and having a condensed version is a good exercise to see if the API scales down to simpler cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, all of these examples are using a framework, so it's harder to tell what is going on unless you understand the internals of what the framework is doing. So one or two plain JS examples would be good.
I wonder what this would look like? The container would presumably need some way for the host to signify which slots to target. At the limit there isn't a way to do this because the container could assign to slots by any criteria. It could specify something like "the host should render an |
Important side note for this if you use slot assignment manual, then you could be reducing the depth flexibility of your slottables. All items must be manually slotted, and you can't manually slot through multiple slots without needing updated content in each DOM tree, which may be prohibitive from a performance and bookkeeping standpoint. I also was drawn to the idea, especially how it does some interesting things for complex accessibility conversations to bring the "options" into the shadow root as multiple
|
Something I really feel is being overlooked here is that web components are already the standard mechanism for constructing a DOM tree on-demand and passing data to it, which seemingly is what this proposal effectively seeks to reinvent, which is a little puzzling. #8 and lit/lit#1478 seem to be helpful context for understanding this proposal. I'm very confused by the talk of different templating and rendering implementations, because I don't see how they are relevant; web components already abstract their implementation details from their users, and there's no reason why we'd want to leak them; even better, web components encapsulate their internals more broadly. We can very easily pass a web component as a prop to another web component, either as a reference to a class or as a string name, and easily construct the passed component since all web components have a standard API contract for construction. We can also trivially render a web component inside another web component without concern about styles, since web components encapsulate their styles. And, of course, we can pass data to web components using attributes/properties/slots, with the added benefit that web components can respond to updates without the user needing any knowledge of this. Here is a bare-bones, vanilla list rendering example. Advantages of this approach over the proposed approach include:
Reading the proposal and the provided proof-of-concepts, I'm struggling to see any meaningful benefit this proposal would achieve that my above example does not show, in principle, can be achieved in a much simpler way just by using web components. I look at the code for handling slottable requests in the provided list proof-of-concept, and I see the code for imperatively constructing a list item element and appending it to the DOM, and I think "this thing is just a web component, except it comes with extra steps, and it's not as good". If I'm missing something obvious, I'm very happy to have it explained to me. |
I think one clear benefit here with the proposal and the PoC is that within a React context this proposal makes it possible to easily create and use React components that wrap a Web Component, but also provides an easy way to provide content via callback in a React-like way (JSX template). With this (as in the PoC):
Without this proposed solution in the React context the application developer would need to be ok with managing both React components and Web Components code with two different kinds of rendering/templating instead of just being able to do everything in one way (as plain React components). It will add much unnecessary complexity to a React app if the React app needs to directly mix implementation of Web Components and React components. I'm not sure if this proposal has some non-obvious limitations or performance issues, but at least I see the value it provides for some use cases and it does seem to work with good DX for the component consumer. |
} | ||
} | ||
|
||
export const remove = Symbol('slottable-request-remove'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that remove
is a short, common identifier, we may want to consider exposing this as a static symbol like SlottableRequestEvent.remove
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we move this PR to target the "Proposal" status, the lowest possible status, we agreed as per the March monthly WCCG meeting that merging this PR is appropriate. We'll do our best to capture any conversations into issues for follow-up as the protocol moves up the various status levels (or not), so feel free to get your last comments posted over the next week and a half. On the 25th of March this PR will be merged as a "Proposal" protocol.
@Westbrook I've been absent for a little while so I'm just catching up with this now, and I must say I'm a little perplexed. Back in November, I was trying to raise the alarm that this whole idea is misguided. I was really hoping that if you folks disagreed with me (which I expected you did), you folks would explain why and we could discuss it. You folks making no real acknowledgment of my contribution and then merging this anyway feels bizarre to me. You say
and I can tell that you're deliberately trying to reassure me in saying this, but this doesn't reassure me of anything because:
I also feel the same way regarding #15. I want to be disagreed with and I want to have technically rigourous conversations that will make the web better. But I don't see how I can make constructive/productive contributions to this group when the constructive/productive contributions I try to make are dismissed and ignored. |
@mattlucock I think this comment did begin to address the need for this protocol that your proposed solution does nothing to support: #45 (comment) You should follow up if you have a counter proposal, but I suspect the lack of response from you was taken as a sign that the proposal as stated with that reasoning was valid enough to continue with. Those of us working in mixed technology environments are keenly aware that interop between React, Angular and other rendering frameworks with web components is a highly desirable capability. |
@mattlucock at work we’ve been testing an early implementation of this proposal on a non-trivial app. During that test, I’ve observed some properties of slottable requests that are desirable and, I think, not achievable via the element-name-as-string approach. If there are ways to achieve them with simple strings and custom element definitions, I would be interested in exploring that further: Styling from the hostWhen I provide a Notification of useUsing stringly-typed element names and relying on Event listeners at the host levelI commonly need to attach event listeners to slotted children. For example, listening to events and state changes on slotted, nested menu items within a menu. Delegating element creation to the menu container means - as a consumer of a menu/menu-item system - I can no longer configure those children. Event listeners are a common example of this, but other configurations like inline styles, conditional attributes, etc are also not possible. You might work around this by passing in a more complex configuration object that mirrors the structure of the DOM, but at that point, you’ve abandoned simplicity. Multiple or heterogeneous childrenFollowing along the theme of customization, another common need is to provide something like Optimization, not architectureFinally, one of the nicest properties we’ve seen from our test implementation is that it’s an optimization pattern that leverages existing primitives (like slots). If I adopt a stringly-typed approach that puts the burden of element creation & updating on the internal container, that’s an architectural decision that I have to make early and carry through. It impacts the scope of my styles, the mechanism by which I create and configure and render elements, etc. However, I can naively implement a system that leverages standard slotting behavior and build out a complex application. I can profile the application to find large DOM subtrees of unrendered, slotted elements, and I can recursively augment them with slottable requests to optimize them. I might start at a top-level menu with a thousand nodes that mostly don’t need to be shown, for the biggest immediate performance win. Then, I could address each sub-menu. Then, perhaps surgically optimize particularly large sub-sub-menus. The ability to start with built-in, standard behaviors, then progressively measure-and-optimize, has proved to be a large benefit for us. |
I struggle to believe this because:
Every engineering decision involves both benefits and tradeoffs; if you folks had given consideration to the concerns and tradeoffs I touched on but felt as though they were justified given the benefits, I really think someone should have said that. Nobody did. I think it is reasonable to feel troubled that nobody made direct acknowledgment of the points I had made, as if it was an unserious contribution not even worthy of being acknowledged. I'll post a follow-up comment in response to both you and @hunterloftis, grappling with the technical aspect more holistically. |
Firstly, I just want to make clear that that I do not actually think that globally defined custom elements with globally unique string names are a good idea; in fact, there may actually be no bigger critic of them than me, and if you could see what I'm working on, you'd know what I mean when I say that. Indeed, I'm comforted by the concern being raised about them here, because I think it's more evidence that I'm right about this. The vanilla bare-bones example I showed was using the standard APIs in the simplest way possible, and so necessarily it was a 'purist' web components example adhering to the traditional web components dogma. Again, I don't really think that this is actually the right way to go about things. So @hunterloftis,
I'm afraid I'm going to disappoint you on that one, but I think there are other ways we can use web components to achieve what we want to. My understanding of these proposals was that they were primarily concerned with interoperability between varying web component implementations, so you'll have to forgive me for considering this proposal in that context. But my general response to the framework interop point is this: allowing standards-unfriendly frameworks to interop with web components necessitates web components implementing a complex and unnatural abstraction, specifically for the purpose of this interop, because... why, exactly? Web Components should have responsibility and knowledge of this because why exactly? The proof-of-concept linked in the proposal shows that this proposal requires abstraction on the React side anyway—which of course is almost certainly unavoidable, but it actually means that this proposal is effectively the worst of both worlds, since both sides need to be abstracted. It would be much, much better if only the React side had to be abstracted. Thinking about this in my head and referring to the React abstraction in the proposal's proof-of-concept as inspiration, I think we could likely develop a React abstraction that would be end up being functionally equivalent to what is proposed here in terms of behavior, and that would both equivalent to the consuming React component in terms of API, and equivalent to the web component in terms of API. I would suggest that the reason what I'm thinking of hasn't already been thought of is because it getting it to work would require a significant amount of creative problem solving. Would this React abstraction be elegant? No, but React is inherently inelegant, so I think React users would be fine with that. I guess my overall thesis here is that, if a standards-unfriendly framework wants to interoperate with a standard component, that's got nothing to do with the web component itself; that's the framework's problem, and the onus is on the framework to solve it. And while it's true that web components seem like they have really problematic limitations, they're standard APIs, and I think it's actually very possible to use other standard APIs to overcome many of those limitations if we're clever about it and employ some creative problem solving. If we step outside of a particular set of preconceived notions, we can use standard APIs to do basically whatever we want. Funnily enough, even though what I'm working on only relates to interoperating web components with other web components, I've actually been toying with an idea that was directly inspired by this proposal, and achieves a functionally very similar result—that is, defining a template that is part of your component, but actually rendering it inside of another. Like, imagine render props, but for web components! I had cooled on my particular idea because I wasn't sure if it would really be useful or necessary, but this has me thinking again that it probably would be. This mechanism would actually be elegant, as opposed to an equivalent React abstraction which wouldn't be, but refer to my previous remarks about that. I realise this all sounds quite hypothetical, and that I'm saying this can be done but I haven't proved that it can. I guess probably the best thing I can do to action this would be to ship what I have to prove that the mechanism works between web components, and then show how the mechanism could be extended to interoperate with a standards-unfriendly framework. Life is messy at the moment (hence my absence) but I'm hopeful I can ship something relatively soon. But to be clear: I have no counter-proposal for a protocol, because I don't think this needs to be or a should be a protocol. I've largely focused on limitations of web components since that seems to be the main source of your concern @hunterloftis, but if you have deeper concerns I'll try to speak to them. |
@mattlucock, unless I misread, it sounds like you both initially and presently hold that this proposal / standard is unnecessary. I read your original comment as a proposal of an alternative mechanism to solve the problems addressed by the slottable requests approach, based on:
But based on your most recent comments, would it be accurate to say that you are not proposing an alternative approach that solves these problems?
Application developers building with web components today need some mechanism of addressing these issues:
Slottable requests address these via:
|
@hunterloftis I must admit that I'm confused by your confusion. I am indeed proposing an alternative approach that solves these problems and I spent multiple paragraphs trying to motivate such a thing. I was deliberately vague about how such a thing would work because this is something I've actively working on behind the scenes, so I'd rather just get it implemented, ship it, and then show it to you (as I tried to explain in my previous comment). In my original comment, the central claim I was making was
and the bare-bones, vanilla example I showed was in support of that claim; I was trying to show that the concept worked. You're right to point out that there are some practical issues with doing it the way I showed, but this doesn't really surprise me since it was just a bare-bones, vanilla example trying to show that the concept was correct. To address those limitations we would unfortunately need to make things a little bit more complicated, but I'm confident we can develop something that addresses those limitations and is based on the same concept I showed in the example—no protocol required. And even though it would be a bit more complicated of an abstraction, I think it would still be significantly less complex than what this proposal entails. Again, if you think there are deeper concerns with the concept beyond just practical limitations of web components, I would be keen to hear your thoughts on them. |
@mattlucock when you're ready to share an actual proposal for how to address the goals outlined in the draft proposal in this repo feel free to file an issue and detail them. Thats how this process is supposed to work, an initial draft of a protocol that solves a number of issues has been made, its a concrete proposal, but is still only in 'proposal' status. It is now time for counter proposals, and revisions to be made as separate issues in this repo for folks to be able to concretely discuss the relative merits of them. When you've got details to share, please file the ticket and I'm sure folks will be happy to discuss your approach. Concrete examples will help the discussion I'm sure. |
A proposal is essentially meaningless. You could propose that all web components have the The problem is that almost no other proposal process works that way and essentially everyone in the general community thinks a merged proposal is an acceptance and a pseudo standard. |
Three things: Firstly, @benjamind I hear you loud and clear, and I appreciate you engaging with me in good faith. Will do. Secondly, In my original comment I said
When I said this I was referring to web components being a basis upon which we can do this, and not necessarily standard conventional web components actually being the way to do this. I can see now that I didn't convey that clearly, and so I consider that I misspoke, and I apologise for that. Thirdly, I feel like I've already dug myself into a bit of a hole here, and so I don't want to dig deeper right now, but I believe that I definitely share some of @matthewp's concerns about all of this. |
There are often use cases that require a component to render UI based on data, but where the specific rendering of the data should be controllable by the user.
This proposal describes an interoperable protocol for components to request that their owning context render slotted content into themselves, using data provided via a protocol-defined event.