Phoenix 1.7.2 released

Announcement post dup’d here for convenience:


Phoenix 1.7.2 is out! This minor release includes a couple features worth talking about. Let’s get to it!

Easier Tailwind opt-out

Phoenix 1.7 included tailwindcss by default along with a new form component-based architecture. This makes it super easy to opt-out of Tailwind in favor for another tool or custom CSS, but there were still a few manual steps. Namely, you needed to remove the tailwind.config.js file and the dev :watchers configuration. We introduced a mix phx.new --no-tailwind flag to let folks skip these steps when generating a new project.

Channel and LiveView Socket Draining

Cold deploys at high scale for stateful apps like channels or LiveView can be challenging to orchestrate. Deploys often involve custom tuning your deploy tools or proxies to roll out the release slowly to avoid overloading the new servers with a thundering herd of traffic. Phoenix 1.7.2 introduces a new channel socket drain feature, which orchestrates a batched, staged drain process on application shutdown. Draining is enabled by default and you can configure the shutdown from the socket macro in your endpoint.

From the docs:

  @doc """
  ...
  * `:drainer` - a keyword list configuring how to drain sockets
    on application shutdown. The goal is to notify all channels (and
    LiveViews) clients to reconnect. The supported options are:

    * `:batch_size` - How many clients to notify at once in a given batch.
      Defaults to 10000.
    * `:batch_interval` - The amount of time in milliseconds given for a
      batch to terminate. Defaults to 2000ms.
    * `:shutdown` - The maximum amount of time in milliseconds allowed
      to drain all batches. Defaults to 30000ms.

  For example, if you have 150k connections, the default values will
  split them into 15 batches of 10k connections. Each batch takes
  2000ms before the next batch starts. In this case, we will do everything
  right under the maximum shutdown time of 30000ms. Therefore, as
  you increase the number of connections, remember to adjust the shutdown
  accordingly. Finally, after the socket drainer runs, the lower level
  HTTP/HTTPS connection drainer will still run, and apply to all connections.
  Set it to `false` to disable draining.
  """

If you’re not operating at high scale, you don’t need to to worry about this feature, but it will be there and waiting when you need it. If you’re already operating at high scale, this feature should make deploys less stressful on your infrastructure or allow you to ditch cloud-specific configuration.

JS.exec

We introduced a new JS command for executing an existing command on the page located on another DOM element. This greatly optimizes payloads in certain cases that otherwise involved shoving the same duplicated command in the DOM multiple times. For example, the default modal in your core_components.ex module previously contained a duplicated command for closing the modal in multiple places:

  def modal(assigns) do
    ~H"""
    <div
      id={@id}
      phx-mounted={@show && show_modal(@id)}
      phx-remove={hide_modal(@id)}
    >
      <div id={"#{@id}-bg"}>
      <div>
        <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={hide_modal(@on_cancel, @id)}
              phx-key="escape"
              phx-click-away={hide_modal(@on_cancel, @id)}
    """
  end

We had the same command for phx-window-keydown and phx-click-away, and a similar command on phx-remove. These hide the modal and invoke the caller’s own command both on ESC and when clicking outside the modal, and transition the modal when the user navigates away. This produces a sizable payload when the JS commands contain things like transitions with their own classes and timing values. We can cut the payload by more than half by specifying the command only once and telling LiveView where it can execute it. The new modal looks like this:

  def modal(assigns) do
    ~H"""
    <div
      id={@id}
      phx-mounted={@show && show_modal(@id)}
      phx-remove={hide_modal(@id)}
      data-cancel={JS.exec(@on_cancel, "phx-remove")}
    >
      <div id={"#{@id}-bg"}>
      <div>
        <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("data-cancel", to: "##{@id}")}
              phx-key="escape"
              phx-click-away={JS.exec("data-cancel", to: "##{@id}")}
    """
  end

We use JS.exec to execute the data-cancel attribute where we share the hide_modal command specified in phx-remove. We have the same behavior as before, but now it’s only sent a single time, instead of three times.

Happy coding!

–Chris

118 Likes

Thank you!

2 Likes

Congrats on the new release! :clap:
The idea behind JS.exec is so clever, I love it! Is this an original invention for Phoenix or was it inspired by another framework?

1 Like

Hello sir @chrismccord, can we have a DOM remover in JS command instead of using hook or window.addEventListener? Should be easy to use?

Something like this, find the element by ID or class or count in children …

<p phx-click={JS.delete()}>hi this is test</p>
<div id="new" phx-click={JS.delete()}>hi this is test</div>
<span class="new" phx-click={JS.delete()}>hi this is test</span>

After clicking each of top lines, it deletes the element in HTML like JavaScript DOM.

Thank you for all your efforts

1 Like

@shahryarjb doing so is not really encouraged because if the content was rendered by the server, the next time the server renders, it will add the same contents back.

You can only do so if you are inside phx-update="ignore", which is also a construct you should avoid, so our recommendation is to either hide the element using the existing helpers or rely on JavaScript if you really want to remove it.

6 Likes

Chris, Jose and all! Thank you so much for the release! Exciting times!

Quick question: How far are you from implementing emptying a stream? I’m postponing my filtering & sorting until there’s a first-class support for it :smiley:

4 Likes

How does one smoothly upgrade a project from 1.7.1 to 1.7.2? How does one upgrade their tools from 1.7.1 to 1.7.2?
Thanks,
–Patrick

Check the changelog for anything that might explode on you then mix deps.update phoenix. You may have to fiddle with mix.exs if your version constraint is restrictive.

2 Likes

There’s the handy PhoenixDiff · v1.7.1 to v1.7.2 to show exactly what’s changed between two given versions of a newly generated phoenix app.

It’s also worth running mix local.phx — Phoenix v1.7.2 to update the Phoenix project generator locally, especially before creating any new apps.

5 Likes

Thanks everyone!