Liveview traffic over the wire

Dear all,

I am wondering whether I miss something in my understanding of Liveview.

By running a typical mix phx.gen.live Context Resource resources name:string, I navigate to the http://localhost:4000/resources URL and I “toggle” between the “New Resource” button and an escape (moving back without adding any new resource). All code is automatically generated.

By doing the aforementioned toggling, the generated network traffic demonstrates two messages of size 10KB and 8KB approximately, repeatedly as I toggle.

I am wondering whether this is the way it is supposed to work, since I thought that showing and hiding a modal is supposed to be client-side only.

On a second relevant aspect, the size of the page (HTML) that is generated after adding some new resources (even having only a single attribute such as name) gets significantly larger, though the actual presented information is really minimal. In this particular example, every new Resource item adds about 2KB in the size of the generated page.

Concluding, although I am really enthousiastic with the Liveview development experience, I am puzzled with the generated traffic over the wire.

Of course, I have tried experiments coupling Liveview with Javascript Hooks, wherein I programmatically add HTML elements with data recived from the server via messages and the traffic gets significanlty less.

What are your thoughts and solutions to this?

P.S. For the record, I employ the latest versions of Phoenix (1.7.1) and Liveview (0.18.16) and the generated code uses streams.

8 Likes

I’m not exactly sure what your concern is but here are a couple thoughts about rendering less often and with less data.

Temporary assigns saves you from having the rerender long lists

The other common pitfall is "accessing variables inside LiveViews, as code that access variables is always executed on every render. This also applies to the assigns variable. (source)

Example of code to avoid:

# Please avoid these incantations
<%= assigns[:greetings] %>
<%= assigns.greetings %>
<.hello_component {assigns} />
<.hello_component greeting={assigns[:greeting]} person={assigns[:person]} />
<%= hello_component(assigns) %>
<%= render WelcomeView, "hello.html", assigns %>

Pulled these examples from Dockyard.

3 Likes

Thank you for your response!

The temporary assigns are supposed to be replaced by the newest “stream” functionality. This could be related to some degree with the second aspect of my concerns above. However, even in view of that, requiring around 2KB merely to display a name (certainly in a hypertext format) is more that what I would have imagined.

Notwithstanding this, the first experiment mentioned above, involves no actual assign variables, i.e. in the experiment I have not added yet any resource, hence, it shall correspond to an empty list (I imagine).

I will keep in mind the tips you provided!

1 Like

Welcome @bdarla!

In order to make the modal be client-side only, you would have to send the modal to the client, even without knowing if the client is going to use it. This means a higher upfront-cost.

With LiveView, you send it to the client when they need it. It is closer to think about it as full page navigation (but optimized as we don’t sent the full page, only the modal), rather than SPA.

I checked the 10kb matches the size of the modal but the 8kb seems unexpected. We will investigate and follow up.

3 Likes

I agree that 2000 characters seems like a lot for just updating a name, but my guess is that there is a certain fixed overhead cost for using websockets.

Maybe paste the data passing through the websocket here. I’m curious what all it includes but honestly it is never going to impact performance.

3 Likes

We found it. It is something silly: on_cancel={...} in your modal should be JS.patch(~p"/resources"), not JS.navigate(~p"/resources"). Navigate does loads the full page, which is not what we want.

Thanks for the sanity check, we will address it in the next patch release.

19 Likes

Fixed on main for the generators. We used to live navigate which is analogous to a full page reload but with the stream-based generators we now use live patch. Thanks!

16 Likes

Thank you for the solution regarding the second traffic cost! I just tested it and it works.

Now, with respect to the first 10K for appearing the modal, I guess it is up to the developer to depart from the default solution and send the modal in advance and “play” with show/hide classes.

As a side note, I have the impression that the quite large size of the generated traffic is significantly due to the Tailwind overhead. I have not verified this feeling though.

3 Likes

Nice. :v:

Probably not a top priority compared to other features but I think there’s more caching that can be done here.

1 Like

That may be one reason, and you can extract it to classes if necessary, but JS commands are also part of it. We were working on tiny improvements to reduce the amount of commands which will probably make it to the next version as well.

7 Likes

To close this thread, we have added a new JS.exec for delegating to an existing attribute. Here is how a new Phoenix v1.17 app modal will look like:

  def modal(assigns) do
    ~H"""
    <div
      id={@id}
      phx-mounted={@show && show_modal(@id)}
      phx-remove={hide_modal(@id)}
      phx-cancel={JS.exec(@on_cancel, "phx-remove")}
      class="relative z-50 hidden"
    >
      <div id={"#{@id}-bg"} class="fixed inset-0 bg-zinc-50/90 transition-opacity" aria-hidden="true" />
      <div
        class="fixed inset-0 overflow-y-auto"
        aria-labelledby={"#{@id}-title"}
        aria-describedby={"#{@id}-description"}
        role="dialog"
        aria-modal="true"
        tabindex="0"
      >
        <div class="flex min-h-full items-center justify-center">
          <div class="w-full max-w-3xl p-4 sm:p-6 lg:py-8">
            <.focus_wrap
              id={"#{@id}-container"}
              phx-window-keydown={JS.exec("phx-cancel", to: "##{@id}")}
              phx-key="escape"
              phx-click-away={JS.exec("phx-cancel", to: "##{@id}")}
              class="hidden relative rounded-2xl bg-white p-14 shadow-lg shadow-zinc-700/10 ring-1 ring-zinc-700/10 transition"
            >
              <div class="absolute top-6 right-5">
                <button
                  phx-click={JS.exec("phx-cancel", to: "##{@id}")}
                  type="button"
                  class="-m-3 flex-none p-3 opacity-20 hover:opacity-40"
                  aria-label={gettext("close")}
                >
                  <.icon name="hero-x-mark-solid" class="w-5 h-5" />
                </button>
              </div>
              <div id={"#{@id}-content"}>
                <%= render_slot(@inner_block) %>
              </div>
            </.focus_wrap>
          </div>
        </div>
      </div>
    </div>
    """
  end

This will be available in the next Phoenix and Phoenix LiveView releases which should be out soon (currently main only).

14 Likes