Announcing Phoenix Playground

Elixir v1.12 added Mix.install/2, an ability to install and use dependencies which is especially useful in IEx sessions and single-file scripts. Ever since this capability existed, I wanted to create one particular kind of single-file scripts: single-file Phoenix LiveView apps. Phoenix Core Team member Gary Rennie created just that and it has been extremely helpful for prototyping, easily sharing Phoenix code, and even submitting Phoenix LiveView bug reports.

I kept thinking there must be an even easier way and in late 2023 I had an early prototype that removed all unnecessary boilerplate. Turns out I wasn’t alone, @lubien had a similar idea and created liveview_playground. We decided to join forces and today I’m happy to announce a new project, Phoenix Playground, the easiest way to run single-file Phoenix applications.

Demo

Create a demo_live.exs file:

Mix.install([
  {:phoenix_playground, "~> 0.1.0"}
])

defmodule DemoLive do
  use Phoenix.LiveView

  def mount(_params, _session, socket) do
    {:ok, assign(socket, count: 0)}
  end

  def render(assigns) do
    ~H"""
    <span><%= @count %></span>
    <button phx-click="inc">+</button>
    <button phx-click="dec">-</button>

    <style type="text/css">
      body { padding: 1em; }
    </style>
    """
  end

  def handle_event("inc", _params, socket) do
    {:noreply, assign(socket, count: socket.assigns.count + 1)}
  end

  def handle_event("dec", _params, socket) do
    {:noreply, assign(socket, count: socket.assigns.count - 1)}
  end
end

PhoenixPlayground.start(live: DemoLive)

and run it:

That’s it!

Phoenix Playground ships with all the basic pieces (endpoint, router, layout, etc) so you can focus on just your app. Besides Mix.install and PhoenixPlayground.start, the rest of the file is just a “vanilla” LiveView module and that is by design. It means you can drop that module into any Phoenix project and it should just work.

Phoenix Playground supports live code reloading: change the file, save it, and you’ll see updates right away, without even losing your LiveView state. It also streams real-time server logs to the browser development console out of the box too.

(State-preserving reloads require upcoming Phoenix LiveView 1.0.0-rc.1, i.e. add {:phoenix_live_view, github: "phoenixframework/phoenix_Live_view", override: true} to your Mix.install in the meantime!)

Phoenix LiveView ships with fantastic testing infrastructure. It feels like you are writing end-to-end tests with a browser but they are actually running without it and thus are blazingly fast. Phoenix Playground has built-in support for testing too:

Mix.install([
  {:phoenix_playground, "~> 0.1.0"}
])

defmodule DemoLive do
  use Phoenix.LiveView

  def mount(_params, _session, socket) do
    {:ok, assign(socket, count: 0)}
  end

  def render(assigns) do
    ~H"""
    <span>Count: <%= @count %></span>
    <button phx-click="inc">+</button>
    """
  end

  def handle_event("inc", _params, socket) do
    {:noreply, update(socket, :count, &(&1 + 1))}
  end
end

Logger.configure(level: :info)
ExUnit.start()

defmodule DemoLiveTest do
  use ExUnit.Case
  use PhoenixPlayground.Test, live: DemoLive

  test "it works" do
    {:ok, view, html} = live(build_conn(), "/")

    assert html =~ "Count: 0"
    assert render_click(view, :inc, %{}) =~ "Count: 1"
    assert render_click(view, :inc, %{}) =~ "Count: 2"
  end
end
$ elixir demo_live_test.exs
Running ExUnit with seed: 53183, max_cases: 16

.
Finished in 0.05 seconds (0.01s on load, 0.00s async, 0.04s sync)
1 test, 0 failures

Finally, Phoenix Playground also allows you to write good old controllers and even just plugs, all with code reloading out of the box.

I’ve been using Phoenix Playground to experiment with new ideas, learn new libraries, reproduce issues from our Elixir Development Subscription clients, and easily share all of this with people since it’s all encapsulated into single files. I can’t wait to hear what you will build with Phoenix Playground.

Happy hacking!