Closed kbond closed 1 year ago
A bit of a DX brainstorm:
wire:init
)I'm sort of leaning towards a combination of 2 and 3 (opposed to livewire's 1 and 2):
<twig:MyComponent data-defer="true" />
{# MyComponent.html.twig #}
<div {{ attributes }}>
{% if isInitializing %} {# new attribute provided by live components #}
Loading... (spinner or maybe a cool animated placeholder skeleton)
{% else %}
... expensive operation ...
{% endif %}
</div>
On the first render, isInitializing
would be true
. The js would know to, as soon as the component is initialized on the page, immediately re-render. During this render (and subsequent renders), isInitializing
would be false
.
At least one problem with this idea: forcing the component to be lazy in the component template (<div {{ attributes }} data-defer="true">
wouldn't work. This would be too late to know to set isInitializing
.
I'm very open to other ideas. I was hoping to leverage the current loading state system but I do not think this is possible.
// cc @WebMamba, @sneakyvv any interest in this feature? I'd love some thoughts.
One thing I like about the Livewire way of doing things is that it doesn’t add any new concepts: you create an action, then the only magic is that you ask the action to be automatically executed.
With that in mind, what about something like:
<div {{ attributes.defaults({
'data-action': 'appear->live#action',
'data-action-name': 'loadPosts',
}) }}>
<div>
Or, you should be able to equally do this from the outside:
<twig:MyComponent data-action="appear->live#action" data-action-name="loadPosts" />
This takes the normal "action-calling" syntax + Stimulus's normal data-action
syntax (which is data-action="EVENT->CONTROLLER#ACTION"
). The new addition would be an appear
event (name could be changed) that we trigger internally when the element "comes into the viewport. This is modeled off of https://github.com/stimulus-use/stimulus-use/blob/main/docs/use-intersection.md
If you want to simply "load immediately on page load", then instead of appear
, I think that this would already work, though the name is a bit cumbersome (the live:connect
- we could shorten to connect
or load
).
<twig:MyComponent data-action="live:connect->live#action" data-action-name="loadPosts" />
Good points. Let's keep it similar to livewire.
What about adding a LazyTrait
helper? Something like:
trait LazyTrait
{
public bool $isLoaded = false;
#[LiveAction]
public function load(): void
{
$this->isLoaded = true;
}
}
{# MyComponent.html.twig #}
<div {{ attributes }}>
{% if not isLoaded %} {# from the trait #}
Loading... (spinner or maybe a cool animated placeholder skeleton)
{% else %}
... expensive operation ...
{% endif %}
</div>
Calling:
<twig:MyComponent data-action="appear->live#action" data-action-name="load" />
Maybe we could create a shortcut?
<twig:MyComponent data-lazy />
I like all of this - I'm definitely not against having a shortcut, like data-lazy
.
In #996, you mentioned:
One thing we should ensure, if component is lazy and polling is enabled: do not start polling until after the initial load
With this latest iteration, "lazy loading" isn't something the component would be aware of. There would be a convention (via the trait + data-lazy
shortcut) that it means that there is some isLoaded
property set to true
/false
, but that's it. The best I can think of is to recommend that people make data-poll
conditional:
<div
{{ attributes }}
{{ isLoading ? '' : 'data-poll="save"' }}
>
Hum I am not really agree here. Since the laziness of a component is a template concern, only the template should know that the component is lazy.
My proposal is as follow:
<twig:MyComponent data-lazy />
You only have to set this data-lazy attribute, if the data-lazy attribute is set the mount method will be called by ajax when the compose is visible, if not the mount method is call as before.
@WebMamba I think this is valid, but a separate level of laziness. Here are the 2 cases:
A) (existing idea): the component renders, but it has a flag to avoid doing something heavy. That flag changes on load, and then you do the heavy stuff. An advantage of this is that you can easily control the "loading" state - e.g. "Loading...".
B) (your idea): the ENTIRE component is lazy - this is like a lazy <turbo-frame>
and would have the highest level of laziness. I like this, but it could be tricky in practice. You would need a way to control the loading state (e.g. "Loading...") and you would need to be able to construct a URL that would render the component, which would come from only the "input props" (as we'd want to avoid actually mounting the component). There is a component_url()
Twig function, but that mounts the component then dehydrates it. In this case, you would need to create a URL based off of the "input props", which are not something that we normally need to try to dehydrate. My guess is that we could only reasonably make this work if we required all "input props" to be scalars (e.g. so we could just pop them onto a URL as query params).
I like all the ideas here, but I'm wondering if this is taking the (not yet accepted/merged) live embedded components into account. (#913). I guess it would work well together since these can also be loaded lazily, and wouldn't be impacted by any of this, right?🤔 Would a component that's being replaced in the DOM again be treated the same and potentially be loaded lazily? Even if it's being replaced within the current viewport?
Btw, Livewire 3's lazy feature is like @WebMamba suggested :)
My proposal is as follow:
<twig:MyComponent data-lazy />
You only have to set this data-lazy attribute, if the data-lazy attribute is set the mount method will be called by ajax when the compose is visible, if not the mount method is call as before.
The key thing is that: the component is NOT mounted initially. As I mentioned, this means that you actually need to dehydrate the input props, which is not something that's done anywhere else. But, Livewire does this. Likely, we would need to require the input props to be scalars so that we can just serialize them easily. I think that Livewire does NOT require this, but that means they leak code info (e.g. prop foo
is an App\Entity\Foo
object) to the frontend, which I think they are generally ok with, but we have avoided thus far.
@sneakyvv
guess it would work well together since these can also be loaded lazily, and wouldn't be impacted by any of this, right?🤔
My guess is that this won't be impacted by your work over there.
Would a component that's being replaced in the DOM again be treated the same and potentially be loaded lazily? Even if it's being replaced within the current viewport?
Not sure what you mean here.
I think that Livewire does NOT require this, but that means they leak code info (e.g. prop
foo
is anApp\Entity\Foo
object) to the frontend, which I think they are generally ok with, but we have avoided thus far.
I was looking at the code of the LiveRenderer and...
Would it be possible to use an intermediate "proxy/flyweight" ?
Twig -> ProxyComponent -> LongTaskComponent
The "ProxyComponent" would store the data (in backend, file, session... anywhere BUT in the front space) then after some time it would then transmit data to the LongTaskComponent ..
I mean it could fullfill some of the cases you discussed no ?
From https://github.com/symfony/ux/issues/102
Livewire's docs for this feature