Minimize LiveView payload - maybe with a macro?

Hi everyone :wave: I’m building LiveVue, it’s LiveSvelte rewrite for Vue.

Things are mostly working, but I’m experimenting with one thing. Currently, props are propagated to the frontend as data-props={Jason.encode!(@props)}. It’s working fine, but updating a single prop sends all of them in the update payload. I’m wondering if it could be optimized by splitting props into individual data-vue-prop-[name]=prop.

If I’m correct all data-vue-prop-[name] must be there at compile time to be treated as a static fragment? So, I’d like to create a HEEX macro component that would transform this:

~H"<.vue vue-component="Counter" prop1={@val1} prop2={@val2} vue-socket={@socket} />

into

~H"""
  <div
        id={id(@name)}
        data-live-vue
        data-name={@name}
        data-vue-prop-prop1={Jason.encode!(@val1)}
        data-vue-prop-prop2={Jason.encode!(@val2)}
        phx-update="ignore"
        phx-hook="VueHook"
   />
"""

in a way to track changed independently for each prop. Is it possible? I’ve tried experimenting with

defmacro vue(assigns) do
  quote do
     ~H"""
    <div>test</div>
     """
  end
end

but it fails with Phoenix.Component.Declarative.__on_definition__ checks:

could not define attributes for function vu1/1. Please make sure that you have `use Phoenix.Component` and that the function has no default arguments

Is what I’m trying to accomplish possible? Maybe I could accomplish what I want in some other way? Or just give up on this since it’s not worth it?

Heex doesn’t support macros for the function component syntax. <.vue /> is expected to be a function component and therefore a function.

Ok, that’s what I thought.

So in other words, there is no way to send only updated props to the frontend?

It’s a bit sad, because it looks like it could be written by hand to achieve a smaller update payload, but there is no easy way to automatically achieve this.

Tbh I‘m not sure how macros at compile time could even help here in the first place given sending updates is a runtime concern.

3 Likes

Well in LiveView dynamic updates are limited to {} or <%= %> blocks (mostly). So if I have props={@props} then each time any key updates, the whole expression {@props} is recomputed and sent.

But if I would write key1={@val1} key2={@val2} then these updates are more granular, so change to {@val1} won’t send over unchanged {@val2}.

Since I want to let my users write key1={@val1} key2={@val2} and build these props for them (similar to this file) I was wondering if I could somehow just map these key / values and pass them to the resulting div.

But as I understand the answer is sadly no :frowning:

But at compile time you cannot know that there is a key1 or key2 in the first place, because it’s going to be different keys for each call to the function component. So LV needs to send both keys/attributes and their values. I’m not sure if it can optimize for a dynamic key not having changed as well.

1 Like

If you are rewriting LiveSvelte, I’d advise you checkout live_json. LiveSvelte, mentions it in this section docs and gives an example here. live_json uses json diffs to minimize data transfer.


On your mount, initialize the state:


def mount(_params, _, socket) do
  data = get_your_data()

  {:ok,
    socket
    |> LiveJson.initialize("dataviz", data)
  }
end

Then, to send updated data:

def handle_info({:new_data_to_visualize, new_data} = _event, socket) do
  {:noreply, 
    socket
    |> LiveJson.push_patch("dataviz", new_data)
  }
end

You might be able to achieve something like what you’re after with your own sigil and/or compiler step.