Open PatrickG opened 4 years ago
Sounded easy, but is complexer than I initially thought:
<div style="--color: {color}">
works on both client and server (Although performance with a lot of styles might be a problem) for the data-* example the spread props could be a solution: REPL
(Ps. if an attribute already exists it is overwritten, but which one depends)Just wanted to chime in and say I think there is a strong use-case for setting SSR attributes in an action. I'm not sure that the main focus for users of actions is dom access, but rather abstractions of element logic that can be shared, and that can certainly apply on the server. I know a bunch of my most commonly used actions would really benefit from being able to render certain attributes in an SSR context, outside of dom access.
For me at least the answers to the questions raised (1. and 2.) are simple — behave in the same way as if the attribute was written in markup. If some hydrated code clobbers the attribute the action set, so be it, that's up to both implementer and user to work around. I don't think it has to be very nuanced or complex in terms of behaviour.
I'd just love a way to be able to do something like this
function action(...) {
// clientside only stuff
return {
// SSR-able attribute map
attributes() {
return {
attr: val
}
}
}
}
And have those attributes rendered in an SSR context as well as clientside.
Highly relevant use case mentioned in another issue on spreading events, where this feature would've helped with:
I also looked into how to create renderless components such as React Aria and Headless UI in Svelte. Since actions only run in the browser, aria-attributes would not be present on the server-side rendered HTML, making actions a non-starter. I guess you could use a combination of spread props for aria-attributes and actions for event listeners, but that complicates the API quite a bit. Being able to spread event props would make it a lot easier to write renderless components.
Originally posted by @LeanderG in https://github.com/sveltejs/svelte/issues/5112#issuecomment-829594149
Event spreading seems to be achieved through actions in Svelte. For this use case I believe it would be a good in-between until spreadable events + props and dynamic elements (as
/svelte:element
) become available.
Hello,
I'd just love a way to be able to do something like this
function action(...) { // clientside only stuff return { // SSR-able attribute map attributes() { return { attr: val } } } }
I think it's not possible like that, because the function expects an DOM node as the first parameter.
But after some try, it seem possible to add a complementary SSR function in order to populate the attributes of the node. Example :
function action(node, args) {
// clientside only stuff
}
action.SRR = function(attrs, args) {
// serverside stuff here
// attrs is an object containing the tag attributes...
}
The only small problem is that I think the SSR function will be exported in the client code (even if it will not be used). But I don't think it's very problematic.
I tried a quick prototype and it's seem to work pretty well. Exemple, this component :
<script lang="ts">
type ActionArgs = { title: string };
function close(node: HTMLButtonElement, args: ActionArgs) {
node.classList.add("close-button");
node.setAttribute("aria-label", args.title);
// other Client side stuff
}
close.SSR = function(attrs: Record<string,any>, args: ActionArgs) {
attrs.class = (attrs.class||'') + ' close-button'
attrs['aria-label'] = args.title;
}
</script>
<button use:close={{title:"Close"}} class="btn">X</button>
Will generate the following HTML on SSR/prerendering :
<button class="btn close-button" aria-label="Close">X</button>
I think some things can still be improved :
Record<string,any>
for the type of the first arg is pretty poor. It could at least define some fields more precisely (like class/style which are strings)My prototype is available here : https://github.com/adiguba/svelte/tree/ssr-actions
Interesting! Has there any update regarding this feature request?
No, and I'm still not fully clear on the benefits to be honest. Can't we just use spread attributes here?
I'm disconnected from this problem now and thus' have less of an opinion, but for me Svelte 5 ergonomics make this less of an issue since spreading events was solved. And probably other stuff too. I'd love to hear more about the issues others get solved by potentially having this implemented.
But this still serves as a good example: https://melt-ui.com/docs/preprocessor
Ergonomics are so bad that it actually makes sense to make a preprocessor for an action lib's usecase, even if all the attributes can be controlled by the action.
P.S. Still love that if you build using an action lib, you don't lose access to directives e.g. animate and such. A big part of component libs in Svelte being a pain in the past was having to reinvent element directives and conveniences, especially when you're like a headless lib where almost all your components mirror an element 1:1. It seems with Svelte 5, things like event directives (modifers?) have been removed in favor of making them functions. It does bring into question if other directives should be given the same consideration though
No, and I'm still not fully clear on the benefits to be honest. Can't we just use spread attributes here?
For reusable state/behavior, we have 3 ways to go, and none is really satisfying:
onclick
behavior is set in this way any further onclick
will override the behavior. So if we want to be able to add many listeners actions are preferred. REPLExample. <button {...toggle} onclick={()=>"oops it's not toggling anymore"}> Toggle</button>
hidden
. REPLExample. <button use:toggle={{pressed: true}}> See that flash of unpressed?<button>
Example <button use:toggle.action {...toggle.attributes}>Not a way to live your life<button>
SSR actions would improve the situation greatly by allowing to set the attributes in ssr, set the behavior on mount, and have a great DX.
Bonus point if we can access some kind of virtual node in ssr allowing to grab and set attributes on the parent!
const createTab= (node: HTMLButtonElement, active:boolean)=>{
if(node.ssr){
node.role="tab";
node.ariaSelected = `${active}`
node.parent.role = "tablist"; <- that's cool!
} else {
node.addEventListener("click", ()=>...)
}
}
Is your feature request related to a problem? Please describe. It would be nice if actions could output attributes in SSR context.
Describe the solution you'd like
How important is this feature to you? Somewhat. It's just an idea i had, that could play well with something like svelte-css-vars. Imagine that svelte-css-vars could return
{ style: '--color: red' }
when rendered on the server.