phoenixframework / phoenix_live_view

Rich, real-time user experiences with server-rendered HTML
https://hex.pm/packages/phoenix_live_view
MIT License
6.18k stars 930 forks source link

Statics page patching optimization #2426

Closed josevalim closed 1 year ago

josevalim commented 1 year ago

Today, when rendering a large page, all of the static components of the page will be parsed and diffed by morphdom on every patch. This is an proposal to optimize rendering by relying on the HEEx engine to skip large parts of the tree. This will bring performance benefits similar to those found in Svelte.

Imagine this template:

<div>
  foo
  <tag attrs>
    LARGE
  </tag>
  <%= ... %>
</div>

Where <tag is any element with optional attrs and a LARGE amount of content and no dynamic content.

Today, it will be sent over the wire like this:

["<div>foo\n<tag attrs>LARGE</tag>\n", "</div>"]

Our proposal is to convert the static into a list of three elements, containing:

So we would get this:

[["<div>foo\n","<tag attrs>LARGE</tag>","\n"], "</div>"]

Now, on the initial render, will we render convert it back to these statics:

["<div>foo<tag phx-magic-id=... attrs>LARGE</tag>\n", "</div>"]

Where phx-magic-id is a unique client ID (it can be a client-based counter). Immediately after rendering it, we will have:

["<div>foo<tag phx-magic-id=... phx-skip></tag>\n", "</div>"]

Notice that we were able to remove all attrs and the LARGE contents of the tag. We will instruct morphdom to first consider phx-magic-id and then the DOM id when comparing elements, reducing the amount of parsed and diffed content. This will also reduce the amount of data stored on the page.

Tasks

Implementing this feature is not necessarily complex. The trickiest part is changing the engine to identify the largest single-element subtree according to a threshold. Furthermore, part of the initial work in scrubbing and skip tags can already be implemented for components and then shared in the future.

ismaelga commented 1 year ago

@josevalim Would love to work on this. Is it still relevant and do you still think it's a good approach?

If so could you give me some pointers to get started?

josevalim commented 1 year ago

Hi @ismaelga! This is definitely relevant but this is a complex task because it touches on several parts of the tag engine, the diff rendering mechanism, and the JS client.

Good news is that it is on my todo list for the coming days, so we should have some progress on it soon.

josevalim commented 1 year ago

I tried this and unfortunately this optimization wouldn't trigger as frequently as we thought it would. :(

tmjoen commented 1 year ago

Would it be possible to mark parts of a component as something that should not be sent over the wire on every render? My usecase for this is that I have a dynamic form that has a block builder that's built from an embeds_many field. This will send huge updates on every change, even when I try to pull the whole field out with phx-update="ignore". The embeds_many field won't change track (I think?) so I'm back to thinking that my use case maybe is the wrong fit for live view?

josevalim commented 1 year ago

If your whole template is dynamic, there isn’t much we can do. At best you can try to isolate the dynamic parts, so you send less things when only one part changes.

arkadiyk commented 1 year ago

actually would be nice to have some kind of guide on how to design component structure to optimize rendering performance

josevalim commented 1 year ago

We do talk about best practices when it comes to using assigns in templates. All other optimizations, both on client and server happen automatically, you don’t have to do anything. If that changes, we will document it.