March 24, 2023 ~ 5 min read

LiveSvelte

Introducing LiveSvelte, a package that integrates Svelte with Phoenix LiveView, delivering end-to-end reactivity. Inspired by Ryan Cooke on E2E reactivity.

To see an example of what’s possible with LiveSvelte, check out the game I’ve been working on: Territoriez.io. Made with Elixir, Phoenix, LiveView, LiveSvelte and Pixi.js.

What is LiveView

LiveView is reactive server-rendered HTML. It uses websockets to update pages in real-time. You don’t have to touch any JavaScript to get this to work, the framework takes care of it.

Advantages & Features:

  • No API needed! (no REST, GraphQL, RPC, …)
  • Real time AJAX-like updates to the page with minimal code
  • Declarative
  • Fast!

What is Svelte

Svelte is a JavaScript framework, just like React and Vue, but better :). It compiles into pure JavaScript, just leaving the absolute essential JavaScript behind. On top of that is has an excellent developer experience.

Advantages & Features:

  • Conceptually easy to learn and understand
  • Scoped CSS with support for preprocessors
  • Built in store that’s very easy to use and understand
  • Great support for animations
  • Fast!

Why LiveSvelte

Where LiveView falls short is whenever you have any client side functionality. LiveView has the concept of Hooks. While it does solve some of the problems that you might encounter whenever you want client side Javascript functionality, it quickly can become cumbersome to use, and you’ll feel like you’re being transported back to the age of jQuery.

Lightweight frameworks like Alpine.js and HTMX do solve some of the concerns, and are a great solution for a lot of use cases.

But whenever you need a more comprehensive tool for managing client side state, there’s nothing out there.

This is where LiveSvelte comes in.

Web Stacks

Before we take a closer look at LiveSvelte, we need to understand where we’re coming from and what kind of problem we’re trying to solve. So let’s take a look at some different web stacks, what features they have and don’t have, and what the advantages and disadvantages are.

Classic Web Stack

A picture of what a classic web stack looks like
A picture of what a classic web stack looks like

In the classic web stack, you have your backend, which serves webpages with a templating system. Django or Ruby on Rails are examples of these.

They work great, but lack some features you expect to see in a modern web application.

They usually have some system to do ‘dumb’ components, and if you want to do in page fetching and pushing of state, you can achieve it by using something like HTMX or just Ajax.

There’s not a built in way to do local/offline state, Alpine.js can alleviate some of this.

Overall the experience is good, but it’s not great once you want more advanced functionality in the frontend.

Modern Web Stack

A picture of what a modern web stack looks like
A picture of what a modern web stack looks like

A modern web stack greatly improves the situation. You connect your backend to a frontend (like SvelteKit for example) through an API.

The downside is that you now have state you need to keep in sync. If something changes in the fontend, you update the backend through the API. If something changes in the backend ideally you update the frontend (this is usually not so easy!).

In addition, you have 2 different stacks, which is not that bad but it does increase the complexity of your setup.

Phoenix LiveView

Phoenix without LiveSvelte
Phoenix without LiveSvelte

The situation is greatly improved compared to the classic web stack, but we do lose some of the functionality of the modern web stack.

LiveView removes the need for an API. But the downside is you lose some of the JavaScript functionality you get in a modern web stack.

We do get some great new features like pub-sub and a built in Key Value store.

If only there was a way to get that functionality back, we could have our cake and eat it too.

Phoenix LiveView with LiveSvelte

Phoenix with LiveSvelte
Phoenix with LiveSvelte

We can get back to modern web stack parity, while still getting all the advantages of the Phoenix LiveView.

In addition, we get E2E reactivity with the server and Svelte, allowing you to stay in sync with with the server with minimal effort.

LiveSvelte Example

SimpleCounter.svelte javascript
<script>
  export let number
</script>

<div class="flex flex-col justify-center items-center gap-4 p-4">
  <div class="flex flex-row items-center justify-center gap-10">
    <span class="text-xl">{number}</span>
    <button class="plus" phx-click="increment">+1</button>
  </div>
</div>
live_view_example.ex elixir
defmodule ExampleWeb.LiveExample2 do
  use ExampleWeb, :live_view

  def render(assigns) do
    ~H"""
    <div>
      <div class="bg-[#eee] rounded p-2 m-2 w-[fit-content]">
        <h1 class="text-xs font-bold flex items-end justify-end">LiveView</h1>
        <div class="flex flex-col justify-center items-center gap-4 p-4">
          <div class="flex flex-row items-center justify-center gap-10">
            <span class="text-xl"><%= @number %></span>
            <button class="plus" phx-click="increment">+1</button>
          </div>
        </div>
      </div>
      <div class="bg-[#eee] rounded p-2 m-2 w-[fit-content]">
        <h1 class="text-xs font-bold flex items-end justify-end">LiveSvelte</h1>
        <LiveSvelte.render name="SimpleCounter" props={%{number: @number}} />
      </div>
    </div>
    """
  end

  def mount(_session, _params, socket) do
    {:ok, assign(socket, :number, 10)}
  end

  def handle_event("increment", _values, socket) do
    {:noreply, assign(socket, :number, socket.assigns.number + 1)}
  end
end

What’s interesting here is that we actually don’t set the number directly in the Svelte code! Usually you would do something like:

javascript
<button class="plus" on:click={() => (number += 1)}>
    +1
</button>

What we actually do is this:

javascript
<button class="plus" phx-click="increment">
    +1
</button>

The event increment gets sent to the server, and updates automatically in the frontend. This all happens automatically through the websocket.

This idea is very powerful.

You can very easily combine local state with server side state inside a Svelte component.

There are more examples of it that can be found here:

Conclusion

Overall I’m very excited about this. It feels like the best of both worlds with none of the downsides. I’m still exploring the concept and would love your feedback.

I’m also experimenting with (named) slot support, which allows you to pass the slot from inside the LiveView directly into the <slot/> within Svelte. Svelte doesn’t have good support for this though so I wouldn’t recommend using it in production at this time.