Phoenix 1.7.2 released
Posted on March 20th, 2023 by Chris McCord
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