<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Pheonix Blog</title>
  <link rel="alternate" type="text/html" href="http://www.phoenixframework.org/blog"/>
  <link rel="self" type="application/atom+xml" href="http://www.phoenixframework.org/feed"/>
  <id>http://www.phoenixframework.org/feed</id>
  <updated>2025-07-30T00:00:00Z</updated>

  <entry>
    <title>Phoenix LiveView 1.1 released!</title>
    <link rel="alternate" href="http://www.phoenixframework.org/blog/phoenix-liveview-1-1-released" />
    <id>http://www.phoenixframework.org/blog/phoenix-liveview-1-1-released</id>
    <updated>2025-07-30T00:00:00Z</updated>
    <author>
      <name>Steffen Deusch</name>
    </author>
    <summary type="html"><![CDATA[LiveView 1.1 brings quality of life improvements and some big new features like Colocated Hooks and keyed comprehensions.]]></summary>
    <content type="html"><![CDATA[<p>
LiveView 1.1.0 is available now!</p>
<p>
LiveView reached the 1.0 milestone in December 2024. Since then, we’ve been hard at work building some long awaited features and improving the overall LiveView experience.</p>
<p>
To update from LiveView 1.0 to 1.1, simply follow the <a href="https://github.com/phoenixframework/phoenix_live_view/blob/v1.1/CHANGELOG.md">CHANGELOG with the upgrade steps</a> or alternatively run the Igniter upgrade task:</p>
<pre><code class="makeup bash">mix archive.install hex igniter_new
mix igniter.upgrade phoenix_live_view</code></pre>
<h2>
Colocated Hooks</h2>
<p>
At ElixirConf EU 2023, Chris briefly mentioned <a href="https://www.youtube.com/watch?v=FADQAnq0RpA&t=3341s">colocated hooks</a> to address the friction when you need to write a small JavaScript hook for your HEEx component, which would fit neatly next to your component code, but hooks required you to write that code to a whole separate file, maybe deal with JavaScript imports, etc.; While the feature did not make it into 1.0, in LiveView 1.1, you can now write:</p>
<pre><code class="makeup heex"><span class="p">&lt;</span><span class="nt">div</span><span class="w"> </span><span class="na">id</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">t</span><span class="s2">o</span><span class="s2">d</span><span class="s2">o</span><span class="s2">s</span><span class="p">&quot;</span><span class="w"> </span><span class="na">phx-update</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">stream</span><span class="p">&quot;</span><span class="w"> </span><span class="na">phx-hook</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">.Sortable</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="w">
</span><span class="n">  ...
</span><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span><span class="w">
</span><span class="p">&lt;</span><span class="nt">script</span><span class="w"> </span><span class="na">:type</span><span class="p">=</span><span class="p" data-group-id="6688160242-1">{</span><span class="nc">Phoenix.LiveView.ColocatedHook</span><span class="p" data-group-id="6688160242-1">}</span><span class="w"> </span><span class="na">name</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">.Sortable</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="o">import</span><span class="w"> </span><span class="nv">Sortable</span><span class="w"> </span><span class="o">from</span><span class="w"> </span><span class="p">&quot;</span><span class="s">@/vendor/sortable</span><span class="p">&quot;</span><span class="w">
</span><span class="w">
</span><span class="w">  </span><span class="o">export</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="p" data-group-id="6688160242-2">{</span><span class="w">
    </span><span class="n">mounted</span><span class="p" data-group-id="6688160242-ex-1">(</span><span class="p" data-group-id="6688160242-ex-1">)</span><span class="p" data-group-id="6688160242-ex-2">{</span><span class="w">
      </span><span class="n">let</span><span class="w"> </span><span class="n">group</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">this</span><span class="o">.</span><span class="n">el</span><span class="o">.</span><span class="n">dataset</span><span class="o">.</span><span class="n">group</span><span class="w">
      </span><span class="n">let</span><span class="w"> </span><span class="n">sorter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">new</span><span class="w"> </span><span class="nc">Sortable</span><span class="p" data-group-id="6688160242-ex-3">(</span><span class="n">this</span><span class="o">.</span><span class="n">el</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="6688160242-ex-4">{</span><span class="w">
        </span><span class="ss">group</span><span class="p">:</span><span class="w"> </span><span class="n">group</span><span class="w"> </span><span class="sc">? </span><span class="p" data-group-id="6688160242-ex-5">{</span><span class="ss">name</span><span class="p">:</span><span class="w"> </span><span class="n">group</span><span class="p">,</span><span class="w"> </span><span class="ss">pull</span><span class="p">:</span><span class="w"> </span><span class="no">true</span><span class="p">,</span><span class="w"> </span><span class="ss">put</span><span class="p">:</span><span class="w"> </span><span class="no">true</span><span class="p" data-group-id="6688160242-ex-5">}</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="n">undefined</span><span class="p">,</span><span class="w">
        </span><span class="ss">animation</span><span class="p">:</span><span class="w"> </span><span class="mi">150</span><span class="p">,</span><span class="w">
        </span><span class="ss">dragClass</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;drag-item&quot;</span><span class="p">,</span><span class="w">
        </span><span class="ss">ghostClass</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;drag-ghost&quot;</span><span class="p">,</span><span class="w">
        </span><span class="ss">onEnd</span><span class="p">:</span><span class="w"> </span><span class="n">e</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p" data-group-id="6688160242-ex-6">{</span><span class="w">
          </span><span class="n">let</span><span class="w"> </span><span class="n">params</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p" data-group-id="6688160242-ex-7">{</span><span class="ss">old</span><span class="p">:</span><span class="w"> </span><span class="n">e</span><span class="o">.</span><span class="n">oldIndex</span><span class="p">,</span><span class="w"> </span><span class="ss">new</span><span class="p">:</span><span class="w"> </span><span class="n">e</span><span class="o">.</span><span class="n">newIndex</span><span class="p">,</span><span class="w"> </span><span class="ss">to</span><span class="p">:</span><span class="w"> </span><span class="n">e</span><span class="o">.</span><span class="n">to</span><span class="o">.</span><span class="n">dataset</span><span class="p">,</span><span class="w"> </span><span class="n">...</span><span class="n">e</span><span class="o">.</span><span class="n">item</span><span class="o">.</span><span class="n">dataset</span><span class="p" data-group-id="6688160242-ex-7">}</span><span class="w">
          </span><span class="n">this</span><span class="o">.</span><span class="n">pushEventTo</span><span class="p" data-group-id="6688160242-ex-8">(</span><span class="n">this</span><span class="o">.</span><span class="n">el</span><span class="p">,</span><span class="w"> </span><span class="n">this</span><span class="o">.</span><span class="n">el</span><span class="o">.</span><span class="n">dataset</span><span class="p" data-group-id="6688160242-ex-9">[</span><span class="s">&quot;drop&quot;</span><span class="p" data-group-id="6688160242-ex-9">]</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="s">&quot;reposition&quot;</span><span class="p">,</span><span class="w"> </span><span class="n">params</span><span class="p" data-group-id="6688160242-ex-8">)</span><span class="w">
        </span><span class="p" data-group-id="6688160242-ex-6">}</span><span class="w">
      </span><span class="p" data-group-id="6688160242-ex-4">}</span><span class="p" data-group-id="6688160242-ex-3">)</span><span class="w">
    </span><span class="p" data-group-id="6688160242-ex-2">}</span><span class="w">
  </span><span class="p" data-group-id="6688160242-2">}</span><span class="w">
</span><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span></code></pre>
<p>
And LiveView will automatically extract the JavaScript at compile time. The only extra plumbing you need is a new import in your <code class="inline">app.js</code>:</p>
<pre><code class="makeup diff"><span class="n">  import {LiveSocket} from &quot;phoenix_live_view&quot;
</span><span class="gi">+</span><span class="gi"> import {hooks as colocatedHooks} from &quot;phoenix-colocated/my_app&quot;
</span><span class="n">  import topbar from &quot;../vendor/topbar&quot;
</span><span class="w">
</span><span class="n">  const csrfToken = document.querySelector(&quot;meta[name=&#39;csrf-token&#39;]&quot;).getAttribute(&quot;content&quot;)
</span><span class="n">  const liveSocket = new LiveSocket(&quot;/live&quot;, Socket, {
</span><span class="n">    longPollFallbackMs: 2500,
</span><span class="n">    params: {_csrf_token: csrfToken},
</span><span class="gi">+</span><span class="gi">   hooks: {...colocatedHooks},
</span><span class="n">  })</span></code></pre>
<p>
There are also two small changes needed in your esbuild configuration to tell it where to find the <code class="inline">phoenix-colocated</code> folder. We include that configuration by default for new Phoenix 1.8 apps, and we also have <a href="https://github.com/phoenixframework/phoenix_live_view/blob/v1.1/CHANGELOG.md">instructions in the changelog</a> for existing apps.</p>
<p>
If you carefully read the example code above, you might also have spotted the leading dot in the hook name <code class="inline">.Sortable</code>. This is a new feature in LiveView 1.1 to automatically prefix the hook name with the current module. To prevent name conflicts - LiveView hook names are global - when using colocated hooks where a hook is meant to belong to a specific component, LiveView enforces the hook name to be prefixed. This also allows library authors to not think about how to name their hooks. They can just call them <code class="inline">.whatever</code> and it is ensured that the name won’t conflict with any other user hooks. This feature is most useful for colocated hooks, but it can also be used with any other hook in your project.</p>
<p>
Note that if you happened to name your hooks with a leading dot before LiveView 1.1, you’ll need to adjust those hook names.</p>
<h3>
Colocated JavaScript</h3>
<p>
When I first built the colocated hooks feature, José asked “why should it only be about hooks?”. Perhaps we could colocate <em>anything</em>?</p>
<p>
Our abstraction for colocated hooks builds upon a feature we call “macro components”. For now, macro components are still a private API, since we want to think them through a little bit more, but the general idea is that you implement some callback that receives an AST representation of the HTML and can transform it. The transformation for colocated hooks is writing the text to a file and dropping it altogether from the rendered page. In a separate step, whenever code is compiled, we check the directory into which the code is extracted and generate a manifest file that contains all the necessary JavaScript imports.</p>
<p>
A macro component could also be used to <a href="https://bsky.app/profile/steffend.me/post/3lpt67tlqdc24">render markdown at compile time</a> or <a href="https://bsky.app/profile/steffend.me/post/3lqf27ft5es24">perform compile time syntax highlighting</a>. We hope to ship the API to allow this in LiveView 1.2.</p>
<p>
Colocated hooks are built on a more generic <code class="inline">Phoenix.LiveView.ColocatedJS</code> module, which contains all the code for writing JavaScript to the <code class="inline">phoenix-colocated</code> folder and generating the manifest file. This means that you can use <code class="inline">Phoenix.LiveView.ColocatedJS</code> to extract generic JavaScript code, like global event handlers, and colocate those within your components:</p>
<pre><code class="makeup elixir"><span class="kd">def</span><span class="w"> </span><span class="nf">navbar</span><span class="p" data-group-id="6825766541-1">(</span><span class="n">assigns</span><span class="p" data-group-id="6825766541-1">)</span><span class="w"> </span><span class="k" data-group-id="6825766541-2">do</span><span class="w">
  </span><span class="sx">~H&quot;&quot;&quot;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nt">nav</span><span class="p">&gt;</span><span class="w">
</span><span class="n">    ...
</span><span class="w">    </span><span class="p">&lt;</span><span class="nf">.button</span><span class="w">
</span><span class="w">      </span><span class="na">phx-click</span><span class="p">=</span><span class="p" data-group-id="8908897433-1">{</span><span class="w">
        </span><span class="nc">JS</span><span class="o">.</span><span class="n">toggle_class</span><span class="p" data-group-id="6825766541-3">(</span><span class="s">&quot;expanded&quot;</span><span class="p">,</span><span class="w"> </span><span class="ss">to</span><span class="p">:</span><span class="w"> </span><span class="p" data-group-id="6825766541-4">{</span><span class="ss">:closest</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;nav&quot;</span><span class="p" data-group-id="6825766541-4">}</span><span class="p" data-group-id="6825766541-3">)</span><span class="w">
        </span><span class="o">|&gt;</span><span class="w"> </span><span class="nc">JS</span><span class="o">.</span><span class="n">dispatch</span><span class="p" data-group-id="6825766541-5">(</span><span class="s">&quot;nav:store-expanded&quot;</span><span class="p">,</span><span class="w"> </span><span class="ss">to</span><span class="p">:</span><span class="w"> </span><span class="p" data-group-id="6825766541-6">{</span><span class="ss">:closest</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;nav&quot;</span><span class="p" data-group-id="6825766541-6">}</span><span class="p" data-group-id="6825766541-5">)</span><span class="w">
      </span><span class="p" data-group-id="8908897433-1">}</span><span class="w">
</span><span class="w">    </span><span class="p">&gt;</span><span class="w">
</span><span class="w">      </span><span class="p">&lt;</span><span class="nf">.icon</span><span class="w"> </span><span class="na">name</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">hero-bars-3</span><span class="p">&quot;</span><span class="w"> </span><span class="p">/&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;/</span><span class="nf">.button</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;/</span><span class="nt">nav</span><span class="p">&gt;</span><span class="w">
</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nt">script</span><span class="w"> </span><span class="na">:type</span><span class="p">=</span><span class="p" data-group-id="8908897433-2">{</span><span class="nc">Phoenix.LiveView.ColocatedJS</span><span class="p" data-group-id="8908897433-2">}</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="nb">window</span><span class="o">.</span><span class="nf">addEventListener</span><span class="n">(</span><span class="p">&quot;</span><span class="s">DOMContentLoaded</span><span class="p">&quot;</span><span class="n">, </span><span class="p">(</span><span class="p">)</span><span class="w"> </span><span class="kt">=&gt;</span><span class="w"> </span><span class="p" data-group-id="8908897433-3">{</span><span class="w">
      </span><span class="n">const</span><span class="w"> </span><span class="n">expanded</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">localStorage</span><span class="o">.</span><span class="n">getItem</span><span class="p" data-group-id="6825766541-7">(</span><span class="s">&quot;nav:expanded&quot;</span><span class="p" data-group-id="6825766541-7">)</span><span class="w"> </span><span class="o">===</span><span class="w"> </span><span class="s">&quot;true&quot;</span><span class="p">;</span><span class="w">
      </span><span class="n">const</span><span class="w"> </span><span class="n">nav</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">document</span><span class="o">.</span><span class="n">querySelector</span><span class="p" data-group-id="6825766541-8">(</span><span class="s">&quot;nav&quot;</span><span class="p" data-group-id="6825766541-8">)</span><span class="p">;</span><span class="w">
      </span><span class="k">if</span><span class="w"> </span><span class="p" data-group-id="6825766541-9">(</span><span class="n">expanded</span><span class="p" data-group-id="6825766541-9">)</span><span class="w"> </span><span class="p" data-group-id="6825766541-10">{</span><span class="w">
        </span><span class="n">window</span><span class="o">.</span><span class="n">liveSocket</span><span class="o">.</span><span class="n">js</span><span class="p" data-group-id="6825766541-11">(</span><span class="p" data-group-id="6825766541-11">)</span><span class="o">.</span><span class="n">addClass</span><span class="p" data-group-id="6825766541-12">(</span><span class="n">nav</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;expanded&quot;</span><span class="p" data-group-id="6825766541-12">)</span><span class="p">;</span><span class="w">
      </span><span class="p" data-group-id="6825766541-10">}</span><span class="w">
    </span><span class="p" data-group-id="8908897433-3">}</span><span class="n">)</span><span class="p">;</span><span class="w">
</span><span class="w">
</span><span class="w">    </span><span class="nb">window</span><span class="o">.</span><span class="nf">addEventListener</span><span class="n">(</span><span class="p">&quot;</span><span class="s">nav:store-expanded</span><span class="p">&quot;</span><span class="n">, </span><span class="p">(</span><span class="nv">e</span><span class="p">)</span><span class="w"> </span><span class="kt">=&gt;</span><span class="w"> </span><span class="p" data-group-id="8908897433-4">{</span><span class="w">
      </span><span class="n">const</span><span class="w"> </span><span class="n">expanded</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">localStorage</span><span class="o">.</span><span class="n">getItem</span><span class="p" data-group-id="6825766541-13">(</span><span class="s">&quot;nav:expanded&quot;</span><span class="p" data-group-id="6825766541-13">)</span><span class="w"> </span><span class="o">===</span><span class="w"> </span><span class="s">&quot;true&quot;</span><span class="p">;</span><span class="w">
      </span><span class="n">localStorage</span><span class="o">.</span><span class="n">setItem</span><span class="p" data-group-id="6825766541-14">(</span><span class="s">&quot;nav:expanded&quot;</span><span class="p">,</span><span class="w"> </span><span class="o">!</span><span class="n">expanded</span><span class="p" data-group-id="6825766541-14">)</span><span class="p">;</span><span class="w">
    </span><span class="p" data-group-id="8908897433-4">}</span><span class="n">)</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="sx">&quot;&quot;&quot;</span><span class="w">
</span><span class="k" data-group-id="6825766541-2">end</span></code></pre>
<p>
This example demonstrates a navbar that stores its expanded state in the browser’s localStorage. You could also write a hook for this, but hooks only execute when the LiveView is mounted, which would often show a noticeable delay between the first load of the page until the expanded state is restored. With colocated JS, any non-hook JavaScript code that belongs to a component can have its code directly inside the HEEx template.</p>
<p>
Colocated hooks and JS are most useful for small code snippets. While using a regular <code class="inline">&lt;script&gt;</code> tag means that syntax highlighting should work, most editors provide limited or no autocompletion.</p>
<h2>
Keyed Comprehensions</h2>
<p>
Rendering lists or other collections of items in LiveView has a potential pitfall: whenever an item in the list is changed, the whole list is re-rendered and sent over the wire.</p>
<pre><code class="makeup heex"><span class="p">&lt;</span><span class="nt">ul</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nt">li</span><span class="w"> </span><span class="na">:for</span><span class="p">=</span><span class="p" data-group-id="6257682324-1">{</span><span class="n">i</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="na">@items</span><span class="p" data-group-id="6257682324-1">}</span><span class="p">&gt;</span><span class="p" data-group-id="6257682324-2">{</span><span class="n">i</span><span class="o">.</span><span class="n">name</span><span class="p" data-group-id="6257682324-2">}</span><span class="p">&lt;/</span><span class="nt">li</span><span class="p">&gt;</span><span class="w">
</span><span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span></code></pre>
<p>
If you change the <code class="inline">@items</code> assign, all items are sent again, even if you only added, removed or modified a single one. The Phoenix generators scaffold LiveViews that use <a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#stream/4">streams</a>, which is a mechanism to deal with large collections that was introduced during LiveView v0.18. With streams, the server does not keep the items in memory, which is very useful for large collections. The example above would look like this using streams:</p>
<pre><code class="makeup heex"><span class="p">&lt;</span><span class="nt">ul</span><span class="w"> </span><span class="na">id</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">i</span><span class="s2">t</span><span class="s2">e</span><span class="s2">m</span><span class="s2">s</span><span class="p">&quot;</span><span class="w"> </span><span class="na">phx-update</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">stream</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nt">li</span><span class="w"> </span><span class="na">id</span><span class="p">=</span><span class="p" data-group-id="6595831087-1">{</span><span class="n">id</span><span class="p" data-group-id="6595831087-1">}</span><span class="w"> </span><span class="na">:for</span><span class="p">=</span><span class="p" data-group-id="6595831087-2">{</span><span class="p" data-group-id="6595831087-ex-1">{</span><span class="n">id</span><span class="p">,</span><span class="w"> </span><span class="n">i</span><span class="p" data-group-id="6595831087-ex-1">}</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="na">@streams</span><span class="o">.</span><span class="n">items</span><span class="p" data-group-id="6595831087-2">}</span><span class="p">&gt;</span><span class="p" data-group-id="6595831087-3">{</span><span class="n">i</span><span class="o">.</span><span class="n">name</span><span class="p" data-group-id="6595831087-3">}</span><span class="p">&lt;/</span><span class="nt">li</span><span class="p">&gt;</span><span class="w">
</span><span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span></code></pre>
<p>
Any change must then use stream management functions like <code class="inline">stream_insert/4</code>.</p>
<p>
Still, streams are not a one size fits all solution for collections. In some cases you don’t want to deal with the management overhead of streams, you just want to declaratively write your for comprehension and still get an optimized diff over the wire.</p>
<p>
LiveView does have another solution for this: <em>LiveComponents</em>.</p>
<p>
LiveComponents were the first component abstraction introduced back in LiveView v0.4 to “compartmentalize state, markup, and events”. Because LiveComponents have their own state, they also perform their own change tracking.</p>
<pre><code class="makeup heex"><span class="p">&lt;</span><span class="nt">ul</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nf">.live_component</span><span class="w"> </span><span class="na">id</span><span class="p">=</span><span class="p" data-group-id="1003107433-1">{</span><span class="n">i</span><span class="o">.</span><span class="n">id</span><span class="p" data-group-id="1003107433-1">}</span><span class="w"> </span><span class="na">:for</span><span class="p">=</span><span class="p" data-group-id="1003107433-2">{</span><span class="n">i</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="na">@items</span><span class="p" data-group-id="1003107433-2">}</span><span class="w"> </span><span class="na">module</span><span class="p">=</span><span class="p" data-group-id="1003107433-3">{</span><span class="nc">MyListItem</span><span class="p" data-group-id="1003107433-3">}</span><span class="w"> </span><span class="na">item</span><span class="p">=</span><span class="p" data-group-id="1003107433-4">{</span><span class="n">i</span><span class="p" data-group-id="1003107433-4">}</span><span class="w"> </span><span class="p">/&gt;</span><span class="w">
</span><span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span></code></pre>
<p>
With a LiveComponent for each list item, changing a single item in the list only sends the diff for this item (and a list of component IDs!). While this code produces small diffs, it feels a bit clunky to create a whole separate module for the LiveComponent, if all you care about is the diff and you don’t need to handle any events inside the component.</p>
<p>
Using LiveComponents to optimize the diff over the wire is <a href="https://thepugautomatic.com/2020/07/optimising-data-over-the-wire-in-phoenix-liveview/">not an unknown solution</a> to this problem, but it’s something you need to look for and also easy to forget until you’re tasked with optimizing message sizes. Last year, <a href="https://elixirforum.com/t/possible-payload-size-improvement-to-heex-list-comprehensions/64986">a proposal</a> was sent in the Elixir forum about introducing a <code class="inline">:key</code> attribute which would allow LiveView to optimize diffing in regular <code class="inline">:for</code> comprehensions:</p>
<pre><code class="makeup heex"><span class="p">&lt;</span><span class="nt">ul</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nt">li</span><span class="w"> </span><span class="na">:for</span><span class="p">=</span><span class="p" data-group-id="5075336463-1">{</span><span class="n">i</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="na">@items</span><span class="p" data-group-id="5075336463-1">}</span><span class="w"> </span><span class="na">:key</span><span class="p">=</span><span class="p" data-group-id="5075336463-2">{</span><span class="n">i</span><span class="o">.</span><span class="n">id</span><span class="p" data-group-id="5075336463-2">}</span><span class="p">&gt;</span><span class="p" data-group-id="5075336463-3">{</span><span class="n">i</span><span class="o">.</span><span class="n">name</span><span class="p" data-group-id="5075336463-3">}</span><span class="p">&lt;/</span><span class="nt">li</span><span class="p">&gt;</span><span class="w">
</span><span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span></code></pre>
<p>
Initially, we weren’t sure how to tackle it, but after starting at Dashbit and having a couple discussions with the team, it turned out that LiveComponents could actually be the solution. In the first release candidate of LiveView v1.1, we officially introduced the <code class="inline">:key</code> attribute! Under the hood, each list item was still rendered by a LiveComponent, but we introduced some special handling to the variables introduced by <code class="inline">:for</code> to allow them to be correctly change tracked.</p>
<p>
There were a couple of problems with this though. Because there was still a LiveComponent under the hood, <code class="inline">:key</code> only worked on regular HTML tags, not components. LiveComponents are patched separately by morphdom and require a single root element for the patch to be applied to. Since components can render multiple root elements, we cannot guarantee they meet this requirement.
Furthermore, there’s still a comprehension involved, sending the list of all component IDs, even when only one actually changed. For small templates, the extra component IDs and slightly different diff structure for LiveComponents could actually lead to more overhead. There was an even bigger problem though: when rendering nested comprehensions, LiveView usually extracts all the static parts of the template into a special “template” section of the diff, only sending it once. With LiveComponents, this sharing breaks. If you then combine keyed and non-keyed comprehensions, you’d get an excessively large diff as LiveView would try to calculate the templates for each LiveComponent separately, sending lots of duplicate strings over the wire. We were not happy with those results, so we decided to start from scratch and completely rewrite how LiveView handles comprehensions.</p>
<p>
LiveView 1.1 performs change tracking in comprehensions by default. It does not matter if you write <code class="inline">&lt;%= for i &lt;- @items do %&gt;...&lt;% end %&gt;</code> or <code class="inline">:for</code>, when no key is given LiveView automatically uses an element’s index as the key. This already improves diffs a lot for cases where few entries in a list change. Using the index can lead to suboptimal situations though: if you prepend an item to the list, most of the time all subsequent items will be considered changed. To help LiveView detect this, a <code class="inline">:key</code> can be provided to optimize the diff even further.</p>
<p>
In the past, using slots in HEEx could lead to unexpectly large diffs, because LiveView uses a comprehension under the hood to iterate over each slot entry when rendering slots:</p>
<pre><code class="makeup heex"><span class="p">&lt;</span><span class="nf">.my_list</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="ss">:list_item</span><span class="w"> </span><span class="na">label</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">Name</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nf">.some_component</span><span class="p">&gt;</span><span class="n">The name</span><span class="p">&lt;/</span><span class="nf">.some_component</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;/</span><span class="ss">:list_item</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="ss">:list_item</span><span class="w"> </span><span class="na">label</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">Price</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="p" data-group-id="8322923677-1">{</span><span class="na">@price</span><span class="p" data-group-id="8322923677-1">}</span><span class="p">&lt;/</span><span class="ss">:list_item</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="ss">:list_item</span><span class="w"> </span><span class="na">label</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">Actions</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="n">Something to do</span><span class="p">&lt;/</span><span class="ss">:list_item</span><span class="p">&gt;</span><span class="w">
</span><span class="n">  Some more text.
</span><span class="p">&lt;/</span><span class="nf">.my_list</span><span class="p">&gt;</span></code></pre>
<p>
Even though there’s no <code class="inline">:for</code> in sight, a change to <code class="inline">@price</code> would also send the name and actions columns over the wire. This is solved now with the new comprehension handling.</p>
<p>
There is one remaining case that we cannot optimize yet: <code class="inline">:for</code> on slots.</p>
<pre><code class="makeup heex"><span class="p">&lt;</span><span class="nf">.my_list</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="ss">:list_item</span><span class="w"> </span><span class="na">:for</span><span class="p">=</span><span class="p" data-group-id="2318768595-1">{</span><span class="n">item</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="na">@items</span><span class="p" data-group-id="2318768595-1">}</span><span class="w"> </span><span class="na">label</span><span class="p">=</span><span class="p" data-group-id="2318768595-2">{</span><span class="n">item</span><span class="o">.</span><span class="n">label</span><span class="p" data-group-id="2318768595-2">}</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p" data-group-id="2318768595-3">{</span><span class="n">item</span><span class="o">.</span><span class="n">text</span><span class="p" data-group-id="2318768595-3">}</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;/</span><span class="ss">:list_item</span><span class="p">&gt;</span><span class="w">
</span><span class="p">&lt;/</span><span class="nf">.my_list</span><span class="p">&gt;</span></code></pre>
<p>
If you have a template like this, because of the way slot rendering works at the moment, this cannot use keyed comprehensions for the slots. Furthermore, while you can use <code class="inline">:key</code> on components when using <code class="inline">:for</code>, you cannot use <code class="inline">:key</code> on slots like <code class="inline">&lt;:list_item&gt;</code>. Change tracking for anything inside of the <code class="inline">&lt;:list_item&gt;</code> is also not optimized in this case and will behave the same as in previous versions of LiveView.</p>
<h2>
Types for public interfaces</h2>
<p>
LiveView tries to enable writing rich user interfaces without the need to write large amounts of JavaScript code. Still, sometimes you need more control over what happens on the client - that’s why we have hooks - and every LiveView application ships with an <code class="inline">app.js</code> JavaScript file configuring LiveView’s <code class="inline">LiveSocket</code>. Most editors have great autocompletion support when working in JavaScript and to make interacting with LiveView’s JavaScript interfaces more pleasing, LiveView 1.1 now ships with type declarations for all of its public JavaScript API.</p>
<video src="/images/blog/lv-1.1/types.mp4" autoplay="autoplay" loop="loop" controls="controls" aria-label="Demonstration of LiveView 1.1 JavaScript type declarations">
</video>
<p>
Because of the way Phoenix configures <a href="https://hexdocs.pm/phoenix/asset_management.html">esbuild</a> by default, you may need to hint your editor to look for types in the <code class="inline">deps</code> folder as well by creating an <code class="inline">assets/tsconfig.json</code> with the following content:</p>
<pre><code class="makeup json"><span class="p">{</span><span class="w">
</span><span class="w">  </span><span class="p">&quot;</span><span class="s2">compilerOptions</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="w">    </span><span class="p">&quot;</span><span class="s2">baseUrl</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">.</span><span class="p">&quot;</span><span class="p">,</span><span class="w">
</span><span class="w">    </span><span class="p">&quot;</span><span class="s2">paths</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="w">      </span><span class="p">&quot;</span><span class="s2">*</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="p">&quot;</span><span class="s2">node_modules/*</span><span class="p">&quot;</span><span class="p">,</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">../deps/*</span><span class="p">&quot;</span><span class="p">]</span><span class="w">
</span><span class="w">    </span><span class="p">}</span><span class="p">,</span><span class="w">
</span><span class="w">    </span><span class="p">&quot;</span><span class="s2">allowJs</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span><span class="w">    </span><span class="p">&quot;</span><span class="s2">noEmit</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span><span class="w">  </span><span class="p">}</span><span class="p">,</span><span class="w">
</span><span class="w">  </span><span class="p">&quot;</span><span class="s2">include</span><span class="p">&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="p">&quot;</span><span class="s2">js/**/*</span><span class="p">&quot;</span><span class="p">]</span><span class="w">
</span><span class="p">}</span></code></pre>
<h2>
Portals</h2>
<p>
LiveView tracks each event sent to the server <a href="https://dashbit.co/blog/web-apps-have-client-and-server-state">with clocks</a> to prevent outdated server updates from rendering on the client. This required updates to <a href="https://github.com/patrick-steele-idem/morphdom">morphdom</a> - the library LiveView uses to efficiently update the DOM - to allow LiveView to apply updates to a cloned DOM tree instead of the real one while it is locked.</p>
<p>
Could we also use this to build a “teleportation” feature like <a href="https://vuejs.org/guide/built-ins/teleport"><code class="inline">&lt;Teleport&gt;</code> in Vue.js</a> or <a href="https://react.dev/reference/react-dom/createPortal"><code class="inline">createPortal</code> in React</a> by telling morphdom that instead of applying a patch to the actual element, it should instead apply it to some other element in the DOM? And it turns out that, yes, we can make it work!</p>
<p>
First, I wanted to introduce a new binding called <code class="inline">phx-portal</code> with the idea that you just annotate an element with that binding and tell LiveView where to teleport it to:</p>
<pre><code class="makeup html"><span class="p">&lt;</span><span class="nt">div</span><span class="w"> </span><span class="na">id</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">f</span><span class="s2">o</span><span class="s2">o</span><span class="p">&quot;</span><span class="w"> </span><span class="na">phx-portal</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">bar</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="w">
</span><span class="n">  ...
</span><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span></code></pre>
<p>
This had one big problem though: if you try to teleport an element because it would otherwise be invalid, for example a <code class="inline">&lt;form&gt;</code> inside another form, browsers would fix the HTML before LiveView had a chance to do the teleportation. The solution to this is to use <code class="inline">&lt;template&gt;</code> elements. Moreover, using a template also prevents the non-teleported elements from being seen in the initial disconnected render. So the second iteration of portals looked like this:</p>
<pre><code class="makeup html"><span class="p">&lt;</span><span class="nt">template</span><span class="w"> </span><span class="na">id</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">f</span><span class="s2">o</span><span class="s2">o</span><span class="p">&quot;</span><span class="w"> </span><span class="na">phx-portal</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">bar</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nt">div</span><span class="w"> </span><span class="na">id</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">f</span><span class="s2">o</span><span class="s2">o</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="n">...</span><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span><span class="w">
</span><span class="p">&lt;/</span><span class="nt">template</span><span class="p">&gt;</span></code></pre>
<p>
Because of the way template elements work and to ensure that morphdom can correctly apply updates, the template element itself must contain a single HTML element with an <code class="inline">ID</code> attribute. Writing this by hand feel awkward though - why should users even know about template elements in the first place? At the time, I let the PR sit for a while, because I somehow didn’t see the rather obvious solution: function components.</p>
<p>
We just need to define a function component that accepts an ID, a target and its block content. So the final portal code looks like this:</p>
<pre><code class="makeup heex"><span class="p">&lt;</span><span class="nf">.portal</span><span class="w"> </span><span class="na">id</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">f</span><span class="s2">o</span><span class="s2">o</span><span class="p">&quot;</span><span class="w"> </span><span class="na">target</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">#bar</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="w">
</span><span class="n">  ...
</span><span class="p">&lt;/</span><span class="nf">.portal</span><span class="p">&gt;</span></code></pre>
<p>
Now, you might ask: when would I need a portal in the first place?</p>
<p>
Portals are useful whenever you need to render something that should not be constrained by its surrounding context. If you try to render a simple tooltip which happens to be inside an element with <code class="inline">overflow: hidden</code>, you can quickly run into situations where content gets unexpectedly cut off. There are modern solutions to this with the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Popover_API">Popover API</a> or things like the native <code class="inline">&lt;dialog&gt;</code> element, which we do recommend to try out before resorting to <code class="inline">&lt;.portal&gt;</code>, but if you cannot use those for whatever reason, LiveView now ships with a solution.</p>
<p>
For a smooth teleportation experience, we also adjusted the internal LiveView event handling code to ensure any events from inside teleported content are properly handled by the correct LiveView. You can even teleport LiveComponents and nested LiveViews through a <code class="inline">&lt;.portal&gt;</code> and everything should work as expected.</p>
<h2>
Moving from Floki to LazyHTML</h2>
<p>
Earlier this year, Dashbit released <a href="https://github.com/dashbitco/lazy_html">lazy_html</a>, a small library based on <a href="https://github.com/lexbor/lexbor">lexbor</a>, which strives to efficiently create output that “should match that of modern browsers, meeting industry specifications”. This means that LiveViewTest now supports modern CSS selectors like <code class="inline">:is()</code> and <code class="inline">:has()</code>.</p>
<p>
This should be mostly backwards compatible with existing tests. The only case where this will affect your tests is if you used Floki specific CSS selectors (<code class="inline">fl-contains</code>, <code class="inline">fl-icontains</code>) and passed those the <a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveViewTest.html#element/3"><code class="inline">element/3</code></a> function. Phoenix versions prior to v1.8 generated such a selector when using <code class="inline">mix phx.gen.auth</code>. Changing those tests to use the <code class="inline">text_filter</code> option included since LiveView v0.12 should be enough to make your tests pass again:</p>
<pre><code class="makeup diff"><span class="n"> {:ok, _login_live, login_html} =
</span><span class="n">   lv
</span><span class="gd">-</span><span class="gd">  |&gt; element(~s|main a:fl-contains(&quot;Sign up&quot;)|)
</span><span class="gi">+</span><span class="gi">  |&gt; element(&quot;main a&quot;, &quot;Sign up&quot;)
</span><span class="n">   |&gt; render_click()
</span><span class="n">   |&gt; follow_redirect(conn, ~p&quot;/users/register&quot;)</span></code></pre>
<p>
This changes only affects how LiveView parses your pages during test. If you use any other library in your own tests, you are not required to change them when you update to LiveView v1.1 (although users did report better performance with LazyHTML).</p>
<h2>
Closing thoughts</h2>
<p>
So, that’s LiveView 1.1! Please also have a look at <a href="https://github.com/phoenixframework/phoenix_live_view/blob/v1.1/CHANGELOG.md">the changelog</a>, which contains mostly the same content as this blog post, but also includes a quick update guide and some notes about smaller improvements, such as <a href="https://github.com/phoenixframework/phoenix_live_view/blob/v1.1/CHANGELOG.md#jsignore_attributes"><code class="inline">JS.ignore_attributes</code></a> and <a href="https://github.com/phoenixframework/phoenix_live_view/blob/v1.1/CHANGELOG.md#slot-and-line-annotations">improved HEEx slot and line annotations</a>.</p>
<p>
If you have any feedback, please don’t hesitate to post <a href="https://elixirforum.com/c/phoenix-forum/20">a thread in the Elixir forum</a> or <a href="https://bsky.app/profile/steffend.me">contact me on BlueSky</a>. In case you come across any regressions or new bugs in LiveView 1.1, please <a href="https://github.com/phoenixframework/phoenix_live_view/issues">open up an issue in the repo</a>.</p>
<p>
A massive thank you to <a href="https://dashbit.co/">Dashbit</a> for sponsoring this work and José Valim for his help implementing and reviewing countless pull requests!</p>
<p>
Happy coding!</p>
<p>
-Steffen</p>
]]></content>
  </entry>

  <entry>
    <title>Phoenix 1.8.0-rc released!</title>
    <link rel="alternate" href="http://www.phoenixframework.org/blog/phoenix-1-8-released" />
    <id>http://www.phoenixframework.org/blog/phoenix-1-8-released</id>
    <updated>2025-03-30T00:00:00Z</updated>
    <author>
      <name>Chris McCord</name>
    </author>
    <summary type="html"><![CDATA[The release candidate of Phoenix 1.8 is here!]]></summary>
    <content type="html"><![CDATA[<p>
The first release candidate of Phoenix 1.8 is out with some big quality-of-life improvements! We’ve focused on making the getting started experience smoother, tightened up our code generators, and introduced scopes for secure data access that scales with your codebase. On the UX side, we added long-requested dark mode support — plus a few extra perks along the way. And <code class="inline">phx.gen.auth</code> now ships with magic link support out of the box for a better login and registration experience.</p>
<p>
<em>Note</em>: This release requires Erlang/OTP 25+.</p>
<h3>
Extensible Tailwind Theming and Components with daisyUI</h3>
<p>
Phoenix 1.8 extends our built-in tailwindcss support with <a href="https://daisyui.com/">daisyUI</a>, adding a flexible component and theming system. As a tailwind veteran, I appreciate how daisyUI is <em>just tailwind</em> with components you drop into existing workflows, while also making it simpler to apply consistent styling across your app when desired. And if you’re new or simply want to adjust the overall look and feel, daisyUI’s theming lets you make broad changes with just a few config tweaks. No fiddly markup rewrites required. Check out <a href="https://daisyui.com/theme-generator/">daisyUI’s theme generator</a> to see what’s possible.</p>
<blockquote>
  <p>
<em>Note</em>: the <code class="inline">phx.gen.*</code> Generators do not depend on daisyUI, and because it is a tailwind plugin, it is easy to remove and leaves no additional footprints  </p>
</blockquote>
<p>
All <code class="inline">phx.new</code> apps now ship with light and dark themes out of the box, with a toggle built into the layout. Here’s the landing page and a <code class="inline">phx.gen.live</code> form:</p>
<video src="/images/blog/darklight.mp4" autoplay="autoplay" loop="loop" controls="controls">
</video>
<p>
Forms, rounding, shadows, typography, and more can all be adjusted with simple config changes to your <code class="inline">app.css</code>. This is all possible thanks to daisyUI. We also ship with the latest and greatest from the recent tailwind v4 release.</p>
<h3>
Magic Link / Passwordless Authentication by default</h3>
<p>
The <code class="inline">phx.gen.auth</code> generator now uses magic links by default for login and registration.</p>
<blockquote>
  <p>
If you’re a password enthusiast, don’t fret — standard email/pass auth remains opt-in via user settings.  </p>
</blockquote>
<p>
Magic links offer a user-friendly and secure alternative to regular passwords. They’ve grown in popularity for good reason:</p>
<ul>
  <li>
No passwords to remember – fewer failed logins or locked accounts  </li>
  <li>
Faster onboarding, especially from mobile devices  </li>
</ul>
<p>
We also include a <code class="inline">require_sudo_mode</code> plug, which can be used for pages that contain sensitive operations and enforces recent authentication.</p>
<p>
Our generators handle all the security details for you, and with the Dev Preview Mailbox, your dev workflow stays hassle free. Thanks to our integration with <a href="https://hex.pm/packages/swoosh">Swoosh</a>, your email-backed auth system is ready to go the moment you’re ready to ship to prod.</p>
<p>
Let’s see it in action:</p>
<video src="/images/blog/magicauth.mp4" autoplay="autoplay" loop="loop" controls="controls">
</video>
<h3>
Scopes for data access and authorization patterns that grow with you</h3>
<p>
Scopes are a new first-class pattern in Phoenix, designed to make secure data access the <em>default</em>, not something you remember (or forget) to do later. Reminder that broken access control is <a href="https://owasp.org/Top10/A01_2021-Broken_Access_Control/"><em>the most common OWASP vulnerability</em></a>.</p>
<p>
Scopes also help your interfaces grow with your application needs. Think about it as a container that holds information that is required in the huge majority of pages in your app. It can also hold important request metadata, such as IP addresses for rate limiting or audit tracking.</p>
<p>
 Generators like <code class="inline">phx.gen.live</code>, <code class="inline">phx.gen.html</code>, and <code class="inline">phx.gen.json</code> now use the current scope for generated code. From the original request, the tasks automatically thread the current scope through all the context functions like <code class="inline">list_posts(scope)</code> or <code class="inline">get_post!(scope, id)</code>, ensuring your application stays locked down by default. This gives you scoped data access (queries <em>and</em> PubSub!), automatic filtering by user or organization, and proper foreign key fields in migrations – all out of the box.</p>
<p>
And scopes are <em>simple</em>. It’s just a plain struct in your app that your app wholly owns. The moment you run <code class="inline">phx.gen.auth</code>, you gain a new <code class="inline">%MyApp.Accounts.Scope{}</code> data structure that centralizes the information for the current request or session — like the current user, their organization, or anything else your app needs to know to securely load and manipulate data, or interact with the system.</p>
<p>
Scopes also scale with your codebase. You can define multiple scopes, augment existing ones, such as adding an <code class="inline">:organization</code> field to your user scope, or even configure how scope values appear in URLs for user-friendly slugs.</p>
<p>
Scopes aren’t just a security feature. They provide a foundation for building multi-tenant, team-based, or session-isolated apps, slot cleanly into the router and LiveView lifecycle, and stay useful even outside the context of a request. Think role verification, or programmatic access patterns where you need to know if a call came from the system or an end-user.</p>
<p>
Be sure to check out the <a href="https://hexdocs.pm/phoenix/1.8.0-rc.0/scopes.html">full scopes guide</a> for in depth instructions on using scopes in your own applications, but let’s walk through a quick example from the guide.</p>
<h4>
Integration of scopes in the Phoenix generators</h4>
<p>
If a default scope is defined in your application’s config, the generators will generate scoped resources by default. The generated LiveViews / Controllers will automatically pass the scope to the context functions. <code class="inline">mix phx.gen.auth</code> automatically sets its scope as default, if there is not already a default scope defined:</p>
<pre><code class="makeup elixir"><span class="c1"># config/config.exs</span><span class="w">
</span><span class="n">config</span><span class="w"> </span><span class="ss">:my_app</span><span class="p">,</span><span class="w"> </span><span class="ss">:scopes</span><span class="p">,</span><span class="w">
  </span><span class="ss">user</span><span class="p">:</span><span class="w"> </span><span class="p" data-group-id="1780110881-1">[</span><span class="w">
    </span><span class="ss">default</span><span class="p">:</span><span class="w"> </span><span class="no">true</span><span class="p">,</span><span class="w">
    </span><span class="n">...</span><span class="w">
  </span><span class="p" data-group-id="1780110881-1">]</span></code></pre>
<p>
Let’s look at the code generated once a default scope is set:</p>
<pre><code class="makeup console">$ mix phx.gen.live Blog Post posts title:string body:text</code></pre>
<p>
This creates a new <code class="inline">Blog</code> context, with a <code class="inline">Post</code> resource. To ensure the scope is available, for LiveViews the routes in your <code class="inline">router.ex</code> must be added to a <code class="inline">live_session</code> that ensures the user is authenticated:</p>
<pre><code class="makeup diff"><span class="n">   scope &quot;/&quot;, MyAppWeb do
</span><span class="n">     pipe_through [:browser, :require_authenticated_user]
</span><span class="w">
</span><span class="n">     live_session :require_authenticated_user,
</span><span class="n">       on_mount: [{MyAppWeb.UserAuth, :ensure_authenticated}] do
</span><span class="n">       live &quot;/users/settings&quot;, UserLive.Settings, :edit
</span><span class="n">       live &quot;/users/settings/confirm-email/:token&quot;, UserLive.Settings, :confirm_email
</span><span class="w">
</span><span class="gi">+</span><span class="gi">      live &quot;/posts&quot;, PostLive.Index, :index
</span><span class="gi">+</span><span class="gi">      live &quot;/posts/new&quot;, PostLive.Form, :new
</span><span class="gi">+</span><span class="gi">      live &quot;/posts/:id&quot;, PostLive.Show, :show
</span><span class="gi">+</span><span class="gi">      live &quot;/posts/:id/edit&quot;, PostLive.Form, :edit
</span><span class="n">     end
</span><span class="w">
</span><span class="n">     post &quot;/users/update-password&quot;, UserSessionController, :update_password
</span><span class="n">   end</span></code></pre>
<p>
Now, let’s look at the generated LiveView (<code class="inline">lib/my_app_web/live/post_live/index.ex</code>):</p>
<pre><code class="makeup elixir"><span class="kd">defmodule</span><span class="w"> </span><span class="nc">MyAppWeb.PostLive.Index</span><span class="w"> </span><span class="k" data-group-id="7228350796-1">do</span><span class="w">
  </span><span class="kn">use</span><span class="w"> </span><span class="nc">MyAppWeb</span><span class="p">,</span><span class="w"> </span><span class="ss">:live_view</span><span class="w">

  </span><span class="kn">alias</span><span class="w"> </span><span class="nc">MyApp.Blog</span><span class="w">

  </span><span class="n">...</span><span class="w">

  </span><span class="na">@impl</span><span class="w"> </span><span class="no">true</span><span class="w">
  </span><span class="kd">def</span><span class="w"> </span><span class="nf">mount</span><span class="p" data-group-id="7228350796-2">(</span><span class="c">_params</span><span class="p">,</span><span class="w"> </span><span class="c">_session</span><span class="p">,</span><span class="w"> </span><span class="n">socket</span><span class="p" data-group-id="7228350796-2">)</span><span class="w"> </span><span class="k" data-group-id="7228350796-3">do</span><span class="w">
    </span><span class="nc">Blog</span><span class="o">.</span><span class="n">subscribe_posts</span><span class="p" data-group-id="7228350796-4">(</span><span class="n">socket</span><span class="o">.</span><span class="n">assigns</span><span class="o">.</span><span class="n">current_scope</span><span class="p" data-group-id="7228350796-4">)</span><span class="w">

    </span><span class="p" data-group-id="7228350796-5">{</span><span class="ss">:ok</span><span class="p">,</span><span class="w">
     </span><span class="n">socket</span><span class="w">
     </span><span class="o">|&gt;</span><span class="w"> </span><span class="n">assign</span><span class="p" data-group-id="7228350796-6">(</span><span class="ss">:page_title</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;Listing Posts&quot;</span><span class="p" data-group-id="7228350796-6">)</span><span class="w">
     </span><span class="o">|&gt;</span><span class="w"> </span><span class="n">stream</span><span class="p" data-group-id="7228350796-7">(</span><span class="ss">:posts</span><span class="p">,</span><span class="w"> </span><span class="nc">Blog</span><span class="o">.</span><span class="n">list_posts</span><span class="p" data-group-id="7228350796-8">(</span><span class="n">socket</span><span class="o">.</span><span class="n">assigns</span><span class="o">.</span><span class="n">current_scope</span><span class="p" data-group-id="7228350796-8">)</span><span class="p" data-group-id="7228350796-7">)</span><span class="p" data-group-id="7228350796-5">}</span><span class="w">
  </span><span class="k" data-group-id="7228350796-3">end</span><span class="w">

  </span><span class="na">@impl</span><span class="w"> </span><span class="no">true</span><span class="w">
  </span><span class="kd">def</span><span class="w"> </span><span class="nf">handle_event</span><span class="p" data-group-id="7228350796-9">(</span><span class="s">&quot;delete&quot;</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="7228350796-10">%{</span><span class="s">&quot;id&quot;</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="n">id</span><span class="p" data-group-id="7228350796-10">}</span><span class="p">,</span><span class="w"> </span><span class="n">socket</span><span class="p" data-group-id="7228350796-9">)</span><span class="w"> </span><span class="k" data-group-id="7228350796-11">do</span><span class="w">
    </span><span class="n">post</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nc">Blog</span><span class="o">.</span><span class="n">get_post!</span><span class="p" data-group-id="7228350796-12">(</span><span class="n">socket</span><span class="o">.</span><span class="n">assigns</span><span class="o">.</span><span class="n">current_scope</span><span class="p">,</span><span class="w"> </span><span class="n">id</span><span class="p" data-group-id="7228350796-12">)</span><span class="w">
    </span><span class="p" data-group-id="7228350796-13">{</span><span class="ss">:ok</span><span class="p">,</span><span class="w"> </span><span class="bp">_</span><span class="p" data-group-id="7228350796-13">}</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nc">Blog</span><span class="o">.</span><span class="n">delete_post</span><span class="p" data-group-id="7228350796-14">(</span><span class="n">socket</span><span class="o">.</span><span class="n">assigns</span><span class="o">.</span><span class="n">current_scope</span><span class="p">,</span><span class="w"> </span><span class="n">post</span><span class="p" data-group-id="7228350796-14">)</span><span class="w">

    </span><span class="p" data-group-id="7228350796-15">{</span><span class="ss">:noreply</span><span class="p">,</span><span class="w"> </span><span class="n">stream_delete</span><span class="p" data-group-id="7228350796-16">(</span><span class="n">socket</span><span class="p">,</span><span class="w"> </span><span class="ss">:posts</span><span class="p">,</span><span class="w"> </span><span class="n">post</span><span class="p" data-group-id="7228350796-16">)</span><span class="p" data-group-id="7228350796-15">}</span><span class="w">
  </span><span class="k" data-group-id="7228350796-11">end</span><span class="w">

  </span><span class="na">@impl</span><span class="w"> </span><span class="no">true</span><span class="w">
  </span><span class="kd">def</span><span class="w"> </span><span class="nf">handle_info</span><span class="p" data-group-id="7228350796-17">(</span><span class="p" data-group-id="7228350796-18">{</span><span class="n">type</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="7228350796-19">%</span><span class="nc" data-group-id="7228350796-19">MyApp.Blog.Post</span><span class="p" data-group-id="7228350796-19">{</span><span class="p" data-group-id="7228350796-19">}</span><span class="p" data-group-id="7228350796-18">}</span><span class="p">,</span><span class="w"> </span><span class="n">socket</span><span class="p" data-group-id="7228350796-17">)</span><span class="w">
      </span><span class="ow">when</span><span class="w"> </span><span class="n">type</span><span class="w"> </span><span class="ow">in</span><span class="w"> </span><span class="p" data-group-id="7228350796-20">[</span><span class="ss">:created</span><span class="p">,</span><span class="w"> </span><span class="ss">:updated</span><span class="p">,</span><span class="w"> </span><span class="ss">:deleted</span><span class="p" data-group-id="7228350796-20">]</span><span class="w"> </span><span class="k" data-group-id="7228350796-21">do</span><span class="w">
    </span><span class="p" data-group-id="7228350796-22">{</span><span class="ss">:noreply</span><span class="p">,</span><span class="w"> </span><span class="n">stream</span><span class="p" data-group-id="7228350796-23">(</span><span class="n">socket</span><span class="p">,</span><span class="w"> </span><span class="ss">:posts</span><span class="p">,</span><span class="w"> </span><span class="nc">Blog</span><span class="o">.</span><span class="n">list_posts</span><span class="p" data-group-id="7228350796-24">(</span><span class="n">socket</span><span class="o">.</span><span class="n">assigns</span><span class="o">.</span><span class="n">current_scope</span><span class="p" data-group-id="7228350796-24">)</span><span class="p">,</span><span class="w"> </span><span class="ss">reset</span><span class="p">:</span><span class="w"> </span><span class="no">true</span><span class="p" data-group-id="7228350796-23">)</span><span class="p" data-group-id="7228350796-22">}</span><span class="w">
  </span><span class="k" data-group-id="7228350796-21">end</span><span class="w">
</span><span class="k" data-group-id="7228350796-1">end</span></code></pre>
<p>
Note that every function from the <code class="inline">Blog</code> context that we call gets the <code class="inline">current_scope</code> assign passed in as the first argument. The <code class="inline">list_posts/1</code> function then uses that information to properly filter posts:</p>
<pre><code class="makeup elixir"><span class="c1"># lib/my_app/blog.ex</span><span class="w">
</span><span class="kd">def</span><span class="w"> </span><span class="nf">list_posts</span><span class="p" data-group-id="8239281965-1">(</span><span class="p" data-group-id="8239281965-2">%</span><span class="nc" data-group-id="8239281965-2">Scope</span><span class="p" data-group-id="8239281965-2">{</span><span class="p" data-group-id="8239281965-2">}</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">scope</span><span class="p" data-group-id="8239281965-1">)</span><span class="w"> </span><span class="k" data-group-id="8239281965-3">do</span><span class="w">
  </span><span class="nc">Repo</span><span class="o">.</span><span class="n">all</span><span class="p" data-group-id="8239281965-4">(</span><span class="n">from</span><span class="w"> </span><span class="n">post</span><span class="w"> </span><span class="ow">in</span><span class="w"> </span><span class="nc">Post</span><span class="p">,</span><span class="w"> </span><span class="ss">where</span><span class="p">:</span><span class="w"> </span><span class="n">post</span><span class="o">.</span><span class="n">user_id</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="o">^</span><span class="n">scope</span><span class="o">.</span><span class="n">user</span><span class="o">.</span><span class="n">id</span><span class="p" data-group-id="8239281965-4">)</span><span class="w">
</span><span class="k" data-group-id="8239281965-3">end</span></code></pre>
<p>
The LiveView even subscribes to scoped PubSub messages and automatically updates the rendered list whenever a new post is created or an existing post is updated or deleted, while ensuring that only messages for the current scope are processed.</p>
<h3>
Streamlined Onboarding</h3>
<p>
Phoenix v1.7 introduced a unified developer experience for building both old-fashioned HTML apps and realtime interactive apps with LiveView. This made LiveView and HEEx function components front and center as the new building blocks for modern Phoenix applications.</p>
<p>
As part of this effort, we introduced a <code class="inline">core_components.ex</code> file that provides declarative components to use throughout your app. Those components were the back-bone of the <code class="inline">phx.gen.html</code> code generator as well as the new <code class="inline">phx.gen.live</code> task. Phoenix developers would then evolve those components over time, as needed by their applications.</p>
<p>
While these additions were useful to showcase what you can achieve with Phoenix LiveView, they would often get in the way of experienced developers, by generating too much opinionated code. At the same time, they could also overwhelm someone who was just starting with Phoenix.</p>
<p>
Now, after receiving feedback from new and senior developers alike, and with Phoenix LiveView 1.0 officially out, we chose to simplify several of our code generators. Our goal is to give seasoned folks a better foundation to build the real features they want to ship, while giving newcomers simplified code which will help them get up to speed on the basics more quickly:</p>
<p>
The <code class="inline">mix phx.gen.live</code> generator now follows its sibling, <code class="inline">mix phx.gen.html</code>, to provide a straight-forward CRUD interface you can build on top of, while showcasing the main LiveView concepts. In turn, this allows us to trim down the core components to only the main building blocks.</p>
<p>
Even the <code class="inline">mix phx.gen.auth</code> generator, which received the magic link support and sudo mode mentioned above, does so in fewer files, fewer functions, and fewer lines of code.</p>
<p>
The <a href="https://hexdocs.pm/phoenix/1.8.0-rc.0/contexts.html">context guide</a> has been broken apart into a few separate guides that now better explores data modeling, using ecommerce to drive the examples.</p>
<p>
We also have new guides for authentication and authorization, which combined with scopes, gives you a solid starting point to designing secure and maintainable projects.</p>
<h3>
Simplified Layouts</h3>
<p>
We have also revised Phoenix nested layouts, <code class="inline">root.html.heex</code> and <code class="inline">app.html.heex</code>, in favor of a single layout that is then augmented with function components.</p>
<p>
Previously, <code class="inline">root.html.heex</code> was the static layout and <code class="inline">app.html.heex</code> was the dynamic one that can be updated throughout the lifecycle of your LiveViews. This remains the case, but the app layout usage in 1.8 has been simplified.</p>
<p>
Our prior approach set apps up with a fixed app layout via <code class="inline">use Phoenix.LiveView, layout: ...</code> options, which could be confusing and also required too much ceremony to support multiple app layouts programmatically. Instead, root layout remains unchanged, while the app layout has been made an explicit function component call wherever you want to include a dynamic app layout.</p>
<p>
Here is an example of how we could augment the new app layout in Phoenix with breadcrumbs and then easily reuse them across pages.</p>
<p>
First, we’d add an optional <code class="inline">:breadcrumb</code> slot to our <code class="inline">&lt;Layouts.app&gt;</code> function component, which renders the breadcrumbs above the caller’s inner block:</p>
<pre><code class="makeup elixir"><span class="kd">defmodule</span><span class="w"> </span><span class="nc">AppWeb.Layouts</span><span class="w"> </span><span class="k" data-group-id="0421840638-1">do</span><span class="w">
  </span><span class="kn">use</span><span class="w"> </span><span class="nc">AppWeb</span><span class="p">,</span><span class="w"> </span><span class="ss">:html</span><span class="w">

  </span><span class="n">embed_templates</span><span class="w"> </span><span class="s">&quot;layouts/*&quot;</span><span class="w">

  </span><span class="n">slot</span><span class="w"> </span><span class="ss">:breadcrumb</span><span class="p">,</span><span class="w"> </span><span class="ss">required</span><span class="p">:</span><span class="w"> </span><span class="no">false</span><span class="w">

  </span><span class="kd">def</span><span class="w"> </span><span class="nf">app</span><span class="p" data-group-id="0421840638-2">(</span><span class="n">assigns</span><span class="p" data-group-id="0421840638-2">)</span><span class="w">
    </span><span class="sx">~H&quot;&quot;&quot;</span><span class="w">
</span><span class="n">    ...
</span><span class="w">    </span><span class="p">&lt;</span><span class="nt">main</span><span class="w"> </span><span class="na">class</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">p</span><span class="s2">x</span><span class="s2">-</span><span class="s2">4 </span><span class="s2">p</span><span class="s2">y</span><span class="s2">-</span><span class="s2">2</span><span class="s2">0 </span><span class="s2">s</span><span class="s2">m</span><span class="s2">:</span><span class="s2">p</span><span class="s2">x</span><span class="s2">-</span><span class="s2">6 </span><span class="s2">l</span><span class="s2">g</span><span class="s2">:</span><span class="s2">p</span><span class="s2">x</span><span class="s2">-</span><span class="s2">8</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="w">
</span><span class="w">      </span><span class="p">&lt;</span><span class="nt">div</span><span class="w"> </span><span class="na">:if</span><span class="p">=</span><span class="p" data-group-id="8418646114-1">{</span><span class="na">@breadcrumb</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="p" data-group-id="0421840638-3">[</span><span class="p" data-group-id="0421840638-3">]</span><span class="p" data-group-id="8418646114-1">}</span><span class="w"> </span><span class="na">class</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">p</span><span class="s2">y</span><span class="s2">-</span><span class="s2">4 </span><span class="s2">b</span><span class="s2">r</span><span class="s2">e</span><span class="s2">a</span><span class="s2">d</span><span class="s2">c</span><span class="s2">r</span><span class="s2">u</span><span class="s2">m</span><span class="s2">b</span><span class="s2">s </span><span class="s2">t</span><span class="s2">e</span><span class="s2">x</span><span class="s2">t</span><span class="s2">-</span><span class="s2">s</span><span class="s2">m</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="w">
</span><span class="w">        </span><span class="p">&lt;</span><span class="nt">ul</span><span class="p">&gt;</span><span class="w">
</span><span class="w">          </span><span class="p">&lt;</span><span class="nt">li</span><span class="w"> </span><span class="na">:for</span><span class="p">=</span><span class="p" data-group-id="8418646114-2">{</span><span class="n">item</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="na">@breadcrumb</span><span class="p" data-group-id="8418646114-2">}</span><span class="p">&gt;</span><span class="p" data-group-id="8418646114-3">{</span><span class="n">render_slot</span><span class="p" data-group-id="0421840638-4">(</span><span class="n">item</span><span class="p" data-group-id="0421840638-4">)</span><span class="p" data-group-id="8418646114-3">}</span><span class="p">&lt;/</span><span class="nt">li</span><span class="p">&gt;</span><span class="w">
</span><span class="w">        </span><span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span><span class="w">
</span><span class="w">      </span><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span><span class="w">
</span><span class="w">      </span><span class="p">&lt;</span><span class="nt">div</span><span class="w"> </span><span class="na">class</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">m</span><span class="s2">x</span><span class="s2">-</span><span class="s2">a</span><span class="s2">u</span><span class="s2">t</span><span class="s2">o </span><span class="s2">m</span><span class="s2">a</span><span class="s2">x</span><span class="s2">-</span><span class="s2">w</span><span class="s2">-</span><span class="s2">2</span><span class="s2">x</span><span class="s2">l </span><span class="s2">s</span><span class="s2">p</span><span class="s2">a</span><span class="s2">c</span><span class="s2">e</span><span class="s2">-</span><span class="s2">y</span><span class="s2">-</span><span class="s2">4</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="w">
</span><span class="w">        </span><span class="p" data-group-id="8418646114-4">{</span><span class="n">render_slot</span><span class="p" data-group-id="0421840638-5">(</span><span class="na">@inner_block</span><span class="p" data-group-id="0421840638-5">)</span><span class="p" data-group-id="8418646114-4">}</span><span class="w">
</span><span class="w">      </span><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;/</span><span class="nt">main</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="sx">&quot;&quot;&quot;</span><span class="w">
  </span><span class="k" data-group-id="0421840638-1">end</span><span class="w">
</span><span class="k">end</span></code></pre>
<p>
Then in any of our LiveViews that want breadcrumbs, the caller can declare them:</p>
<pre><code class="makeup elixir"><span class="na">@impl</span><span class="w"> </span><span class="no">true</span><span class="w">
</span><span class="kd">def</span><span class="w"> </span><span class="nf">render</span><span class="p" data-group-id="9718900265-1">(</span><span class="n">assigns</span><span class="p" data-group-id="9718900265-1">)</span><span class="w"> </span><span class="k" data-group-id="9718900265-2">do</span><span class="w">
  </span><span class="sx">~H&quot;&quot;&quot;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nc">Layouts</span><span class="o">.</span><span class="n">app</span><span class="w"> </span><span class="na">flash</span><span class="p">=</span><span class="p" data-group-id="0784884810-1">{</span><span class="na">@flash</span><span class="p" data-group-id="0784884810-1">}</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="ss">:breadcrumb</span><span class="p">&gt;</span><span class="w">
</span><span class="w">      </span><span class="p">&lt;</span><span class="nf">.link</span><span class="w"> </span><span class="na">navigate</span><span class="p">=</span><span class="p" data-group-id="0784884810-2">{</span><span class="sx">~p&quot;/posts&quot;</span><span class="p" data-group-id="0784884810-2">}</span><span class="p">&gt;</span><span class="n">All Posts</span><span class="p">&lt;/</span><span class="nf">.link</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;/</span><span class="ss">:breadcrumb</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="ss">:breadcrumb</span><span class="p">&gt;</span><span class="w">
</span><span class="w">      </span><span class="p">&lt;</span><span class="nf">.link</span><span class="w"> </span><span class="na">navigate</span><span class="p">=</span><span class="p" data-group-id="0784884810-3">{</span><span class="sx">~p&quot;/posts/</span><span class="si" data-group-id="9718900265-3">#{</span><span class="na">@post</span><span class="si" data-group-id="9718900265-3">}</span><span class="sx">&quot;</span><span class="p" data-group-id="0784884810-3">}</span><span class="p">&gt;</span><span class="n">View Post</span><span class="p">&lt;/</span><span class="nf">.link</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;/</span><span class="ss">:breadcrumb</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nf">.header</span><span class="p">&gt;</span><span class="w">
</span><span class="n">      Post </span><span class="p" data-group-id="0784884810-4">{</span><span class="na">@post</span><span class="o">.</span><span class="n">id</span><span class="p" data-group-id="0784884810-4">}</span><span class="n">
</span><span class="w">      </span><span class="p">&lt;</span><span class="ss">:subtitle</span><span class="p">&gt;</span><span class="n">This is a post record from your database.</span><span class="p">&lt;/</span><span class="ss">:subtitle</span><span class="p">&gt;</span><span class="w">
</span><span class="w">      </span><span class="p">&lt;</span><span class="ss">:actions</span><span class="p">&gt;</span><span class="w">
</span><span class="w">        </span><span class="p">&lt;</span><span class="nf">.link</span><span class="w"> </span><span class="na">class</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">b</span><span class="s2">t</span><span class="s2">n</span><span class="p">&quot;</span><span class="w"> </span><span class="na">navigate</span><span class="p">=</span><span class="p" data-group-id="0784884810-5">{</span><span class="sx">~p&quot;/posts/</span><span class="si" data-group-id="9718900265-4">#{</span><span class="na">@post</span><span class="si" data-group-id="9718900265-4">}</span><span class="sx">/edit&quot;</span><span class="p" data-group-id="0784884810-5">}</span><span class="p">&gt;</span><span class="n">Edit post</span><span class="p">&lt;/</span><span class="nf">.link</span><span class="p">&gt;</span><span class="w">
</span><span class="w">      </span><span class="p">&lt;/</span><span class="ss">:actions</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;/</span><span class="nf">.header</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span><span class="w">
</span><span class="n">      My LiveView Page
</span><span class="w">    </span><span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;/</span><span class="nc">Layouts</span><span class="o">.</span><span class="n">app</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="sx">&quot;&quot;&quot;</span><span class="w">
</span><span class="k" data-group-id="9718900265-2">end</span></code></pre>
<p>
And this is what it looks like in action:</p>
<video src="/images/blog/breadcrumbs.mp4" autoplay="autoplay" loop="loop" controls="controls">
</video>
<p>
Notice how the app layout is now an explicit call. If you have other app layouts, like a cart page, admin page, etc, you simply write a new <code class="inline">&lt;Layouts.admin flash={@flash}&gt;</code>, passing whatever assigns you need.</p>
<p>
Supporting something like this with our prior app layout approach would have required feeding assigns to the app layout and branching based on some conditions, and mucking with <code class="inline">app_web.ex</code> to support additional layout options. Now, the caller is simply free to handle their layout concerns inline.</p>
<p>
Combined with daisyUI component classes, the streamlined layouts and onboarding experience offers a fantastic starting point for rapid development, with easy customization and a robust component system to choose from.</p>
<h3>
Try it out</h3>
<p>
You can update existing <code class="inline">phx.new</code> installations with:</p>
<pre><code class="makeup shell">mix archive.install hex phx_new 1.8.0-rc.0 --force</code></pre>
<p>
Reminder, we launched <code class="inline">new.phoenixframework.org</code> several months ago which lets you get up and running in seconds with Elixir and your first Phoenix project with a single command.</p>
<p>
You can use it to take Phoenix v1.8 release candidate for a spin:</p>
<p>
For osx/linux:</p>
<pre><code class="makeup bash">$ curl https://new.phoenixframework.org/myapp | sh</code></pre>
<p>
For Windows PowerShell:</p>
<pre><code class="makeup cmd">&gt; curl.exe -fsSO https://new.phoenixframework.org/app.bat; .\app.bat</code></pre>
<p>
Phoenix 1.8 brings improvements to developer productivity and app structure, from scoped data access that grows with your domain to tailwind theming that grows with your design. Combined with passwordless auth and lighter generators this release should streamline your app development whether you’re a new user or building along with us for the last 10 years.</p>
<p>
Huge shoutout to <a href="https://github.com/steffenDE">Steffen Deusch</a> and his <a href="https://dashbit.co/">Dashbit</a> sponsored work for making the majority of the features here happen!</p>
<p>
<em>Note</em>: This is a backwards compatible release with <a href="https://github.com/phoenixframework/phoenix/blob/b1c459943b3279f97725787b9150ff4950958d12/CHANGELOG.md">a few deprecations</a>.</p>
<p>
As always, find us on elixirforum, slack, or discord if you have questions or need help.</p>
<p>
Happy coding!</p>
<p>
–Chris</p>
]]></content>
  </entry>

  <entry>
    <title>Phoenix LiveView 1.0.0 is here!</title>
    <link rel="alternate" href="http://www.phoenixframework.org/blog/phoenix-liveview-1.0-released" />
    <id>http://www.phoenixframework.org/blog/phoenix-liveview-1.0-released</id>
    <updated>2024-12-03T00:00:00Z</updated>
    <author>
      <name>Chris McCord</name>
    </author>
    <summary type="html"><![CDATA[LiveView 1.0 is out!]]></summary>
    <content type="html"><![CDATA[<p>
LiveView 1.0.0 is out!</p>
<p>
This 1.0 milestone comes six years after the first LiveView commit.</p>
<p>
  <img src="/images/blog/lv-1.0/commits.png" alt="">
</p>
<h2>
Why LiveView</h2>
<p>
I started LiveView to scratch an itch. I wanted to create dynamic server-rendered applications without writing JavaScript. I was tired of the inevitable ballooning complexity that it brings.</p>
<p>
Think realtime form validations, updating the quantity in a shopping cart, or real-time streaming updates. Why does it require moving mountains to solve in a traditional stack? We write the HTTP glue or GraphQL schemas  and resolvers, then we figure out which validation logic needs shared or dup’d. It goes on and on from there – how do we get localization information to the client? What data serializers do we need? How do we wire up WebSockets and IPC back to our code? Is our js bundle getting too large? I guess it’s time to start turning the Webpack or Parcel knobs. Wait Vite is a thing now? Or I guess Bun configuration is what we want? We’ve all felt this pain.</p>
<p>
The idea was, what if we removed these problems entirely? HTTP can go away, and the server can handle all the rendering and dynamic update concerns. It felt like a heavy approach, but I knew Elixir and Phoenix was perfectly suited for it.</p>
<p>
Six years later this programming model still feels like cheating.  Everything is super fast. Payloads are tiny. Latency is best-in-class. Not only do you write less code, there’s simply less to think about when writing features.</p>
<h2>
Real-time foundations unlock superpowers</h2>
<p>
Interesting things happen when you give every user and UI a real-time, bidirectional foundation as a matter of course. You suddenly have superpowers. You almost don’t notice it. Being freed from all the mundane concerns of typical full-stack development lets you focus on just shipping features. And with Elixir, you start shipping features that other platforms can’t even conceive as possible.</p>
<p>
Want to <a href="https://fly.io/phoenix-files/phoenix-dev-blog-server-logs-in-the-browser-console/">ship real-time server logs to the js console in development</a>? No problem!</p>
<video src="/images/blog/lv-1.0/server-logs.mp4" autoplay="autoplay" loop="loop" controls="controls" muted="muted">
</video>
<p>
What about supporting production hot code upgrades where browsers can auto re-render anytime CSS stylesheets, images, or templates change – without losing state or dropping connections? Sure!</p>
<video src="/images/blog/lv-1.0/hot-deploy.mp4" autoplay="autoplay" loop="loop" controls="controls" muted="muted">
</video>
<p>
Or maybe you have an app deployed planet-wide where you do work across the cluster and aggregate the results in real-time back to the UI. Would you believe the entire LiveView, including the template markup and RPC calls, is <a href="https://github.com/fly-apps/wps/blob/0cd4f4d46e873b3a0937fe230d26f5a195687ecf/lib/wps_web/live/page_speed_live.ex">350 LOC</a>?</p>
<video src="/images/blog/lv-1.0/what.mp4" controls="controls">
</video>
<p>
These are the kinds of applications that LiveView enables. It feels incredible to ship these kinds of things, but it took a while to arrive here for good reasons. There was a lot to solve to make this programming model truly great.</p>
<h2>
How it started</h2>
<p>
Conceptually, what I really wanted is something like what we do in React – change some state, our template re-renders automatically, and the UI updates. But instead of a bit of UI running on the client, what if we ran it on the server? The LiveView could look like this:</p>
<pre><code class="makeup elixir"><span class="kd">defmodule</span><span class="w"> </span><span class="nc">ThermoLive</span><span class="w"> </span><span class="k" data-group-id="9993044099-1">do</span><span class="w">
  </span><span class="kd">def</span><span class="w"> </span><span class="nf">render</span><span class="p" data-group-id="9993044099-2">(</span><span class="n">assigns</span><span class="p" data-group-id="9993044099-2">)</span><span class="w"> </span><span class="k" data-group-id="9993044099-3">do</span><span class="w">
    </span><span class="sx">~H&quot;&quot;&quot;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nt">div</span><span class="w"> </span><span class="na">id</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">t</span><span class="s2">h</span><span class="s2">e</span><span class="s2">r</span><span class="s2">m</span><span class="s2">o</span><span class="s2">s</span><span class="s2">t</span><span class="s2">a</span><span class="s2">t</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="w">
</span><span class="w">      </span><span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span><span class="n">Temperature: </span><span class="p" data-group-id="6764982557-1">{</span><span class="na">@thermostat</span><span class="o">.</span><span class="n">temperature</span><span class="p" data-group-id="6764982557-1">}</span><span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span><span class="w">
</span><span class="w">      </span><span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span><span class="n">Mode: </span><span class="p" data-group-id="6764982557-2">{</span><span class="na">@thermostat</span><span class="o">.</span><span class="n">mode</span><span class="p" data-group-id="6764982557-2">}</span><span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span><span class="w">
</span><span class="w">      </span><span class="p">&lt;</span><span class="nt">button</span><span class="w"> </span><span class="na">phx-click</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">inc</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="n">+</span><span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span><span class="w">
</span><span class="w">      </span><span class="p">&lt;</span><span class="nt">button</span><span class="w"> </span><span class="na">phx-click</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">dec</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="n">-</span><span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="sx">&quot;&quot;&quot;</span><span class="w">
  </span><span class="k" data-group-id="9993044099-3">end</span><span class="w">

  </span><span class="kd">def</span><span class="w"> </span><span class="nf">mount</span><span class="p" data-group-id="9993044099-4">(</span><span class="p" data-group-id="9993044099-5">%{</span><span class="s">&quot;id&quot;</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="n">id</span><span class="p" data-group-id="9993044099-5">}</span><span class="p">,</span><span class="w"> </span><span class="c">_session</span><span class="p">,</span><span class="w"> </span><span class="n">socket</span><span class="p" data-group-id="9993044099-4">)</span><span class="w"> </span><span class="k" data-group-id="9993044099-6">do</span><span class="w">
    </span><span class="n">thermostat</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nc">ThermoControl</span><span class="o">.</span><span class="n">get_thermostat!</span><span class="p" data-group-id="9993044099-7">(</span><span class="n">id</span><span class="p" data-group-id="9993044099-7">)</span><span class="w">
    </span><span class="ss">:ok</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nc">ThermoControl</span><span class="o">.</span><span class="n">subscribe</span><span class="p" data-group-id="9993044099-8">(</span><span class="n">thermostat</span><span class="p" data-group-id="9993044099-8">)</span><span class="w">
    </span><span class="p" data-group-id="9993044099-9">{</span><span class="ss">:ok</span><span class="p">,</span><span class="w"> </span><span class="n">assign</span><span class="p" data-group-id="9993044099-10">(</span><span class="n">socket</span><span class="p">,</span><span class="w"> </span><span class="ss">thermostat</span><span class="p">:</span><span class="w"> </span><span class="n">thermostat</span><span class="p" data-group-id="9993044099-10">)</span><span class="p" data-group-id="9993044099-9">}</span><span class="w">
  </span><span class="k" data-group-id="9993044099-6">end</span><span class="w">

  </span><span class="kd">def</span><span class="w"> </span><span class="nf">handle_info</span><span class="p" data-group-id="9993044099-11">(</span><span class="p" data-group-id="9993044099-12">{</span><span class="nc">ThermoControl</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="9993044099-13">%</span><span class="nc" data-group-id="9993044099-13">ThermoStat</span><span class="p" data-group-id="9993044099-13">{</span><span class="p" data-group-id="9993044099-13">}</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">new_thermo</span><span class="p" data-group-id="9993044099-12">}</span><span class="p">,</span><span class="w"> </span><span class="bp">_</span><span class="p">,</span><span class="w"> </span><span class="n">socket</span><span class="p" data-group-id="9993044099-11">)</span><span class="w"> </span><span class="k" data-group-id="9993044099-14">do</span><span class="w">
    </span><span class="p" data-group-id="9993044099-15">{</span><span class="ss">:noreply</span><span class="p">,</span><span class="w"> </span><span class="n">assign</span><span class="p" data-group-id="9993044099-16">(</span><span class="n">socket</span><span class="p">,</span><span class="w"> </span><span class="ss">thermostat</span><span class="p">:</span><span class="w"> </span><span class="n">new_thermo</span><span class="p" data-group-id="9993044099-16">)</span><span class="p" data-group-id="9993044099-15">}</span><span class="w">
  </span><span class="k" data-group-id="9993044099-14">end</span><span class="w">

  </span><span class="kd">def</span><span class="w"> </span><span class="nf">handle_event</span><span class="p" data-group-id="9993044099-17">(</span><span class="s">&quot;inc&quot;</span><span class="p">,</span><span class="w"> </span><span class="bp">_</span><span class="p">,</span><span class="w"> </span><span class="n">socket</span><span class="p" data-group-id="9993044099-17">)</span><span class="w"> </span><span class="k" data-group-id="9993044099-18">do</span><span class="w">
    </span><span class="n">thermostat</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nc">ThermoControl</span><span class="o">.</span><span class="n">inc</span><span class="p" data-group-id="9993044099-19">(</span><span class="n">socket</span><span class="o">.</span><span class="n">assigns</span><span class="o">.</span><span class="n">thermostat</span><span class="p" data-group-id="9993044099-19">)</span><span class="w">
    </span><span class="p" data-group-id="9993044099-20">{</span><span class="ss">:noreply</span><span class="p">,</span><span class="w"> </span><span class="n">assign</span><span class="p" data-group-id="9993044099-21">(</span><span class="n">socket</span><span class="p">,</span><span class="w"> </span><span class="ss">thermostat</span><span class="p">:</span><span class="w"> </span><span class="n">thermostat</span><span class="p" data-group-id="9993044099-21">)</span><span class="p" data-group-id="9993044099-20">}</span><span class="w">
  </span><span class="k" data-group-id="9993044099-18">end</span><span class="w">
</span><span class="k" data-group-id="9993044099-1">end</span></code></pre>
<p>
Like React, we have a render function and something that sets our initial state when the LiveView mounts. When state changes, we call render with the new state and the UI is updated.</p>
<p>
Interactions like <code class="inline">phx-click</code> on the <code class="inline">+</code> or <code class="inline">-</code> button, can be sent as RPC’s from client to server and the server can respond with fresh page HTML. These client/server messages use Phoenix Channels which <a href="https://www.phoenixframework.org/blog/the-road-to-2-million-websocket-connections">scale to millions of connections per server</a>.</p>
<p>
Likewise, if the server wants to send an update to the client, such as another user changing the thermostat, the client can listen for it and replace the page HTML in the same fashion. My naive first pass on the <code class="inline">phoenix_live_view.js</code> client looked something like this.</p>
<pre><code class="makeup javascript"><span class="kt">let</span><span class="w"> </span><span class="nv">main</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">document</span><span class="p">.</span><span class="nf">querySelector</span><span class="p">(</span><span class="p">&quot;</span><span class="s2">[phx-main]</span><span class="p">&quot;</span><span class="p">)</span><span class="w">
</span><span class="kt">let</span><span class="w"> </span><span class="nv">channel</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="nv">socket</span><span class="p">.</span><span class="nf">channel</span><span class="p">(</span><span class="p">&quot;</span><span class="s2">lv</span><span class="p">&quot;</span><span class="p">)</span><span class="w">
</span><span class="nv">channel</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="p">)</span><span class="p">.</span><span class="nf">receive</span><span class="p">(</span><span class="p">&quot;</span><span class="s2">ok</span><span class="p">&quot;</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="p">{</span><span class="nv">html</span><span class="p">}</span><span class="p">)</span><span class="w"> </span><span class="kt">=&gt;</span><span class="w"> </span><span class="nv">main</span><span class="p">.</span><span class="n">innerHTML</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">html</span><span class="p">)</span><span class="w">
</span><span class="nv">channel</span><span class="p">.</span><span class="nf">on</span><span class="p">(</span><span class="p">&quot;</span><span class="s2">update</span><span class="p">&quot;</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="p">{</span><span class="nv">html</span><span class="p">}</span><span class="p">)</span><span class="w"> </span><span class="kt">=&gt;</span><span class="w"> </span><span class="nv">main</span><span class="p">.</span><span class="n">innerHTML</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">html</span><span class="p">)</span><span class="w">
</span><span class="w">
</span><span class="nb">window</span><span class="p">.</span><span class="nf">addEventListener</span><span class="p">(</span><span class="p">&quot;</span><span class="s2">click</span><span class="p">&quot;</span><span class="p">,</span><span class="w"> </span><span class="nv">e</span><span class="w"> </span><span class="kt">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="w">  </span><span class="kt">let</span><span class="w"> </span><span class="nv">event</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">e</span><span class="p">.</span><span class="nf">getAttribute</span><span class="p">(</span><span class="p">&quot;</span><span class="s2">phx-click</span><span class="p">&quot;</span><span class="p">)</span><span class="w">
</span><span class="w">  </span><span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nv">event</span><span class="p">)</span><span class="p">{</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="w">  </span><span class="nv">channel</span><span class="p">.</span><span class="nf">push</span><span class="p">(</span><span class="p">&quot;</span><span class="s2">event</span><span class="p">&quot;</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="nv">event</span><span class="p">}</span><span class="p">)</span><span class="p">.</span><span class="nf">receive</span><span class="p">(</span><span class="p">&quot;</span><span class="s2">ok</span><span class="p">&quot;</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="p">{</span><span class="nv">html</span><span class="p">}</span><span class="p">)</span><span class="w"> </span><span class="kt">=&gt;</span><span class="w"> </span><span class="nv">main</span><span class="p">.</span><span class="n">innerHTML</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">html</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="p">)</span></code></pre>
<p>
This is how LiveView started. We went to the server for interactions, re-rendered the entire template on state change, and sent the entire page down to the client. The client then swapped out the inner HTML.</p>
<p>
It worked, but it was not great. Partial state changes required re-executing the entire template and sending down gobs of HTML for otherwise tiny updates.</p>
<p>
Still the basic programming model was exactly what I wanted. As HTTP fell away from my concerns, entire layers of full-stack considerations disappeared.</p>
<p>
Next the challenge was making this something truly great. Little did we know we’d accidentally our way to outperforming many SPA use-cases along the way.</p>
<h2>
How we optimized the programming model</h2>
<p>
LiveView’s diffing engine solved two problems with a single mechanism. The first problem was only executing those dynamic parts of a template that actually changed from a previous render. The second was only sending the minimal data necessary to update the client.</p>
<p>
It solves both by splitting the template into static and dynamic parts. Considering the following LiveView template:</p>
<pre><code class="makeup elixir"><span class="sx">~H&quot;&quot;&quot;</span><span class="w">
</span><span class="p">&lt;</span><span class="nt">p</span><span class="w"> </span><span class="na">class</span><span class="p">=</span><span class="p" data-group-id="0710806792-1">{</span><span class="na">@mode</span><span class="p" data-group-id="0710806792-1">}</span><span class="p">&gt;</span><span class="n">Temperature: </span><span class="p" data-group-id="0710806792-2">{</span><span class="n">format_unit</span><span class="p" data-group-id="8547850594-1">(</span><span class="na">@temperature</span><span class="p" data-group-id="8547850594-1">)</span><span class="p" data-group-id="0710806792-2">}</span><span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span><span class="w">
</span><span class="sx">&quot;&quot;&quot;</span></code></pre>
<p>
At compile time, we convert the template into a struct like this:</p>
<pre><code class="makeup elixir"><span class="p" data-group-id="4820758266-1">%</span><span class="nc" data-group-id="4820758266-1">Phoenix.LiveView.Rendered</span><span class="p" data-group-id="4820758266-1">{</span><span class="w">
  </span><span class="ss">static</span><span class="p">:</span><span class="w"> </span><span class="p" data-group-id="4820758266-2">[</span><span class="s">&quot;&lt;p class=</span><span class="se">\&quot;</span><span class="s">&quot;</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;</span><span class="se">\&quot;</span><span class="s">&gt;Temperature:&quot;</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;&lt;/p&gt;&quot;</span><span class="p" data-group-id="4820758266-2">]</span><span class="w">
  </span><span class="ss">dynamic</span><span class="p">:</span><span class="w"> </span><span class="k" data-group-id="4820758266-3">fn</span><span class="w"> </span><span class="n">assigns</span><span class="w"> </span><span class="o">-&gt;</span><span class="w">
    </span><span class="p" data-group-id="4820758266-4">[</span><span class="w">
      </span><span class="k">if</span><span class="w"> </span><span class="n">changed?</span><span class="p" data-group-id="4820758266-5">(</span><span class="n">assigns</span><span class="p">,</span><span class="w"> </span><span class="ss">:mode</span><span class="p" data-group-id="4820758266-5">)</span><span class="p">,</span><span class="w"> </span><span class="ss">do</span><span class="p">:</span><span class="w"> </span><span class="n">assigns</span><span class="o">.</span><span class="n">mode</span><span class="p">,</span><span class="w">
      </span><span class="k">if</span><span class="w"> </span><span class="n">changed?</span><span class="p" data-group-id="4820758266-6">(</span><span class="n">assigns</span><span class="p">,</span><span class="w"> </span><span class="ss">:temperature</span><span class="p" data-group-id="4820758266-6">)</span><span class="p">,</span><span class="w"> </span><span class="ss">do</span><span class="p">:</span><span class="w"> </span><span class="n">format_unit</span><span class="p" data-group-id="4820758266-7">(</span><span class="n">assigns</span><span class="o">.</span><span class="n">temperature</span><span class="p" data-group-id="4820758266-7">)</span><span class="w">
    </span><span class="p" data-group-id="4820758266-4">]</span><span class="w">
  </span><span class="k" data-group-id="4820758266-3">end</span><span class="w">
</span><span class="p" data-group-id="4820758266-1">}</span></code></pre>
<p>
We know the static parts never change, so they are split from the dynamic Elixir expressions. Next, we compile each expression with change tracking based on the variables accessed within each expression. On render, we compare the previous template values with the new and only execute the template expression if the value has changed.</p>
<p>
Instead of sending the entire template down on change, we can send the client all the static and dynamic parts on <code class="inline">mount</code>. After mount  we only send the partial diff of dynamic values for each update.</p>
<p>
To see how this works, we can imagine the following payload being sent on <code class="inline">mount</code> for the template above:</p>
<pre><code class="makeup javascript"><span class="p">{</span><span class="w">
</span><span class="w">  </span><span class="n">s</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="p">&quot;</span><span class="s2">&lt;p class=</span><span class="esc">\&quot;</span><span class="p">&quot;</span><span class="p">,</span><span class="w"> </span><span class="p">&quot;</span><span class="esc">\&quot;</span><span class="s2">&gt;Temperature: </span><span class="p">&quot;</span><span class="p">,</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">&lt;/p&gt;</span><span class="p">&quot;</span><span class="p">]</span><span class="o">,</span><span class="w">
</span><span class="w">  </span><span class="mi">0</span><span class="n">: </span><span class="p">&quot;</span><span class="s2">cooling</span><span class="p">&quot;</span><span class="o">,</span><span class="w">
</span><span class="w">  </span><span class="mi">1</span><span class="n">: </span><span class="p">&quot;</span><span class="s2">68℉</span><span class="p">&quot;</span><span class="w">
</span><span class="p">}</span></code></pre>
<p>
The client receives a map of static values in the <code class="inline">s</code> key, and dynamic values keyed by their index in the statics. For the client to render the full template string, it only needs to zips the static list with the dynamic values. For example:</p>
<pre><code class="makeup javascript"><span class="p">[</span><span class="p">&quot;</span><span class="s2">&lt;p class=</span><span class="esc">\&quot;</span><span class="p">&quot;</span><span class="p">,</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">cooling</span><span class="p">&quot;</span><span class="p">,</span><span class="w"> </span><span class="p">&quot;</span><span class="esc">\&quot;</span><span class="s2">&gt;Temperature: </span><span class="p">&quot;</span><span class="p">,</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">68℉</span><span class="p">&quot;</span><span class="p">,</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">&lt;/p&gt;</span><span class="p">&quot;</span><span class="p">]</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="p">&quot;</span><span class="p">&quot;</span><span class="p">)</span><span class="w">
</span><span class="p">&quot;</span><span class="s2">&lt;p class=</span><span class="esc">\&quot;</span><span class="s2">cooling</span><span class="esc">\&quot;</span><span class="s2">&gt;Temperature: 68℉&lt;/p&gt;</span><span class="p">&quot;</span></code></pre>
<p>
With the client holding a static/dynamic cache, optimizing network updates is no work at all. Any server render following <code class="inline">mount</code>  simply returns the new dynamic values at their known index. Unchanged dynamic values and statics are ignored entirely.</p>
<p>
If a LiveView runs <code class="inline">assign(socket, :temperature, 70)</code>, the <code class="inline">render/1</code> function is invoked, and the following payload gets sent down the wire:</p>
<pre><code class="makeup javascript"><span class="p">{</span><span class="mi">1</span><span class="n">: </span><span class="p">&quot;</span><span class="s2">70℉</span><span class="p">&quot;</span><span class="p">}</span></code></pre>
<p>
Thats it! To update the UI, the client simply merges this object with its static/dynamic cache:</p>
<pre><code class="makeup javascript"><span class="p">{</span><span class="w">                     </span><span class="p">{</span><span class="w">
</span><span class="w">                        </span><span class="n">s</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="p">&quot;</span><span class="s2">&lt;p class=</span><span class="esc">\&quot;</span><span class="p">&quot;</span><span class="p">,</span><span class="w"> </span><span class="p">&quot;</span><span class="esc">\&quot;</span><span class="s2">&gt;Temperature: </span><span class="p">&quot;</span><span class="p">,</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">&lt;/p&gt;</span><span class="p">&quot;</span><span class="p">]</span><span class="o">,</span><span class="w">
</span><span class="w">                        </span><span class="mi">0</span><span class="n">: </span><span class="p">&quot;</span><span class="s2">cooling</span><span class="p">&quot;</span><span class="o">,</span><span class="w">
</span><span class="w">  </span><span class="mi">1</span><span class="n">: </span><span class="p">&quot;</span><span class="s2">70F</span><span class="p">&quot;</span><span class="n">     =</span><span class="o">&gt;</span><span class="w">       </span><span class="mi">1</span><span class="n">: </span><span class="p">&quot;</span><span class="s2">70℉</span><span class="p">&quot;</span><span class="w">
</span><span class="p">}</span><span class="w">                     </span><span class="p">}</span></code></pre>
<p>
Then the data is zipped together on the client to produce the full HTML of the UI.</p>
<p>
Of course <code class="inline">innerHTML</code> updates blow away UI state and are expensive to perform. So like any client-side framework, we compute minimal DOM diffs to efficiently update the DOM. In fact, we’ve had folks migrate from React to Phoenix LiveView because <a href="https://podcast.thinkingelixir.com/156">LiveView client rendering was faster what their React app could offer</a>.</p>
<p>
Optimizations continued from there. Including fingerprinting, for comprehensions, tree sharing, and more. You can <a href="https://dashbit.co/blog/latency-rendering-liveview">read all about each optimization</a> on the Dashbit blog.</p>
<p>
We apply these optimizations <em>automatically and for free</em> thanks to our stateful client and server connection. Most other server rendered HTML solutions send the whole fragment on every update or require users to fine tune updates by hand.</p>
<h2>
Best in class latency</h2>
<p>
We’ve seen how LiveView payloads are smaller than the best hand-written JSON API or GraphQL query, but it’s even better than that. Every LiveView holds a connection to the server so page navigation happens via live navigation. TLS handshakes, current user auth, etc happen a <em>single time</em> for the lifetime of the user’s visit. This allows page navigation to happen via a single WebSocket frame, and fewer database queries for any client action. The result is fewer round trips from the client, and simply less work done by the server. This provides less latency for the end-user compared to an SPA fetching data or sending mutations up to a server.</p>
<p>
Holding a stateful connections comes at the cost of server memory, but it’s far cheaper than folks expect. At a baseline, a given channel connection consumes 40kb of memory. This gives a 1GB server a theoretical ceiling of ~25,000 concurrent LiveViews. Of course the more state you store, the more memory you consume, but you only hold onto the state you need. We also have <code class="inline">stream</code> primitives for handling large collections without impacting memory. Elixir and the Erlang VM were designed for this. Scaling a stateful system to millions of concurrent users isn’t theoretical – we do it all the time. See WhatsApp, Discord, or <a href="https://www.phoenixframework.org/blog/the-road-to-2-million-websocket-connections">our own benchmarks</a> as examples.</p>
<p>
With the programming model optimized on both client and server, we expanded into higher level building blocks that take advantage of our unique diffing engine.</p>
<h2>
Reusable Components with HEEx</h2>
<p>
Change tracking and minimal diffs were ground-breaking features, but our HTML templates still lacked composability. The best we could offer is “partial”-like template rendering where a function could encapsulate some partial template content. This works, but it composes poorly and is mismatched in the way we write markup. Fortunately Marlus Saraiva from the <a href="https://surface-ui.org/">Surface project</a> spearheaded development of an HTML-aware component system and contributed back to the LiveView project. With HEEx components, we have a declarative component system, HTML validation, and  compile-time checking of component attributes and slots.</p>
<p>
HEEx components are just annotated functions. They look like this:</p>
<pre><code class="makeup elixir"><span class="na">@doc</span><span class="w"> </span><span class="s">&quot;&quot;&quot;
Renders a button.

## Examples

    &lt;.button&gt;Send!&lt;/.button&gt;
    &lt;.button phx-click=&quot;go&quot;&gt;Send!&lt;/.button&gt;
&quot;&quot;&quot;</span><span class="w">
</span><span class="n">attr</span><span class="w"> </span><span class="ss">:type</span><span class="p">,</span><span class="w"> </span><span class="ss">:string</span><span class="p">,</span><span class="w"> </span><span class="ss">default</span><span class="p">:</span><span class="w"> </span><span class="no">nil</span><span class="w">
</span><span class="n">attr</span><span class="w"> </span><span class="ss">:rest</span><span class="p">,</span><span class="w"> </span><span class="ss">:global</span><span class="p">,</span><span class="w"> </span><span class="ss">include</span><span class="p">:</span><span class="w"> </span><span class="sx">~w(disabled form name value)</span><span class="w">

</span><span class="n">slot</span><span class="w"> </span><span class="ss">:inner_block</span><span class="p">,</span><span class="w"> </span><span class="ss">required</span><span class="p">:</span><span class="w"> </span><span class="no">true</span><span class="w">

</span><span class="kd">def</span><span class="w"> </span><span class="nf">button</span><span class="p" data-group-id="1658551456-1">(</span><span class="n">assigns</span><span class="p" data-group-id="1658551456-1">)</span><span class="w"> </span><span class="k" data-group-id="1658551456-2">do</span><span class="w">
  </span><span class="sx">~H&quot;&quot;&quot;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nt">button</span><span class="w">
</span><span class="w">    </span><span class="na">type</span><span class="p">=</span><span class="p" data-group-id="6420664394-1">{</span><span class="na">@type</span><span class="p" data-group-id="6420664394-1">}</span><span class="w">
</span><span class="w">    </span><span class="na">class</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">r</span><span class="s2">o</span><span class="s2">u</span><span class="s2">n</span><span class="s2">d</span><span class="s2">e</span><span class="s2">d</span><span class="s2">-</span><span class="s2">l</span><span class="s2">g </span><span class="s2">b</span><span class="s2">g</span><span class="s2">-</span><span class="s2">z</span><span class="s2">i</span><span class="s2">n</span><span class="s2">c</span><span class="s2">-</span><span class="s2">9</span><span class="s2">0</span><span class="s2">0 </span><span class="s2">h</span><span class="s2">o</span><span class="s2">v</span><span class="s2">e</span><span class="s2">r</span><span class="s2">:</span><span class="s2">b</span><span class="s2">g</span><span class="s2">-</span><span class="s2">z</span><span class="s2">i</span><span class="s2">n</span><span class="s2">c</span><span class="s2">-</span><span class="s2">7</span><span class="s2">0</span><span class="s2">0 </span><span class="s2">p</span><span class="s2">y</span><span class="s2">-</span><span class="s2">2 </span><span class="s2">p</span><span class="s2">x</span><span class="s2">-</span><span class="s2">3 </span><span class="s2">t</span><span class="s2">e</span><span class="s2">x</span><span class="s2">t</span><span class="s2">-</span><span class="s2">w</span><span class="s2">h</span><span class="s2">i</span><span class="s2">t</span><span class="s2">e</span><span class="p">&quot;</span><span class="w">
</span><span class="w">    </span><span class="p" data-group-id="6420664394-2">{</span><span class="na">@rest</span><span class="p" data-group-id="6420664394-2">}</span><span class="w">
</span><span class="w">  </span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p" data-group-id="6420664394-3">{</span><span class="n">render_slot</span><span class="p" data-group-id="1658551456-3">(</span><span class="na">@inner_block</span><span class="p" data-group-id="1658551456-3">)</span><span class="p" data-group-id="6420664394-3">}</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="sx">&quot;&quot;&quot;</span><span class="w">
</span><span class="k" data-group-id="1658551456-2">end</span></code></pre>
<p>
An invalid call to a component, such as <code class="inline">&lt;.button click=&quot;bad&quot;&gt;</code> produces a compile-time warning:</p>
<pre><code class="makeup plain">warning: undefined attribute &quot;click&quot; for component AppWeb.CoreComponents.button/1
  lib/app_web/live/page_live.ex:123: (file)</code></pre>
<p>
Slots allows the component to accept arbitrary content from a caller. This allows components to be much more extensible by the caller without creating a bunch of bespoke partial templates to handle every scenario.</p>
<h2>
Streamlined HEEx syntax</h2>
<p>
When we introduced HEEx and function components, we added a new syntax for interpolating values within tag attributes along with <code class="inline">:if</code> and <code class="inline">:for</code> conveniences for conditionally generating templates. It looked like this:</p>
<pre><code class="makeup heex"><span class="p">&lt;</span><span class="nt">div</span><span class="w"> </span><span class="na">:if</span><span class="p">=</span><span class="p" data-group-id="8393993793-1">{</span><span class="na">@some_condition?</span><span class="p" data-group-id="8393993793-1">}</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nt">ul</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nt">li</span><span class="w"> </span><span class="na">:for</span><span class="p">=</span><span class="p" data-group-id="8393993793-2">{</span><span class="n">val</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="na">@values</span><span class="p" data-group-id="8393993793-2">}</span><span class="p">&gt;</span><span class="n">Value </span><span class="p" data-group-id="8393993793-3">&lt;%=</span><span class="w"> </span><span class="n">val</span><span class="w"> </span><span class="p" data-group-id="8393993793-3">%&gt;</span><span class="p">&lt;/</span><span class="nt">li</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span><span class="w">
</span><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span></code></pre>
<p>
Note the use of standard EEx <code class="inline">&lt;%= %&gt;</code> interpolation. With the release of LiveView 1.0, we are extending the HTML-aware <code class="inline">{}</code> attribute interpolation syntax to within tag bodies as well. This means you can now interpolate values directly within the tag body in a streamlined syntax:</p>
<pre><code class="makeup heex"><span class="p">&lt;</span><span class="nt">div</span><span class="w"> </span><span class="na">:if</span><span class="p">=</span><span class="p" data-group-id="9062799813-1">{</span><span class="na">@some_condition?</span><span class="p" data-group-id="9062799813-1">}</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nt">ul</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nt">li</span><span class="w"> </span><span class="na">:for</span><span class="p">=</span><span class="p" data-group-id="9062799813-2">{</span><span class="n">val</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="na">@values</span><span class="p" data-group-id="9062799813-2">}</span><span class="p">&gt;</span><span class="n">Value </span><span class="p" data-group-id="9062799813-3">{</span><span class="n">val</span><span class="p" data-group-id="9062799813-3">}</span><span class="p">&lt;/</span><span class="nt">li</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span><span class="w">
</span><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span></code></pre>
<p>
The EEx <code class="inline">&lt;%= %&gt;</code> remains supported and is required for generating dynamic blocks of distinct markup, as well as for interpolating values within <code class="inline">&lt;script&gt;</code> and <code class="inline">&lt;style&gt;</code> tags.</p>
<h2>
HEEx markup annotations</h2>
<p>
Gone are the days of examining your browser’s HTML and then hunting for where that HTML was generated within your code. The final browser markup can be rendered  within several nested layers of component calls. How do we quickly trace back who rendered what?</p>
<p>
HEEx solves this with a <code class="inline">debug_heex_annotations</code> configuration. When set, all rendered markup will be annotated with the file:line of the function component definition, <em>as well as,</em> the file:line of the caller invocation of the component. In practice your dev HTML will look like this in the browser inspector:</p>
<p>
  <img src="/images/blog/lv-1.0/heex-anno.png" alt="Debug HEEx annotations">
</p>
<p>
It annotates the document both at the caller site and the function component definition. If you find the above hard to navigate, you can use the new <code class="inline">Phoenix.LiveReloader</code> features that have your editor jump to an element’s nearest caller or definition file:line when clicked with a special key sequence of your choosing.</p>
<p>
Let’s see it in action:</p>
<video src="/images/blog/lv-1.0/keydebug.mp4" autoplay="autoplay" loop="loop" controls="controls" muted="muted">
</video>
<p>
First, we can see how holding <code class="inline">c</code> while clicking jumped to the caller file:Line location for that <code class="inline">&lt;.button&gt;</code> invocation. Next, we see that holding <code class="inline">d</code> while clicking the button jumped to the function definition file:line.</p>
<p>
This is such a simple quality of life improvement. It will become a key part of your workflow as soon as you try it out.</p>
<h2>
Interactive Uploads</h2>
<p>
A few years ago, LiveView tackled the file upload problem. Something that should be easy has historically been unnecessarily difficult. We wanted a single abstraction for interactive uploads for both direct to cloud, and direct to server use-cases.</p>
<p>
With a few lines of server code you can have file uploads with drag and drop, file progress, selection pruning, file previews, and more.</p>
<p>
More recently, we defined an <code class="inline">UploadWriter</code> behavior. This gives you access to the raw upload stream as it’s being chunked by the client. This lets you do things like <a href="https://fly.io/phoenix-files/streaming-uploads-with-liveview/">stream uploads to a different server</a> or <a href="https://youtu.be/GICJ42OyBGg?si=8SaAL2Sh74qsFaI3&t=1930">transcode a video as it’s being uploaded</a>.</p>
<p>
Since the uploads happen over the existing LiveView connection, reflecting the upload progress or advanced file operations <a href="https://github.com/fly-apps/thumbnail_generator/blob/ce7e2ede394eed3b2a1b1aa5e41d323643950f5e/lib/thumbs_web/live/home_live.ex">become trivial to implement</a>:</p>
<video src="/images/blog/lv-1.0/puppy.mp4" autoplay="autoplay" loop="loop" controls="controls">
</video>
<h2>
Streams and Async</h2>
<p>
Following uploads, we shipped a streams primitive for efficiently handling large collections without needing to hold those collections in server memory. We also introduced <code class="inline">assign_async</code> and <code class="inline">start_async</code> primitives, which makes handling async operations and rendering async results a breeze.</p>
<p>
For example, imagine you have an expensive operation that calls out to an external service. The results can be latent or spotty, or both.  Your LiveView can use <code class="inline">assign_async/2</code> to offload this operation to a new process and <code class="inline">&lt;.async_result&gt;</code> to render the results with each loading, success, or failure state.</p>
<pre><code class="makeup elixir"><span class="kd">def</span><span class="w"> </span><span class="nf">render</span><span class="p" data-group-id="9962440478-1">(</span><span class="n">assigns</span><span class="p" data-group-id="9962440478-1">)</span><span class="w"> </span><span class="k" data-group-id="9962440478-2">do</span><span class="w">
  </span><span class="sx">~H&quot;&quot;&quot;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nf">.async_result</span><span class="w"> </span><span class="na">:let</span><span class="p">=</span><span class="p" data-group-id="2324681729-1">{</span><span class="n">org</span><span class="p" data-group-id="2324681729-1">}</span><span class="w"> </span><span class="na">assign</span><span class="p">=</span><span class="p" data-group-id="2324681729-2">{</span><span class="na">@org</span><span class="p" data-group-id="2324681729-2">}</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="ss">:loading</span><span class="p">&gt;</span><span class="n">Loading organization </span><span class="p">&lt;</span><span class="nf">.spinner</span><span class="w"> </span><span class="p">/&gt;</span><span class="p">&lt;/</span><span class="ss">:loading</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="ss">:failed</span><span class="w"> </span><span class="na">:let</span><span class="p">=</span><span class="p" data-group-id="2324681729-3">{</span><span class="c">_failure</span><span class="p" data-group-id="2324681729-3">}</span><span class="p">&gt;</span><span class="n">there was an error loading the organization</span><span class="p">&lt;/</span><span class="ss">:failed</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p" data-group-id="2324681729-4">{</span><span class="n">org</span><span class="o">.</span><span class="n">name</span><span class="p" data-group-id="2324681729-4">}</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;/</span><span class="nf">.async_result</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="sx">&quot;&quot;&quot;</span><span class="w">
</span><span class="k" data-group-id="9962440478-2">end</span><span class="w">

</span><span class="kd">def</span><span class="w"> </span><span class="nf">mount</span><span class="p" data-group-id="9962440478-3">(</span><span class="p" data-group-id="9962440478-4">%{</span><span class="s">&quot;slug&quot;</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="n">slug</span><span class="p" data-group-id="9962440478-4">}</span><span class="p">,</span><span class="w"> </span><span class="bp">_</span><span class="p">,</span><span class="w"> </span><span class="n">socket</span><span class="p" data-group-id="9962440478-3">)</span><span class="w"> </span><span class="k" data-group-id="9962440478-5">do</span><span class="w">
  </span><span class="p" data-group-id="9962440478-6">{</span><span class="ss">:ok</span><span class="p">,</span><span class="w"> </span><span class="n">assign_async</span><span class="p" data-group-id="9962440478-7">(</span><span class="ss">:org</span><span class="p">,</span><span class="w"> </span><span class="k" data-group-id="9962440478-8">fn</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="p" data-group-id="9962440478-9">{</span><span class="ss">:ok</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="9962440478-10">%{</span><span class="ss">org</span><span class="p">:</span><span class="w"> </span><span class="n">fetch_org</span><span class="p" data-group-id="9962440478-11">(</span><span class="n">slug</span><span class="p" data-group-id="9962440478-11">)</span><span class="p" data-group-id="9962440478-10">}</span><span class="p" data-group-id="9962440478-9">}</span><span class="w"> </span><span class="k" data-group-id="9962440478-8">end</span><span class="p" data-group-id="9962440478-7">)</span><span class="p" data-group-id="9962440478-6">}</span><span class="w">
</span><span class="k" data-group-id="9962440478-5">end</span></code></pre>
<p>
Now instead of worrying about an async task crashing the UI, or carefully monitoring async ops while updating the template with a bunch of conditionals, you have a single abstraction for performing the work and rendering the results. As soon as the LiveView disconnects, the async processes are cleaned up, ensuring no wasted resources go to a UI that is no longer around.</p>
<p>
Here we can also see slots in action with the <code class="inline">&lt;:loading&gt;</code> and <code class="inline">&lt;:failed&gt;</code> slots of the  <code class="inline">&lt;.async_result&gt;</code> function component. Slots allow the caller to extend components with their own dynamic content, including their own markup and function component calls.</p>
<h2>
LiveView goes mainstream</h2>
<p>
LiveView and <a href="https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor">.NET Blazor</a> both started about the same time. I like to think both projects helped spearhead the adoption of this programming model.</p>
<p>
Since getting started, this model has been embraced in various ways in the Go, Rust, Java, PHP, JavaScript, Ruby, and Haskell communities. And I’m sure others I haven’t yet heard of.</p>
<p>
Most don’t offer LiveView’s declarative model. Instead developers are required to annotate how individual elements are updated and removed, leading to fragile applications, akin to client-side applications before the introduction of React and other declarative frameworks. Most also lack the optimizations LiveView developers get for free. Large payloads are sent on every event unless developers manually fine tune them.</p>
<p>
React itself liked the idea of putting React on the server so much, they shipped their own <a href="https://react.dev/reference/rsc/server-components">React Server Components</a> to tackle a cross section of similar goals with LiveView. In the case of RSC, pushing real-time events are left to external means.</p>
<p>
React, like most, chose different tradeoffs because <em>they had no choice</em>. The majority skip the stateful, bidirectional communication layer because most platforms are poorly suited for it. Elixir and the Erlang VM are truly what make this programming model shine. And we have only barely discussed our built-in globally distributed clustering and PubSub. There are truly extraordinary features built into the platform that are at your fingertips.</p>
<h2>
Try it out</h2>
<p>
Now is a great time to dive in and give LiveView a try! We have launched <code class="inline">new.phoenixframework.org</code> which lets you get up and running in seconds with Elixir and your first Phoenix project with a single command:</p>
<p>
For osx/linux:</p>
<pre><code class="makeup bash">$ curl https://new.phoenixframework.org/myapp | sh</code></pre>
<p>
For Windows PowerShell:</p>
<pre><code class="makeup cmd">&gt; curl.exe -fsSO https://new.phoenixframework.org/app.bat; .\app.bat</code></pre>
<p>
For existing applications, check the <a href="https://github.com/phoenixframework/phoenix_live_view/blob/main/CHANGELOG.md">changelog</a> for breaking changes to bring your existing apps up to speed.</p>
<h2>
What’s Next</h2>
<p>
Following this release, we’ll be continuing efforts around collocated JavaScript hooks, enhancing Web Component integration, supporting navigation guards, and more as outlined in our issue tracker.</p>
<h2>
Special Thanks</h2>
<p>
Arriving here wouldn’t have been possible without the help of the Phoenix team, especially Steffen Deusch, who has tackled countless LiveView issues over the last year.</p>
<p>
Happy Hacking!</p>
<p>
–Chris</p>
]]></content>
  </entry>

  <entry>
    <title>Phoenix LiveView 0.19 released</title>
    <link rel="alternate" href="http://www.phoenixframework.org/blog/phoenix-liveview-0.19-released" />
    <id>http://www.phoenixframework.org/blog/phoenix-liveview-0.19-released</id>
    <updated>2023-05-29T00:00:00Z</updated>
    <author>
      <name>Chris McCord</name>
    </author>
    <summary type="html"><![CDATA[LiveView 0.19.0 is out! This release includes long awaited dynamic form features, new stream primitives, and closes the gap towards a 1.0 release.]]></summary>
    <content type="html"><![CDATA[<p>
LiveView 0.19.0 is out! This release includes long awaited dynamic form features, new stream primitives, and closes the gap on what you wished was possible with LiveView. It’s our last planned major release ahead of LiveView 1.0.</p>
<h3>
Open source <code class="inline">TodoTrek</code> showcase application</h3>
<p>
As demo’d and as promised in my <a href="https://www.youtube.com/watch?v=FADQAnq0RpA">ElixirConfEU keynote</a>, I’m open sourcing the <a href="https://github.com/chrismccord/todo_trek">TodoTrek</a> application, a Trello-like app which showcases the new stream features like drag-and-drop, infinite scrolling, dynamic forms, and more.</p>
<p>
Here’s TodoTrek in action:</p>
<video src="/images/blog/todo_trek.mp4" autoplay="autoplay" loop="loop" controls="controls">
</video>
<p>
Now onto the features!</p>
<h3>
Enhanced Stream interface with resets and limits</h3>
<p>
Streams in LiveView 0.18 introduced a powerful way to handle large collections on the client without storing the collection in server memory. Streams allow developers to append, prepend, or arbitrary insert and update items in collection on the UI. This opened the door to many SPA-like usecases, such as realtime timelines, and infinite feeds, but a couple primitives were lacking.</p>
<p>
With LiveView 0.19, streams close the gap on those primitives. Streams can be limited on the UI with the <code class="inline">:limit</code> option. This allows the developer to instruct the UI to keep the first N, or N items in collection when inserting new items to a stream. This is essential for a number of cases, such as the prevention of overwhelming clients with too much data in the DOM. Combined with new viewport bindings, virtualized, infinite lists can be implemented effortless, which we’ll see in a moment.</p>
<p>
In addition to stream limits, streams also now support a <code class="inline">:reset</code> optionw, whichs clears the stream container on the client when updating the stream. This is useful for any case you need to clear results or reset a list, such as search autocomplete, or traditional pagination.</p>
<p>
With just a simple <code class="inline">stream(socket, :posts, posts, limit: 10)</code> or <code class="inline">stream(socket, :posts, [], reset: true)</code>, you can now accomplish complex client collection handling without having to think about it. But that’s just the start of what’s possible.</p>
<h2>
Nested streams with drop-in drag and drop</h2>
<p>
LiveView 0.19 supports nested streams, which allows drag and drop to be implemented in just a few lines of code. Imagine a trello board where your have named lists holding todo items. You can drag an drop re-order the lists themselves, or todos within the lists. You can also drag and drop todos across lists. All this is possible in LiveView now with a shockingly small amount of code on the client and server. For client-side drag-and-drop itself, you can bring your own library and integrate with a dozen of lines of code. For example, here’s the <code class="inline">TodoTrek</code> drag and drop for handling todos and lists:</p>
<pre><code class="makeup javascript"><span class="k">import</span><span class="w"> </span><span class="nv">Sortable</span><span class="w"> </span><span class="k">from</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">../vendor/sortable</span><span class="p">&quot;</span><span class="w">
</span><span class="bp">Hooks</span><span class="p">.</span><span class="n">Sortable</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="w">  </span><span class="nf">mounted</span><span class="p">(</span><span class="p">)</span><span class="p">{</span><span class="w">
</span><span class="w">    </span><span class="kt">let</span><span class="w"> </span><span class="nv">group</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">this</span><span class="p">.</span><span class="n">el</span><span class="p">.</span><span class="n">dataset</span><span class="p">.</span><span class="n">group</span><span class="w">
</span><span class="w">    </span><span class="kt">let</span><span class="w"> </span><span class="nv">sorter</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="nv">Sortable</span><span class="p">(</span><span class="nb">this</span><span class="p">.</span><span class="n">el</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="w">      </span><span class="n">group</span><span class="p">:</span><span class="w"> </span><span class="nv">group</span><span class="w"> </span><span class="o">?</span><span class="w"> </span><span class="p">{</span><span class="n">name</span><span class="p">:</span><span class="w"> </span><span class="nv">group</span><span class="p">,</span><span class="w"> </span><span class="n">pull</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="n">put</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">}</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kc">undefined</span><span class="p">,</span><span class="w">
</span><span class="w">      </span><span class="n">animation</span><span class="p">:</span><span class="w"> </span><span class="mi">150</span><span class="p">,</span><span class="w">
</span><span class="w">      </span><span class="n">dragClass</span><span class="p">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">drag-item</span><span class="p">&quot;</span><span class="p">,</span><span class="w">
</span><span class="w">      </span><span class="n">ghostClass</span><span class="p">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">drag-ghost</span><span class="p">&quot;</span><span class="p">,</span><span class="w">
</span><span class="w">      </span><span class="nf">onEnd</span><span class="p">:</span><span class="w"> </span><span class="nv">e</span><span class="w"> </span><span class="kt">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="w">        </span><span class="kt">let</span><span class="w"> </span><span class="nv">params</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="n">old</span><span class="p">:</span><span class="w"> </span><span class="nv">e</span><span class="p">.</span><span class="n">oldIndex</span><span class="p">,</span><span class="w"> </span><span class="n">new</span><span class="p">:</span><span class="w"> </span><span class="nv">e</span><span class="p">.</span><span class="n">newIndex</span><span class="p">,</span><span class="w"> </span><span class="n">to</span><span class="p">:</span><span class="w"> </span><span class="nv">e</span><span class="p">.</span><span class="n">to</span><span class="p">.</span><span class="n">dataset</span><span class="p">,</span><span class="w"> </span><span class="o">...</span><span class="nv">e</span><span class="p">.</span><span class="n">item</span><span class="p">.</span><span class="n">dataset</span><span class="p">}</span><span class="w">
</span><span class="w">        </span><span class="nb">this</span><span class="p">.</span><span class="nf">pushEventTo</span><span class="p">(</span><span class="nb">this</span><span class="p">.</span><span class="n">el</span><span class="p">,</span><span class="w"> </span><span class="nb">this</span><span class="p">.</span><span class="n">el</span><span class="p">.</span><span class="n">dataset</span><span class="p">[</span><span class="p">&quot;</span><span class="s2">drop</span><span class="p">&quot;</span><span class="p">]</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">reposition</span><span class="p">&quot;</span><span class="p">,</span><span class="w"> </span><span class="nv">params</span><span class="p">)</span><span class="w">
</span><span class="w">      </span><span class="p">}</span><span class="w">
</span><span class="w">    </span><span class="p">}</span><span class="p">)</span><span class="w">
</span><span class="w">  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span></code></pre>
<p>
Here we import <a href="https://sortablejs.github.io/Sortable/">sortable.js</a>, then wire it up as a <code class="inline">phx-hook</code>. Now any stream container can wire up streams with drag-and-drop with the following markup:</p>
<pre><code class="makeup heex"><span class="p">&lt;</span><span class="nt">div</span><span class="w"> </span><span class="na">id</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">t</span><span class="s2">o</span><span class="s2">d</span><span class="s2">o</span><span class="s2">s</span><span class="p">&quot;</span><span class="w"> </span><span class="na">phx-update</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">stream</span><span class="p">&quot;</span><span class="w"> </span><span class="na">phx-hook</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">Sortable</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="w">
</span><span class="n">  ...
</span><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span></code></pre>
<p>
When an item is dropped, the LiveView will receive a “reposition” event with the new and old indexes, plus whatever data attributes exists.</p>
<h2>
Viewport bindings for virtualized, infinite scrolling</h2>
<p>
LiveView 0.19 introduces two new <code class="inline">phx</code> bindings for handling viewport events – <code class="inline">phx-viewport-top</code> and <code class="inline">phx-viewport-bottom</code>. These events a triggered when the first child of a container reaches the top of the viewport, or the last child reaches the bottom of the viewport. Combining these two events with stream limits, allows you to perform “virtualzed” infinite scrolling, where only a small set of the items exist in the DOM, while the user experiences the list a large, or infinite set of items. Prior to LiveView 0.19, this kind of feature required users to escape to complex JavaScript hooks, but no more.</p>
<h2>
Dynamic forms with new <code class="inline">inputs_for</code></h2>
<p>
Dynamicaly adding and removing inputs with <code class="inline">inputs_for</code> is now supported by rendering checkboxes for inserts and removals. Libraries such as Ecto, or custom param filtering, can inspect the paramters and handle the added or removed fields. This can be combined with <code class="inline">Ecto.Changeset.cast/3</code>‘s <code class="inline">:sort_param</code> and <code class="inline">:drop_param</code> options. For example, imagine a parent <code class="inline">Ecto.Schema</code> with an <code class="inline">:emails</code> <code class="inline">has_many</code> or <code class="inline">embeds_many</code> association. To cast the user input from a nested <code class="inline">inputs_for</code>, one simply needs to configure the options:</p>
<pre><code class="makeup elixir"><span class="n">schema</span><span class="w"> </span><span class="s">&quot;lists&quot;</span><span class="w"> </span><span class="k" data-group-id="2328770371-1">do</span><span class="w">
  </span><span class="n">field</span><span class="w"> </span><span class="ss">:title</span><span class="p">,</span><span class="w"> </span><span class="ss">:string</span><span class="w">

  </span><span class="n">embeds_many</span><span class="w"> </span><span class="ss">:emails</span><span class="p">,</span><span class="w"> </span><span class="nc">EmailNotification</span><span class="p">,</span><span class="w"> </span><span class="ss">on_replace</span><span class="p">:</span><span class="w"> </span><span class="ss">:delete</span><span class="w"> </span><span class="k" data-group-id="2328770371-2">do</span><span class="w">
    </span><span class="n">field</span><span class="w"> </span><span class="ss">:email</span><span class="p">,</span><span class="w"> </span><span class="ss">:string</span><span class="w">
    </span><span class="n">field</span><span class="w"> </span><span class="ss">:name</span><span class="p">,</span><span class="w"> </span><span class="ss">:string</span><span class="w">
  </span><span class="k" data-group-id="2328770371-2">end</span><span class="w">
</span><span class="k" data-group-id="2328770371-1">end</span><span class="w">

</span><span class="kd">def</span><span class="w"> </span><span class="nf">changeset</span><span class="p" data-group-id="2328770371-3">(</span><span class="n">list</span><span class="p">,</span><span class="w"> </span><span class="n">attrs</span><span class="p" data-group-id="2328770371-3">)</span><span class="w"> </span><span class="k" data-group-id="2328770371-4">do</span><span class="w">
  </span><span class="n">list</span><span class="w">
  </span><span class="o">|&gt;</span><span class="w"> </span><span class="n">cast</span><span class="p" data-group-id="2328770371-5">(</span><span class="n">attrs</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="2328770371-6">[</span><span class="ss">:title</span><span class="p" data-group-id="2328770371-6">]</span><span class="p" data-group-id="2328770371-5">)</span><span class="w">
  </span><span class="o">|&gt;</span><span class="w"> </span><span class="n">cast_embed</span><span class="p" data-group-id="2328770371-7">(</span><span class="ss">:emails</span><span class="p">,</span><span class="w">
    </span><span class="ss">with</span><span class="p">:</span><span class="w"> </span><span class="o">&amp;</span><span class="n">email_changeset</span><span class="o">/</span><span class="mi">2</span><span class="p">,</span><span class="w">
    </span><span class="ss">sort_param</span><span class="p">:</span><span class="w"> </span><span class="ss">:emails_sort</span><span class="p">,</span><span class="w">
    </span><span class="ss">drop_param</span><span class="p">:</span><span class="w"> </span><span class="ss">:emails_drop</span><span class="w">
  </span><span class="p" data-group-id="2328770371-7">)</span><span class="w">
</span><span class="k" data-group-id="2328770371-4">end</span></code></pre>
<p>
Here we see the <code class="inline">:sort_param</code> and <code class="inline">:drop_param</code> options in action.</p>
<blockquote>
  <p>
<em>Note</em>: <code class="inline">on_replace: :delete</code> on the <code class="inline">has_many</code> and <code class="inline">embeds_many</code> is required when using these options.  </p>
</blockquote>
<p>
When Ecto sees the specified sort or drop parameter from the form, it will sort the children based on the order they appear in the form, add new children it hasn’t seen, or drop children if the drop parameter intructs it to do so.</p>
<p>
The markup for such a schema and association would look like this:</p>
<pre><code class="makeup heex"><span class="p">&lt;</span><span class="nf">.inputs_for</span><span class="w"> </span><span class="na">:let</span><span class="p">=</span><span class="p" data-group-id="9199234479-1">{</span><span class="n">ef</span><span class="p" data-group-id="9199234479-1">}</span><span class="w"> </span><span class="na">field</span><span class="p">=</span><span class="p" data-group-id="9199234479-2">{</span><span class="na">@form</span><span class="p" data-group-id="9199234479-ex-1">[</span><span class="ss">:emails</span><span class="p" data-group-id="9199234479-ex-1">]</span><span class="p" data-group-id="9199234479-2">}</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nt">input</span><span class="w"> </span><span class="na">type</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">hidden</span><span class="p">&quot;</span><span class="w"> </span><span class="na">name</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">list[emails_sort][]</span><span class="p">&quot;</span><span class="w"> </span><span class="na">value</span><span class="p">=</span><span class="p" data-group-id="9199234479-3">{</span><span class="n">ef</span><span class="o">.</span><span class="n">index</span><span class="p" data-group-id="9199234479-3">}</span><span class="w"> </span><span class="p">/&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nf">.input</span><span class="w"> </span><span class="na">type</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">text</span><span class="p">&quot;</span><span class="w"> </span><span class="na">field</span><span class="p">=</span><span class="p" data-group-id="9199234479-4">{</span><span class="n">ef</span><span class="p" data-group-id="9199234479-ex-2">[</span><span class="ss">:email</span><span class="p" data-group-id="9199234479-ex-2">]</span><span class="p" data-group-id="9199234479-4">}</span><span class="w"> </span><span class="na">placeholder</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">email</span><span class="p">&quot;</span><span class="w"> </span><span class="p">/&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nf">.input</span><span class="w"> </span><span class="na">type</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">text</span><span class="p">&quot;</span><span class="w"> </span><span class="na">field</span><span class="p">=</span><span class="p" data-group-id="9199234479-5">{</span><span class="n">ef</span><span class="p" data-group-id="9199234479-ex-3">[</span><span class="ss">:name</span><span class="p" data-group-id="9199234479-ex-3">]</span><span class="p" data-group-id="9199234479-5">}</span><span class="w"> </span><span class="na">placeholder</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">name</span><span class="p">&quot;</span><span class="w"> </span><span class="p">/&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nt">label</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nt">input</span><span class="w"> </span><span class="na">type</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">checkbox</span><span class="p">&quot;</span><span class="w"> </span><span class="na">name</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">list[emails_drop][]</span><span class="p">&quot;</span><span class="w"> </span><span class="na">value</span><span class="p">=</span><span class="p" data-group-id="9199234479-6">{</span><span class="n">ef</span><span class="o">.</span><span class="n">index</span><span class="p" data-group-id="9199234479-6">}</span><span class="w"> </span><span class="na">class</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">h</span><span class="s2">i</span><span class="s2">d</span><span class="s2">d</span><span class="s2">e</span><span class="s2">n</span><span class="p">&quot;</span><span class="w"> </span><span class="p">/&gt;</span><span class="w">
</span><span class="n">    delete
</span><span class="w">  </span><span class="p">&lt;/</span><span class="nt">label</span><span class="p">&gt;</span><span class="w">
</span><span class="p">&lt;/</span><span class="nf">.inputs_for</span><span class="p">&gt;</span><span class="w">
</span><span class="w">
</span><span class="p">&lt;</span><span class="nt">label</span><span class="w"> </span><span class="na">class</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">b</span><span class="s2">l</span><span class="s2">o</span><span class="s2">c</span><span class="s2">k </span><span class="s2">c</span><span class="s2">u</span><span class="s2">r</span><span class="s2">s</span><span class="s2">o</span><span class="s2">r</span><span class="s2">-</span><span class="s2">p</span><span class="s2">o</span><span class="s2">i</span><span class="s2">n</span><span class="s2">t</span><span class="s2">e</span><span class="s2">r</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nt">input</span><span class="w"> </span><span class="na">type</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">checkbox</span><span class="p">&quot;</span><span class="w"> </span><span class="na">name</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">list[emails_sort][]</span><span class="p">&quot;</span><span class="w"> </span><span class="na">class</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">h</span><span class="s2">i</span><span class="s2">d</span><span class="s2">d</span><span class="s2">e</span><span class="s2">n</span><span class="p">&quot;</span><span class="w"> </span><span class="p">/&gt;</span><span class="w">
</span><span class="n">  add more
</span><span class="p">&lt;/</span><span class="nt">label</span><span class="p">&gt;</span></code></pre>
<p>
We used <code class="inline">inputs_for</code> to render inputs for the <code class="inline">:emails</code> association, which containes an email address and name input for each child. Within the nested inputs, we render a hidden <code class="inline">list[emails_sort][]</code> input, which is set to the index of the given child. This tells Ecto’s cast operation how to sort existing children, or where to insert new children. Next, we render the email and name inputs as usual. Then we render a label containing the “delete” text and a hidden checkbox input with the name <code class="inline">list[emails_drop][]</code>, containing the index of the child as its value. Like before, this tells Ecto to delete the child at this index when the checkbox is checked. Wrapping the checkbox and textual content in a label makes any clicked content within the label check and uncheck the checkbox.</p>
<p>
Finally, outside the <code class="inline">inputs_for</code>, we render another label with a value-less <code class="inline">list[emails_sort][]</code> checkbox with accompanied “add more” text. Ecto will treat unknown sort params as new children and build a new child.</p>
<p>
One great thing about this approach is allows both LiveView and traditional views to use the same form and casting primitives.</p>
<h2>
Closing the gap on what’s possible</h2>
<p>
The features in 0.19 allows effortless implementation of all kinds of usecases previously relegated to single page applications. Think chat messaging with infinite scroll history like Slack, or bidirectional social timelines like Twitter, or Todo apps like Trello with nested drag and drop re-ordering. It’s now possible without having to even think about the necesary details on the client.
This release sets us up to focus on getting LiveView 1.0 out the door. Stay tuned!</p>
<p>
Happy coding!</p>
<p>
–Chris</p>
]]></content>
  </entry>

  <entry>
    <title>Phoenix 1.7.2 released</title>
    <link rel="alternate" href="http://www.phoenixframework.org/blog/phoenix-1.7-2-released" />
    <id>http://www.phoenixframework.org/blog/phoenix-1.7-2-released</id>
    <updated>2023-03-20T00:00:00Z</updated>
    <author>
      <name>Chris McCord</name>
    </author>
    <summary type="html"><![CDATA[Phoenix 1.7.2 is out! This release includes a new socket drain feature and `--no-tailwind` flag]]></summary>
    <content type="html"><![CDATA[<p>
Phoenix 1.7.2 is out! This minor release includes a couple features worth talking about. Let’s get to it!</p>
<h3>
Easier Tailwind opt-out</h3>
<p>
Phoenix 1.7 included <a href="https://tailwindcss.com">tailwindcss</a> 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 <code class="inline">tailwind.config.js</code> file and the dev <code class="inline">:watchers</code> configuration. We introduced a <code class="inline">mix phx.new --no-tailwind</code> flag to let folks skip these steps when generating a new project.</p>
<h3>
Channel and LiveView Socket Draining</h3>
<p>
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 <code class="inline">socket</code> macro in your endpoint.</p>
<p>
From the docs:</p>
<pre><code class="makeup elixir"><span class="w">  </span><span class="na">@doc</span><span class="w"> </span><span class="s">&quot;&quot;&quot;
  ...
  * `: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.
  &quot;&quot;&quot;</span></code></pre>
<p>
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.</p>
<h3>
JS.exec</h3>
<p>
We introduced a new <code class="inline">JS</code> 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 <code class="inline">core_components.ex</code> module previously contained a duplicated command for closing the modal in multiple places:</p>
<pre><code class="makeup elixir"><span class="w">  </span><span class="kd">def</span><span class="w"> </span><span class="nf">modal</span><span class="p" data-group-id="5838286003-1">(</span><span class="n">assigns</span><span class="p" data-group-id="5838286003-1">)</span><span class="w"> </span><span class="k" data-group-id="5838286003-2">do</span><span class="w">
    </span><span class="sx">~H&quot;&quot;&quot;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nt">div</span><span class="w">
</span><span class="w">      </span><span class="na">id</span><span class="p">=</span><span class="p" data-group-id="0075059411-1">{</span><span class="na">@id</span><span class="p" data-group-id="0075059411-1">}</span><span class="w">
</span><span class="w">      </span><span class="na">phx-mounted</span><span class="p">=</span><span class="p" data-group-id="0075059411-2">{</span><span class="na">@show</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="n">show_modal</span><span class="p" data-group-id="5838286003-3">(</span><span class="na">@id</span><span class="p" data-group-id="5838286003-3">)</span><span class="p" data-group-id="0075059411-2">}</span><span class="w">
</span><span class="w">      </span><span class="na">phx-remove</span><span class="p">=</span><span class="p" data-group-id="0075059411-3">{</span><span class="n">hide_modal</span><span class="p" data-group-id="5838286003-4">(</span><span class="na">@id</span><span class="p" data-group-id="5838286003-4">)</span><span class="p" data-group-id="0075059411-3">}</span><span class="w">
</span><span class="w">    </span><span class="p">&gt;</span><span class="w">
</span><span class="w">      </span><span class="p">&lt;</span><span class="nt">div</span><span class="w"> </span><span class="na">id</span><span class="p">=</span><span class="p" data-group-id="0075059411-4">{</span><span class="s">&quot;</span><span class="si" data-group-id="5838286003-5">#{</span><span class="na">@id</span><span class="si" data-group-id="5838286003-5">}</span><span class="s">-bg&quot;</span><span class="p" data-group-id="0075059411-4">}</span><span class="p">&gt;</span><span class="w">
</span><span class="w">      </span><span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span><span class="w">
</span><span class="w">        </span><span class="p">&lt;</span><span class="nt">div</span><span class="w"> </span><span class="na">class</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">f</span><span class="s2">l</span><span class="s2">e</span><span class="s2">x </span><span class="s2">m</span><span class="s2">i</span><span class="s2">n</span><span class="s2">-</span><span class="s2">h</span><span class="s2">-</span><span class="s2">f</span><span class="s2">u</span><span class="s2">l</span><span class="s2">l </span><span class="s2">i</span><span class="s2">t</span><span class="s2">e</span><span class="s2">m</span><span class="s2">s</span><span class="s2">-</span><span class="s2">c</span><span class="s2">e</span><span class="s2">n</span><span class="s2">t</span><span class="s2">e</span><span class="s2">r </span><span class="s2">j</span><span class="s2">u</span><span class="s2">s</span><span class="s2">t</span><span class="s2">i</span><span class="s2">f</span><span class="s2">y</span><span class="s2">-</span><span class="s2">c</span><span class="s2">e</span><span class="s2">n</span><span class="s2">t</span><span class="s2">e</span><span class="s2">r</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="w">
</span><span class="w">          </span><span class="p">&lt;</span><span class="nt">div</span><span class="w"> </span><span class="na">class</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">w</span><span class="s2">-</span><span class="s2">f</span><span class="s2">u</span><span class="s2">l</span><span class="s2">l </span><span class="s2">m</span><span class="s2">a</span><span class="s2">x</span><span class="s2">-</span><span class="s2">w</span><span class="s2">-</span><span class="s2">3</span><span class="s2">x</span><span class="s2">l </span><span class="s2">p</span><span class="s2">-</span><span class="s2">4 </span><span class="s2">s</span><span class="s2">m</span><span class="s2">:</span><span class="s2">p</span><span class="s2">-</span><span class="s2">6 </span><span class="s2">l</span><span class="s2">g</span><span class="s2">:</span><span class="s2">p</span><span class="s2">y</span><span class="s2">-</span><span class="s2">8</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="w">
</span><span class="w">            </span><span class="p">&lt;</span><span class="nf">.focus_wrap</span><span class="w">
</span><span class="w">              </span><span class="na">id</span><span class="p">=</span><span class="p" data-group-id="0075059411-5">{</span><span class="s">&quot;</span><span class="si" data-group-id="5838286003-6">#{</span><span class="na">@id</span><span class="si" data-group-id="5838286003-6">}</span><span class="s">-container&quot;</span><span class="p" data-group-id="0075059411-5">}</span><span class="w">
</span><span class="w">              </span><span class="na">phx-window-keydown</span><span class="p">=</span><span class="p" data-group-id="0075059411-6">{</span><span class="n">hide_modal</span><span class="p" data-group-id="5838286003-7">(</span><span class="na">@on_cancel</span><span class="p">,</span><span class="w"> </span><span class="na">@id</span><span class="p" data-group-id="5838286003-7">)</span><span class="p" data-group-id="0075059411-6">}</span><span class="w">
</span><span class="w">              </span><span class="na">phx-key</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">escape</span><span class="p">&quot;</span><span class="w">
</span><span class="w">              </span><span class="na">phx-click-away</span><span class="p">=</span><span class="p" data-group-id="0075059411-7">{</span><span class="n">hide_modal</span><span class="p" data-group-id="5838286003-8">(</span><span class="na">@on_cancel</span><span class="p">,</span><span class="w"> </span><span class="na">@id</span><span class="p" data-group-id="5838286003-8">)</span><span class="p" data-group-id="0075059411-7">}</span><span class="w">
</span><span class="w">    </span><span class="sx">&quot;&quot;&quot;</span><span class="w">
  </span><span class="k" data-group-id="5838286003-2">end</span></code></pre>
<p>
We had the same command for <code class="inline">phx-window-keydown</code> and <code class="inline">phx-click-away</code>, and a similar command on <code class="inline">phx-remove</code>. These hide the modal and invoke the caller’s own command both on <code class="inline">ESC</code> 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:</p>
<pre><code class="makeup elixir"><span class="w">  </span><span class="kd">def</span><span class="w"> </span><span class="nf">modal</span><span class="p" data-group-id="8562706059-1">(</span><span class="n">assigns</span><span class="p" data-group-id="8562706059-1">)</span><span class="w"> </span><span class="k" data-group-id="8562706059-2">do</span><span class="w">
    </span><span class="sx">~H&quot;&quot;&quot;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nt">div</span><span class="w">
</span><span class="w">      </span><span class="na">id</span><span class="p">=</span><span class="p" data-group-id="6501047828-1">{</span><span class="na">@id</span><span class="p" data-group-id="6501047828-1">}</span><span class="w">
</span><span class="w">      </span><span class="na">phx-mounted</span><span class="p">=</span><span class="p" data-group-id="6501047828-2">{</span><span class="na">@show</span><span class="w"> </span><span class="o">&amp;&amp;</span><span class="w"> </span><span class="n">show_modal</span><span class="p" data-group-id="8562706059-3">(</span><span class="na">@id</span><span class="p" data-group-id="8562706059-3">)</span><span class="p" data-group-id="6501047828-2">}</span><span class="w">
</span><span class="w">      </span><span class="na">phx-remove</span><span class="p">=</span><span class="p" data-group-id="6501047828-3">{</span><span class="n">hide_modal</span><span class="p" data-group-id="8562706059-4">(</span><span class="na">@id</span><span class="p" data-group-id="8562706059-4">)</span><span class="p" data-group-id="6501047828-3">}</span><span class="w">
</span><span class="w">      </span><span class="na">data-cancel</span><span class="p">=</span><span class="p" data-group-id="6501047828-4">{</span><span class="nc">JS</span><span class="o">.</span><span class="n">exec</span><span class="p" data-group-id="8562706059-5">(</span><span class="na">@on_cancel</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;phx-remove&quot;</span><span class="p" data-group-id="8562706059-5">)</span><span class="p" data-group-id="6501047828-4">}</span><span class="w">
</span><span class="w">    </span><span class="p">&gt;</span><span class="w">
</span><span class="w">      </span><span class="p">&lt;</span><span class="nt">div</span><span class="w"> </span><span class="na">id</span><span class="p">=</span><span class="p" data-group-id="6501047828-5">{</span><span class="s">&quot;</span><span class="si" data-group-id="8562706059-6">#{</span><span class="na">@id</span><span class="si" data-group-id="8562706059-6">}</span><span class="s">-bg&quot;</span><span class="p" data-group-id="6501047828-5">}</span><span class="p">&gt;</span><span class="w">
</span><span class="w">      </span><span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span><span class="w">
</span><span class="w">        </span><span class="p">&lt;</span><span class="nt">div</span><span class="w"> </span><span class="na">class</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">f</span><span class="s2">l</span><span class="s2">e</span><span class="s2">x </span><span class="s2">m</span><span class="s2">i</span><span class="s2">n</span><span class="s2">-</span><span class="s2">h</span><span class="s2">-</span><span class="s2">f</span><span class="s2">u</span><span class="s2">l</span><span class="s2">l </span><span class="s2">i</span><span class="s2">t</span><span class="s2">e</span><span class="s2">m</span><span class="s2">s</span><span class="s2">-</span><span class="s2">c</span><span class="s2">e</span><span class="s2">n</span><span class="s2">t</span><span class="s2">e</span><span class="s2">r </span><span class="s2">j</span><span class="s2">u</span><span class="s2">s</span><span class="s2">t</span><span class="s2">i</span><span class="s2">f</span><span class="s2">y</span><span class="s2">-</span><span class="s2">c</span><span class="s2">e</span><span class="s2">n</span><span class="s2">t</span><span class="s2">e</span><span class="s2">r</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="w">
</span><span class="w">          </span><span class="p">&lt;</span><span class="nt">div</span><span class="w"> </span><span class="na">class</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">w</span><span class="s2">-</span><span class="s2">f</span><span class="s2">u</span><span class="s2">l</span><span class="s2">l </span><span class="s2">m</span><span class="s2">a</span><span class="s2">x</span><span class="s2">-</span><span class="s2">w</span><span class="s2">-</span><span class="s2">3</span><span class="s2">x</span><span class="s2">l </span><span class="s2">p</span><span class="s2">-</span><span class="s2">4 </span><span class="s2">s</span><span class="s2">m</span><span class="s2">:</span><span class="s2">p</span><span class="s2">-</span><span class="s2">6 </span><span class="s2">l</span><span class="s2">g</span><span class="s2">:</span><span class="s2">p</span><span class="s2">y</span><span class="s2">-</span><span class="s2">8</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="w">
</span><span class="w">            </span><span class="p">&lt;</span><span class="nf">.focus_wrap</span><span class="w">
</span><span class="w">              </span><span class="na">id</span><span class="p">=</span><span class="p" data-group-id="6501047828-6">{</span><span class="s">&quot;</span><span class="si" data-group-id="8562706059-7">#{</span><span class="na">@id</span><span class="si" data-group-id="8562706059-7">}</span><span class="s">-container&quot;</span><span class="p" data-group-id="6501047828-6">}</span><span class="w">
</span><span class="w">              </span><span class="na">phx-window-keydown</span><span class="p">=</span><span class="p" data-group-id="6501047828-7">{</span><span class="nc">JS</span><span class="o">.</span><span class="n">exec</span><span class="p" data-group-id="8562706059-8">(</span><span class="s">&quot;data-cancel&quot;</span><span class="p">,</span><span class="w"> </span><span class="ss">to</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;#</span><span class="si" data-group-id="8562706059-9">#{</span><span class="na">@id</span><span class="si" data-group-id="8562706059-9">}</span><span class="s">&quot;</span><span class="p" data-group-id="8562706059-8">)</span><span class="p" data-group-id="6501047828-7">}</span><span class="w">
</span><span class="w">              </span><span class="na">phx-key</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">escape</span><span class="p">&quot;</span><span class="w">
</span><span class="w">              </span><span class="na">phx-click-away</span><span class="p">=</span><span class="p" data-group-id="6501047828-8">{</span><span class="nc">JS</span><span class="o">.</span><span class="n">exec</span><span class="p" data-group-id="8562706059-10">(</span><span class="s">&quot;data-cancel&quot;</span><span class="p">,</span><span class="w"> </span><span class="ss">to</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;#</span><span class="si" data-group-id="8562706059-11">#{</span><span class="na">@id</span><span class="si" data-group-id="8562706059-11">}</span><span class="s">&quot;</span><span class="p" data-group-id="8562706059-10">)</span><span class="p" data-group-id="6501047828-8">}</span><span class="w">
</span><span class="w">    </span><span class="sx">&quot;&quot;&quot;</span><span class="w">
  </span><span class="k" data-group-id="8562706059-2">end</span></code></pre>
<p>
We use <code class="inline">JS.exec</code> to execute the <code class="inline">data-cancel</code> attribute where we share the <code class="inline">hide_modal</code> command specified in <code class="inline">phx-remove</code>. We have the same behavior as before, but now it’s only sent a single time, instead of three times.</p>
<p>
Happy coding!</p>
<p>
–Chris</p>
]]></content>
  </entry>

  <entry>
    <title>Embed and broadcast Whisper speech-to-text in your Phoenix app in 15 minutes</title>
    <link rel="alternate" href="http://www.phoenixframework.org/blog/whisper-speech-to-text-phoenix" />
    <id>http://www.phoenixframework.org/blog/whisper-speech-to-text-phoenix</id>
    <updated>2023-03-14T00:00:00Z</updated>
    <author>
      <name>Chris McCord</name>
    </author>
    <summary type="html"><![CDATA[Elixir has rich features for Machine learning thru it's Nx, Axon, and Bumblee libraries. This screencast shows just how quickly you can add Whisper audio transcriptions.]]></summary>
    <content type="html"><![CDATA[<p>
Elixir provides a rich and powerful set of Machine Learning libraries. Models are implemented directly in Elixir. From stable diffusion, to Whisper audio transcription, these features are now at your fingertips to use in your own applications.</p>
<p>
In this screencast, I show just how quickly we can add audio transcription to the open-source LiveBeats application we saw in a previous video. There’s no third-party APIs to call out to, no background jobs to kick off, and no JavaScript to write. The only external dependency is <code class="inline">ffmpeg</code>. Let’s go!</p>
<iframe width="100%" height="515" src="https://www.youtube.com/embed/Yd220Te8cHc" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="allowfullscreen">
</iframe>
<p>
Happy coding!</p>
<p>
–Chris</p>
]]></content>
  </entry>

  <entry>
    <title>Phoenix 1.7.0 released: Built-in Tailwind, Verified Routes, LiveView Streams, and what&#39;s next</title>
    <link rel="alternate" href="http://www.phoenixframework.org/blog/phoenix-1.7-final-released" />
    <id>http://www.phoenixframework.org/blog/phoenix-1.7-final-released</id>
    <updated>2023-02-24T00:00:00Z</updated>
    <author>
      <name>Chris McCord</name>
    </author>
    <summary type="html"><![CDATA[Phoenix 1.7.0 is here! This releases includes major new features, component-based generators, updated conventions, LiveView Streams, and more!]]></summary>
    <content type="html"><![CDATA[<p>
The final release of Phoenix 1.7 is out! Phoenix 1.7 packs a number of long-awaited new features like verified routes, Tailwind support, LiveView authentication generators, unified HEEx templates, LiveView Streams for optimized collections, and more. This is a backwards compatible release with a few deprecations. Most folks should be able to update just by changing a couple dependencies.</p>
<p>
<strong>Note</strong>: To generate a new 1.7 project, you’ll need to install the <code class="inline">phx.new</code> generator from hex:</p>
<pre><code class="makeup console">mix archive.install hex phx_new</code></pre>
<h2>
Verified Routes</h2>
<p>
Verified routes replace router helpers with a sigil-based (<code class="inline">~p</code>), compile-time verified approach.</p>
<blockquote>
  <p>
<strong>note</strong>: Verified routes make use of new Elixir 1.14 compiler features. Phoenix still supports older Elixir versions, but you’ll need to update to enjoy the new compile-time verification features.  </p>
</blockquote>
<p>
In practice this means where before you used autogenerated functions like:</p>
<pre><code class="makeup elixir"><span class="w">  </span><span class="c1"># router</span><span class="w">
  </span><span class="n">get</span><span class="w"> </span><span class="s">&quot;/oauth/callbacks/:id&quot;</span><span class="p">,</span><span class="w"> </span><span class="nc">OAuthCallbackController</span><span class="p">,</span><span class="w"> </span><span class="ss">:new</span><span class="w">

  </span><span class="c1"># usage</span><span class="w">
  </span><span class="nc">MyRouter.Helpers</span><span class="o">.</span><span class="n">o_auth_callback_path</span><span class="p" data-group-id="9072009617-1">(</span><span class="n">conn</span><span class="p">,</span><span class="w"> </span><span class="ss">:new</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;github&quot;</span><span class="p" data-group-id="9072009617-1">)</span><span class="w">
  </span><span class="c1"># =&gt; &quot;/oauth/callbacks/github&quot;</span><span class="w">

  </span><span class="nc">MyRouter.Helpers</span><span class="o">.</span><span class="n">o_auth_callback_url</span><span class="p" data-group-id="9072009617-2">(</span><span class="n">conn</span><span class="p">,</span><span class="w"> </span><span class="ss">:new</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;github&quot;</span><span class="p" data-group-id="9072009617-2">)</span><span class="w">
  </span><span class="c1"># =&gt; &quot;http://localhost:4000/oauth/callbacks/github&quot;</span></code></pre>
<p>
You can now do:</p>
<pre><code class="makeup elixir"><span class="w">  </span><span class="c1"># router</span><span class="w">
  </span><span class="n">get</span><span class="w"> </span><span class="s">&quot;/oauth/callbacks/:id&quot;</span><span class="p">,</span><span class="w"> </span><span class="nc">OAuthCallbackController</span><span class="p">,</span><span class="w"> </span><span class="ss">:new</span><span class="w">

  </span><span class="c1"># usage</span><span class="w">
  </span><span class="sx">~p&quot;/oauth/callbacks/github&quot;</span><span class="w">
  </span><span class="c1"># =&gt; &quot;/oauth/callbacks/github&quot;</span><span class="w">

  </span><span class="n">url</span><span class="p" data-group-id="1640169748-1">(</span><span class="sx">~p&quot;/oauth/callbacks/github&quot;</span><span class="p" data-group-id="1640169748-1">)</span><span class="w">
  </span><span class="c1"># =&gt; &quot;http://localhost:4000/oauth/callbacks/github&quot;</span></code></pre>
<p>
This has a number of advantages. There’s no longer guesswork on which function was inflected – is it <code class="inline">Helpers.oauth_callback_path</code> or <code class="inline">o_auth_callback_path</code>, etc. You also no longer need to include the <code class="inline">%Plug.Conn{}</code>, or <code class="inline">%Phoenix.Socket{}</code>, or endpoint module everywhere when 99% of the time you know which endpoint configuration should be used.</p>
<p>
There is also now a 1:1 mapping between the routes you write in the router, and how you call them with <code class="inline">~p</code>. You simply write it as if you’re hard-coding strings everywhere in your app – except you don’t have maintenance issues that come with hardcoding strings. We can get the best of both worlds with ease of use and maintenance because <code class="inline">~p</code> is a compile-time verified against the routes in your router.</p>
<p>
For example, imagine we typo a route:</p>
<pre><code class="makeup heex"><span class="p">&lt;</span><span class="nf">.link</span><span class="w"> </span><span class="na">href</span><span class="p">=</span><span class="p" data-group-id="1010738380-1">{</span><span class="sx">~p&quot;/userz/profile&quot;</span><span class="p" data-group-id="1010738380-1">}</span><span class="p">&gt;</span><span class="n">Profile</span><span class="p">&lt;/</span><span class="nf">.link</span><span class="p">&gt;</span></code></pre>
<p>
The compiler will dispatch all <code class="inline">~p</code>‘s at compile-time against your router, and let you know when it can’t find a matching route:</p>
<pre><code class="makeup console">    warning: no route path for AppWeb.Router matches &quot;/postz/#{post}&quot;
      lib/app_web/live/post_live.ex:100: AppWeb.PostLive.render/1</code></pre>
<p>
Dynamic “named params” are also simply interpolated like a regular string, instead of arbitrary function arguments:</p>
<pre><code class="makeup elixir"><span class="sx">~p&quot;/posts/</span><span class="si" data-group-id="9516799863-1">#{</span><span class="n">post</span><span class="o">.</span><span class="n">id</span><span class="si" data-group-id="9516799863-1">}</span><span class="sx">&quot;</span></code></pre>
<p>
Additionally, interpolated <code class="inline">~p</code> values are encoded via the <code class="inline">Phoenix.Param</code> protocol.
For example, a <code class="inline">%Post{}</code> struct in your application may derive the <code class="inline">Phoenix.Param</code>
protocol to generate slug-based paths rather than ID based ones. This allows you to
use <code class="inline">~p&quot;/posts/#{post}&quot;</code> rather than <code class="inline">~p&quot;/posts/#{post.slug}&quot;</code> throughout your
application.</p>
<p>
Query strings are also supported in verified routes, either in traditional query
string form:</p>
<pre><code class="makeup elixir"><span class="sx">~p&quot;/posts?page=</span><span class="si" data-group-id="5166981375-1">#{</span><span class="n">page</span><span class="si" data-group-id="5166981375-1">}</span><span class="sx">&quot;</span></code></pre>
<p>
Or as a keyword list or map of values:</p>
<pre><code class="makeup elixir"><span class="n">params</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p" data-group-id="9893335764-1">%{</span><span class="ss">page</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="ss">direction</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;asc&quot;</span><span class="p" data-group-id="9893335764-1">}</span><span class="w">
</span><span class="sx">~p&quot;/posts?</span><span class="si" data-group-id="9893335764-2">#{</span><span class="n">params</span><span class="si" data-group-id="9893335764-2">}</span><span class="sx">&quot;</span></code></pre>
<p>
Like path segments, query strings params are proper URL encoded and may be interpolated
directly into the <code class="inline">~p</code> string.</p>
<p>
Once you try out the new feature, you won’t be able to go back to router helpers. The new <code class="inline">phx.gen.html|live|json|auth</code> generators use verified routes.</p>
<h2>
Component-based Tailwind generators</h2>
<p>
Phoenix 1.7 ships with <a href="https://tailwindcss.com">TailwindCSS</a> by default, with no dependency on nodejs on the system. TailwindCSS is the best way I’ve found to style interfaces in my 20 years of web development. Its utility-first approach is far more maintainable and productive than any CSS system or framework I’ve used. It’s collocated approach also aligns perfectly within the function component and LiveView landscape.</p>
<p>
The Tailwind team also generously designed the new project landing page, CRUD pages, and authentication system pages for new projects, giving you a first-class and polished starting point for building out your apps.</p>
<p>
A new <code class="inline">phx.new</code> project will contain a <code class="inline">CoreComponents</code> module, housing a core set of UI components like tables, modals, forms, and data lists. The suite of Phoenix generators (<code class="inline">phx.gen.html|live|json|auth</code>) make use of the core components. This has a number of neat advantages.</p>
<p>
First, you can customize your core UI components to suit whatever needs, designs, and tastes that you have. If you want to use Bulma or Bootstrap instead of Tailwind – no problem! Simply replace the function definitions in <code class="inline">core_components.ex</code> with your framework/UI specific implementations and the generators continue to provide a great starting point for new features whether you’re a beginner, or seasoned expert building bespoke product features.</p>
<p>
In practice, the generators give you templates that make use of your core components, which look like this:</p>
<pre><code class="makeup heex"><span class="p">&lt;</span><span class="nf">.header</span><span class="p">&gt;</span><span class="w">
</span><span class="n">  New Post
</span><span class="w">  </span><span class="p">&lt;</span><span class="ss">:subtitle</span><span class="p">&gt;</span><span class="n">Use this form to manage post records in your database.</span><span class="p">&lt;/</span><span class="ss">:subtitle</span><span class="p">&gt;</span><span class="w">
</span><span class="p">&lt;/</span><span class="nf">.header</span><span class="p">&gt;</span><span class="w">
</span><span class="w">
</span><span class="p">&lt;</span><span class="nf">.simple_form</span><span class="w"> </span><span class="na">for</span><span class="p">=</span><span class="p" data-group-id="3011894452-1">{</span><span class="na">@form</span><span class="p" data-group-id="3011894452-1">}</span><span class="w"> </span><span class="na">action</span><span class="p">=</span><span class="p" data-group-id="3011894452-2">{</span><span class="sx">~p&quot;/posts&quot;</span><span class="p" data-group-id="3011894452-2">}</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nt">input</span><span class="w"> </span><span class="na">field</span><span class="p">=</span><span class="p" data-group-id="3011894452-3">{</span><span class="na">@form</span><span class="p" data-group-id="3011894452-ex-1">[</span><span class="ss">:title</span><span class="p" data-group-id="3011894452-ex-1">]</span><span class="p" data-group-id="3011894452-3">}</span><span class="w"> </span><span class="na">type</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">text</span><span class="p">&quot;</span><span class="w"> </span><span class="na">label</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">Title</span><span class="p">&quot;</span><span class="w"> </span><span class="p">/&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nt">input</span><span class="w"> </span><span class="na">field</span><span class="p">=</span><span class="p" data-group-id="3011894452-4">{</span><span class="na">@form</span><span class="p" data-group-id="3011894452-ex-2">[</span><span class="ss">:views</span><span class="p" data-group-id="3011894452-ex-2">]</span><span class="p" data-group-id="3011894452-4">}</span><span class="w"> </span><span class="na">type</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">number</span><span class="p">&quot;</span><span class="w"> </span><span class="na">label</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">Views</span><span class="p">&quot;</span><span class="w"> </span><span class="p">/&gt;</span><span class="w">
</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="ss">:actions</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nf">.button</span><span class="p">&gt;</span><span class="n">Save Post</span><span class="p">&lt;/</span><span class="nf">.button</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;/</span><span class="ss">:actions</span><span class="p">&gt;</span><span class="w">
</span><span class="p">&lt;/</span><span class="nf">.simple_form</span><span class="p">&gt;</span><span class="w">
</span><span class="w">
</span><span class="p">&lt;</span><span class="nf">.back</span><span class="w"> </span><span class="na">navigate</span><span class="p">=</span><span class="p" data-group-id="3011894452-5">{</span><span class="sx">~p&quot;/posts&quot;</span><span class="p" data-group-id="3011894452-5">}</span><span class="p">&gt;</span><span class="n">Back to posts&gt;</span><span class="p">&lt;/</span><span class="nf">.back</span><span class="p">&gt;</span></code></pre>
<p>
We love what the Tailwind team designed for new applications, but we also can’t wait to see the community release their own drop-in replacements for <code class="inline">core_components.ex</code> for various frameworks of choice.</p>
<h2>
Unified function components across Controllers and LiveViews</h2>
<p>
Function components provided by HEEx, with declarative assigns and slots, are massive step-change in the way we write HTML in Phoenix projects. Function components provide UI building blocks, allowing features to be encapsulated and better extended over the previous template approach in <code class="inline">Phoenix.View</code>. You get a more natural way to write dynamic markup, reusable UI that can be extended by the caller, and compile-time features to make writing HTML-based applications a truly first-class experience.</p>
<p>
Function components bring a new way to write HTML applications in Phoenix, with new sets of conventions. Additionally, users have struggled with how to marry controller-based <code class="inline">Phoenix.View</code> features with <code class="inline">Phoenix.LiveView</code> features in their applications. Users found themselves writing <code class="inline">render(&quot;table&quot;, user: user)</code> in controller-based templates, while their LiveViews made use of the new <code class="inline">&lt;.table rows={@users}&gt;</code> features. There was no great way to share the approaches in an application.</p>
<p>
For these reasons, the Phoenix team unified the HTML rendering approaches whether from a controller request, or a LiveView. This shift also allowed us to revisit conventions and align with the LiveView approach of collocating templates and app code together.</p>
<p>
New applications (and the phx generators), remove <code class="inline">Phoenix.View</code> as a dependency in favor of a new <code class="inline">Phoenix.Template</code> dependency, which uses function components as the basis for all rendering in the framework.</p>
<p>
Your controllers still look the same:</p>
<pre><code class="makeup elixir"><span class="kd">defmodule</span><span class="w"> </span><span class="nc">AppWeb.UserController</span><span class="w"> </span><span class="k" data-group-id="5549632855-1">do</span><span class="w">
  </span><span class="kn">use</span><span class="w"> </span><span class="nc">MyAppWeb</span><span class="p">,</span><span class="w"> </span><span class="ss">:controller</span><span class="w">

  </span><span class="kd">def</span><span class="w"> </span><span class="nf">index</span><span class="p" data-group-id="5549632855-2">(</span><span class="n">conn</span><span class="p">,</span><span class="w"> </span><span class="c">_params</span><span class="p" data-group-id="5549632855-2">)</span><span class="w"> </span><span class="k" data-group-id="5549632855-3">do</span><span class="w">
    </span><span class="n">users</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">...</span><span class="w">
    </span><span class="n">render</span><span class="p" data-group-id="5549632855-4">(</span><span class="n">conn</span><span class="p">,</span><span class="w"> </span><span class="ss">:index</span><span class="p">,</span><span class="w"> </span><span class="ss">users</span><span class="p">:</span><span class="w"> </span><span class="n">users</span><span class="p" data-group-id="5549632855-4">)</span><span class="w">
  </span><span class="k" data-group-id="5549632855-3">end</span><span class="w">
</span><span class="k" data-group-id="5549632855-1">end</span></code></pre>
<p>
But instead of the controller calling <code class="inline">AppWeb.UserView.render(&quot;index.html&quot;, assigns)</code>, we’ll now first look for an <code class="inline">index/1</code> function component on the view module, and call that for rendering if it exists. Additionally, we also renamed the inflected view module to look for <code class="inline">AppWeb.UserHTML</code>, or <code class="inline">AppWeb.UserJSON</code>, and so on for a view-per-format approach for rendering templates. This is all done in backwards compatible way, and is opt-in based on options to <code class="inline">use Phoenix.Controller</code>.</p>
<p>
All HTML rendering is then based on function components, which can be written directly in a module, or embedded from an external file with the new <code class="inline">embed_templates</code> macro provided by <code class="inline">Phoenix.Component</code>. Your <code class="inline">PageHTML</code> module in a new application looks like this:</p>
<pre><code class="makeup elixir"><span class="kd">defmodule</span><span class="w"> </span><span class="nc">AppWeb.PageHTML</span><span class="w"> </span><span class="k" data-group-id="7870123231-1">do</span><span class="w">
  </span><span class="kn">use</span><span class="w"> </span><span class="nc">AppWeb</span><span class="p">,</span><span class="w"> </span><span class="ss">:html</span><span class="w">

  </span><span class="n">embed_templates</span><span class="w"> </span><span class="s">&quot;page_html/*&quot;</span><span class="w">
</span><span class="k" data-group-id="7870123231-1">end</span></code></pre>
<p>
The new directory structure will look something like this:</p>
<pre><code class="makeup console">lib/app_wb
├── controllers
│   ├── page_controller.ex
│   ├── page_html.ex
│   ├── error_html.ex
│   ├── error_json.ex
│   └── page_html
│       └── home.html.heex
├── live
│   ├── home_live.ex
├── components
│   ├── core_components.ex
│   ├── layouts.ex
│   └── layouts
│       ├── app.html.heex
│       └── root.html.heex
├── endpoint.ex
└── router.ex</code></pre>
<p>
Your controllers-based rendering or LiveView-based rendering now all share the same function components and layouts. Whether running <code class="inline">phx.gen.html</code>, <code class="inline">phx.gen.live</code>, or <code class="inline">phx.gen.auth</code>, the new generated templates all make use of your <code class="inline">components/core_components.ex</code> definitions.</p>
<p>
Additionally, we have collocated the view modules next to their controller files. This brings the same benefits of LiveView collocation – highly coupled files live together. Files that must change together now live together, whether writing LiveView or controller features.</p>
<p>
These changes were all about a better way to write HTML-based applications, but they also simplified rendering other formats, like JSON. For example, JSON based view modules follow the same conventions – Phoenix will first look for an <code class="inline">index/1</code> function when rendering the index template, before trying <code class="inline">render/2</code>. This allowed us to simplify JSON rendering in general and do away with concepts like <code class="inline">Phoenix.View.render_one|render_many</code>.</p>
<p>
For example, this is a JSON view generated by <code class="inline">phx.gen.json</code>:</p>
<pre><code class="makeup elixir"><span class="kd">defmodule</span><span class="w"> </span><span class="nc">AppWeb.PostJSON</span><span class="w"> </span><span class="k" data-group-id="3065081529-1">do</span><span class="w">
  </span><span class="kn">alias</span><span class="w"> </span><span class="nc">AppWeb.Blog.Post</span><span class="w">

  </span><span class="na">@doc</span><span class="w"> </span><span class="s">&quot;&quot;&quot;
  Renders a list of posts.
  &quot;&quot;&quot;</span><span class="w">
  </span><span class="kd">def</span><span class="w"> </span><span class="nf">index</span><span class="p" data-group-id="3065081529-2">(</span><span class="p" data-group-id="3065081529-3">%{</span><span class="ss">posts</span><span class="p">:</span><span class="w"> </span><span class="n">posts</span><span class="p" data-group-id="3065081529-3">}</span><span class="p" data-group-id="3065081529-2">)</span><span class="w"> </span><span class="k" data-group-id="3065081529-4">do</span><span class="w">
    </span><span class="p" data-group-id="3065081529-5">%{</span><span class="ss">data</span><span class="p">:</span><span class="w"> </span><span class="k">for</span><span class="p" data-group-id="3065081529-6">(</span><span class="n">post</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="n">posts</span><span class="p">,</span><span class="w"> </span><span class="ss">do</span><span class="p">:</span><span class="w"> </span><span class="n">data</span><span class="p" data-group-id="3065081529-7">(</span><span class="n">post</span><span class="p" data-group-id="3065081529-7">)</span><span class="p" data-group-id="3065081529-6">)</span><span class="p" data-group-id="3065081529-5">}</span><span class="w">
  </span><span class="k" data-group-id="3065081529-4">end</span><span class="w">

  </span><span class="na">@doc</span><span class="w"> </span><span class="s">&quot;&quot;&quot;
  Renders a single post.
  &quot;&quot;&quot;</span><span class="w">
  </span><span class="kd">def</span><span class="w"> </span><span class="nf">show</span><span class="p" data-group-id="3065081529-8">(</span><span class="p" data-group-id="3065081529-9">%{</span><span class="ss">post</span><span class="p">:</span><span class="w"> </span><span class="n">post</span><span class="p" data-group-id="3065081529-9">}</span><span class="p" data-group-id="3065081529-8">)</span><span class="w"> </span><span class="k" data-group-id="3065081529-10">do</span><span class="w">
    </span><span class="p" data-group-id="3065081529-11">%{</span><span class="ss">data</span><span class="p">:</span><span class="w"> </span><span class="n">data</span><span class="p" data-group-id="3065081529-12">(</span><span class="n">post</span><span class="p" data-group-id="3065081529-12">)</span><span class="p" data-group-id="3065081529-11">}</span><span class="w">
  </span><span class="k" data-group-id="3065081529-10">end</span><span class="w">

  </span><span class="kd">defp</span><span class="w"> </span><span class="nf">data</span><span class="p" data-group-id="3065081529-13">(</span><span class="p" data-group-id="3065081529-14">%</span><span class="nc" data-group-id="3065081529-14">Post</span><span class="p" data-group-id="3065081529-14">{</span><span class="p" data-group-id="3065081529-14">}</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">post</span><span class="p" data-group-id="3065081529-13">)</span><span class="w"> </span><span class="k" data-group-id="3065081529-15">do</span><span class="w">
    </span><span class="p" data-group-id="3065081529-16">%{</span><span class="w">
      </span><span class="ss">id</span><span class="p">:</span><span class="w"> </span><span class="n">post</span><span class="o">.</span><span class="n">id</span><span class="p">,</span><span class="w">
      </span><span class="ss">title</span><span class="p">:</span><span class="w"> </span><span class="n">post</span><span class="o">.</span><span class="n">title</span><span class="w">
    </span><span class="p" data-group-id="3065081529-16">}</span><span class="w">
  </span><span class="k" data-group-id="3065081529-15">end</span><span class="w">
</span><span class="k" data-group-id="3065081529-1">end</span></code></pre>
<p>
Notice how it’s all simply regular Elixir functions – as it should be!</p>
<p>
These features provide a unified rendering model for applications going forward with a new and improved way to write UIs, but they are a deviation from previous practices. Most large, established applications are probably best served by continuing to depend on <code class="inline">Phoenix.View</code>.</p>
<h2>
LiveView Streams</h2>
<p>
LiveView now includes a streaming interface for managing large collections in the UI without having to store the collections in memory on the server. With a couple function calls you can insert new items into the UI, append or prepend dynamically, or re-order items without reloading the items on the server.</p>
<p>
The <code class="inline">phx.gen.live</code> live CRUD generator in Phoenix 1.7 uses streams to manage the your list of items. This allows data entry, updates, and deletes without ever needing to refetch the list of items after the initial load. Let’s see how.</p>
<p>
The following <code class="inline">PostLive.Index</code> module is generated when you run <code class="inline">mix phx.gen.live Blog Post posts title views:integer</code></p>
<pre><code class="makeup elixir"><span class="kd">defmodule</span><span class="w"> </span><span class="nc">DemoWeb.PostLive.Index</span><span class="w"> </span><span class="k" data-group-id="2760634312-1">do</span><span class="w">
  </span><span class="kn">use</span><span class="w"> </span><span class="nc">DemoWeb</span><span class="p">,</span><span class="w"> </span><span class="ss">:live_view</span><span class="w">

  </span><span class="kn">alias</span><span class="w"> </span><span class="nc">Demo.Blog</span><span class="w">
  </span><span class="kn">alias</span><span class="w"> </span><span class="nc">Demo.Blog.Post</span><span class="w">

  </span><span class="na">@impl</span><span class="w"> </span><span class="no">true</span><span class="w">
  </span><span class="kd">def</span><span class="w"> </span><span class="nf">mount</span><span class="p" data-group-id="2760634312-2">(</span><span class="c">_params</span><span class="p">,</span><span class="w"> </span><span class="c">_session</span><span class="p">,</span><span class="w"> </span><span class="n">socket</span><span class="p" data-group-id="2760634312-2">)</span><span class="w"> </span><span class="k" data-group-id="2760634312-3">do</span><span class="w">
    </span><span class="p" data-group-id="2760634312-4">{</span><span class="ss">:ok</span><span class="p">,</span><span class="w"> </span><span class="n">stream</span><span class="p" data-group-id="2760634312-5">(</span><span class="n">socket</span><span class="p">,</span><span class="w"> </span><span class="ss">:posts</span><span class="p">,</span><span class="w"> </span><span class="nc">Blog</span><span class="o">.</span><span class="n">list_posts</span><span class="p" data-group-id="2760634312-6">(</span><span class="p" data-group-id="2760634312-6">)</span><span class="p" data-group-id="2760634312-5">)</span><span class="p" data-group-id="2760634312-4">}</span><span class="w">
  </span><span class="k" data-group-id="2760634312-3">end</span><span class="w">

  </span><span class="n">...</span><span class="w">
</span><span class="k" data-group-id="2760634312-1">end</span></code></pre>
<p>
Notice how instead of the regular <code class="inline">assign(socket, :posts, Blog.list_posts())</code>, we have a new <code class="inline">stream/3</code> interface. This sets up the stream with the initial collection of posts. Then in the generated <code class="inline">index.html.heex</code> template, we consume the stream to render the posts table:</p>
<pre><code class="makeup heex"><span class="p">&lt;</span><span class="nf">.table</span><span class="w">
</span><span class="w">  </span><span class="na">id</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">p</span><span class="s2">o</span><span class="s2">s</span><span class="s2">t</span><span class="s2">s</span><span class="p">&quot;</span><span class="w">
</span><span class="w">  </span><span class="na">rows</span><span class="p">=</span><span class="p" data-group-id="0744257451-1">{</span><span class="na">@streams</span><span class="o">.</span><span class="n">posts</span><span class="p" data-group-id="0744257451-1">}</span><span class="w">
</span><span class="w">  </span><span class="na">row_click</span><span class="p">=</span><span class="p" data-group-id="0744257451-2">{</span><span class="k" data-group-id="0744257451-ex-1">fn</span><span class="w"> </span><span class="p" data-group-id="0744257451-ex-2">{</span><span class="c">_id</span><span class="p">,</span><span class="w"> </span><span class="n">post</span><span class="p" data-group-id="0744257451-ex-2">}</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="nc">JS</span><span class="o">.</span><span class="n">navigate</span><span class="p" data-group-id="0744257451-ex-3">(</span><span class="sx">~p&quot;/posts/</span><span class="si" data-group-id="0744257451-ex-4">#{</span><span class="n">post</span><span class="si" data-group-id="0744257451-ex-4">}</span><span class="sx">&quot;</span><span class="p" data-group-id="0744257451-ex-3">)</span><span class="w"> </span><span class="k" data-group-id="0744257451-ex-1">end</span><span class="p" data-group-id="0744257451-2">}</span><span class="w">
</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="ss">:col</span><span class="w"> </span><span class="na">:let</span><span class="p">=</span><span class="p" data-group-id="0744257451-3">{</span><span class="p" data-group-id="0744257451-ex-5">{</span><span class="c">_id</span><span class="p">,</span><span class="w"> </span><span class="n">post</span><span class="p" data-group-id="0744257451-ex-5">}</span><span class="p" data-group-id="0744257451-3">}</span><span class="w"> </span><span class="na">label</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">Title</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="p" data-group-id="0744257451-4">&lt;%=</span><span class="w"> </span><span class="n">post</span><span class="o">.</span><span class="n">title</span><span class="w"> </span><span class="p" data-group-id="0744257451-4">%&gt;</span><span class="p">&lt;/</span><span class="ss">:col</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="ss">:col</span><span class="w"> </span><span class="na">:let</span><span class="p">=</span><span class="p" data-group-id="0744257451-5">{</span><span class="p" data-group-id="0744257451-ex-6">{</span><span class="c">_id</span><span class="p">,</span><span class="w"> </span><span class="n">post</span><span class="p" data-group-id="0744257451-ex-6">}</span><span class="p" data-group-id="0744257451-5">}</span><span class="w"> </span><span class="na">label</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">Views</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="p" data-group-id="0744257451-6">&lt;%=</span><span class="w"> </span><span class="n">post</span><span class="o">.</span><span class="n">views</span><span class="w"> </span><span class="p" data-group-id="0744257451-6">%&gt;</span><span class="p">&lt;/</span><span class="ss">:col</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="ss">:action</span><span class="w"> </span><span class="na">:let</span><span class="p">=</span><span class="p" data-group-id="0744257451-7">{</span><span class="p" data-group-id="0744257451-ex-7">{</span><span class="c">_id</span><span class="p">,</span><span class="w"> </span><span class="n">post</span><span class="p" data-group-id="0744257451-ex-7">}</span><span class="p" data-group-id="0744257451-7">}</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nt">div</span><span class="w"> </span><span class="na">class</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">s</span><span class="s2">r</span><span class="s2">-</span><span class="s2">o</span><span class="s2">n</span><span class="s2">l</span><span class="s2">y</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="w">
</span><span class="w">      </span><span class="p">&lt;</span><span class="nf">.link</span><span class="w"> </span><span class="na">navigate</span><span class="p">=</span><span class="p" data-group-id="0744257451-8">{</span><span class="sx">~p&quot;/posts/</span><span class="si" data-group-id="0744257451-ex-8">#{</span><span class="n">post</span><span class="si" data-group-id="0744257451-ex-8">}</span><span class="sx">&quot;</span><span class="p" data-group-id="0744257451-8">}</span><span class="p">&gt;</span><span class="n">Show</span><span class="p">&lt;/</span><span class="nf">.link</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nf">.link</span><span class="w"> </span><span class="na">patch</span><span class="p">=</span><span class="p" data-group-id="0744257451-9">{</span><span class="sx">~p&quot;/posts/</span><span class="si" data-group-id="0744257451-ex-9">#{</span><span class="n">post</span><span class="si" data-group-id="0744257451-ex-9">}</span><span class="sx">/edit&quot;</span><span class="p" data-group-id="0744257451-9">}</span><span class="p">&gt;</span><span class="n">Edit</span><span class="p">&lt;/</span><span class="nf">.link</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;/</span><span class="ss">:action</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="ss">:action</span><span class="w"> </span><span class="na">:let</span><span class="p">=</span><span class="p" data-group-id="0744257451-10">{</span><span class="p" data-group-id="0744257451-ex-10">{</span><span class="n">id</span><span class="p">,</span><span class="w"> </span><span class="n">post</span><span class="p" data-group-id="0744257451-ex-10">}</span><span class="p" data-group-id="0744257451-10">}</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nf">.link</span><span class="w">
</span><span class="w">      </span><span class="na">phx-click</span><span class="p">=</span><span class="p" data-group-id="0744257451-11">{</span><span class="nc">JS</span><span class="o">.</span><span class="n">push</span><span class="p" data-group-id="0744257451-ex-11">(</span><span class="s">&quot;delete&quot;</span><span class="p">,</span><span class="w"> </span><span class="ss">value</span><span class="p">:</span><span class="w"> </span><span class="p" data-group-id="0744257451-ex-12">%{</span><span class="ss">id</span><span class="p">:</span><span class="w"> </span><span class="n">post</span><span class="o">.</span><span class="n">id</span><span class="p" data-group-id="0744257451-ex-12">}</span><span class="p" data-group-id="0744257451-ex-11">)</span><span class="w"> </span><span class="o">|&gt;</span><span class="w"> </span><span class="n">hide</span><span class="p" data-group-id="0744257451-ex-13">(</span><span class="s">&quot;#</span><span class="si" data-group-id="0744257451-ex-14">#{</span><span class="n">id</span><span class="si" data-group-id="0744257451-ex-14">}</span><span class="s">&quot;</span><span class="p" data-group-id="0744257451-ex-13">)</span><span class="p" data-group-id="0744257451-11">}</span><span class="w">
</span><span class="w">      </span><span class="na">data-confirm</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">Are you sure?</span><span class="p">&quot;</span><span class="w">
</span><span class="w">    </span><span class="p">&gt;</span><span class="w">
</span><span class="n">      Delete
</span><span class="w">    </span><span class="p">&lt;/</span><span class="nf">.link</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;/</span><span class="ss">:action</span><span class="p">&gt;</span><span class="w">
</span><span class="p">&lt;/</span><span class="nf">.table</span><span class="p">&gt;</span></code></pre>
<p>
This looks very similar to the old template, but instead of accessing the bare <code class="inline">@posts</code> assign, we pass the <code class="inline">@stream.posts</code> to our table. When consuming a stream we also are passed the stream’s DOM id, along with the item.</p>
<p>
Back on the server, we can see how simple it is to insert new items into the table. When our generated <code class="inline">FormComponent</code> updates or saves a post via the form, we send a message pack to the parent <code class="inline">PostLive.Index</code> LiveView about the new or updated post:</p>
<p>
<code class="inline">PostLive.FormComponent</code>:</p>
<pre><code class="makeup elixir"><span class="kd">defmodule</span><span class="w"> </span><span class="nc">DemoWeb.PostLive.FormComponent</span><span class="w"> </span><span class="k" data-group-id="0913570934-1">do</span><span class="w">
  </span><span class="n">...</span><span class="w">
  </span><span class="kd">defp</span><span class="w"> </span><span class="nf">save_post</span><span class="p" data-group-id="0913570934-2">(</span><span class="n">socket</span><span class="p">,</span><span class="w"> </span><span class="ss">:new</span><span class="p">,</span><span class="w"> </span><span class="n">post_params</span><span class="p" data-group-id="0913570934-2">)</span><span class="w"> </span><span class="k" data-group-id="0913570934-3">do</span><span class="w">
    </span><span class="k">case</span><span class="w"> </span><span class="nc">Blog</span><span class="o">.</span><span class="n">create_post</span><span class="p" data-group-id="0913570934-4">(</span><span class="n">post_params</span><span class="p" data-group-id="0913570934-4">)</span><span class="w"> </span><span class="k" data-group-id="0913570934-5">do</span><span class="w">
      </span><span class="p" data-group-id="0913570934-6">{</span><span class="ss">:ok</span><span class="p">,</span><span class="w"> </span><span class="n">post</span><span class="p" data-group-id="0913570934-6">}</span><span class="w"> </span><span class="o">-&gt;</span><span class="w">
        </span><span class="n">notify_parent</span><span class="p" data-group-id="0913570934-7">(</span><span class="p" data-group-id="0913570934-8">{</span><span class="ss">:saved</span><span class="p">,</span><span class="w"> </span><span class="n">post</span><span class="p" data-group-id="0913570934-8">}</span><span class="p" data-group-id="0913570934-7">)</span><span class="w">

        </span><span class="p" data-group-id="0913570934-9">{</span><span class="ss">:noreply</span><span class="p">,</span><span class="w">
         </span><span class="n">socket</span><span class="w">
         </span><span class="o">|&gt;</span><span class="w"> </span><span class="n">put_flash</span><span class="p" data-group-id="0913570934-10">(</span><span class="ss">:info</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;Post created successfully&quot;</span><span class="p" data-group-id="0913570934-10">)</span><span class="w">
         </span><span class="o">|&gt;</span><span class="w"> </span><span class="n">push_patch</span><span class="p" data-group-id="0913570934-11">(</span><span class="ss">to</span><span class="p">:</span><span class="w"> </span><span class="n">socket</span><span class="o">.</span><span class="n">assigns</span><span class="o">.</span><span class="n">patch</span><span class="p" data-group-id="0913570934-11">)</span><span class="p" data-group-id="0913570934-9">}</span><span class="w">

      </span><span class="p" data-group-id="0913570934-12">{</span><span class="ss">:error</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="0913570934-13">%</span><span class="nc" data-group-id="0913570934-13">Ecto.Changeset</span><span class="p" data-group-id="0913570934-13">{</span><span class="p" data-group-id="0913570934-13">}</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">changeset</span><span class="p" data-group-id="0913570934-12">}</span><span class="w"> </span><span class="o">-&gt;</span><span class="w">
        </span><span class="p" data-group-id="0913570934-14">{</span><span class="ss">:noreply</span><span class="p">,</span><span class="w"> </span><span class="n">assign_form</span><span class="p" data-group-id="0913570934-15">(</span><span class="n">socket</span><span class="p">,</span><span class="w"> </span><span class="n">changeset</span><span class="p" data-group-id="0913570934-15">)</span><span class="p" data-group-id="0913570934-14">}</span><span class="w">
    </span><span class="k" data-group-id="0913570934-5">end</span><span class="w">
  </span><span class="k" data-group-id="0913570934-3">end</span><span class="w">

  </span><span class="kd">defp</span><span class="w"> </span><span class="nf">notify_parent</span><span class="p" data-group-id="0913570934-16">(</span><span class="n">msg</span><span class="p" data-group-id="0913570934-16">)</span><span class="p">,</span><span class="w"> </span><span class="ss">do</span><span class="p">:</span><span class="w"> </span><span class="n">send</span><span class="p" data-group-id="0913570934-17">(</span><span class="n">self</span><span class="p" data-group-id="0913570934-18">(</span><span class="p" data-group-id="0913570934-18">)</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="0913570934-19">{</span><span class="bp">__MODULE__</span><span class="p">,</span><span class="w"> </span><span class="n">msg</span><span class="p" data-group-id="0913570934-19">}</span><span class="p" data-group-id="0913570934-17">)</span><span class="w">
</span><span class="k" data-group-id="0913570934-1">end</span></code></pre>
<p>
Then we pick the message up in a <code class="inline">PostLive.Index</code> <code class="inline">handle_info</code> clause:</p>
<pre><code class="makeup elixir"><span class="na">@impl</span><span class="w"> </span><span class="no">true</span><span class="w">
</span><span class="kd">def</span><span class="w"> </span><span class="nf">handle_info</span><span class="p" data-group-id="9295560735-1">(</span><span class="p" data-group-id="9295560735-2">{</span><span class="nc">DemoWeb.PostLive.FormComponent</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="9295560735-3">{</span><span class="ss">:saved</span><span class="p">,</span><span class="w"> </span><span class="n">post</span><span class="p" data-group-id="9295560735-3">}</span><span class="p" data-group-id="9295560735-2">}</span><span class="p">,</span><span class="w"> </span><span class="n">socket</span><span class="p" data-group-id="9295560735-1">)</span><span class="w"> </span><span class="k" data-group-id="9295560735-4">do</span><span class="w">
  </span><span class="p" data-group-id="9295560735-5">{</span><span class="ss">:noreply</span><span class="p">,</span><span class="w"> </span><span class="n">stream_insert</span><span class="p" data-group-id="9295560735-6">(</span><span class="n">socket</span><span class="p">,</span><span class="w"> </span><span class="ss">:posts</span><span class="p">,</span><span class="w"> </span><span class="n">post</span><span class="p" data-group-id="9295560735-6">)</span><span class="p" data-group-id="9295560735-5">}</span><span class="w">
</span><span class="k" data-group-id="9295560735-4">end</span></code></pre>
<p>
So the form tells us it saved a post, and we simply <code class="inline">stream_insert</code> the post in our stream. That’s it! If a post already exists in the UI, it will be updated in place. Otherwise it is appended to the container by default. You can also prepend with <code class="inline">stream_insert(socket, :posts, post, at: 0)</code>, or pass any index to <code class="inline">:at</code> for arbitrary item insertion or re-ordering.</p>
<p>
Streams were one of the final building blocks on our way to LiveView 1.0 and I’m super happy where we landed.</p>
<h2>
New Form field datastructure</h2>
<p>
We’re all familiar with the Phoenix.HTML form primitives of <code class="inline">&lt;.form for={@changeset}&gt;</code>, where the form takes a datastructure that implements the <code class="inline">Phoenix.HTML.FormData</code> protocol and returns a <code class="inline">%Phoenix.HTML.Form{}</code>. One issue our approach had is the form datastructure couldn’t track individual form field changes. This made optimizations impossible in LiveView where we’d have to re-render and resend the form on any individual change. With the introduction of <code class="inline">Phoenix.HTML.FormData.to_form</code> and <code class="inline">Phoenix.Component.to_form</code>, we now have a <code class="inline">%Phoenix.HTML.FormField{}</code> datastructue for individual field changes.</p>
<p>
The new <code class="inline">phx.gen.live</code> generators and your <code class="inline">core_components.ex</code> take advantage of these new additions.</p>
<h2>
What’s Next for Phoenix and LiveView</h2>
<p>
The Phoenix generators make use of LiveViews latest features, and that will continue to expand. With streaming collections as the default, we can move towards more advanced out-of-the-box features for our live CRUD generators in <code class="inline">phx.gen.live</code>. For example, we plan to introduce synced UIs out-of-the-box for resources. The generated Phoenix form features will continue to evolve with the addition of new the <code class="inline">to_form</code> interface.</p>
<p>
For LiveView, <code class="inline">to_form</code> allowed us to ship the basis of optimized forms. Now an individual change to one form field will produce an optimized diff.</p>
<p>
Following this optimization work, the major remaining feature for LiveView 1.0 is an expanded form API that better supports dynamic forms inputs, wizard-style forms, and delegating form inputs to child LiveComponents.</p>
<h2>
Alternative Webserver Support</h2>
<p>
Thanks to work by Mat Trudel, we now have the basis for first-class webserver support in Plug and Phoenix, allowing other webservers like <a href="https://github.com/mtrudel/bandit">Bandit</a> to be swapped in Phoenix while enjoying all features like WebSockets, Channels, and LiveView. Stay tuned to the Bandit project if you’re interested in a pure Elixir HTTP server or give it a try in your own Phoenix projects!</p>
<h2>
What’s Next</h2>
<p>
As always, <a href="https://gist.github.com/chrismccord/00a6ea2a96bc57df0cce526bd20af8a7">step-by-step upgrade guides</a> are there to take your existing 1.6.x apps up to 1.7.</p>
<p>
The full changelog can be found <a href="https://github.com/phoenixframework/phoenix/blob/a8707baed3e67ffaaa1e3e8c2b2271291b57407c/CHANGELOG.md">here</a>.</p>
<p>
Find us on elixir slack or <a href="https://elixirforum.com">the forums</a> if you have issues.</p>
<p>
Happy coding!</p>
<p>
–Chris</p>
]]></content>
  </entry>

  <entry>
    <title>Phoenix 1.7-rc released</title>
    <link rel="alternate" href="http://www.phoenixframework.org/blog/phoenix-1.7-released" />
    <id>http://www.phoenixframework.org/blog/phoenix-1.7-released</id>
    <updated>2022-11-07T00:00:00Z</updated>
    <author>
      <name>Chris McCord</name>
    </author>
    <summary type="html"><![CDATA[The first release candidate is out!]]></summary>
    <content type="html"><![CDATA[<p>
The first release candidate of Phoenix 1.7 is out! Phoenix 1.7 packs a number of long-awaited new features like verified routes, Tailwind support, LiveView authentication generators, unified HEEx templates, and more. This is a backwards compatible release with a few deprecations. Most folks should be able to update just by changing a couple dependencies.</p>
<p>
<strong>Note</strong>: For the rc period, you’ll need to explicitly install the <code class="inline">phx.new</code> generator from hex to try out a fresh project:</p>
<pre><code class="makeup console">mix archive.install hex phx_new 1.7.0-rc.0</code></pre>
<h2>
Verified Routes</h2>
<p>
Verified routes replace router helpers with a sigil-based (<code class="inline">~p</code>), compile-time verified approach.</p>
<blockquote>
  <p>
<strong>note</strong>: Verified routes make use of new Elixir 1.14 compiler features. Phoenix still supports older Elixir versions, but you’ll need to update to enjoy the new compile-time verification features.  </p>
</blockquote>
<p>
In practice this means where before you used autogenerated functions like:</p>
<pre><code class="makeup elixir"><span class="w">  </span><span class="c1"># router</span><span class="w">
  </span><span class="n">get</span><span class="w"> </span><span class="s">&quot;/oauth/callbacks/:id&quot;</span><span class="p">,</span><span class="w"> </span><span class="nc">OAuthCallbackController</span><span class="p">,</span><span class="w"> </span><span class="ss">:new</span><span class="w">

  </span><span class="c1"># usage</span><span class="w">
  </span><span class="nc">MyRouter.Helpers</span><span class="o">.</span><span class="n">o_auth_callback_path</span><span class="p" data-group-id="9737705838-1">(</span><span class="n">conn</span><span class="p">,</span><span class="w"> </span><span class="ss">:new</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;github&quot;</span><span class="p" data-group-id="9737705838-1">)</span><span class="w">
  </span><span class="c1"># =&gt; &quot;/oauth/callbacks/github&quot;</span><span class="w">

  </span><span class="nc">MyRouter.Helpers</span><span class="o">.</span><span class="n">o_auth_callback_url</span><span class="p" data-group-id="9737705838-2">(</span><span class="n">conn</span><span class="p">,</span><span class="w"> </span><span class="ss">:new</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;github&quot;</span><span class="p" data-group-id="9737705838-2">)</span><span class="w">
  </span><span class="c1"># =&gt; &quot;http://localhost:4000/oauth/callbacks/github&quot;</span></code></pre>
<p>
You can now do:</p>
<pre><code class="makeup elixir"><span class="w">  </span><span class="c1"># router</span><span class="w">
  </span><span class="n">get</span><span class="w"> </span><span class="s">&quot;/oauth/callbacks/:id&quot;</span><span class="p">,</span><span class="w"> </span><span class="nc">OAuthCallbackController</span><span class="p">,</span><span class="w"> </span><span class="ss">:new</span><span class="w">

  </span><span class="c1"># usage</span><span class="w">
  </span><span class="sx">~p&quot;/oauth/callbacks/github&quot;</span><span class="w">
  </span><span class="c1"># =&gt; &quot;/oauth/callbacks/github&quot;</span><span class="w">

  </span><span class="n">url</span><span class="p" data-group-id="4726670139-1">(</span><span class="sx">~p&quot;/oauth/callbacks/github&quot;</span><span class="p" data-group-id="4726670139-1">)</span><span class="w">
  </span><span class="c1"># =&gt; &quot;http://localhost:4000/oauth/callbacks/github&quot;</span></code></pre>
<p>
This has a number of advantages. There’s no longer guesswork on which function was inflected – is it <code class="inline">Helpers.oauth_callback_path</code> or <code class="inline">o_auth_callback_path</code>, etc. You also no longer need to include the <code class="inline">%Plug.Conn{}</code>, or <code class="inline">%Phoenix.Socket{}</code>, or endpoint module everywhere when 99% of the time you know which endpoint configuration should be used.</p>
<p>
There is also now a 1:1 mapping between the routes you write in the router, and how you call them with <code class="inline">~p</code>. You simply write it as if you’re hard-coding strings everywhere in your app – except you don’t have maintenance issues that come with hardcoding strings. We can get the best of both worlds with ease of use and maintenance because <code class="inline">~p</code> is a compile-time verified against the routes in your router.</p>
<p>
For example, imagine we typo a route:</p>
<pre><code class="makeup heex"><span class="p">&lt;</span><span class="nf">.link</span><span class="w"> </span><span class="na">href</span><span class="p">=</span><span class="p" data-group-id="2645485380-1">{</span><span class="sx">~p&quot;/userz/profile&quot;</span><span class="p" data-group-id="2645485380-1">}</span><span class="p">&gt;</span><span class="n">Profile</span><span class="p">&lt;/</span><span class="nf">.link</span><span class="p">&gt;</span></code></pre>
<p>
The compiler will dispatch all <code class="inline">~p</code>‘s at compile-time against your router, and let you know when it can’t find a matching route:</p>
<pre><code class="makeup console">    warning: no route path for AppWeb.Router matches &quot;/postz/#{post}&quot;
      lib/app_web/live/post_live.ex:100: AppWeb.PostLive.render/1</code></pre>
<p>
Dynamic “named params” are also simply interpolated like a regular string, instead of arbitrary function arguments:</p>
<pre><code class="makeup elixir"><span class="sx">~p&quot;/posts/</span><span class="si" data-group-id="6732808742-1">#{</span><span class="n">post</span><span class="o">.</span><span class="n">id</span><span class="si" data-group-id="6732808742-1">}</span><span class="sx">&quot;</span></code></pre>
<p>
Additionally, interpolated <code class="inline">~p</code> values are encoded via the <code class="inline">Phoenix.Param</code> protocol.
For example, a <code class="inline">%Post{}</code> struct in your application may derive the <code class="inline">Phoenix.Param</code>
protocol to generate slug-based paths rather than ID based ones. This allows you to
use <code class="inline">~p&quot;/posts/#{post}&quot;</code> rather than <code class="inline">~p&quot;/posts/#{post.slug}&quot;</code> throughout your
application.</p>
<p>
Query strings are also supported in verified routes, either in traditional query
string form:</p>
<pre><code class="makeup elixir"><span class="sx">~p&quot;/posts?page=</span><span class="si" data-group-id="9992553144-1">#{</span><span class="n">page</span><span class="si" data-group-id="9992553144-1">}</span><span class="sx">&quot;</span></code></pre>
<p>
Or as a keyword list or map of values:</p>
<pre><code class="makeup elixir"><span class="n">params</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p" data-group-id="6667938947-1">%{</span><span class="ss">page</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="ss">direction</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;asc&quot;</span><span class="p" data-group-id="6667938947-1">}</span><span class="w">
</span><span class="sx">~p&quot;/posts?</span><span class="si" data-group-id="6667938947-2">#{</span><span class="n">params</span><span class="si" data-group-id="6667938947-2">}</span><span class="sx">&quot;</span></code></pre>
<p>
Like path segments, query strings params are proper URL encoded and may be interpolated
directly into the <code class="inline">~p</code> string.</p>
<p>
Once you try out the new feature, you won’t be able to go back to router helpers. The new <code class="inline">phx.gen.html|live|json|auth</code> generators use verified routes.</p>
<h2>
Component-based Tailwind generators</h2>
<p>
Phoenix 1.7 ships with <a href="https://tailwindcss.com">TailwindCSS</a> by default, with no dependency on nodejs on the system. TailwindCSS is the best way I’ve found to style interfaces in my 20 years of web development. Its utility-first approach is far more maintainable and productive than any CSS system or framework I’ve used. It’s collocated approach also aligns perfectly within the fucntion component and LiveView landscape.</p>
<p>
The Tailwind team also generously designed the new project landing page, CRUD pages, and authentication system pages for new projects, giving you a first-class and polished starting point for building out your apps.</p>
<p>
A new <code class="inline">phx.new</code> project will contain a <code class="inline">CoreComponents</code> module, housing a core set of UI components like tables, modals, forms, and data lists. The suite of Phoenix generators (<code class="inline">phx.gen.html|live|json|auth</code>) make use of the core components. This has a number of neat advantages.</p>
<p>
First, you can customize your core UI components to suit whatever needs, designs, and tastes that you have. If you want to use Bulma or Bootstrap instead of Tailwind – no problem! Simply replace the function definitions in <code class="inline">core_components.ex</code> with your framework/UI specific implementations and the generators continue to provide a great starting point for new features whether you’re a beginner, or seasoned expert building bespoke product features.</p>
<p>
In practice, the generators give you templates that make use of your core components, which look like this:</p>
<pre><code class="makeup heex"><span class="p">&lt;</span><span class="nf">.header</span><span class="p">&gt;</span><span class="w">
</span><span class="n">  New Post
</span><span class="w">  </span><span class="p">&lt;</span><span class="ss">:subtitle</span><span class="p">&gt;</span><span class="n">Use this form to manage post records in your database.</span><span class="p">&lt;/</span><span class="ss">:subtitle</span><span class="p">&gt;</span><span class="w">
</span><span class="p">&lt;/</span><span class="nf">.header</span><span class="p">&gt;</span><span class="w">
</span><span class="w">
</span><span class="p">&lt;</span><span class="nf">.simple_form</span><span class="w"> </span><span class="na">:let</span><span class="p">=</span><span class="p" data-group-id="0124233464-1">{</span><span class="n">f</span><span class="p" data-group-id="0124233464-1">}</span><span class="w"> </span><span class="na">for</span><span class="p">=</span><span class="p" data-group-id="0124233464-2">{</span><span class="na">@changeset</span><span class="p" data-group-id="0124233464-2">}</span><span class="w"> </span><span class="na">action</span><span class="p">=</span><span class="p" data-group-id="0124233464-3">{</span><span class="sx">~p&quot;/posts&quot;</span><span class="p" data-group-id="0124233464-3">}</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nf">.error</span><span class="w"> </span><span class="na">:if</span><span class="p">=</span><span class="p" data-group-id="0124233464-4">{</span><span class="na">@changeset</span><span class="o">.</span><span class="n">action</span><span class="p" data-group-id="0124233464-4">}</span><span class="p">&gt;</span><span class="w">
</span><span class="n">    Oops, something went wrong! Please check the errors below.
</span><span class="w">  </span><span class="p">&lt;/</span><span class="nf">.error</span><span class="p">&gt;</span><span class="w">
</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nt">input</span><span class="w"> </span><span class="na">field</span><span class="p">=</span><span class="p" data-group-id="0124233464-5">{</span><span class="p" data-group-id="0124233464-ex-1">{</span><span class="n">f</span><span class="p">,</span><span class="w"> </span><span class="ss">:title</span><span class="p" data-group-id="0124233464-ex-1">}</span><span class="p" data-group-id="0124233464-5">}</span><span class="w"> </span><span class="na">type</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">text</span><span class="p">&quot;</span><span class="w"> </span><span class="na">label</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">Title</span><span class="p">&quot;</span><span class="w"> </span><span class="p">/&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nt">input</span><span class="w"> </span><span class="na">field</span><span class="p">=</span><span class="p" data-group-id="0124233464-6">{</span><span class="p" data-group-id="0124233464-ex-2">{</span><span class="n">f</span><span class="p">,</span><span class="w"> </span><span class="ss">:views</span><span class="p" data-group-id="0124233464-ex-2">}</span><span class="p" data-group-id="0124233464-6">}</span><span class="w"> </span><span class="na">type</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">number</span><span class="p">&quot;</span><span class="w"> </span><span class="na">label</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">Views</span><span class="p">&quot;</span><span class="w"> </span><span class="p">/&gt;</span><span class="w">
</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="ss">:actions</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nf">.button</span><span class="p">&gt;</span><span class="n">Save Post</span><span class="p">&lt;/</span><span class="nf">.button</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;/</span><span class="ss">:actions</span><span class="p">&gt;</span><span class="w">
</span><span class="p">&lt;/</span><span class="nf">.simple_form</span><span class="p">&gt;</span><span class="w">
</span><span class="w">
</span><span class="p">&lt;</span><span class="nf">.back</span><span class="w"> </span><span class="na">navigate</span><span class="p">=</span><span class="p" data-group-id="0124233464-7">{</span><span class="sx">~p&quot;/posts&quot;</span><span class="p" data-group-id="0124233464-7">}</span><span class="p">&gt;</span><span class="n">Back to posts&gt;</span><span class="p">&lt;/</span><span class="nf">.back</span><span class="p">&gt;</span></code></pre>
<p>
We love what the Tailwind team designed for new applications, but we also can’t wait to see the community release their own drop-in replacements for <code class="inline">core_components.ex</code> for various frameworks of choice.</p>
<h2>
Unified function components across Controllers and LiveViews</h2>
<p>
Function components provided by HEEx, with declarative assigns and slots, are massive step-change in the way we write HTML in Phoenix projects. Function components provide UI building blocks, allowing features to be encapsulated and better extended over the previous template approach in <code class="inline">Phoenix.View</code>. You get a more natural way to write dynamic markup, reusable UI that can be extended by the caller, and compile-time features to make writing HTML-based applications a truly first-class experience.</p>
<p>
Function components bring a new way to write HTML applications in Phoenix, with new sets of conventions. Additionally, users have struggled with how to marry controller-based <code class="inline">Phoenix.View</code> features with <code class="inline">Phoenix.LiveView</code> features in their applications. Users found themselves writing <code class="inline">render(&quot;table&quot;, user: user)</code> in controller-based templates, while their LiveViews made use of the new <code class="inline">&lt;.table rows={@users}&gt;</code> features. There was no great way to share the approaches in an application.</p>
<p>
For these reasons, the Phoenix team unified the HTML rendering approaches whether from a controller request, or a LiveView. This shift also allowed us to revisit conventions and align with the LiveView approach of collocating templates and app code together.</p>
<p>
New applications (and the phx generators), remove <code class="inline">Phoenix.View</code> as a dependency in favor of a new <code class="inline">Phoenix.Template</code> dependency, which uses function components as the basis for all rendering in the framework.</p>
<p>
Your controllers still look the same:</p>
<pre><code class="makeup elixir"><span class="kd">defmodule</span><span class="w"> </span><span class="nc">AppWeb.UserController</span><span class="w"> </span><span class="k" data-group-id="0604668706-1">do</span><span class="w">
  </span><span class="kn">use</span><span class="w"> </span><span class="nc">MyAppWeb</span><span class="p">,</span><span class="w"> </span><span class="ss">:controller</span><span class="w">

  </span><span class="kd">def</span><span class="w"> </span><span class="nf">index</span><span class="p" data-group-id="0604668706-2">(</span><span class="n">conn</span><span class="p">,</span><span class="w"> </span><span class="c">_params</span><span class="p" data-group-id="0604668706-2">)</span><span class="w"> </span><span class="k" data-group-id="0604668706-3">do</span><span class="w">
    </span><span class="n">users</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">...</span><span class="w">
    </span><span class="n">render</span><span class="p" data-group-id="0604668706-4">(</span><span class="n">conn</span><span class="p">,</span><span class="w"> </span><span class="ss">:index</span><span class="p">,</span><span class="w"> </span><span class="ss">users</span><span class="p">:</span><span class="w"> </span><span class="n">users</span><span class="p" data-group-id="0604668706-4">)</span><span class="w">
  </span><span class="k" data-group-id="0604668706-3">end</span><span class="w">
</span><span class="k" data-group-id="0604668706-1">end</span></code></pre>
<p>
But instead of the controller calling <code class="inline">AppWeb.UserView.render(&quot;index.html&quot;, assigns)</code>, we’ll now first look for an <code class="inline">index/1</code> function component on the view module, and call that for rendering if it exists. Additionally, we also renamed the inflected view module to look for <code class="inline">AppWeb.UserHTML</code>, or <code class="inline">AppWeb.UserJSON</code>, and so on for a view-per-format approach for rendering templates. This is all done in backwards compatible way, and is opt-in based on options to <code class="inline">use Phoenix.Controller</code>.</p>
<p>
All HTML rendering is then based on function components, which can be written directly in a module, or embedded from an external file with the new <code class="inline">embed_templates</code> macro provided by <code class="inline">Phoenix.Component</code>. Your <code class="inline">PageHTML</code> module in a new application looks like this:</p>
<pre><code class="makeup elixir"><span class="kd">defmodule</span><span class="w"> </span><span class="nc">AppWeb.PageHTML</span><span class="w"> </span><span class="k" data-group-id="4459295250-1">do</span><span class="w">
  </span><span class="kn">use</span><span class="w"> </span><span class="nc">AppWeb</span><span class="p">,</span><span class="w"> </span><span class="ss">:html</span><span class="w">

  </span><span class="n">embed_templates</span><span class="w"> </span><span class="s">&quot;page_html/*&quot;</span><span class="w">
</span><span class="k" data-group-id="4459295250-1">end</span></code></pre>
<p>
The new directory structure will look something like this:</p>
<pre><code class="makeup console">lib/app_wb
├── controllers
│   ├── page_controller.ex
│   ├── page_html.ex
│   ├── error_html.ex
│   ├── error_json.ex
│   └── page_html
│       └── home.html.heex
├── live
│   ├── home_live.ex
├── components
│   ├── core_components.ex
│   ├── layouts.ex
│   └── layouts
│       ├── app.html.heex
│       └── root.html.heex
├── endpoint.ex
└── router.ex</code></pre>
<p>
Your controllers-based rendering or LiveView-based rendering now all share the same function components and layouts. Whether running <code class="inline">phx.gen.html</code>, <code class="inline">phx.gen.live</code>, or <code class="inline">phx.gen.auth</code>, the new generated templates all make use of your <code class="inline">components/core_components.ex</code> definitions.</p>
<p>
Additionally, we have collocated the view modules next to their controller files. This brings the same benefits of LiveView collocation – highly coupled files live together. Files that must change together now live together, whether writing LiveView or controller features.</p>
<p>
These changes were all about a better way to write HTML-based applications, but they also simplified rendering other formats, like JSON. For example, JSON based view modules follow the same conventions – Phoenix will first look for an <code class="inline">index/1</code> function when rendering the index template, before trying <code class="inline">render/2</code>. This allowed us to simplify JSON rendering in general and do away with concepts like <code class="inline">Phoenix.View.render_one|render_many</code>.</p>
<p>
For example, this is a JSON view generated by <code class="inline">phx.gen.json</code>:</p>
<pre><code class="makeup elixir"><span class="kd">defmodule</span><span class="w"> </span><span class="nc">AppWeb.PostJSON</span><span class="w"> </span><span class="k" data-group-id="5028840736-1">do</span><span class="w">
  </span><span class="kn">alias</span><span class="w"> </span><span class="nc">AppWeb.Blog.Post</span><span class="w">

  </span><span class="na">@doc</span><span class="w"> </span><span class="s">&quot;&quot;&quot;
  Renders a list of posts.
  &quot;&quot;&quot;</span><span class="w">
  </span><span class="kd">def</span><span class="w"> </span><span class="nf">index</span><span class="p" data-group-id="5028840736-2">(</span><span class="p" data-group-id="5028840736-3">%{</span><span class="ss">posts</span><span class="p">:</span><span class="w"> </span><span class="n">posts</span><span class="p" data-group-id="5028840736-3">}</span><span class="p" data-group-id="5028840736-2">)</span><span class="w"> </span><span class="k" data-group-id="5028840736-4">do</span><span class="w">
    </span><span class="p" data-group-id="5028840736-5">%{</span><span class="ss">data</span><span class="p">:</span><span class="w"> </span><span class="k">for</span><span class="p" data-group-id="5028840736-6">(</span><span class="n">post</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="n">posts</span><span class="p">,</span><span class="w"> </span><span class="ss">do</span><span class="p">:</span><span class="w"> </span><span class="n">data</span><span class="p" data-group-id="5028840736-7">(</span><span class="n">post</span><span class="p" data-group-id="5028840736-7">)</span><span class="p" data-group-id="5028840736-6">)</span><span class="p" data-group-id="5028840736-5">}</span><span class="w">
  </span><span class="k" data-group-id="5028840736-4">end</span><span class="w">

  </span><span class="na">@doc</span><span class="w"> </span><span class="s">&quot;&quot;&quot;
  Renders a single post.
  &quot;&quot;&quot;</span><span class="w">
  </span><span class="kd">def</span><span class="w"> </span><span class="nf">show</span><span class="p" data-group-id="5028840736-8">(</span><span class="p" data-group-id="5028840736-9">%{</span><span class="ss">post</span><span class="p">:</span><span class="w"> </span><span class="n">post</span><span class="p" data-group-id="5028840736-9">}</span><span class="p" data-group-id="5028840736-8">)</span><span class="w"> </span><span class="k" data-group-id="5028840736-10">do</span><span class="w">
    </span><span class="p" data-group-id="5028840736-11">%{</span><span class="ss">data</span><span class="p">:</span><span class="w"> </span><span class="n">data</span><span class="p" data-group-id="5028840736-12">(</span><span class="n">post</span><span class="p" data-group-id="5028840736-12">)</span><span class="p" data-group-id="5028840736-11">}</span><span class="w">
  </span><span class="k" data-group-id="5028840736-10">end</span><span class="w">

  </span><span class="kd">defp</span><span class="w"> </span><span class="nf">data</span><span class="p" data-group-id="5028840736-13">(</span><span class="p" data-group-id="5028840736-14">%</span><span class="nc" data-group-id="5028840736-14">Post</span><span class="p" data-group-id="5028840736-14">{</span><span class="p" data-group-id="5028840736-14">}</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">post</span><span class="p" data-group-id="5028840736-13">)</span><span class="w"> </span><span class="k" data-group-id="5028840736-15">do</span><span class="w">
    </span><span class="p" data-group-id="5028840736-16">%{</span><span class="w">
      </span><span class="ss">id</span><span class="p">:</span><span class="w"> </span><span class="n">post</span><span class="o">.</span><span class="n">id</span><span class="p">,</span><span class="w">
      </span><span class="ss">title</span><span class="p">:</span><span class="w"> </span><span class="n">post</span><span class="o">.</span><span class="n">title</span><span class="w">
    </span><span class="p" data-group-id="5028840736-16">}</span><span class="w">
  </span><span class="k" data-group-id="5028840736-15">end</span><span class="w">
</span><span class="k" data-group-id="5028840736-1">end</span></code></pre>
<p>
Notice how it’s all simply regular Elixir functions – as it should be!</p>
<p>
These features provide a unified rendering model for applications going forward with a new and improved way to write UIs, but they are a deviation from previous practices. Most large, established applications are probably best served by continuing to depend on <code class="inline">Phoenix.View</code>.</p>
<h2>
Alternative Webserver Support</h2>
<p>
Thanks to work by Mat Trudel, we now have the basis for first-class webserver support in Plug and Phoenix, allowing other webservers like <a href="https://github.com/mtrudel/bandit">Bandit</a> to be swapped in Phoenix while enjoying all features like WebSockets, Channels, and LiveView. Stay tuned to the Bandit project if you’re interested in a pure Elixir HTTP server or give it a try in your own Phoenix projects!</p>
<p>
As always, <a href="https://gist.github.com/chrismccord/00a6ea2a96bc57df0cce526bd20af8a7">step-by-step upgrade guides</a> are there to take your existing 1.6.x apps up to 1.7.</p>
<p>
The full changelog can be found <a href="https://github.com/phoenixframework/phoenix/blob/a8707baed3e67ffaaa1e3e8c2b2271291b57407c/CHANGELOG.md">here</a>.</p>
<p>
Find us on elixir slack or <a href="https://elixirforum.com">the forums</a> if you have issues.</p>
<p>
Happy coding!</p>
<p>
–Chris</p>
]]></content>
  </entry>

  <entry>
    <title>LiveView 0.18 Released</title>
    <link rel="alternate" href="http://www.phoenixframework.org/blog/phoenix-liveview-0.18-released" />
    <id>http://www.phoenixframework.org/blog/phoenix-liveview-0.18-released</id>
    <updated>2022-09-21T00:00:00Z</updated>
    <author>
      <name>Chris McCord</name>
    </author>
    <summary type="html"><![CDATA[LiveView 0.18 is out! This release includes major new declarative assigns features for compile-time warnings, new JS commands, and more!]]></summary>
    <content type="html"><![CDATA[<p>
We’ve been working on some game-changing features for LiveView 0.18.0. Declarative assigns and slots provide compile-time warnings and enhanced docs that make building out your own UI or consuming UI libraries such a pleasant experience. These new features take function components to the next level to provide a truly first-class composable component system.</p>
<p>
Additionally, new out-of-the-box focus components and JS commands provide accessibility improvements to ensure LiveView applications work well for all users. We also shipped a new mix formatter plugin for formatting HEEx templates, which is something you can’t live without after using it.</p>
<p>
To understand why these features are a such a big deal, let’s take a look at a simple function component. Let’s say we have a <code class="inline">modal</code> component in our application and we want to call it. Prior to function components, you would write your Elixir template like this:</p>
<pre><code class="makeup heex"><span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p" data-group-id="9199285457-1">&lt;%=</span><span class="w"> </span><span class="n">modal</span><span class="p" data-group-id="9199285457-ex-1">(</span><span class="ss">title</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;Your file is ready!&quot;</span><span class="p" data-group-id="9199285457-ex-1">)</span><span class="w"> </span><span class="k" data-group-id="9199285457-ex-2">do</span><span class="w"> </span><span class="p" data-group-id="9199285457-1">%&gt;</span><span class="w">
</span><span class="n">    The file will only be available for 10 minutes
</span><span class="w">  </span><span class="p" data-group-id="9199285457-2">&lt;%</span><span class="w"> </span><span class="k" data-group-id="9199285457-ex-2">end</span><span class="w"> </span><span class="p" data-group-id="9199285457-2">%&gt;</span><span class="w">
</span><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span></code></pre>
<p>
This is simple enough, but the issue comes when we want to place arbitrary content inside our modal title, such as a link to the download. We end up trying to concat raw HTML string together and it’s a nonstarter:</p>
<pre><code class="makeup heex"><span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p" data-group-id="7192483041-1">&lt;%=</span><span class="w"> </span><span class="n">modal</span><span class="p" data-group-id="7192483041-ex-1">(</span><span class="ss">title</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;Your </span><span class="si" data-group-id="7192483041-ex-2">#{</span><span class="s">&quot;&lt;a href=</span><span class="se">\&quot;</span><span class="si" data-group-id="7192483041-ex-3">#{</span><span class="na">@url</span><span class="si" data-group-id="7192483041-ex-3">}</span><span class="se">\&quot;</span><span class="s"> download&gt;file&lt;/a&gt;&quot;</span><span class="si" data-group-id="7192483041-ex-2">}</span><span class="s"> is ready!&quot;</span><span class="p" data-group-id="7192483041-ex-1">)</span><span class="w"> </span><span class="k" data-group-id="7192483041-ex-4">do</span><span class="w"> </span><span class="p" data-group-id="7192483041-1">%&gt;</span><span class="w">
</span><span class="n">    The file will only be available for 10 minutes
</span><span class="w">  </span><span class="p" data-group-id="7192483041-2">&lt;%</span><span class="w"> </span><span class="k" data-group-id="7192483041-ex-4">end</span><span class="w"> </span><span class="p" data-group-id="7192483041-2">%&gt;</span><span class="w">
</span><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span></code></pre>
<p>
This fails because Phoenix performs HTML escaping on strings, so we’d need to carefully unpack our own HTML input. The standard template model breaks down in terms of composability. Function components and slots solve this issue by allowing components to declare named slots where the caller can provide arbitrary content to named sections such as a modal title, header, or footer. Let’s rewrite the above with a function component and slot:</p>
<pre><code class="makeup heex"><span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nf">.modal</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="ss">:title</span><span class="p">&gt;</span><span class="w">
</span><span class="n">      Your </span><span class="p">&lt;</span><span class="nf">.link</span><span class="w"> </span><span class="na">href</span><span class="p">=</span><span class="p" data-group-id="0735406564-1">{</span><span class="na">@url</span><span class="p" data-group-id="0735406564-1">}</span><span class="w"> </span><span class="na">download</span><span class="p">&gt;</span><span class="n">file</span><span class="p">&lt;/</span><span class="nf">.link</span><span class="p">&gt;</span><span class="n"> is ready!
</span><span class="w">    </span><span class="p">&lt;/</span><span class="ss">:title</span><span class="p">&gt;</span><span class="w">
</span><span class="n">    The file will only be available for 10 minutes
</span><span class="w">  </span><span class="p">&lt;/</span><span class="nf">.modal</span><span class="p">&gt;</span><span class="w">
</span><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span></code></pre>
<p>
Now we can start to see the real power of function components and slots. Function components compose nicely within markup, and slots allow the caller to provide their own arbitrary structure, like the <code class="inline">&lt;:title&gt;</code> slot above. Here we are passing our own markup content, including calls to other function components! This allows encapsulated UI building blocks that compose together. No more arbitrary string concatenation or bespoke strict templates for each and every usecase.</p>
<p>
Slots provide even more powerful composition. Instead of placing a single named thing in a component, slots are collections, which allows the caller to provide an arbitrary number of slot entries to a component. For example, imagine a table component where the caller needs to provide an arbitrary number of table columns. This is impossible to compose cleanly with common static HTML template abstractions, but with slots it’s beautiful to read and write:</p>
<pre><code class="makeup heex"><span class="p">&lt;</span><span class="nf">.table</span><span class="w"> </span><span class="na">id</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">f</span><span class="s2">i</span><span class="s2">l</span><span class="s2">e</span><span class="s2">s</span><span class="p">&quot;</span><span class="w"> </span><span class="na">rows</span><span class="p">=</span><span class="p" data-group-id="3915005384-1">{</span><span class="na">@files</span><span class="p" data-group-id="3915005384-1">}</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="ss">:col</span><span class="w"> </span><span class="na">:let</span><span class="p">=</span><span class="p" data-group-id="3915005384-2">{</span><span class="n">file</span><span class="p" data-group-id="3915005384-2">}</span><span class="w"> </span><span class="na">label</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">Name</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="p" data-group-id="3915005384-3">&lt;%=</span><span class="w"> </span><span class="n">file</span><span class="o">.</span><span class="n">name</span><span class="w"> </span><span class="p" data-group-id="3915005384-3">%&gt;</span><span class="p">&lt;/</span><span class="ss">:col</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="ss">:col</span><span class="w"> </span><span class="na">:let</span><span class="p">=</span><span class="p" data-group-id="3915005384-4">{</span><span class="n">file</span><span class="p" data-group-id="3915005384-4">}</span><span class="w"> </span><span class="na">label</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">Size</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="p" data-group-id="3915005384-5">&lt;%=</span><span class="w"> </span><span class="n">file</span><span class="o">.</span><span class="n">size</span><span class="w"> </span><span class="p" data-group-id="3915005384-5">%&gt;</span><span class="p">&lt;/</span><span class="ss">:col</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="ss">:action</span><span class="w"> </span><span class="na">:let</span><span class="p">=</span><span class="p" data-group-id="3915005384-6">{</span><span class="n">file</span><span class="p" data-group-id="3915005384-6">}</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nf">.link</span><span class="w"> </span><span class="na">patch</span><span class="p">=</span><span class="p" data-group-id="3915005384-7">{</span><span class="sx">~p&quot;/files/</span><span class="si" data-group-id="3915005384-ex-1">#{</span><span class="n">file</span><span class="si" data-group-id="3915005384-ex-1">}</span><span class="sx">/edit&quot;</span><span class="p" data-group-id="3915005384-7">}</span><span class="p">&gt;</span><span class="n">Edit</span><span class="p">&lt;/</span><span class="nf">.link</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;/</span><span class="ss">:action</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="ss">:action</span><span class="w"> </span><span class="na">:let</span><span class="p">=</span><span class="p" data-group-id="3915005384-8">{</span><span class="n">file</span><span class="p" data-group-id="3915005384-8">}</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nf">.link</span><span class="w"> </span><span class="na">phx-click</span><span class="p">=</span><span class="p" data-group-id="3915005384-9">{</span><span class="nc">JS</span><span class="o">.</span><span class="n">push</span><span class="p" data-group-id="3915005384-ex-2">(</span><span class="s">&quot;delete&quot;</span><span class="p">,</span><span class="w"> </span><span class="ss">value</span><span class="p">:</span><span class="w"> </span><span class="p" data-group-id="3915005384-ex-3">%{</span><span class="ss">id</span><span class="p">:</span><span class="w"> </span><span class="n">file</span><span class="o">.</span><span class="n">id</span><span class="p" data-group-id="3915005384-ex-3">}</span><span class="p" data-group-id="3915005384-ex-2">)</span><span class="p" data-group-id="3915005384-9">}</span><span class="w"> </span><span class="na">data-confirm</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">Are you sure?</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="w">
</span><span class="n">      Delete
</span><span class="w">    </span><span class="p">&lt;/</span><span class="nf">.link</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;/</span><span class="ss">:action</span><span class="p">&gt;</span><span class="w">
</span><span class="p">&lt;/</span><span class="nf">.table</span><span class="p">&gt;</span></code></pre>
<p>
Rather than writing raw <code class="inline">&lt;table&gt;</code> tags everywhere or bespoke functions like <code class="inline">&lt;%= file_table(rows: @files)&gt;</code>, we can leverage slots to define a single <code class="inline">&lt;.table&gt;</code> function component in our application which accepts a <code class="inline">&lt;:col&gt;</code> and <code class="inline">&lt;:action&gt;</code> slot. Notice how we passed multiple columns and actions above? This is a beautiful feature of slots. Internally the table can render the <code class="inline">&lt;thead&gt;</code> and <code class="inline">&lt;th&gt;</code> entries based on each <code class="inline">&lt;:col&gt;</code> <code class="inline">label</code> we provided. Next, to render each row, the component simply iterates over each of the <code class="inline">rows</code> we passed and renders a column and inner content based on each of our <code class="inline">&lt;:col&gt;</code>‘s. This makes writing markup far more pleasant and reusable compared to writing raw HTML and copy pasting styles and tags around. You’ll find once you have a core set of UI components established in your application, you rarely need to extend them with new features because of component and slot composition.</p>
<p>
This is all looking really nice, but with more attributes and slots that are added over time, how do we discover what’s actually supported? How confident can we be with consuming such components? Enter declarative assigns and slots, where the compiler has your back.</p>
<h2>
Declarative Assigns and Slots</h2>
<p>
Thanks to Marlus Saraiva’s work trailblazing these features in the <a href="https://surface-ui.org">Surface</a> library, and then contributing them to LiveView, components have now been taken to the next-level of usability and productivity.</p>
<p>
To see how, let’s head back over to our modal. Let’s say we way to automatically show the modal when it is rendered on page load vs programmatically showing it later. Assuming someone on the team decently documented the function, we could go spelunking and maybe find an example with the <code class="inline">show</code> attribute to auto-show the modal. But even the best documentation can’t save us from typos or incorrect attributes or slots. For example, imagine we incorrectly pass <code class="inline">&lt;.modal autoshow&gt;</code>:</p>
<p>
  <img src="/images/blog/lv018/editor-warning.png" alt="">
</p>
<p>
Fortunately, the compiler has our back! Here we see that our <code class="inline">autoshow</code> is an unknown attribute, and we learn we left off the required <code class="inline">id</code> attribute. We get this feedback in real-time in our editor rather than waiting for an error to pop up at runtime.</p>
<p>
With declarative assigns and slots, function components specify the attributes, types, and slots they accept along with inline documentation. This not only provides enhanced docs, but each call of the component will be compile-time verified to provide feedback. No more runtime footguns or guesswork. This also allows the community to release first-class UI libraries where users can hit the ground running while building out their applications. Here’s what it looks like in practice:</p>
<p>
  <img src="/images/blog/lv018/def-modal.png" alt="">
</p>
<h2>
HEEx HTML Formatter</h2>
<p>
Thanks to fantastic work by <a href="https://twitter.com/FeelipeRenan">Feelipe Renan</a>, your <code class="inline">.heex</code> files and any <code class="inline">~H</code> template in the app will now be HTML formatted when running <code class="inline">mix format</code>. This is especially helpful because your markup and function components have embedded Elixir expressions, and you want those expressions formatted with regular rules like any other Elixir code. The HEEx formatter handles all those cases – formatting generic markup, formatting Elixir expressions within tags, and formatting your Elixir expressions within EEx content <code class="inline">&lt;%= %&gt;</code>. The formatter also makes sure both front-end and backend developers follow a unified coding guideline across their applications.</p>
<p>
Let’s see it in action:</p>
<p>
  <img src="/images/blog/lv018/format.gif" alt="">
</p>
<h2>
Accessibility</h2>
<p>
LiveView should be a fantastic web experience for all users, including those with accessibility needs such as screen reader users. LiveView 0.18 includes a few primitives to help on this front, with more coming soon.</p>
<p>
First, we shipped a new <code class="inline">&lt;.focus_wrap&gt;</code> component that allows you to simply wrap any template content in <code class="inline">&lt;.focus_wrap&gt;...&lt;/.focus_wrap&gt;</code> to have tab focus wrap back around the element. This may not sound exciting, but for screen reader users navigating with the keyboard, it is an essential primitive when displaying dialogs and modals. It’s also a feature that otherwise requires developer intervention with custom JavaScript. Let’s see it in action:</p>
<p>
  <img src="/images/blog/lv018/focus-wrap.gif" alt="">
</p>
<p>
We also shipped new JS commands for handling focus states, including <code class="inline">JS.focus</code> and <code class="inline">JS.focus_first</code>. Both allow you to programmatically set the focus on an element, but <code class="inline">JS.focus_first</code> is especially nice as a set-and-forget command that does the right thing from an accessibility perspective. It simply finds the first focusable element within a container and sets focus there, without you having to think about it. For example, when popping a modal, you don’t need to consider if the modal has form inputs in one case, or only confirm/cancel buttons in another. Simply use the command to find the first thing to focus on and LiveView will place focus there.</p>
<p>
Your show modal function might look something like this:</p>
<pre><code class="makeup elixir"><span class="kd">def</span><span class="w"> </span><span class="nf">show_modal</span><span class="p" data-group-id="6416472808-1">(</span><span class="n">js</span><span class="w"> </span><span class="o">\\</span><span class="w"> </span><span class="p" data-group-id="6416472808-2">%</span><span class="nc" data-group-id="6416472808-2">JS</span><span class="p" data-group-id="6416472808-2">{</span><span class="p" data-group-id="6416472808-2">}</span><span class="p">,</span><span class="w"> </span><span class="n">id</span><span class="p" data-group-id="6416472808-1">)</span><span class="w"> </span><span class="ow">when</span><span class="w"> </span><span class="n">is_binary</span><span class="p" data-group-id="6416472808-3">(</span><span class="n">id</span><span class="p" data-group-id="6416472808-3">)</span><span class="w"> </span><span class="k" data-group-id="6416472808-4">do</span><span class="w">
  </span><span class="n">js</span><span class="w">
  </span><span class="o">|&gt;</span><span class="w"> </span><span class="nc">JS</span><span class="o">.</span><span class="n">show</span><span class="p" data-group-id="6416472808-5">(</span><span class="ss">to</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;#</span><span class="si" data-group-id="6416472808-6">#{</span><span class="n">id</span><span class="si" data-group-id="6416472808-6">}</span><span class="s">&quot;</span><span class="p" data-group-id="6416472808-5">)</span><span class="w">
  </span><span class="o">|&gt;</span><span class="w"> </span><span class="nc">JS</span><span class="o">.</span><span class="n">show</span><span class="p" data-group-id="6416472808-7">(</span><span class="ss">to</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;#</span><span class="si" data-group-id="6416472808-8">#{</span><span class="n">id</span><span class="si" data-group-id="6416472808-8">}</span><span class="s">-bg&quot;</span><span class="p" data-group-id="6416472808-7">)</span><span class="w">
  </span><span class="o">|&gt;</span><span class="w"> </span><span class="n">show</span><span class="p" data-group-id="6416472808-9">(</span><span class="s">&quot;#</span><span class="si" data-group-id="6416472808-10">#{</span><span class="n">id</span><span class="si" data-group-id="6416472808-10">}</span><span class="s">-container&quot;</span><span class="p" data-group-id="6416472808-9">)</span><span class="w">
  </span><span class="o">|&gt;</span><span class="w"> </span><span class="nc">JS</span><span class="o">.</span><span class="n">focus_first</span><span class="p" data-group-id="6416472808-11">(</span><span class="ss">to</span><span class="p">:</span><span class="w"> </span><span class="s">&quot;#</span><span class="si" data-group-id="6416472808-12">#{</span><span class="n">id</span><span class="si" data-group-id="6416472808-12">}</span><span class="s">-container&quot;</span><span class="p" data-group-id="6416472808-11">)</span><span class="w">
</span><span class="k" data-group-id="6416472808-4">end</span></code></pre>
<p>
Now any place that shows a modal in the app will focus within that modal. Coupled with <code class="inline">&lt;.focus_wrap&gt;</code> and the aria labels that <code class="inline">phx.new</code> will generate, screen readers users will have a fully accessible modal out of the box when using your application.</p>
<h2>
JS commands and hooks in dead views</h2>
<p>
JS commands are another declarative LiveView feature that allows you to manipulate the DOM on the client without a trip to the server. They are especially useful for showing modals and dropdowns, dispatching events, animating content, and toggling attributes, but so far have been a LiveView specific feature. Regular static views also share these same use-cases where folks want to show or hide content without bespoke JavaScript or brining in frameworks. LiveView 0.18 now includes support for JS commands and hooks inside content rendered outside of LiveView. This allows many of your core UI function components to be used across LiveViews or regular views, such as modals, flash messages, and dropdowns.</p>
<p>
We are super excited about this release and the productivity improvements it brings, and we can’t wait to show off all the neat things it enables with upcoming Phoenix 1.7 applications.</p>
<p>
Stay tuned and happy coding!</p>
<p>
–Chris</p>
]]></content>
  </entry>

  <entry>
    <title>ElixirConf 2022 Keynote</title>
    <link rel="alternate" href="http://www.phoenixframework.org/blog/elixirconf-closing-keynote" />
    <id>http://www.phoenixframework.org/blog/elixirconf-closing-keynote</id>
    <updated>2022-09-20T00:00:00Z</updated>
    <author>
      <name>Chris McCord</name>
    </author>
    <summary type="html"><![CDATA[Chris McCord's closing keynote of ElixirConf 2022
detailing Phoenix 1.7 and LiveView 0.18]]></summary>
    <content type="html"><![CDATA[<iframe width="100%" height="515" src="https://www.youtube.com/embed/9-rqBLjr5Eo">
</iframe>
]]></content>
  </entry>

  <entry>
    <title>Phoenix 1.6.0 Released</title>
    <link rel="alternate" href="http://www.phoenixframework.org/blog/phoenix-1.6-released" />
    <id>http://www.phoenixframework.org/blog/phoenix-1.6-released</id>
    <updated>2021-08-26T00:00:00Z</updated>
    <author>
      <name>Chris McCord</name>
    </author>
    <summary type="html"><![CDATA[Phoenix 1.6 is out! This release ships with new authentication and mailer generators, a new HEEx template engine, and more.]]></summary>
    <content type="html"><![CDATA[<p>
I’m pleased to announce the release of Phoenix 1.6.0 has landed on the heels of a fresh LiveView 0.16 release! This release brings a number of major additions, quality of life improvements, bug fixes, and a couple deprecations. You can grab the rc <code class="inline">phx.new</code> generator with the following command:</p>
<pre><code class="makeup console">mix archive.install hex phx_new 1.6.0</code></pre>
<h2>
New authentication and mailer generators</h2>
<p>
Thanks to efforts by José Valim and Aaron Renner, Phoenix 1.6 ships with a new <code class="inline">phx.gen.auth</code> command for a complete authentication solution bootstrapped into your application. You can read about the design decisions behind the authentication generators in <a href="https://dashbit.co/blog/a-new-authentication-solution-for-phoenix">José’s post on the Dashbit blog</a>. Phoenix also now includes swoosh by default for mailer support and a new <code class="inline">phx.gen.notifier</code> task for generating a notifier for sending email along with a dev mailbox for local development.</p>
<h2>
New LiveView HEEx engine</h2>
<p>
In addition to the generators, Phoenix LiveView 0.16 was just released with a new HTML engine (HEEx, <code class="inline">~H</code>) for HTML-aware template compilation which you’ll see utilized in all phoenix generated HTML files going forward (<code class="inline">phx.new, phx.gen.html, phx.gen.live, etc</code>). The new engine not only enforces proper HTML, but provides syntax conveniences for rendering components, such as <code class="inline">&lt;.form for={@user} id=&quot;user-form&quot;&gt;</code>. This new engine is thanks to Marlus Saraiva’s excellent work that he extracted from his wonderful <a href="https://surface-ui.org">Surface</a> library, which builds features on top of Phoenix LiveView. We look forward to seeing where each project can continue to innovate and share back as we work towards new engine features. With the HTML engine and function components in place, we have the layed the groundwork for a vibrant ecosystem of shared and resuable components. You can follow along the <a href="https://github.com/phoenixframework/phoenix_live_view/issues/1506">HEEx roadmap</a> to stay up to date on our feature plans. For now, here’s a quick rundown on HEEx from the Phoenix LiveView docs to bring folks up to speed:</p>
<blockquote>
  <p>
Note: <code class="inline">HEEx</code> requires Elixir >= <code class="inline">1.12.0</code> in order to provide accurate
file:line:column information in error messages. Earlier Elixir versions will
work but will show inaccurate error messages.  </p>
</blockquote>
<p>
<code class="inline">HEEx</code> is a HTML-aware and component-friendly extension of <code class="inline">EEx</code> that provides:</p>
<ul>
  <li>
Built-in handling of HTML attributes  </li>
  <li>
An HTML-like notation for injecting function components  </li>
  <li>
Compile-time validation of the structure of the template  </li>
  <li>
The ability to minimize the amount of data sent over the wire  </li>
</ul>
<h3>
Example</h3>
<pre><code class="makeup "><span class="sx">~H&quot;&quot;&quot;</span><span class="w">
</span><span class="p">&lt;</span><span class="nt">div</span><span class="w"> </span><span class="na">title</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">My div</span><span class="p">&quot;</span><span class="w"> </span><span class="na">class</span><span class="p">=</span><span class="p" data-group-id="6384016534-1">{</span><span class="na">@class</span><span class="p" data-group-id="6384016534-1">}</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span><span class="n">Hello </span><span class="p" data-group-id="6384016534-2">&lt;%=</span><span class="w"> </span><span class="na">@name</span><span class="w"> </span><span class="p" data-group-id="6384016534-2">%&gt;</span><span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nc">MyApp.Weather</span><span class="o">.</span><span class="n">city</span><span class="w"> </span><span class="na">name</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">Kraków</span><span class="p">&quot;</span><span class="p">/&gt;</span><span class="w">
</span><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span><span class="w">
</span><span class="sx">&quot;&quot;&quot;</span></code></pre>
<h3>
Syntax</h3>
<p>
<code class="inline">HEEx</code> is built on top of Embedded Elixir (<code class="inline">EEx</code>), a templating syntax that uses
<code class="inline">&lt;%= ... %&gt;</code> for interpolating results. In this section, we are going to cover the
basic constructs in <code class="inline">HEEx</code> templates as well as its syntax extensions.</p>
<h4>
Interpolation</h4>
<p>
Both <code class="inline">HEEx</code> and <code class="inline">EEx</code> templates use <code class="inline">&lt;%= ... %&gt;</code> for interpolating code inside the body
of HTML tags:</p>
<pre><code class="makeup heex"><span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span><span class="n">Hello, </span><span class="p" data-group-id="1832408980-1">&lt;%=</span><span class="w"> </span><span class="na">@name</span><span class="w"> </span><span class="p" data-group-id="1832408980-1">%&gt;</span><span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span></code></pre>
<p>
Similarly, conditionals and other block Elixir constructs are supported:</p>
<pre><code class="makeup heex"><span class="p" data-group-id="6578591915-1">&lt;%=</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="na">@show_greeting?</span><span class="w"> </span><span class="k" data-group-id="6578591915-ex-1">do</span><span class="w"> </span><span class="p" data-group-id="6578591915-1">%&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span><span class="n">Hello, </span><span class="p" data-group-id="6578591915-2">&lt;%=</span><span class="w"> </span><span class="na">@name</span><span class="w"> </span><span class="p" data-group-id="6578591915-2">%&gt;</span><span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span><span class="w">
</span><span class="p" data-group-id="6578591915-3">&lt;%</span><span class="w"> </span><span class="k" data-group-id="6578591915-ex-1">end</span><span class="w"> </span><span class="p" data-group-id="6578591915-3">%&gt;</span></code></pre>
<p>
Note we don’t include the equal sign <code class="inline">=</code> in the closing tag (because the closing
tag does not output anything).</p>
<p>
There is one important difference between <code class="inline">HEEx</code> and Elixir’s builtin <code class="inline">EEx</code>.
<code class="inline">HEEx</code> uses a specific annotation for interpolating HTML tags and attributes.
Let’s check it out.</p>
<h4>
HEEx extension: Defining attributes</h4>
<p>
Since <code class="inline">HEEx</code> must parse and validate the HTML structure, code interpolation using
<code class="inline">&lt;%= ... %&gt;</code> and <code class="inline">&lt;% ... %&gt;</code> are restricted to the body (inner content) of the
HTML/component nodes and it cannot be applied within tags.</p>
<p>
For instance, the following syntax is invalid:</p>
<pre><code class="makeup html_eex"><span class="p">&lt;</span><span class="nt">div</span><span class="w"> </span><span class="na">class</span><span class="p">=</span><span class="p">&quot;</span><span class="p" data-group-id="1368805604-1">&lt;%=</span><span class="w"> </span><span class="na">@class</span><span class="w"> </span><span class="p" data-group-id="1368805604-1">%&gt;</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="w">
</span><span class="n">  ...
</span><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span></code></pre>
<p>
Instead do:</p>
<pre><code class="makeup heex"><span class="p">&lt;</span><span class="nt">div</span><span class="w"> </span><span class="na">class</span><span class="p">=</span><span class="p" data-group-id="5719342580-1">{</span><span class="na">@class</span><span class="p" data-group-id="5719342580-1">}</span><span class="p">&gt;</span><span class="w">
</span><span class="n">  ...
</span><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span></code></pre>
<p>
For multiple dynamic attributes, you can use the same notation but without
assigning the expression to any specific attribute.</p>
<pre><code class="makeup heex"><span class="p">&lt;</span><span class="nt">div</span><span class="w"> </span><span class="p" data-group-id="8000511136-1">{</span><span class="na">@dynamic_attrs</span><span class="p" data-group-id="8000511136-1">}</span><span class="p">&gt;</span><span class="w">
</span><span class="n">  ...
</span><span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span></code></pre>
<p>
The expression inside <code class="inline">{ ... }</code> must be either a keyword list or a map containing
the key-value pairs representing the dynamic attributes.</p>
<h4>
HEEx extension: Defining function components</h4>
<p>
Function components are stateless components implemented as pure functions
with the help of the <code class="inline">Phoenix.Component</code> module. They can be either local
(same module) or remote (external module).</p>
<p>
<code class="inline">HEEx</code> allows invoking whose function components directly in the template
using an HTML-like notation. For example, a remote function:</p>
<pre><code class="makeup heex"><span class="p">&lt;</span><span class="nc">MyApp.Weather</span><span class="o">.</span><span class="n">city</span><span class="w"> </span><span class="na">name</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">Kraków</span><span class="p">&quot;</span><span class="p">/&gt;</span></code></pre>
<p>
A local function can be invoked with a leading dot:</p>
<pre><code class="makeup heex"><span class="p">&lt;</span><span class="nf">.city</span><span class="w"> </span><span class="na">name</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">Kraków</span><span class="p">&quot;</span><span class="p">/&gt;</span></code></pre>
<p>
where the component could be defined as follows:</p>
<pre><code class="makeup "><span class="kd">defmodule</span><span class="w"> </span><span class="nc">MyApp.Weather</span><span class="w"> </span><span class="k" data-group-id="1752238621-1">do</span><span class="w">
  </span><span class="kn">use</span><span class="w"> </span><span class="nc">Phoenix.Component</span><span class="w">

  </span><span class="kd">def</span><span class="w"> </span><span class="nf">city</span><span class="p" data-group-id="1752238621-2">(</span><span class="n">assigns</span><span class="p" data-group-id="1752238621-2">)</span><span class="w"> </span><span class="k" data-group-id="1752238621-3">do</span><span class="w">
    </span><span class="sx">~H&quot;&quot;&quot;</span><span class="w">
</span><span class="n">    The chosen city is: </span><span class="p" data-group-id="3027888063-1">&lt;%=</span><span class="w"> </span><span class="na">@city</span><span class="w"> </span><span class="p" data-group-id="3027888063-1">%&gt;</span><span class="n">.
</span><span class="w">    </span><span class="sx">&quot;&quot;&quot;</span><span class="w">
  </span><span class="k" data-group-id="1752238621-3">end</span><span class="w">

  </span><span class="kd">def</span><span class="w"> </span><span class="nf">country</span><span class="p" data-group-id="1752238621-4">(</span><span class="n">assigns</span><span class="p" data-group-id="1752238621-4">)</span><span class="w"> </span><span class="k" data-group-id="1752238621-5">do</span><span class="w">
    </span><span class="sx">~H&quot;&quot;&quot;</span><span class="w">
</span><span class="n">    The chosen country is: </span><span class="p" data-group-id="8852783826-1">&lt;%=</span><span class="w"> </span><span class="na">@country</span><span class="w"> </span><span class="p" data-group-id="8852783826-1">%&gt;</span><span class="n">.
</span><span class="w">    </span><span class="sx">&quot;&quot;&quot;</span><span class="w">
  </span><span class="k" data-group-id="1752238621-5">end</span><span class="w">
</span><span class="k" data-group-id="1752238621-1">end</span></code></pre>
<p>
It is typically best to group related functions into a single module, as
opposed to having many modules with a single <code class="inline">render/1</code> function. You can
learn more about components in <code class="inline">Phoenix.Component</code>.</p>
<h2>
New LiveView server lifecycle hooks</h2>
<p>
Thanks to work by Michael Crumm on the Phoenix team, LiveView 0.16 introduces <code class="inline">on_mount</code> and <code class="inline">attach_hook</code> hooks which provide a mechanism to tap into key stages of the LiveView lifecycle. This allows developers to bind/update assigns, intercept events, patches, and regular messages when necessary, and to inject common functionality. Hooks may be attached to any of the following lifecycle stages: <code class="inline">:mount</code> (via <code class="inline">on_mount/1</code>), <code class="inline">:handle_params</code>, <code class="inline">:handle_event</code>, and <code class="inline">:handle_info</code>.</p>
<h2>
New LiveView <code class="inline">live_session</code> for optimized navigation</h2>
<p>
LiveView 0.16 also introduces the <code class="inline">live_session</code> macro in the router to group live routes together. This allows all live navigation through live redirects to happen over the existing websocket connection. This avoids an extra HTTP round trip to the server and provides an extremely fast navigation experience because an HTTP handshake is no longer necessary at all.  The <code class="inline">live_session</code> also allows a life-cycle <code class="inline">on_mount</code> hook to be provided, allowing LiveViews to share common code paths such as authentication without needing to specificy the hooks on every module.</p>
<h2>
Node and webpack free asset building with esbuild</h2>
<p>
In addition to the new HTML engine, we’ve also had a major change on the way the <code class="inline">phx.new</code> project generators handles assets. We have dropped webpack and node entirely from the equation. You can now build your js and css bundles without having node or npm on your system! The biggest support issues for Phoenix over the years has revolved around node tooling, breaking changes, and often times unnecessary churn. By using <a href="https://esbuild.github.io">esbuild</a>, projects can utilize a portable binary for multiplatform, dependency-free asset building that is fast and Just Works/tm. Five years from now you shouldn’t been afraid to make some changes to a js or css file and find your tooling has broken underneath you. Advanced front-end users can continue to take advantage of the webpack tooling that suits their work-flows, but we hope this dependency-free solution brings more “peace of mind” around assets, per the Phoenix tagline :). Big thanks to Brian Cardarella of DockYard and Max Veytsman on the Phoenix team for spelunking through our js options and experimenting with solutions, along with Wojtek Mach for heading up the portable binary esbuild extraction. Also shout out to José for writing some go <em>and</em> JavaScript PRs for various tools to handle mix shutdown without zombie processes.</p>
<p>
This release also extracts <code class="inline">Phoenix.View</code> into its own library <code class="inline">phoenix_view</code>, so non-web users can make use of Phoenix’s view rendering without bringing in all of Phoenix.</p>
<p>
As always, step-by-step upgrade guides can be found here:</p>
<p>
<a href="https://gist.github.com/chrismccord/2ab350f154235ad4a4d0f4de6decba7b">https://gist.github.com/chrismccord/2ab350f154235ad4a4d0f4de6decba7b</a></p>
<p>
You can also view the <a href="https://github.com/phoenixframework/phoenix/blob/3ba0f6fc3407d4ddc08c05715ff8b24cb367d8bd/CHANGELOG.md#160-rc0-2021-08-26">full changelog</a> for more details.</p>
<p>
Find us on elixir slack or the forums if you need help. Happy coding!</p>
<p>
–Chris</p>
]]></content>
  </entry>

  <entry>
    <title>Improving Testing &amp; Continuous Integration in Phoenix</title>
    <link rel="alternate" href="http://www.phoenixframework.org/blog/improving-testing-and-continuous-integration-in-phoenix" />
    <id>http://www.phoenixframework.org/blog/improving-testing-and-continuous-integration-in-phoenix</id>
    <updated>2021-01-15T00:00:00Z</updated>
    <author>
      <name>Aaron Renner</name>
    </author>
    <summary type="html"><![CDATA[A walk-through showing how we approach testing and CI for the Phoenix project and how recent changes have made this process much  smoother]]></summary>
    <content type="html"><![CDATA[<p>
Improving Testing & Continuous Integration in Phoenix
Continuous integration (CI) is a powerful thing. Big open source projects need a suite of unit tests, a handful of integration tests and a pipeline to automatically run them.
CI is not without its difficulties though. Build failures, complicated setups and slow iteration cycles can make people loathe waiting for their PR to be built.
This walk-through shows how we approach testing and CI for the Phoenix project and how recent changes have made this process a lot smoother.</p>
<h2>
Phoenix’s test suites</h2>
<p>
Phoenix has 4 different test suites, each with different purposes and different dependencies.</p>
<table>
  <thead>
    <tr>
      <th style="text-align: left;">
Test Suite      </th>
      <th style="text-align: left;">
Purpose      </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left;">
<strong>Main tests</strong><br>Location: /test<br>Dependencies:<br>  - Elixir      </td>
      <td style="text-align: left;">
Core test suite for the phoenix framework. Tests things like endpoints, channels, routers, controllers, etc.      </td>
    </tr>
    <tr>
      <td style="text-align: left;">
<strong>Installer tests</strong><br>Location: /installer/test<br>Dependencies:<br>  - Elixir      </td>
      <td style="text-align: left;">
Test suite for the <code class="inline">mix phx.new</code> generators. These tests ensure the code generators write the correct code in the correct locations.      </td>
    </tr>
    <tr>
      <td style="text-align: left;">
<strong>Integration tests</strong><br>Location: /integration_test<br>Dependencies:<br>  - Elixir<br>  - PostgresSQL<br>  - MySQL<br>  - MSSQL      </td>
      <td style="text-align: left;">
Tests the phoenix code generation experience end to end. These tests create a new project with <code class="inline">mix phx.new</code>, run one or more <code class="inline">mix phx.gen.*</code> commands, and ensure there are no compilation warnings, the code is formatted properly, and the generated test suite passes.      </td>
    </tr>
    <tr>
      <td style="text-align: left;">
<strong>JavaScript tests</strong>      </td>
      <td style="text-align: left;">
Tests the phoenix JavaScript code for Sockets, Channels, and Presence.      </td>
    </tr>
  </tbody>
</table>
<h2>
How we test locally</h2>
<p>
The ability to download a project and easily run its test suite locally, is key to welcoming community contributions. Phoenix uses ExUnit which comes with Elixir, so running the main test suite couldn’t be easier:</p>
<pre><code class="makeup plain">&gt; mix test
....
Finished in 24.8 seconds
11 doctests, 737 tests, 0 failures</code></pre>
<p>
The installer test suite is equally easy to run… it’s just <code class="inline">mix test</code> in the <code class="inline">/installer</code> folder</p>
<p>
Things start to get more complex, however, when we start making changes to the code generators. Although we can ensure our generators create files in the correct location, we don’t actually know the generated code works until we try to run it.  For this reason, Phoenix has an integration testing suite in /integration_test:</p>
<pre><code class="makeup plain">&gt; tree
├── config
│   └── config.exs
├── docker-compose.yml
├── mix.exs
├── mix.lock
└── test
    ├── code_generation
    │   ├── app_with_defaults_test.exs
    │   ├── app_with_mssql_adapter_test.exs
    │   ├── app_with_mysql_adapter_test.exs
    │   ├── app_with_no_options_test.exs
    │   └── umbrella_app_with_defaults_test.exs
    ├── support
    │   └── code_generator_case.ex
    └── test_helper.exs</code></pre>
<p>
To run these tests fully we need access to three separate databases: Postgres, MySQL and MSSQL. This would normally be difficult, but fortunately Phoenix has a docker-compose file:</p>
<pre><code class="makeup yaml"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="p">&#39;</span><span class="s1">3</span><span class="p">&#39;</span><span class="w">
</span><span class="nt">services</span><span class="p">:</span><span class="w">
</span><span class="w">  </span><span class="nt">postgres</span><span class="p">:</span><span class="w">
</span><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="s">postgres</span><span class="w">
</span><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span><span class="w">      </span><span class="p">-</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">5432:5432</span><span class="p">&quot;</span><span class="w">
</span><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span><span class="w">      </span><span class="nt">POSTGRES_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="s">postgres</span><span class="w">
</span><span class="w">  </span><span class="nt">mysql</span><span class="p">:</span><span class="w">
</span><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="s">mysql</span><span class="w">
</span><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span><span class="w">      </span><span class="p">-</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">3306:3306</span><span class="p">&quot;</span><span class="w">
</span><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span><span class="w">      </span><span class="nt">MYSQL_ALLOW_EMPTY_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">yes</span><span class="p">&quot;</span><span class="w">
</span><span class="w">  </span><span class="nt">mssql</span><span class="p">:</span><span class="w">
</span><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="s">mcr.microsoft.com/mssql/server:2019-latest</span><span class="w">
</span><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span><span class="w">      </span><span class="nt">ACCEPT_EULA</span><span class="p">:</span><span class="w"> </span><span class="kc">Y</span><span class="w">
</span><span class="w">      </span><span class="nt">SA_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="s">some!Password</span><span class="w">
</span><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span><span class="w">      </span><span class="p">-</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">1433:1433</span><span class="p">&quot;</span></code></pre>
<p>
This allows us to start up these databases and run the integration tests like this:</p>
<pre><code class="makeup plain">&gt; docker-compose up -d
Starting integration_test_postgres_1 ... done
Starting integration_test_mssql_1    ... done
Starting integration_test_mysql_1    ... done
&gt; mix test --include database
    Finished in 230.6 seconds
    32 tests, 0 failures
    Randomized with seed 896813</code></pre>
<h2>
How we tested in CI</h2>
<p>
The Phoenix project uses GitHub Actions (GHA) to run each of its test suites. Like most CI services,
GitHub depends on its own specific configuration file to install build dependencies, start external services, and execute the various test suites.</p>
<p>
Since Phoenix’s CI and local test environments use different tools, we ended up with duplicate configurations and different environments for the same tests. Duplicate configurations caused issues when things went out of sync. Different environments meant we did not have complete faith that our tests would behave the same on GitHub when compared to our local machines. And when things worked locally but not in CI, it was an incredibly time consuming process to push test commits to Github with debugging statements to try to figure out the cause of the problem.</p>
<h2>
What if I could run the CI build my local machine?</h2>
<p>
Around this time José was talking with <a href="https://twitter.com/VladAIonescu">Vlad</a> about the inconsistencies between local development and CI. Vlad proposed we try <a href="http://earthly.dev">Earthly</a>, an open source format for specifying builds that he created.</p>
<p>
Vlad’s insight is that if we were to define the whole build process, unit tests, integration tests, service setup and so on, in a format that could be run anywhere, then reproducing builds failures would be easy.</p>
<p>
Following this approach, our build would look something like this:</p>
<h2>
Earthfile</h2>
<pre><code class="makeup earthfile">FROM hexpm/elixir:1.11-erlang-21.0-alpine-3.12.0
all:
    BUILD +test
    BUILD +integration-test
test:
    WORKDIR /src/
    COPY . .
    RUN mix test
integration-test:
    WORKDIR /src/integration_test
    COPY . .
    RUN mix deps.get
    WITH DOCKER --compose docker-compose.yml
        RUN mix test --include database
    END</code></pre>
<p>
We could then run the various build targets, all, test or integration-test, locally or in GHA by calling earthly and specifying a target.</p>
<pre><code class="makeup plain">&gt; earthly -P +all</code></pre>
<p>
Now, if an integration test fails in a GHA run, we could have confidence that we will be able to reproduce it locally by running the same command. The whole build is containerized, which makes reproducing things much easier.
This would not only be great for reproducing build failures but could also be nice for working on the build process itself, without having to push and wait for GHA to run.  Earthly even allows us to drop into a shell in the build pipeline to poke around and diagnose problems (more on that later).
The Earthfile syntax builds on top of docker’s layers, so if our <code class="inline">mix.lock</code> file hasn’t changed, it will use the cached layer and not attempt to download our dependencies again. In a post COVID world, where we’re back to traveling again, we could even work on the build pipeline on a plane.</p>
<h2>
Testing multiple dependency versions</h2>
<p>
Phoenix’s build pipeline is more complex than running each test suite once, though. Each release of Phoenix needs to work with not just the latest version of Elixir, but all supported versions. The same for OTP.
GHA has great support for this use case with a feature called “Matrix Strategy”. You define a matrix of parameters and it will execute your job using each of these.</p>
<pre><code class="makeup yaml"><span class="w">  </span><span class="nt">matrix</span><span class="p">:</span><span class="w">
</span><span class="w">    </span><span class="nt">include</span><span class="p">:</span><span class="w">
</span><span class="w">      </span><span class="p">-</span><span class="w"> </span><span class="nt">elixir</span><span class="p">:</span><span class="w"> </span><span class="mf">1</span><span class="mf">.</span><span class="mf">9.4</span><span class="w">
</span><span class="w">        </span><span class="nt">otp</span><span class="p">:</span><span class="w"> </span><span class="mf">20</span><span class="mf">.</span><span class="mf">3.8.26</span><span class="w">
</span><span class="w">      </span><span class="p">-</span><span class="w"> </span><span class="nt">elixir</span><span class="p">:</span><span class="w"> </span><span class="mf">1</span><span class="mf">.</span><span class="mf">10.4</span><span class="w">
</span><span class="w">        </span><span class="nt">otp</span><span class="p">:</span><span class="w"> </span><span class="mf">21</span><span class="mf">.</span><span class="mf">3.8.17</span><span class="w">
</span><span class="w">      </span><span class="p">-</span><span class="w"> </span><span class="nt">elixir</span><span class="p">:</span><span class="w"> </span><span class="mf">1</span><span class="mf">.</span><span class="mf">10.4</span><span class="w">
</span><span class="w">        </span><span class="nt">otp</span><span class="p">:</span><span class="w"> </span><span class="mf">23</span><span class="mf">.</span><span class="mf">0.3</span></code></pre>
<p>
The matrix strategy runs all these jobs in parallel, which means that testing many versions does not impact our build run time. It’s a key feature for a library like Phoenix.
It does make the local reproducibility problem more difficult, however. If we’re running the latest supported version of OTP and a PR fails for one of the supported older OTP versions, we can attempt to switch versions or maybe just rely on the GHA build to test our changes.
In practice there have been times where we end up relying on GHA, which means we now have a longer feedback cycle. Not the end of the world certainly, but again, it is a bit more friction added to the contribution process.</p>
<h2>
Reproducing Matrix Testing Locally</h2>
<p>
Again, using earthly can make this situation a little easier to deal with. We can introduce arguments for the elixir and OTP version and then use those to run tests with any version we please. No local environment changes or additional tooling.</p>
<p>
The solution the Phoenix framework ended up with looks something like this:</p>
<pre><code class="makeup earthfile">setup:
   ARG ELIXIR=1.11
   ARG OTP=23.0.0
   FROM hexpm/elixir:$ELIXIR-erlang-$OTP-alpine-3.12.0
   ...

integration-test:
    FROM +setup
    COPY --dir assets config installer lib integration_test priv test ./
    WORKDIR /src/integration_test
    RUN mix deps.get
    WITH DOCKER --compose docker-compose.yml
        RUN mix test --include database
    END</code></pre>
<p>
This makes it really easy to test any version combination locally:</p>
<pre><code class="makeup plain">&gt;  earthly -P --build-arg ELIXIR=1.11.0 --build-arg OTP=23.1.1 +integration-test
 +integration-test | Including tags: [:database]

  ...
   +integration-test | Finished in 210.5 seconds
   +integration-test | 32 tests, 0 failures

   +integration-test | Randomized with seed 330691
              output | --&gt; exporting outputs
=========================== SUCCESS ===========================</code></pre>
<p>
This is really cool how low friction Earthly’s solution is. Also, Earthly has an –interactive flag, which pops us into a shell when a step in the build returns a non zero status.</p>
<h2>
Adopting Earthly</h2>
<p>
All of this said, I’m excited to announce that the Phoenix project’s CI pipeline is now powered by <a href="http://earthly.dev">Earthly</a>.</p>
<p>
<a href="https://twitter.com/adamgordonbell">Adam Gordon Bell</a> submitted the <a href="https://github.com/phoenixframework/phoenix/pull/4014">PR</a> in early October and was awesome to work with as we went through the evaluation process. The <a href="https://github.com/phoenixframework/phoenix/blob/dfda0ebf5878c2684d7fd1e8440804948298bcb3/Earthfile">final version</a> is more complex than the examples found in this article and continues to evolve.   It’s the early days, but it’s been a huge win for my workflow, personally. It doesn’t take the place of running tests locally while I’m doing TDD cycles, but I’ve made a habit of running the Earthly build locally before I push a PR to minimize the number of failed builds I have on Github Actions.</p>
<p>
Personally, I think Earthly is the start of the next-generation of CI tools that will help reduce the gap between local and CI builds. If you have time, I’d highly recommend taking it for a spin and seeing what you think.</p>
]]></content>
  </entry>

  <entry>
    <title>Phoenix LiveView Uploads Deep Dive</title>
    <link rel="alternate" href="http://www.phoenixframework.org/blog/phoenix-live-view-upload-deep-dive" />
    <id>http://www.phoenixframework.org/blog/phoenix-live-view-upload-deep-dive</id>
    <updated>2020-11-19T00:00:00Z</updated>
    <author>
      <name>Chris McCord</name>
    </author>
    <summary type="html"><![CDATA[A step-by-step deep dive into the new Phoenix LiveView uploads feature. We go from direct-to-server uploads to direct-to-cloud.]]></summary>
    <content type="html"><![CDATA[<p>
The release of Phoenix LiveView 0.15 with the new upload features make it easier than ever to enable rich, interactive file uploads with all the advanced features users expect from a modern application, such as file uploads, post processing, and direct to cloud uploads. We put together a step-by-step deep dive of adding LiveView uploads to the Twitter timeline clone application that we built in our last post. We start by covering basic server uploads, then take it up a notch by refactoring to direct to S3 support.</p>
<p>
We can’t wait to see what folks build with these new features. Previously complex tasks like post processing of files, file progress, and multi-file interactive selections are now a few keystrokes away from your fingertips. Let’s dive in!</p>
<iframe width="100%" height="515" src="https://www.youtube.com/embed/PffpT2eslH8" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="allowfullscreen">
</iframe>
<p>
Happy coding!</p>
]]></content>
  </entry>

  <entry>
    <title>Build a real-time Twitter clone in 15 minutes with LiveView and Phoenix 1.5</title>
    <link rel="alternate" href="http://www.phoenixframework.org/blog/build-a-real-time-twitter-clone-in-15-minutes-with-live-view-and-phoenix-1-5" />
    <id>http://www.phoenixframework.org/blog/build-a-real-time-twitter-clone-in-15-minutes-with-live-view-and-phoenix-1-5</id>
    <updated>2020-04-22T00:00:00Z</updated>
    <author>
      <name>Chris McCord</name>
    </author>
    <summary type="html"><![CDATA[With the Phoenix v1.5 release, learn how easy LiveView makes
it to build interactive, real-time applications.]]></summary>
    <content type="html"><![CDATA[<p>
Phoenix v1.5 has been released with LiveView integration. This release makes it easier than ever to build interactive, real-time applications. We put together a quick screencast to show just how much you can accomplish in 15 minutes with LiveView:</p>
<iframe width="100%" height="515" src="https://www.youtube.com/embed/MZvmYaFkNJI" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="allowfullscreen">
</iframe>
<p>
Outside of Phoenix LiveView integration, this release brings other exciting features, including integration with the new Phoenix LiveDashboard. There have been a few deprecations, but this ugprade should be quick and easy for most folks.</p>
<h3>
Phoenix LiveDashboard</h3>
<p>
On the heels of the official LiveDashboard release, Phoenix 1.5 projects now ship with LiveDashboard by default, for real-time performance monitoring and debugging tools. It’s at this point I also want to welcome Michael Crumm to the phoenix-core team! He has been heading up the Dashboard work with José Valim, and we can thank them both for the amazing results we have today.</p>
<p>
The Dashboard brings you immediate insight into your Phoenix applications with focus on <em>production</em> data. Even if you are just starting with Phoenix, we have tooltips on the widgets so you can learn more about them and if/when you should worry about system limits and the health of your system. With telemetry integration, we also include charting of Phoenix events, along with user-defined metrics.</p>
<p>
Check José’s twitter thread to see a breakdown of features and screenshots:
<a href="https://twitter.com/josevalim/status/1250846714665357315">https://twitter.com/josevalim/status/1250846714665357315</a></p>
<p>
The dashboard also includes a streaming request logger. This is super convenient for diagnosing an issue in production where you need logs for <em>specific</em> requests but the regular logs drown out your requests in noise. With a button click, you can have all of your <em>own</em> request logs streamed to the dashboard instead of sifting thru a flood of production logs.</p>
<p>
We also include a process tab, which is similar to observer, allowing you to sort processes in the system to find large message queues, memory hogs, etc.</p>
<p>
Did we mention this Just Works™ for a cluster of distributed nodes? Using the node drop-down selector, you can access all the data/features listed above for any node on the cluster, regardless of what web node you happened to load-balance to when loading the dashboard.</p>
<h3>
Phoenix LiveView generators</h3>
<p>
The <code class="inline">phx.new</code> project generator now includes a <code class="inline">--live</code> flag to include everything you need to get up and running developing with LiveView. Additionally, we have also added a <code class="inline">phx.gen.live</code> generator for boostrapping CRUD LiveView context/interfaces similar to <code class="inline">phx.gen.html</code>/<code class="inline">phx.gen.json</code>. We recommend taking these generators for a test drive to see all the existing improvements to LiveView that recently shipped:</p>
<ul>
  <li>
Revamped LiveViewTest APIs for more powerful, workflow driven testing  </li>
  <li>
Deep diff tracking for LiveView templates, dramatically reducing server payloads in many cases  </li>
  <li>
Large performance improvements on client rendering  </li>
  <li>
Live Navigation with Live Flash  </li>
</ul>
<h3>
PubSub 2.0</h3>
<p>
Phoenix.PubSub 2.0 has been released with a more flexible and powerful fastlane mechanism. We took this opportunity to also move Phoenix.PubSub out of the endpoint and explicitly into your supervision tree. This prevents race conditions at startup and decouples your PubSub system from the endpoint. Follow the upgrade guides linked below to get up to speed.</p>
<h3>
Revamped Guides and more</h3>
<p>
Thanks to efforts by José Valim, the Phoenix built-in guides have been restructured and revamped, providing a better navigation structure and more content. Be sure to take a look if you’d like to freshen up on your phoenix knowledge.</p>
<p>
Other notable improvements include built-in support for MSSQL databases via the <code class="inline">tds</code> adapter, and inclusion of the <code class="inline">Phoenix.Ecto.CheckRepoStatus</code> plug in new projects to detect and prompt for database creation/migration directly from the comfort of your browser.</p>
<p>
As always, we have provided upgrade guides to bring your existing applications up to speed:
<a href="https://gist.github.com/chrismccord/e53e79ef8b34adf5d8122a47db44d22f">https://gist.github.com/chrismccord/e53e79ef8b34adf5d8122a47db44d22f</a></p>
<p>
For the complete list of changes, see the <a href="https://github.com/phoenixframework/phoenix/blob/v1.5/CHANGELOG.md">CHANGELOG</a>.</p>
<p>
Find us on elixir slack/irc if you have questions. Happy coding!</p>
]]></content>
  </entry>

  <entry>
    <title>ElixirConf 2019 Keynote</title>
    <link rel="alternate" href="http://www.phoenixframework.org/blog/closing-keynote-elixirconf-chris-mccord" />
    <id>http://www.phoenixframework.org/blog/closing-keynote-elixirconf-chris-mccord</id>
    <updated>2019-09-10T00:00:00Z</updated>
    <author>
      <name>Chris McCord</name>
    </author>
    <summary type="html"><![CDATA[Chris McCord's closing keynote of ElixirConf 2019:
Phoenix LiveView – a step-change for web development]]></summary>
    <content type="html"><![CDATA[<iframe width="100%" height="515" src="https://www.youtube.com/embed/txk4WAlabvI" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="allowfullscreen">
</iframe>
]]></content>
  </entry>

  <entry>
    <title>Phoenix 1.4.0 Released</title>
    <link rel="alternate" href="http://www.phoenixframework.org/blog/phoenix-1.4.0-released" />
    <id>http://www.phoenixframework.org/blog/phoenix-1.4.0-released</id>
    <updated>2018-11-07T00:00:00Z</updated>
    <author>
      <name>Chris McCord</name>
    </author>
    <summary type="html"><![CDATA[Phoenix 1.4 is out! This release ships with exciting new features like HTTP2 support, improved development experience, and more.]]></summary>
    <content type="html"><![CDATA[<p>
Phoenix 1.4 is out! This release ships with exciting new features, most notably HTTP2 support, improved development experience with faster compile times, new error pages, and local SSL certificate generation. Additionally, our channel layer internals received an overhaul, providing better structure and extensibility. We also shipped a new and improved Presence javascript API, as well as Elixir formatter integration for our routing and testing DSLs.</p>
<h3>
phx_new hex archive</h3>
<p>
The <code class="inline">mix phx.new</code> archive can now be installed via hex, for a simpler, versioned installation experience.</p>
<p>
To grab the new archive, simply run:</p>
<pre><code class="makeup console">$ mix archive.uninstall phx_new
$ mix archive.install hex phx_new 1.4.0</code></pre>
<p>
The new generators now default to Ecto 3.0, which should be an easy upgrade for existing applications. Ecto 3.0 is packed with new features and improvements, which you can read more about in the <a href="http://blog.plataformatec.com.br/2018/10/a-sneak-peek-at-ecto-3-0-performance-migrations-and-more/">Ecto 3.0 sneak peek post</a>.</p>
<p>
The new generators also use <a href="https://milligram.io">Milligram</a> in favor of Bootstrap to support classless markup generation. The result is nice looking defaults that allow generated markup to be much more easily customized to your individual CSS requirements.</p>
<p>
<strong>Note</strong>: Existing Phoenix applications will continue to work on Elixir 1.4, but the new <code class="inline">phx.new</code> archive requires Elixir 1.5+.</p>
<h3>
HTTP2</h3>
<p>
Thanks to the release of Cowboy 2, Phoenix 1.4 supports HTTP2 with a
single line change to your <code class="inline">mix.exs</code>. Simply add <code class="inline">{:plug_cowboy, &quot;~&gt; 2.0&quot;}</code> to your deps and Phoenix will run with the Cowboy 2 adapter.</p>
<h3>
Local SSL development</h3>
<p>
Most browsers require connections over SSL for HTTP2 requests,
otherwise they fallback to HTTP 1.1 requests. To aid local development
over SSL, phoenix includes a new <code class="inline">phx.gen.cert</code> task which generates a
self-signed certificate for HTTPS testing in development.</p>
<p>
See the <a href="https://hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Cert.html">phx.gen.cert</a> docs for more information.</p>
<h3>
Faster Development Compilation</h3>
<p>
Our development compilation speeds have improved thanks to contributions to plug and compile-time changes. You can read more about the details in my <a href="https://dockyard.com/blog/2018/02/12/what-s-new-in-phoenix-development-february-2018">DockYard Phoenix post</a></p>
<h3>
New Development 404 Page</h3>
<p>
Our 404 page in development now lists the available routes for the
originating router, for example:</p>
<p>
  <img src="//i.imgur.com/sueKW9B.jpg" alt="">
</p>
<h3>
UserSocket connection info</h3>
<p>
A highly requested feature has been access to more underlying transport information when using Phoenix channels. The 1.4 release now provides a <code class="inline">connect/3</code> UserSocket callback, which can provide connection information, such as the peer IP address, host information, and X-Headers of the HTTP request for WebSocket and Long-poll transports.</p>
<h3>
New Presence JavaScript API</h3>
<p>
A new, backwards compatible <code class="inline">Presence</code> JavaScript API has been
introduced to both resolve race conditions as well as simplify the
usage. Previously, multiple channel callbacks against
<code class="inline">&quot;presence_state</code> and <code class="inline">&quot;presence_diff&quot;</code> events were required on the
client which dispatched to <code class="inline">Presence.syncState</code> and
<code class="inline">Presence.syncDiff</code> functions. Now, the interface has been unified to
a single <code class="inline">onSync</code> callback and the presence object tracks its own
channel callbacks and state. For example:</p>
<pre><code class="makeup javascript"><span class="kt">let</span><span class="w"> </span><span class="nv">presence</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="nv">Presence</span><span class="p">(</span><span class="nv">roomChannel</span><span class="p">)</span><span class="w">
</span><span class="nv">presence</span><span class="p">.</span><span class="nf">onSync</span><span class="p">(</span><span class="p">(</span><span class="p">)</span><span class="w"> </span><span class="kt">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="w">  </span><span class="nb">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="p">&quot;</span><span class="s2">users online:</span><span class="p">&quot;</span><span class="p">,</span><span class="w"> </span><span class="nv">presence</span><span class="p">.</span><span class="nf">list</span><span class="p">(</span><span class="p">(</span><span class="nv">id</span><span class="p">,</span><span class="w"> </span><span class="p">{</span><span class="nv">name</span><span class="p">}</span><span class="p">)</span><span class="w"> </span><span class="kt">=&gt;</span><span class="w"> </span><span class="nv">name</span><span class="p">)</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="p">)</span></code></pre>
<p>
That’s all there is to it!</p>
<h3>
webpack</h3>
<p>
The  <code class="inline">mix phx.new</code>  generator now uses webpack for asset generation instead of brunch. The development experience remains the same – javascript goes in  <code class="inline">assets/js</code> , css goes in  <code class="inline">assets/css</code> , static assets live in  <code class="inline">assets/static</code> , so those not interested in JS tooling nuances can continue the same patterns while using webpack. Those in need of optimal js tooling can benefit from webpack’s more sophisticated code bunding, with dead code elimination and more.</p>
<h3>
What’s Next</h3>
<p>
With the release of 1.4, we’re ready to focus on other exciting initiatives around the Elixir and Phoenix ecosystem. Most notably, we are excited to integrate <a href="https://github.com/beam-telemetry/telemetry">telemetry</a> into Phoenix for metric tracking and visualization. Simultaneously, we are also working to rewrite <code class="inline">Phoenix.PubSub</code> into smaller building blocks and provide a first-class distributed programming toolkit for the community. You can track this progress over at the <a href="https://github.com/phoenixframework/firenest">Firenest</a> project.</p>
<p>
In addition to telemetry and firenest initiatives, we are also working on <code class="inline">Phoenix.LiveView</code>, to enable server-rendered real-time experiences without all the complexity of today’s single page application landscape. LiveView can enable rich UX on par with single-page apps in certain usecases and we can’t wait to get the initial release out to the community.</p>
<p>
My ElixirConf keynote covers telemetry and LiveView in detail:</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/Z2DU0qLfPIY" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="allowfullscreen">
</iframe>
<h3>
Programming Phoenix Book</h3>
<p>
The <a href="https://pragprog.com/book/phoenix14/programming-phoenix-1-4">Programming Phoenix Book</a>
is in beta and available through PragProg, and includes all the latest changes for 1.4.
We have titled the book “>= 1.4” and consider it relatively future proof as we continue
minor version releases.</p>
<h3>
Special Thank You’s</h3>
<p>
I would like to specially thank Loïc Hoguin for his work on Cowboy 2,
allowing us to provide a first-class HTTP2 experience. We would also like to thank Bram Verburg, who contributed the local SSL certificate generation, for cross-platform, dependency-free cert generation.</p>
<p>
Additionally, I would like to thank José Valim and <a href="http://plataformatec.com.br">Plataformatec</a> for their work on the channel layer overhaul which provides an extensible foundation going forward.</p>
<p>
As always, we have provided step-by-step instructions for bringing your 1.3.x apps up to speed:
<a href="https://gist.github.com/chrismccord/bb1f8b136f5a9e4abc0bfc07b832257e">https://gist.github.com/chrismccord/bb1f8b136f5a9e4abc0bfc07b832257e</a></p>
<p>
Please report issues to the issue tracker, and find us on
#elixir-lang irc, elixir slack, and the Elixir forum if you have any
questions. The full list of changes from the changelog can be found <a href="https://github.com/phoenixframework/phoenix/blob/v1.4.0/CHANGELOG.md">here</a>.</p>
<p>
Happy hacking!</p>
<p>
–Chris</p>
]]></content>
  </entry>

  <entry>
    <title>Contribution Sprint</title>
    <link rel="alternate" href="http://www.phoenixframework.org/blog/contribution-spring" />
    <id>http://www.phoenixframework.org/blog/contribution-spring</id>
    <updated>2017-09-06T00:00:00Z</updated>
    <author>
      <name>Willy Karam</name>
    </author>
    <summary type="html"><![CDATA[This week we hosted our first contribution sprint]]></summary>
    <content type="html"><![CDATA[<p>
This week we hosted our first contribution sprint, where the Phoenix core team and community members collaborated in a day-long effort aimed at completing updates to the Guides to support Phoenix 1.3 and working through the <a href="https://github.com/phoenixframework/phoenix_guides/issues">issue queue</a>.</p>
<p>
<strong>30+ Issues Resolved</strong></p>
<p>
The sprint was a success overall as we <a href="https://github.com/phoenixframework/phoenix_guides/pulse">resolved over 30 issues</a> that achieved several goals, including moving the <a href="https://github.com/phoenixframework/phoenix/tree/master/guides">Phoenix Guides into the core repository</a> and migrating certain of the Guides to <a href="http://phoenixframework.org/blog">blog posts on the Phoenix Site</a>. We also anticipate being able to close a few more issues in the days ahead, resulting in the Phoenix Guides issue queue only having 2-3 remaining open issues.</p>
<p>
  <img src="/images/blog/contribution-sprint/sprint-team.jpg" alt="">
</p>
<p>
<strong>Thanks to Sprinters</strong></p>
<p>
Special thanks to Phoenix core team members who lead the sprint, <a href="https://github.com/Gazler">Gary (@Gazler)</a> & <a href="https://github.com/jeregrine">Jason (@jeregrine)</a>, and the community members who volunteered their efforts for the day, including: <a href="https://github.com/bryanstearns">@bryanstearns</a>, <a href="https://github.com/gabiz">@gabiz</a>, <a href="https://github.com/lsiqueira">@lsiqueira</a>, <a href="https://github.com/mcelaney">@mcelaney</a>, <a href="https://github.com/mitchellhenke">@mitchellhenke</a>, and <a href="https://github.com/willykaram">@willykaram</a>. Thanks to all those community members who contributed to the guides prior to the sprint, especially <a href="https://github.com/wsmoak">Wendy Smoak(@wsmoak)</a> and <a href="https://github.com/lancehalvorsen">Lance Halvorsen (@lancehalvorsen)</a>.</p>
<p>
  <img src="/images/blog/contribution-sprint/sprint-jose.jpg" alt="">
</p>
<p>
<strong>Thanks to Sprint Sponsors!</strong></p>
<p>
We’d like to thank <a href="https://twitter.com/jimfreeze">Jim Freeze</a> and <a href="https://elixirconf.com/">ElixirConf</a> for their support, including sprint facilities and tickets for the sprint leads. Last but not least, we’d like to thank the the organizations that that became sponsors to help fund the sprint and cover the travel costs of Phoenix core team members who joined for the sprint: <strong><a href="http://www.appsteam.com">Apps Team</a></strong>, the <strong><a href="https://erlangcentral.org/industrial-erlang-user-group/">Erlang Foundation</a></strong>, <strong><a href="https://www.erlang-solutions.com">Erlang Solutions</a></strong>, <strong><a href="https://www.honeybadger.io">Honeybadger.io</a></strong>, <strong><a href="http://plataformatec.com.br">plataformatec</a></strong>, <strong><a href="http://www.rokkincat.com/">RokkinCat</a></strong>, and <strong><a href="https://simplabs.com">simplabs</a></strong>.</p>
<p>
  <img src="/images/blog/contribution-sprint/sprint-sponsors.jpg" alt="">
</p>
<p>
<strong>Next Steps</strong></p>
<p>
It was also great to onboard several new contributors to the Phoenix project. Hopefully we can host more contribution sprints at other upcoming regional conferences or local events, and we look forward to ideall welcoming more new contributors there too. We still have more work to do, including improving contribution guidelines and creating suggested guidance for community generated guides – so we’ll see you in the issue queue!</p>
<p>
Thanks to everyone we helped us create improved guides to ensure immediate productivity for new developers using Phoenix as a web-interface for their greater Elixir applications!</p>
]]></content>
  </entry>

  <entry>
    <title>Phoenix 1.3.0 released</title>
    <link rel="alternate" href="http://www.phoenixframework.org/blog/phoenix-1-3-0-released" />
    <id>http://www.phoenixframework.org/blog/phoenix-1-3-0-released</id>
    <updated>2017-07-28T00:00:00Z</updated>
    <author>
      <name>Chris McCord</name>
    </author>
    <summary type="html"><![CDATA[Phoenix 1.3.0 is out!]]></summary>
    <content type="html"><![CDATA[<p>
Phoenix 1.3.0 is out! This release focuses on code generators with
improved project structure, first class umbrella project support, and
scaffolding that re-enforces Phoenix as a web-interface to your
greater Elixir application. We have also included a new
<code class="inline">action_fallback</code> feature in <code class="inline">Phoenix.Controller</code> that allows you to
translate common datastructures in your domain to valid responses. In
practice, this cleans up your controller code and gives you a single
place to handle otherwise duplicated code-paths. It is <a href="https://swanros.com/2017/03/03/phoenix-1-3-is-pure-love-for-api-development/">particularly
nice for JSON API controllers</a>.
Also making it into the 1.3 release is a V2 of our channel wire protocol that resolves race
conditions under certain messaging patterns as well as an improved
serialization format.</p>
<p>
For those interested in a detailed overview of the changes and design
decisions, check out my LonestarElixir keynote:
<a href="https://www.youtube.com/watch?v=tMO28ar0lW8">https://www.youtube.com/watch?v=tMO28ar0lW8</a>. Note that the directory
structure in the talk is slightly outdated but all ideas still apply.</p>
<p>
To use the new <code class="inline">phx.new</code> project generator, you can install the
archive with the following command:</p>
<pre><code class="makeup "><span class="err">$</span><span class="w"> </span><span class="n">mix</span><span class="w"> </span><span class="n">archive</span><span class="o">.</span><span class="n">install</span><span class="w"> </span><span class="n">https</span><span class="ss">://</span><span class="n">github</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">phoenixframework</span><span class="o">/</span><span class="n">archives</span><span class="o">/</span><span class="n">raw</span><span class="o">/</span><span class="n">master</span><span class="o">/</span><span class="n">phx_new</span><span class="o">.</span><span class="n">ez</span></code></pre>
<p>
1.3.0 uses the <code class="inline">phx.</code> prefix on all generators. The old generators
are still around though to give the community and learning resources
time to catch up. They will be removed on 1.4.0.</p>
<p>
As always, we have <a href="http://phoenixframework.org/blog/upgrading-from-120-to-130">an upgrade guide</a>
with detailed instructions for migrating from 1.2.x projects.</p>
<p>
1.3.0 is a backwards compatible release, so upgrading can be as easy
as bumping your <code class="inline">:phoenix</code> dep in mix.exs to “~> 1.3”. For those wanting
to adopt the new conventions, the upgrade guides will take you
step-by-step. Before you upgrade, it’s worth watching the keynote or
exploring the design decisions outlined below.</p>
<h2>
Phoenix 1.3 – Design With Intent</h2>
<p>
The new project and code generators take the lessons learned from the
last two years and push folks towards better design decisions as
they’re learning. New projects have a <code class="inline">lib/my_app</code> directory for
business logic and a <code class="inline">lib/my_app_web</code> directory that holds all Phoenix
related web modules, which are the web interface into your greater
Elixir application. Along with new project structure, comes new
<code class="inline">phx.gen.html</code> and <code class="inline">phx.gen.json</code> generators that adopt these
goals of isolating your web interface from your domain.</p>
<h3>
Contexts</h3>
<p>
When you generate a HTML or JSON resource with <code class="inline">phx.gen.html|json</code>,
Phoenix will generate code inside a <em>Context</em>. Contexts are dedicated
modules that expose and group related functionality. For example,
anytime you call Elixir’s standard library, be it <code class="inline">Logger.info/1</code> or
<code class="inline">Stream.map/2</code>, you are accessing different contexts. Internally,
Elixir’s logger is made of multiple modules, such as <code class="inline">Logger.Config</code>
and <code class="inline">Logger.Backends</code>, but we never interact with those modules
directly. We call the <code class="inline">Logger</code> module the context, exactly because it
exposes and groups all of the logging functionality.</p>
<p>
For example, to generate a “user” resource we’d run:</p>
<pre><code class="makeup console">mix phx.gen.html Accounts User users email:string:unique</code></pre>
<p>
Notice how “Accounts” is a new required first parameter. This is the
context module where your code will live that carries out the business
logic of user accounts in your application. It could include features
like authentication and user registration. Here’s a peek at part of
the code that’s generated:</p>
<pre><code class="makeup "><span class="c1"># lib/my_app_web/controllers/user_controller.ex</span><span class="w">
</span><span class="kd">defmodule</span><span class="w"> </span><span class="nc">MyAppWeb.UserController</span><span class="w"> </span><span class="k" data-group-id="6503091415-1">do</span><span class="w">
  </span><span class="n">...</span><span class="w">
  </span><span class="kn">alias</span><span class="w"> </span><span class="nc">MyApp.Accounts</span><span class="w">

  </span><span class="kd">def</span><span class="w"> </span><span class="nf">index</span><span class="p" data-group-id="6503091415-2">(</span><span class="n">conn</span><span class="p">,</span><span class="w"> </span><span class="c">_params</span><span class="p" data-group-id="6503091415-2">)</span><span class="w"> </span><span class="k" data-group-id="6503091415-3">do</span><span class="w">
    </span><span class="n">users</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nc">Accounts</span><span class="o">.</span><span class="n">list_users</span><span class="p" data-group-id="6503091415-4">(</span><span class="p" data-group-id="6503091415-4">)</span><span class="w">
    </span><span class="n">render</span><span class="p" data-group-id="6503091415-5">(</span><span class="n">conn</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;index.html&quot;</span><span class="p">,</span><span class="w"> </span><span class="ss">users</span><span class="p">:</span><span class="w"> </span><span class="n">users</span><span class="p" data-group-id="6503091415-5">)</span><span class="w">
  </span><span class="k" data-group-id="6503091415-3">end</span><span class="w">

  </span><span class="kd">def</span><span class="w"> </span><span class="nf">create</span><span class="p" data-group-id="6503091415-6">(</span><span class="n">conn</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="6503091415-7">%{</span><span class="s">&quot;user&quot;</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="n">user_params</span><span class="p" data-group-id="6503091415-7">}</span><span class="p" data-group-id="6503091415-6">)</span><span class="w"> </span><span class="k" data-group-id="6503091415-8">do</span><span class="w">
    </span><span class="k">case</span><span class="w"> </span><span class="nc">Accounts</span><span class="o">.</span><span class="n">create_user</span><span class="p" data-group-id="6503091415-9">(</span><span class="n">user_params</span><span class="p" data-group-id="6503091415-9">)</span><span class="w"> </span><span class="k" data-group-id="6503091415-10">do</span><span class="w">
      </span><span class="p" data-group-id="6503091415-11">{</span><span class="ss">:ok</span><span class="p">,</span><span class="w"> </span><span class="n">user</span><span class="p" data-group-id="6503091415-11">}</span><span class="w"> </span><span class="o">-&gt;</span><span class="w">
        </span><span class="n">conn</span><span class="w">
        </span><span class="o">|&gt;</span><span class="w"> </span><span class="n">put_flash</span><span class="p" data-group-id="6503091415-12">(</span><span class="ss">:info</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;user created successfully.&quot;</span><span class="p" data-group-id="6503091415-12">)</span><span class="w">
        </span><span class="o">|&gt;</span><span class="w"> </span><span class="n">redirect</span><span class="p" data-group-id="6503091415-13">(</span><span class="ss">to</span><span class="p">:</span><span class="w"> </span><span class="n">user_path</span><span class="p" data-group-id="6503091415-14">(</span><span class="n">conn</span><span class="p">,</span><span class="w"> </span><span class="ss">:show</span><span class="p">,</span><span class="w"> </span><span class="n">user</span><span class="p" data-group-id="6503091415-14">)</span><span class="p" data-group-id="6503091415-13">)</span><span class="w">
      </span><span class="p" data-group-id="6503091415-15">{</span><span class="ss">:error</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="6503091415-16">%</span><span class="nc" data-group-id="6503091415-16">Ecto.Changeset</span><span class="p" data-group-id="6503091415-16">{</span><span class="p" data-group-id="6503091415-16">}</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">changeset</span><span class="p" data-group-id="6503091415-15">}</span><span class="w"> </span><span class="o">-&gt;</span><span class="w">
        </span><span class="n">render</span><span class="p" data-group-id="6503091415-17">(</span><span class="n">conn</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;new.html&quot;</span><span class="p">,</span><span class="w"> </span><span class="ss">changeset</span><span class="p">:</span><span class="w"> </span><span class="n">changeset</span><span class="p" data-group-id="6503091415-17">)</span><span class="w">
    </span><span class="k" data-group-id="6503091415-10">end</span><span class="w">
  </span><span class="k" data-group-id="6503091415-8">end</span><span class="w">
  </span><span class="n">...</span><span class="w">
</span><span class="k" data-group-id="6503091415-1">end</span><span class="w">


</span><span class="c1"># lib/my_app/accounts/accounts.ex</span><span class="w">
</span><span class="kd">defmodule</span><span class="w"> </span><span class="nc">MyApp.Accounts</span><span class="w"> </span><span class="k" data-group-id="6503091415-18">do</span><span class="w">
  </span><span class="kn">alias</span><span class="w"> </span><span class="nc">MyApp.Accounts.User</span><span class="w">

  </span><span class="kd">def</span><span class="w"> </span><span class="nf">list_users</span><span class="w"> </span><span class="k" data-group-id="6503091415-19">do</span><span class="w">
    </span><span class="nc">Repo</span><span class="o">.</span><span class="n">all</span><span class="p" data-group-id="6503091415-20">(</span><span class="nc">User</span><span class="p" data-group-id="6503091415-20">)</span><span class="w">
  </span><span class="k" data-group-id="6503091415-19">end</span><span class="w">

  </span><span class="kd">def</span><span class="w"> </span><span class="nf">create_user</span><span class="p" data-group-id="6503091415-21">(</span><span class="n">attrs</span><span class="w"> </span><span class="o">\\</span><span class="w"> </span><span class="p" data-group-id="6503091415-22">%{</span><span class="p" data-group-id="6503091415-22">}</span><span class="p" data-group-id="6503091415-21">)</span><span class="w"> </span><span class="k" data-group-id="6503091415-23">do</span><span class="w">
    </span><span class="p" data-group-id="6503091415-24">%</span><span class="nc" data-group-id="6503091415-24">User</span><span class="p" data-group-id="6503091415-24">{</span><span class="p" data-group-id="6503091415-24">}</span><span class="w">
    </span><span class="o">|&gt;</span><span class="w"> </span><span class="nc">User</span><span class="o">.</span><span class="n">changeset</span><span class="p" data-group-id="6503091415-25">(</span><span class="n">attrs</span><span class="p" data-group-id="6503091415-25">)</span><span class="w">
    </span><span class="o">|&gt;</span><span class="w"> </span><span class="nc">Repo</span><span class="o">.</span><span class="n">insert</span><span class="p" data-group-id="6503091415-26">(</span><span class="p" data-group-id="6503091415-26">)</span><span class="w">
  </span><span class="k" data-group-id="6503091415-23">end</span><span class="w">
  </span><span class="n">...</span><span class="w">
</span><span class="k" data-group-id="6503091415-18">end</span></code></pre>
<p>
You will also have an Ecto schema generated inside
<code class="inline">lib/my_app/accounts/user.ex</code>. Notice how our controller calls into an
API boundary to create or fetch users in the system. Now we can easily
reuse that logic in other controllers, in Phoenix channels, in
administrative tasks, etc. Testing also becomes more straight-forward,
as we can test the ins and outs of domain without going through the
web stack.</p>
<p>
Designing with contexts gives you a solid foundation to grow your
application from. Using discrete, well-defined APIs that expose the
intent of your system allows you to write more maintainable
applications with reusable code. Additionally, we can get a glimpse of
what the application does and its feature-set just by exploring the
application directory structure:</p>
<pre><code class="makeup console">lib
├── my_app
│   ├── accounts
│   │   ├── accounts.ex
│   │   └── user.ex
│   ├── sales
│   │   ├── manager.ex
│   │   ├── sales.ex
│   │   └── ticket.ex
│   └── repo.ex
├── my_app.ex
├── my_app_web
│   ├── channels
│   ├── controllers
│   ├── templates
│   └── views
└── my_app_web.ex</code></pre>
<p>
With just a glance at the directory structure, we can see this
application has a user Accounts system, as well as sales system. We
can also infer that there is a natural API between these systems thru
the <code class="inline">sales.ex</code> and <code class="inline">accounts.ex</code> modules. We gain this insight
<em>without seeing a single line of code</em>. Contrast that to the previous
<code class="inline">web/models</code>, which did not reveal any relationship between files, and
mostly reflected your database structure, providing no insight on how
they actually related to your domain.</p>
<h2>
<code class="inline">action_fallback</code></h2>
<p>
The new <code class="inline">action_fallback</code> feature allows you to specify a plug that is
called if your controller action fails to return a valid <code class="inline">Plug.Conn{}</code>
struct. The action fallback plug’s job is then to take the connection
before the controller action, as well as the result and convert it to
a valid plug response. This is particularly nice for JSON APIs as it
removes duplication across controllers. For example, your previous
controllers probably looked something like this:</p>
<pre><code class="makeup elixir"><span class="kd">def</span><span class="w"> </span><span class="nc">MyAppWeb.PageController</span><span class="w"> </span><span class="k" data-group-id="3265309377-1">do</span><span class="w">
  </span><span class="kn">alias</span><span class="w"> </span><span class="nc">MyApp.CMS</span><span class="w">
  </span><span class="kd">def</span><span class="w"> </span><span class="nf">show</span><span class="p" data-group-id="3265309377-2">(</span><span class="n">conn</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="3265309377-3">%{</span><span class="s">&quot;id&quot;</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="n">id</span><span class="p" data-group-id="3265309377-3">}</span><span class="p" data-group-id="3265309377-2">)</span><span class="w"> </span><span class="k" data-group-id="3265309377-4">do</span><span class="w">
    </span><span class="k">case</span><span class="w"> </span><span class="nc">CMS</span><span class="o">.</span><span class="n">get_page</span><span class="p" data-group-id="3265309377-5">(</span><span class="n">id</span><span class="p">,</span><span class="w"> </span><span class="n">conn</span><span class="o">.</span><span class="n">assigns</span><span class="o">.</span><span class="n">current_user</span><span class="p" data-group-id="3265309377-5">)</span><span class="w"> </span><span class="k" data-group-id="3265309377-6">do</span><span class="w">
      </span><span class="p" data-group-id="3265309377-7">{</span><span class="ss">:ok</span><span class="p">,</span><span class="w"> </span><span class="n">page</span><span class="p" data-group-id="3265309377-7">}</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="n">render</span><span class="p" data-group-id="3265309377-8">(</span><span class="n">conn</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;show.html&quot;</span><span class="p">,</span><span class="w"> </span><span class="ss">page</span><span class="p">:</span><span class="w"> </span><span class="n">page</span><span class="p" data-group-id="3265309377-8">)</span><span class="w">
      </span><span class="p" data-group-id="3265309377-9">{</span><span class="ss">:error</span><span class="p">,</span><span class="w"> </span><span class="ss">:not_found</span><span class="p" data-group-id="3265309377-9">}</span><span class="w"> </span><span class="o">-&gt;</span><span class="w">
        </span><span class="n">conn</span><span class="w">
        </span><span class="o">|&gt;</span><span class="w"> </span><span class="n">put_status</span><span class="p" data-group-id="3265309377-10">(</span><span class="mi">404</span><span class="p" data-group-id="3265309377-10">)</span><span class="w">
        </span><span class="o">|&gt;</span><span class="w"> </span><span class="n">render</span><span class="p" data-group-id="3265309377-11">(</span><span class="nc">MyAppWeb.ErrorView</span><span class="p">,</span><span class="w"> </span><span class="ss">:&quot;404&quot;</span><span class="p" data-group-id="3265309377-11">)</span><span class="w">
      </span><span class="p" data-group-id="3265309377-12">{</span><span class="ss">:error</span><span class="p">,</span><span class="w"> </span><span class="ss">:unauthorized</span><span class="p" data-group-id="3265309377-12">}</span><span class="w"> </span><span class="o">-&gt;</span><span class="w">
        </span><span class="n">conn</span><span class="w">
        </span><span class="o">|&gt;</span><span class="w"> </span><span class="n">put_status</span><span class="p" data-group-id="3265309377-13">(</span><span class="mi">401</span><span class="p" data-group-id="3265309377-13">)</span><span class="w">
        </span><span class="o">|&gt;</span><span class="w"> </span><span class="n">render</span><span class="p" data-group-id="3265309377-14">(</span><span class="nc">MyAppWeb.ErrorView</span><span class="p">,</span><span class="w"> </span><span class="ss">:&quot;401&quot;</span><span class="p" data-group-id="3265309377-14">)</span><span class="w">
    </span><span class="k" data-group-id="3265309377-6">end</span><span class="w">
  </span><span class="k" data-group-id="3265309377-4">end</span><span class="w">
</span><span class="k" data-group-id="3265309377-1">end</span></code></pre>
<p>
This code on its own is fine, but the issue is common data-structures
in our domain, such as <code class="inline">{:error, :not_found}</code>, and <code class="inline">{:error, :unauthorized}</code> must be handled repeatedly across many different
controllers. Now there’s a better way with action fallback. With 1.3,
we can write:</p>
<pre><code class="makeup elixir"><span class="kd">def</span><span class="w"> </span><span class="nc">MyAppWeb.PageController</span><span class="w"> </span><span class="k" data-group-id="4564685714-1">do</span><span class="w">
  </span><span class="kn">alias</span><span class="w"> </span><span class="nc">MyApp.CMS</span><span class="w">

  </span><span class="n">action_fallback</span><span class="w"> </span><span class="nc">MyAppWeb.FallbackController</span><span class="w">

  </span><span class="kd">def</span><span class="w"> </span><span class="nf">show</span><span class="p" data-group-id="4564685714-2">(</span><span class="n">conn</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="4564685714-3">%{</span><span class="s">&quot;id&quot;</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="n">id</span><span class="p" data-group-id="4564685714-3">}</span><span class="p" data-group-id="4564685714-2">)</span><span class="w"> </span><span class="k" data-group-id="4564685714-4">do</span><span class="w">
    </span><span class="k">with</span><span class="w"> </span><span class="p" data-group-id="4564685714-5">{</span><span class="ss">:ok</span><span class="p">,</span><span class="w"> </span><span class="n">page</span><span class="p" data-group-id="4564685714-5">}</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="nc">CMS</span><span class="o">.</span><span class="n">get_page</span><span class="p" data-group-id="4564685714-6">(</span><span class="n">id</span><span class="p">,</span><span class="w"> </span><span class="n">conn</span><span class="o">.</span><span class="n">assigns</span><span class="o">.</span><span class="n">current_user</span><span class="p" data-group-id="4564685714-6">)</span><span class="w"> </span><span class="k" data-group-id="4564685714-7">do</span><span class="w">
      </span><span class="n">render</span><span class="p" data-group-id="4564685714-8">(</span><span class="n">conn</span><span class="p">,</span><span class="w"> </span><span class="s">&quot;show.html&quot;</span><span class="p">,</span><span class="w"> </span><span class="ss">page</span><span class="p">:</span><span class="w"> </span><span class="n">page</span><span class="p" data-group-id="4564685714-8">)</span><span class="w">
    </span><span class="k" data-group-id="4564685714-7">end</span><span class="w">
  </span><span class="k" data-group-id="4564685714-4">end</span><span class="w">
</span><span class="k" data-group-id="4564685714-1">end</span><span class="w">


</span><span class="kd">defmodule</span><span class="w"> </span><span class="nc">MyAppWeb.FallbackController</span><span class="w"> </span><span class="k" data-group-id="4564685714-9">do</span><span class="w">
  </span><span class="kd">def</span><span class="w"> </span><span class="nf">call</span><span class="p" data-group-id="4564685714-10">(</span><span class="n">conn</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="4564685714-11">{</span><span class="ss">:error</span><span class="p">,</span><span class="w"> </span><span class="ss">:not_found</span><span class="p" data-group-id="4564685714-11">}</span><span class="p" data-group-id="4564685714-10">)</span><span class="w"> </span><span class="k" data-group-id="4564685714-12">do</span><span class="w">
    </span><span class="n">conn</span><span class="w">
    </span><span class="o">|&gt;</span><span class="w"> </span><span class="n">put_status</span><span class="p" data-group-id="4564685714-13">(</span><span class="ss">:not_found</span><span class="p" data-group-id="4564685714-13">)</span><span class="w">
    </span><span class="o">|&gt;</span><span class="w"> </span><span class="n">render</span><span class="p" data-group-id="4564685714-14">(</span><span class="nc">MyAppWeb.ErrorView</span><span class="p">,</span><span class="w"> </span><span class="ss">:&quot;404&quot;</span><span class="p" data-group-id="4564685714-14">)</span><span class="w">
  </span><span class="k" data-group-id="4564685714-12">end</span><span class="w">

  </span><span class="kd">def</span><span class="w"> </span><span class="nf">call</span><span class="p" data-group-id="4564685714-15">(</span><span class="n">conn</span><span class="p">,</span><span class="w"> </span><span class="p" data-group-id="4564685714-16">{</span><span class="ss">:error</span><span class="p">,</span><span class="w"> </span><span class="ss">:unauthorized</span><span class="p" data-group-id="4564685714-16">}</span><span class="p" data-group-id="4564685714-15">)</span><span class="w"> </span><span class="k" data-group-id="4564685714-17">do</span><span class="w">
    </span><span class="n">conn</span><span class="w">
    </span><span class="o">|&gt;</span><span class="w"> </span><span class="n">put_status</span><span class="p" data-group-id="4564685714-18">(</span><span class="ss">:unauthorized</span><span class="p" data-group-id="4564685714-18">)</span><span class="w">
    </span><span class="o">|&gt;</span><span class="w"> </span><span class="n">render</span><span class="p" data-group-id="4564685714-19">(</span><span class="nc">MyAppWeb.ErrorView</span><span class="p">,</span><span class="w"> </span><span class="ss">:&quot;401&quot;</span><span class="p" data-group-id="4564685714-19">)</span><span class="w">
  </span><span class="k" data-group-id="4564685714-17">end</span><span class="w">
</span><span class="k" data-group-id="4564685714-9">end</span></code></pre>
<p>
Notice how our controller can now match on the happy path using a
<code class="inline">with</code> expression. We can then specify a fallback controller that
handles the response conversion in a single place. This is a huge win
for code clarity and removing duplication.</p>
<p>
We are excited about these changes and their long-term payoff in
maintainability. We also feel they’ll lead to sharable, isolated
libraries that the whole community can take advantage of – inside and
outside of Phoenix related projects.</p>
<p>
If you have issues upgrading, please find us on #elixir-lang irc or
slack and we’ll get things sorted out!</p>
<p>
Last but not least, I would like to take a moment to thank the companies that
make this project possible. Much love to <a href="http://plataformatec.com.br">plataformatec</a> for their
continued support of Elixir development and to <a href="https://dockyard.com">DockYard</a> for their sponsorship of Phoenix.</p>
<p>
Happy coding! 🐥🔥</p>
<p>
-Chris</p>
<p>
Full changelog:</p>
<h2>
1.3.0-rc.3 (2017-07-24)</h2>
<ul>
  <li>
    <p>
Enhancements    </p>
    <ul>
      <li>
[ChannelTest] Subscribe <code class="inline">connect</code> to <code class="inline">UserSocket.id</code> to support
testing forceful disconnects      </li>
      <li>
[Socket] Support static <code class="inline">:assigns</code> when defining channel routes      </li>
      <li>
[Channel] Add V2 of wire channel wire protocol with resolved race
conditions and compacted payloads      </li>
      <li>
[phx.new] Use new <code class="inline">lib/my_app</code> and <code class="inline">lib/my_app_web</code> directory
structure      </li>
      <li>
[phx.new] Use new <code class="inline">MyAppWeb</code> alias convention for web modules      </li>
      <li>
[phx.gen.context] No longer prefix Ecto table name by context name      </li>
    </ul>
  </li>
  <li>
    <p>
JavaScript client enhancements    </p>
    <ul>
      <li>
Use V2 channel wire protocol support      </li>
    </ul>
  </li>
  <li>
    <p>
JavaScript client bug fixes    </p>
    <ul>
      <li>
Resolve race conditions when join timeouts occur on client, while
server channel successfully joins      </li>
    </ul>
  </li>
</ul>
<h2>
1.3.0-rc.2 (2017-05-15)</h2>
<p>
See
these <a href="https://gist.github.com/chrismccord/71ab10d433c98b714b75c886eff17357"><code class="inline">1.2.x</code> to <code class="inline">1.3.x</code> upgrade instructions</a>
to bring your existing apps up to speed.</p>
<ul>
  <li>
    <p>
Enhancements    </p>
    <ul>
      <li>
[Generator] Add new <code class="inline">phx.new</code>, <code class="inline">phx.new.web</code>, <code class="inline">phx.new.ecto</code>
project generators with improved application structure and support for
umbrella applications      </li>
      <li>
[Generator] Add new <code class="inline">phx.gen.html</code> and <code class="inline">phx.gen.json</code> resource
generators with improved isolation of API boundaries      </li>
      <li>
[Controller] Add <code class="inline">current_path</code> and <code class="inline">current_url</code> to generate a
connection’s path and url      </li>
      <li>
[Controller] Introduce <code class="inline">action_fallback</code> to registers a plug to
call as a fallback to the controller action      </li>
      <li>
[Controller] Wrap exceptions at controller to maintain connection
state      </li>
      <li>
[Channel] Add ability to configure channel event logging with
<code class="inline">:log_join</code> and <code class="inline">:log_handle_in</code> options      </li>
      <li>
[Channel] Warn on unhandled <code class="inline">handle_info/2</code> messages      </li>
      <li>
[Channel] Channels now distinguish from graceful exits and
application restarts, allowing clients to enter error mode and
reconnected after cold deploys.      </li>
      <li>
[Router] Document <code class="inline">match</code> support for matching on any HTTP method
with the special <code class="inline">:*</code> argument      </li>
      <li>
[Router] Populate <code class="inline">conn.path_params</code> with path parameters for the
route      </li>
      <li>
[ConnTest] Add <code class="inline">redirected_params/1</code> to return the named params
matched in the router for the redirected URL      </li>
      <li>
[Digester] Add <code class="inline">mix phx.digest.clean</code> to remove old versions of
compiled assets      </li>
      <li>
[phx.new] Add Erlang 20 support in <code class="inline">phx.new</code> installer archive      </li>
    </ul>
  </li>
  <li>
    <p>
Bug Fixes    </p>
    <ul>
      <li>
[Controller] Harden local redirect against arbitrary URL
redirection      </li>
      <li>
[Controller] Fix issue causing flash session to remain when using
<code class="inline">clear_flash/1</code>      </li>
    </ul>
  </li>
  <li>
    <p>
Deprecations    </p>
    <ul>
      <li>
[Generator] All <code class="inline">phoenix.*</code> mix tasks have been deprecated in
favor of new <code class="inline">phx.*</code> tasks      </li>
    </ul>
  </li>
  <li>
    <p>
JavaScript client enhancements    </p>
    <ul>
      <li>
Add ability to pass <code class="inline">encode</code> and <code class="inline">decode</code> functions to socket
constructor for custom encoding and decoding of outgoing and incoming
messages.      </li>
      <li>
Detect heartbeat timeouts on client to handle ungraceful
connection loss for faster socket error detection      </li>
      <li>
Add support for AMD/RequireJS      </li>
    </ul>
  </li>
</ul>
]]></content>
  </entry>

  <entry>
    <title>The Road to 2 Million Websocket Connections in Phoenix</title>
    <link rel="alternate" href="http://www.phoenixframework.org/blog/the-road-to-2-million-websocket-connections" />
    <id>http://www.phoenixframework.org/blog/the-road-to-2-million-websocket-connections</id>
    <updated>2015-11-03T00:00:00Z</updated>
    <author>
      <name>Gary Rennie</name>
    </author>
    <summary type="html"><![CDATA[If you have been paying attention on Twitter recently, you have likely seen some increasing numbers regarding the number of simultaneous connections the Phoenix web framework can handle. This post documents some of the techniques used to perform the benchmarks.]]></summary>
    <content type="html"><![CDATA[<p>
  <img src="/images/blog/2m-ws/top.png" alt="">
</p>
<p>
If you have been paying attention on Twitter recently, you have likely seen some increasing numbers regarding the number of simultaneous connections the Phoenix web framework can handle. This post documents some of the techniques used to perform the benchmarks.</p>
<h2>
How It Started</h2>
<p>
A couple of weeks ago I was trying to benchmark the number of connections and managed to get 1k connections on my local machine. I wasn’t convinced by the number so I posted in IRC to see if anyone had benchmarked Phoenix channels. It turned out they had not, but some members of the core team found the 1k number I provided suspiciously low. This was the beginning of the journey.</p>
<h2>
How To Run The Benchmarks</h2>
<h3>
The Server</h3>
<p>
To benchmark the number of simultaneous web sockets that can be open at a time, the first thing required is a Phoenix application to accept the sockets. For these tests, we used a slightly modified version of the <a href="https://github.com/chrismccord/phoenix_chat_example">chrismccord/phoenix_chat_application</a> available at <a href="https://github.com/Gazler/phoenix_chat_example/tree/bench">Gazler/phoenix_chat_example</a> - the key difference is:</p>
<ol>
  <li>
The <code class="inline">after_join</code> hook that broadcasts a user has joined a channel has been removed. When measuring concurrent connections, we want to limit the number of messages that are sent. There will be future benchmarks for that.  </li>
</ol>
<p>
Most of these tests were performed on <a href="http://www.rackspace.com/cloud/servers#dd-wrap-iov1">Rackspace 15 GB I/O v1</a> - these machines have 15GB RAM and 4 cores. <a href="https://www.rackspace.com">Rackspace</a> kindly let us use 3 of these servers for our benchmarks free of charge. They also let us use a <a href="http://www.rackspace.com/cloud/servers">OnMetal I/O</a> which had 128GB RAM and showed 40 cores in htop.</p>
<p>
  <img src="/images/blog/2m-ws/htop.png" alt="">
</p>
<p>
One additional change you may want to make is to remove <code class="inline">check_origin</code> in <code class="inline">conf/prod.exs</code> - this will mean that the application can be connected to regardless of the ip address/hostname used.</p>
<p>
To start the server just <code class="inline">git clone</code> it and run:</p>
<ol>
  <li>
<code class="inline">MIX_ENV=prod mix deps.get</code>  </li>
  <li>
<code class="inline">MIX_ENV=prod mix deps.compile</code>  </li>
  <li>
<code class="inline">MIX_ENV=prod PORT=4000 mix phoenix.server</code>  </li>
</ol>
<p>
You can validate this is working by visiting <code class="inline">YOUR_IP_ADDRESS:4000</code></p>
<h3>
The Client</h3>
<p>
For running the client, we used <a href="http://tsung.erlang-projects.org">Tsung</a>. Tsung is an open-source distributed load testing tool that makes it easy to stress test websockets (as well as many other protocols.)</p>
<p>
The way Tsung works when distributing is by using host names. In our example, the first machine was called “phoenix1” which was assigned against the ip in <code class="inline">/etc/hosts</code>. The other machines “phoenix2” and “phoenix3” should also be in <code class="inline">/etc/hosts</code>.</p>
<p>
It is important to run the clients on a different machine from the Phoenix application for the benchmarks. The results will not be a true representation if both are running on the same machine.</p>
<p>
Tsung is configured using XML files. You can read about the particular values in <a href="http://tsung.erlang-projects.org/user_manual/index.html">the documentation</a>. Here is the config file we used (however the numbers have been lowered to reflect the number of clients here, for our bigger tests we used 43 clients). It starts 1k connections a second up to a maximum of 100k connections. For each connection it opens a websocket, joins the “rooms:lobby” topic and then sleeps for 30000 seconds.</p>
<p>
We used a large sleep time because we wanted to keep the connections open to see how responsive the application was after all the clients had connected. We would stop the test manually instead of closing the websockets in the config (Which you can do with <code class="inline">type=&quot;disconnect&quot;</code>).</p>
<pre><code class="makeup xml"><span class="p">&lt;?</span><span class="nt">xml</span><span class="w"> </span><span class="na">version</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">1.0</span><span class="p">&quot;</span><span class="p">?&gt;</span><span class="w">
</span><span class="p">&lt;!</span><span class="kt">DOCTYPE</span><span class="w"> </span><span class="nv">tsung</span><span class="w"> </span><span class="kt">SYSTEM</span><span class="w"> </span><span class="p">&quot;</span><span class="s2">/user/share/tsung/tsung-1.0.dtd</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="w">
</span><span class="p">&lt;</span><span class="nt">tsung</span><span class="w"> </span><span class="na">loglevel</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">debug</span><span class="p">&quot;</span><span class="w"> </span><span class="na">version</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">1.0</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nt">clients</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nt">client</span><span class="w"> </span><span class="na">host</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">phoenix1</span><span class="p">&quot;</span><span class="w"> </span><span class="na">cpu</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">4</span><span class="p">&quot;</span><span class="w"> </span><span class="na">use_controller_vm</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">false</span><span class="p">&quot;</span><span class="w"> </span><span class="na">maxusers</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">64000</span><span class="p">&quot;</span><span class="w"> </span><span class="p">/&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nt">client</span><span class="w"> </span><span class="na">host</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">phoenix2</span><span class="p">&quot;</span><span class="w"> </span><span class="na">cpu</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">4</span><span class="p">&quot;</span><span class="w"> </span><span class="na">use_controller_vm</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">false</span><span class="p">&quot;</span><span class="w"> </span><span class="na">maxusers</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">64000</span><span class="p">&quot;</span><span class="w"> </span><span class="p">/&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nt">client</span><span class="w"> </span><span class="na">host</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">phoenix3</span><span class="p">&quot;</span><span class="w"> </span><span class="na">cpu</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">4</span><span class="p">&quot;</span><span class="w"> </span><span class="na">use_controller_vm</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">false</span><span class="p">&quot;</span><span class="w"> </span><span class="na">maxusers</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">64000</span><span class="p">&quot;</span><span class="w"> </span><span class="p">/&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;/</span><span class="nt">clients</span><span class="p">&gt;</span><span class="w">
</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nt">servers</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nt">server</span><span class="w"> </span><span class="na">host</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">server_ip_address</span><span class="p">&quot;</span><span class="w"> </span><span class="na">port</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">4000</span><span class="p">&quot;</span><span class="w"> </span><span class="na">type</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">tcp</span><span class="p">&quot;</span><span class="w"> </span><span class="p">/&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;/</span><span class="nt">servers</span><span class="p">&gt;</span><span class="w">
</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nt">load</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nt">arrivalphase</span><span class="w"> </span><span class="na">phase</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">1</span><span class="p">&quot;</span><span class="w"> </span><span class="na">duration</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">100</span><span class="p">&quot;</span><span class="w"> </span><span class="na">unit</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">second</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="w">
</span><span class="w">      </span><span class="p">&lt;</span><span class="nt">users</span><span class="w"> </span><span class="na">maxnumber</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">100000</span><span class="p">&quot;</span><span class="w"> </span><span class="na">arrivalrate</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">1000</span><span class="p">&quot;</span><span class="w"> </span><span class="na">unit</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">second</span><span class="p">&quot;</span><span class="w"> </span><span class="p">/&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;/</span><span class="nt">arrivalphase</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;/</span><span class="nt">load</span><span class="p">&gt;</span><span class="w">
</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nt">options</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nt">option</span><span class="w"> </span><span class="na">name</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">ports_range</span><span class="p">&quot;</span><span class="w"> </span><span class="na">min</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">1025</span><span class="p">&quot;</span><span class="w"> </span><span class="na">max</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">65535</span><span class="p">&quot;</span><span class="p">/&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;/</span><span class="nt">options</span><span class="p">&gt;</span><span class="w">
</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nt">sessions</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nt">session</span><span class="w"> </span><span class="na">name</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">websocket</span><span class="p">&quot;</span><span class="w"> </span><span class="na">probability</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">100</span><span class="p">&quot;</span><span class="w"> </span><span class="na">type</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">ts_websocket</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="w">
</span><span class="w">      </span><span class="p">&lt;</span><span class="nt">request</span><span class="p">&gt;</span><span class="w">
</span><span class="w">        </span><span class="p">&lt;</span><span class="nt">websocket</span><span class="w"> </span><span class="na">type</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">connect</span><span class="p">&quot;</span><span class="w"> </span><span class="na">path</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">/socket/websocket</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="p">&lt;/</span><span class="nt">websocket</span><span class="p">&gt;</span><span class="w">
</span><span class="w">      </span><span class="p">&lt;/</span><span class="nt">request</span><span class="p">&gt;</span><span class="w">
</span><span class="w">
</span><span class="w">      </span><span class="p">&lt;</span><span class="nt">request</span><span class="w"> </span><span class="na">subst</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">true</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="w">
</span><span class="w">        </span><span class="p">&lt;</span><span class="nt">websocket</span><span class="w"> </span><span class="na">type</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">message</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="n">{&quot;topic&quot;:&quot;rooms:lobby&quot;, &quot;event&quot;:&quot;phx_join&quot;, &quot;payload&quot;: {&quot;user&quot;:&quot;%%ts_user_server:get_unique_id%%&quot;}, &quot;ref&quot;:&quot;1&quot;}</span><span class="p">&lt;/</span><span class="nt">websocket</span><span class="p">&gt;</span><span class="w">
</span><span class="w">      </span><span class="p">&lt;/</span><span class="nt">request</span><span class="p">&gt;</span><span class="w">
</span><span class="w">
</span><span class="w">      </span><span class="p">&lt;</span><span class="nt">for</span><span class="w"> </span><span class="na">var</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">i</span><span class="p">&quot;</span><span class="w"> </span><span class="na">from</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">1</span><span class="p">&quot;</span><span class="w"> </span><span class="na">to</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">1000</span><span class="p">&quot;</span><span class="w"> </span><span class="na">incr</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">1</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="w">
</span><span class="w">        </span><span class="p">&lt;</span><span class="nt">thinktime</span><span class="w"> </span><span class="na">value</span><span class="p">=</span><span class="p">&quot;</span><span class="s2">30</span><span class="p">&quot;</span><span class="p">/&gt;</span><span class="w">
</span><span class="w">      </span><span class="p">&lt;/</span><span class="nt">for</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;/</span><span class="nt">session</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;/</span><span class="nt">sessions</span><span class="p">&gt;</span><span class="w">
</span><span class="p">&lt;/</span><span class="nt">tsung</span><span class="p">&gt;</span></code></pre>
<h2>
The First 1k Connections</h2>
<p>
Tsung provides a web interface at port <code class="inline">8091</code> which can be used to monitor the status of the test. The only chart that we were really interested in for these tests was similtaneous users. So the first time I ran Tsung on my own was on my own machine with both Tsung and the Phoenix chat application running locally. When doing this Tsung would often crash - when this happens you can’t see the web interface - which means there is no chart to show for this, but it was an unimpressive 1k connections.</p>
<h2>
The First 1k Connections Again!</h2>
<p>
I set up a machine remotely and attempted benchmarking again. This time I was getting 1k connections, but at least Tsung didn’t crash. The reason for this was the system-wide resource limit was being reached. To verify this I ran <code class="inline">ulimit -n</code> which returned <code class="inline">1024</code> which would explain why I could only get 1k connections.</p>
<p>
From this point onwards the following configuration was used. This configuration took us all the way to 2 million connections.</p>
<pre><code class="makeup bourne_again_shell_bash"><span class="nf">sysctl</span><span class="nv"> -</span><span class="nv">w</span><span class="n"> fs.file-max=12000500</span><span class="w">
</span><span class="nf">sysctl</span><span class="nv"> -</span><span class="nv">w</span><span class="n"> fs.nr_open=20000500</span><span class="w">
</span><span class="nf">ulimit</span><span class="w"> </span><span class="nv">-</span><span class="nv">n</span><span class="n"> 20000000</span><span class="w">
</span><span class="nf">sysctl</span><span class="nv"> -</span><span class="nv">w</span><span class="n"> net.ipv4.tcp_mem=</span><span class="p">&#39;</span><span class="s1">10000000 10000000 10000000</span><span class="p">&#39;</span><span class="w">
</span><span class="nf">sysctl</span><span class="nv"> -</span><span class="nv">w</span><span class="n"> net.ipv4.tcp_rmem=</span><span class="p">&#39;</span><span class="s1">1024 4096 16384</span><span class="p">&#39;</span><span class="w">
</span><span class="nf">sysctl</span><span class="nv"> -</span><span class="nv">w</span><span class="n"> net.ipv4.tcp_wmem=</span><span class="p">&#39;</span><span class="s1">1024 4096 16384</span><span class="p">&#39;</span><span class="w">
</span><span class="nf">sysctl</span><span class="nv"> -</span><span class="nv">w</span><span class="n"> net.core.rmem_max=16384</span><span class="w">
</span><span class="nf">sysctl</span><span class="nv"> -</span><span class="nv">w</span><span class="n"> net.core.wmem_max=16384</span></code></pre>
<h2>
The First Real Benchmark</h2>
<p>
I had been talking about Tsung in IRC when Chris McCord (creator of Phoenix) contacted me to let me know RackSpace had set up some instances for us to use for the benchmarks. We got to work setting up the 3 servers with the following config file: <a href="https://gist.github.com/Gazler/c539b7ef443a6ea5a182">https://gist.github.com/Gazler/c539b7ef443a6ea5a182</a></p>
<p>
After we were up and running we dedicated one machine to Phoenix and two for running Tsung. Our first real benchmark ended up with about 27k connections.</p>
<p>
  <img src="/images/blog/2m-ws/30k.png" alt="">
</p>
<p>
In the above image there are two lines on the chart, the line on the top is labeled “users” and the line on the bottom labeled “connected”. The users increases based on arrival rate. For most of these tests we used an arrival rate of 1000 users per second.</p>
<p>
As soon as the results were in, José Valim was on the case with <a href="https://github.com/phoenixframework/phoenix/commit/061c69b43c3b8c6fa19fd129d35a3a25ae767850">this commit</a></p>
<p>
This was our first improvement and it was a big one. From this we got up to about 50k connections.</p>
<p>
  <img src="/images/blog/2m-ws/50k.png" alt="">
</p>
<h2>
Observing the Changes</h2>
<p>
After our first improvement we realized that we were going in blind. If only there was some way we could observe what was happening. Luckily for use Erlang ships with <a href="http://www.erlang.org/doc/apps/observer/observer_ug.html">observer</a> and it can be used remotely. We used the following technique from <a href="https://gist.github.com/pnc/9e957e17d4f9c6c81294">https://gist.github.com/pnc/9e957e17d4f9c6c81294</a> to open a remote observer.</p>
<p>
  <img src="/images/blog/2m-ws/observer.png" alt="">
</p>
<p>
Chris was able to use the observer to order the processes by the size of their mailbox. The <code class="inline">:timer</code> process had about 40k messages in its mailbox. This is due to Phoenix doing a heartbeat every 30 seconds to ensure the client is still connected.</p>
<p>
Luckily, Cowboy already takes care of this, so after <a href="https://github.com/phoenixframework/phoenix/commit/7b252f42cc8552496a5ebd392f59a008ef6c98a9">this commit</a> the results looked like:</p>
<p>
  <img src="/images/blog/2m-ws/100k.png" alt="">
</p>
<p>
I actually killed the pubsub supervisor using observer in this image which explains the 100k drop at the end. This was the second 2x performance gain. The result was 100k concurrent connections using 2 Tsung machines.</p>
<h2>
We Need More Machines</h2>
<p>
There are two problems with the image above. One is that we don’t reach the full number of clients (about 15k were timing out) and two we can only actually generate between 40k and 60k connections per Tsung client (technically per IP address.) For Chris and I this wasn’t good enough. We couldn’t really see the limits unless we could generate more load.</p>
<p>
At this stage RackSpace had given us the 128GB box, so we actually had another machine we could use, using such a powerful machine as a Tsung client limited to 60k connections may seem like a waste, but it’s better than the machine idling! Chris and I set up another 5 boxes between us which is another 300k possible connections.</p>
<p>
We ran the benchmarks again and we got about 330k connected clients.</p>
<p>
  <img src="/images/blog/2m-ws/16gb-330k.png" alt="">
</p>
<p>
The big problem is about 70k didn’t actually connect to the machine. We couldn’t work out why. Probably hardware issues. We decided to try running Phoenix on the 128GB machine instead. Surely there would be no issues reaching our connection limits, right?</p>
<p>
  <img src="/images/blog/2m-ws/128gb-347k.png" alt="">
</p>
<p>
Wrong. The results here are almost identical to those above. Chris and I thought 330k was pretty good. Chris tweeted out the results and we called it a night.</p>
<blockquote class="twitter-tweet" data-lang="en">
  <p lang="en" dir="ltr">Calling it quits trying to max Channels– at 333k clients. It took maxed ports on 8 servers to push that, 40% mem left. We’re out of servers!</p>&mdash; Chris McCord (@chris_mccord) <a href="https://twitter.com/chris_mccord/status/657716607578472448">October 24, 2015</a></blockquote>
<h2>
Know Your ETS Types</h2>
<p>
After achieving 330k and having 2 fairly easy performance gains, we weren’t sure there could be any more performance gains of the same magnitude. We were wrong. I wasn’t aware of it at the time, but my colleague Gabi Zuniga (<a href="https://twitter.com/gabiz">@gabiz</a>) at <a href="http://voicelayer.io">VoiceLayer</a> had been looking at the issue over the weekend. His commit gave us the best performance gain so far. You can see the diff on <a href="https://github.com/phoenixframework/phoenix/pull/1311">the pull request</a>. I’ll also provide it here for convenience:</p>
<pre><code class="makeup diff"><span class="gd">-</span><span class="gd">    ^local = :ets.new(local, [:bag, :named_table, :public,
</span><span class="gi">+</span><span class="gi">    ^local = :ets.new(local, [:duplicate_bag, :named_table, :public,</span></code></pre>
<p>
Those 10 additional characters made the chart look like this:</p>
<p>
  <img src="/images/blog/2m-ws/420k.png" alt="">
</p>
<p>
Not only did it increase the number of concurrent connections. It also allowed us to increase the arrival rate 10x too. Which made subsequent tests much faster.</p>
<p>
The difference between <code class="inline">bag</code> and <code class="inline">duplicate_bag</code> is that <code class="inline">duplicate_bag</code> will allow multiple entries for the same key. Since each socket can only connect once and have one pid, using a duplicate bag didn’t cause any issues for us.</p>
<p>
This maxed out at around 450k connections. At this point the 16GB box was out of memory. We were now ready to really test the larger box.</p>
<blockquote class="twitter-tweet" data-lang="en">
  <p lang="en" dir="ltr">I called it quits too early on the channel bench’s, with an optimization by <a href="https://twitter.com/gabiz">@gabiz</a> , we have now maxed our 4core/15gb box @ 450k clients!</p>&mdash; Chris McCord (@chris_mccord) <a href="https://twitter.com/chris_mccord/status/658393399821795328">October 25, 2015</a></blockquote>
<h2>
We Need Even More Machines</h2>
<p>
Justin Schneck (<a href="https://twitter.com/mobileoverlord">@mobileoverlord</a>) informed us on IRC that he and his company <a href="http://www.livehelpnow.net/">Live Help Now</a> would set up some additional servers on RackSpace for us to use. 45 additional servers to be precise.</p>
<p>
We set up a few machines and set the threshold for Tsung to be 1 million connections. Which was a new milestone that was easily achieved by the 128GB machine:</p>
<p>
  <img src="/images/blog/2m-ws/1m.png" alt="">
</p>
<blockquote class="twitter-tweet" data-lang="en">
  <p lang="en" dir="ltr">On a bigger <a href="https://twitter.com/Rackspace">@Rackspace</a> box we just 1 million phoenix channel clients on a single server! Quick screencast in action:<a href="https://t.co/ONQcVWWdy1">https://t.co/ONQcVWWdy1</a></p>&mdash; Chris McCord (@chris_mccord) <a href="https://twitter.com/chris_mccord/status/658767562231185408">October 26, 2015</a></blockquote>
<p>
By the time Justin finished setting up all 45 boxes we were convinced 2 million connections was possible. Unfortunately that wasn’t the case. There was a new bottleneck that only started appearing at 1.3 million connections!</p>
<p>
  <img src="/images/blog/2m-ws/1.3m.png" alt="">
</p>
<p>
That was it. 1.3M connections is good enough, right? Wrong. At the same time we hit 1.3M subscribers, we started getting regular timeouts when asking to subscribe to the single pubsub server. We also notice a large increase in broadcast time, taking over 5s to broadcast to all subscribers.</p>
<p>
Justin is interested in Internet of (useful) Things, and wanted to see if we could optimize broadcasts for 1.3+M subscribers since he sees real use cases at these levels. He had the idea to shard broadcasts by chunking the subscribers and parellizing the broadcast work. We trialed this idea and it reduced the broadcast time back down to 1-2s. But, we still had those pesky subscribe timeouts. We were at the limits of a single pubsub server and single ets table. So Chris started work to pool the pubsub servers, and we realized we could combine Justin’s broadcast sharding with a pool of pubsub servers and ets tables. So we sharded by subscriber pid into a pool of pubsub servers each managing their own ets table per shard. This let us to reach 2M subscribers without timeouts and maintain 1s broadcasts. The change is on <a href="https://github.com/phoenixframework/phoenix/compare/c114704...cm-local-pool">this commit</a> which is being refined before merging in to master.</p>
<blockquote class="twitter-tweet" data-lang="en">
  <p lang="en" dir="ltr">Final results from Phoenix channel benchmarks on 40core/128gb box. 2 million clients, limited by ulimit<a href="https://twitter.com/hashtag/elixirlang?src=hash">#elixirlang</a> <a href="https://t.co/6wRUIfFyKZ">pic.twitter.com/6wRUIfFyKZ</a></p>&mdash; Chris McCord (@chris_mccord) <a href="https://twitter.com/chris_mccord/status/659430661942550528">October 28, 2015</a></blockquote>
<p>
So there you have it. 2 million connections! Each time we thought there were no more optimizations to be made, another idea was pitched leading to a huge improvement in performance.</p>
<p>
2 million is a figure we are pleased with. However, we did not quite max out the machine and we have not yet made any effort toward reducing the memory usage of each socket handler. In addition, there are more benchmarks we will be performing. This particular set of benchmarks was set exclusively around the number of simultaneous open sockets. A chat room with 2 million users is awesome, especially when the messages are broadcast so quickly. This is not a typical use case though. Here are some future benchmarking ideas:</p>
<ul>
  <li>
One channel with x users sending y messages  </li>
  <li>
X channels with 1000 users sending y messages  </li>
  <li>
Running the Phoenix application across multiple nodes  </li>
  <li>
A simulation sending a random number of messages with users arriving and leaving randomly to behave like a real chat room  </li>
</ul>
<p>
The improvements discovered during this benchmark test will be made available in an upcoming release of Phoenix. Keep an eye out for information on future benchmark tests where Phoenix will continue to push the boundaries of the modern web.</p>
]]></content>
  </entry>

  <entry>
    <title>Phoenix 1.0 – the framework for the modern web just landed</title>
    <link rel="alternate" href="http://www.phoenixframework.org/blog/phoenix-10-the-framework-for-the-modern-web-just-landed" />
    <id>http://www.phoenixframework.org/blog/phoenix-10-the-framework-for-the-modern-web-just-landed</id>
    <updated>2015-08-28T00:00:00Z</updated>
    <author>
      <name>Chris McCord</name>
    </author>
    <summary type="html"><![CDATA[After a year and a half of work, 2500 commits, and 30 releases, Phoenix 1.0 is here!]]></summary>
    <content type="html"><![CDATA[<p>
  <img src="/images/blog/1.0-ann/diff.png" alt="">
</p>
<p>
After a year and a half of work, 2500 commits, and 30 releases, Phoenix 1.0 is here! With 1.0 in place, Phoenix is set to take on the world whether you’re building APIs, HTML5 applications, or network services for native devices. Written in Elixir, you get beautiful syntax, productive tooling and a fast runtime. Along the way, we’ve had many <a href="https://www.youtube.com/watch?v=xT8vDHIvurs&feature=youtu.be">success stories of companies using phoenix in production</a>, and two ElixirConf’s where we showed off Phoenix’s progress.</p>
<h2>
Many Thanks</h2>
<p>
Before we jump into some of the great things Phoenix has to offer, we owe thanks to the people that helped make this possible.</p>
<h3>
José Valim</h3>
<p>
Though he’ll try to downplay his efforts, José paved the way for Phoenix with a level of contribution that is simply amazing. He not only wrote Elixir, but bootstrapped Phoenix with the <a href="https://github.com/elixir-lang/plug">Plug</a> library, opened database access with <a href="https://github.com/elixir-lang/ecto">Ecto</a>, and contributed thousands of lines of code to Phoenix itself. Along the way, he crafted Elixir releases and helped build a community that has been such a pleasure to be a part of. Thank you!</p>
<h3>
phoenix-core</h3>
<p>
The core team devoted many of their nights and weekends to get where we are today. Whether it’s <a href="https://twitter.com/lance_halvorsen">Lance Halvorsen</a> writing the lovely Phoenix guides, <a href="https://twitter.com/peregrine">Jason Stiebs</a> helping flesh out the initial channels layer, <a href="https://twitter.com/emjii">Eric Meadows-Jönsson</a> working on hex.pm and making sure we have graceful fallback for older browsers, or <a href="https://twitter.com/scrogson">Sonny Scroggin</a> contributing in many areas while training newcomers, these people helped make Phoenix what it is today.</p>
<h2>
The real-time web</h2>
<p>
From the beginning, Phoenix has been focused on taking on the real-time web. The goal was to make real-time communication just as trivial as writing a REST endpoint. We’ve realized that goal with channels. This 90 second clip of a collaborative editor should give you a sense of what’s possible:</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/GLa9gtvP13Y" frameborder="0" allowfullscreen="allowfullscreen">
</iframe>
<p>
Channels give you a multiplexed connection to the server for bidirectional communication. Phoenix also abstracts the transport layer, so you no longer have to be concerned with how the user has connected. Whether WebSocket, Long-polling, or a custom transport, your channel code remains the same. You write code against an abstracted “socket”, and Phoenix takes care of the rest. Even on a cluster of machines, your messages are broadcasted across the nodes automatically. Phoenix’s javascript client also provides an API that makes client/server communication beautifully simple. This is what it looks like:</p>
<p>
  <img src="/images/blog/1.0-ann/channels.png" alt="">
</p>
<h2>
Beyond the browser</h2>
<p>
As a “web framework”, Phoenix targets traditional browser applications, but the so-called “web” is evolving. And we need a framework to evolve with it. Phoenix transcends the browser by connecting not only browsers, but iPhones, Android handsets, and smart devices alike. <a href="https://twitter.com/mobileoverlord">Justin Schneck</a>, <a href="https://twitter.com/eoins">Eoin Shanaghy</a>, and <a href="https://twitter.com/davidstump">David Stump</a> helped Phoenix realize this goal by writing channel clients for objC, Swift, C#, and Java. To appreciate what this enables, Justin demo’d a Phoenix chat application running on an Apple Watch, iPhone, and web browser all powered by native phoenix channel clients:</p>
<iframe src="https://player.vimeo.com/video/136679715" width="640" height="400" frameborder="0" webkitallowfullscreen="webkitallowfullscreen" mozallowfullscreen="mozallowfullscreen" allowfullscreen="allowfullscreen">
</iframe>
<h2>
Productivity in the short term and the long term</h2>
<p>
In addition to high connectivity, Phoenix gives you a comfortable feature set to get up and running quickly and be productive with your team. But, Software isn’t just about the short-term. Elixir leverages tried and true patterns for long-term project success and maintainability. The Erlang runtime was designed for systems to run for many years, with minimal downtime. Using these patterns and the runtime innovations, you can deploy systems that self-heal, support hot-code uploading, and have capabilities known to support <em>millions</em> of connected users. Out of the box, Phoenix provides:</p>
<h3>
Short-term productivity</h3>
<ul>
  <li>
Project generation with <code class="inline">mix phoenix.new my_app</code>  </li>
  <li>
Live-reload in development. Make a change to any template, view, or asset and see the results immediately in the browser  </li>
  <li>
Postgres, MySQL, MSSQL, and MongoDB resources through <a href="https://github.com/elixir-lang/ecto">Ecto</a> integration  </li>
  <li>
Resource generators, such as <code class="inline">mix phoenix.gen.html User users name:string age:integer</code> to bootstrap a project and learn the ins and outs of phoenix best practices  </li>
  <li>
A precompiled view layer with EEx templates for lightning fast response times, often measuring in <em>microseconds</em>  </li>
  <li>
Channels for realtime communication  </li>
  <li>
and more  </li>
</ul>
<h3>
Long-term productivity</h3>
<ul>
  <li>
The ability to run multiple phoenix applications side-by-side in the same OS process or break a bigger application into smaller chunks with umbrella apps: <a href="http://blog.plataformatec.com.br/2015/06/elixir-in-times-of-microservices/">http://blog.plataformatec.com.br/2015/06/elixir-in-times-of-microservices/</a>  </li>
  <li>
Erlang OTP tooling to get a live look into your running application and diagnose issues:  </li>
</ul>
<p>
  <img src="/images/blog/1.0-ann/observer.png" alt="">
</p>
<h2>
What’s Next?</h2>
<p>
We’re just getting started with 1.0. With a strong and stable core in place, we’ll be building Channel Presence features, internationalization, and more. Be sure to <a href="">register for ElixirConf</a> in October to find out yet unannounced plans beyond Phoenix 1.1 and other neat things happening in the Elixir ecosystem. José Valim is also hosting a Phoenix webinar on Sept 4 to talk about Phoenix and answer viewer questions.</p>
<ul>
  <li>
<a href="http://pages.plataformatec.com.br/webinar-phoenix-framework-with-jose-valim">Phoenix webinar & Q/A with José Valim</a>, Sept 4  </li>
  <li>
<a href="http://elixirconf.com">ElixirConf</a>, October 1-3 Austin, TX  </li>
</ul>
<h2>
Getting Started</h2>
<p>
So how can you join in on all this fun? The <a href="http://www.phoenixframework.org">Phoenix guides</a> will take you through the basics and get you up and running quickly. If you’re new to Elixir, here’s a few resources to get up to speed before jumping into Phoenix:</p>
<ul>
  <li>
<a href="http://elixir-lang.org/getting-started/introduction.html">elixir-lang.org getting started guides</a>  </li>
  <li>
<a href="https://howistart.org/posts/elixir/1">How I Start:  Elixir</a>  </li>
  <li>
<a href="http://www.chrismccord.com/blog/2014/05/27/all-aboard-the-elixir-express/">Elixir Workshop</a>  </li>
</ul>
<p>
It has been an amazing ride, and we’re just getting started. Let’s show the world what Elixir and Phoenix can do.</p>
<p>
–Chris</p>
]]></content>
  </entry>

  <entry>
    <title>Phoenix 0.10.0 released with assets handling, generators, &amp; more</title>
    <link rel="alternate" href="http://www.phoenixframework.org/blog/phoenix-0100-released-with-assets-handling-generat" />
    <id>http://www.phoenixframework.org/blog/phoenix-0100-released-with-assets-handling-generat</id>
    <updated>2015-03-09T00:00:00Z</updated>
    <author>
      <name>Chris McCord</name>
    </author>
    <summary type="html"><![CDATA[We released Phoenix 0.10.0 this weekend and we’re really excited to share the new features we’ve been working on.]]></summary>
    <content type="html"><![CDATA[<p>
We released Phoenix 0.10.0 this weekend and we’re really excited to
share the new features we’ve been working on. This release brings an
asset build system powered by <a href="http://brunch.io">brunch</a>, live-reloading of
css/js/eex templates, form builders, and new <a href="https://github.com/elixir-lang/ecto">Ecto</a> integration with generators
that lets you get up and running quickly.</p>
<p>
There’s so much good stuff packed into this release, that it deserved
a screencast so you can see it in action:</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/Nh5OQjb8G9E" frameborder="0" allowfullscreen="allowfullscreen">
</iframe>
<h2>
Live-Reload</h2>
<p>
Change any css, js, or EEx template, and watch the browser instantly
reload with your changes. The best part is that we don’t need any browser plugin. Everything is powered by Phoenix channels and works regardless of your javascript tooling of choice. Try it out–you’ll love it.</p>
<h2>
Static Asset Handling</h2>
<p>
We’ve integrated Brunch for fast and simple asset
compilation that Just Works. When you start your <code class="inline">mix phoenix.server</code>
in development, a brunch process is run automatically alongside your
endpoint and your assets in <code class="inline">web/static/js</code> and <code class="inline">web/static/css</code> will
be compiled as the files change. Even better, with our new live-reload
feature, those recompiles get reloaded in the browser for a
streamlined development experience. We’ve also built Brunch
integration in a way that will let you wire up your own asset system,
such as Gulp, Grunt, Webpack, etc.</p>
<p>
Out of the box, we support Sass and ES6 javascript compilation, but
it’s very easy to extend your <code class="inline">brunch-config.js</code> with additional tools
to support your asset workflow.</p>
<h2>
We’ve integrated Brunch for fast and simple asset</h2>
<p>
compilation that Just Works. When you start your <code class="inline">mix phoenix.server</code>
in development, a brunch process is run automatically alongside your
endpoint and your assets in <code class="inline">web/static/js</code> and <code class="inline">web/static/css</code> will
be compiled as the files change. Even better, with our new live-reload
feature, those recompiles get reloaded in the browser for a
streamlined development experience. We’ve also built Brunch
integration in a way that will let you wire up your own asset system,
such as Gulp, Grunt, Webpack, etc.</p>
<p>
Out of the box, we support Sass and ES6 javascript compilation, but
it’s very easy to extend your <code class="inline">brunch-config.js</code> with additional tools
to support your asset workflow.</p>
<p>
This release brings two new protocols, <code class="inline">Phoenix.HTML.FormData</code> and
<code class="inline">Phoenix.Param</code> that makes it simple to integrate your model layer
with Phoenix’s new form and link builders. As a default, but optional
dep, we now include Ecto integration through the
<a href="https://github.com/phoenixframework/phoenix_ecto">phoenix_ecto</a>
project where you can see these two new protocols in action. Let’s check it out:</p>
<pre><code class="makeup html_eex"><span class="p" data-group-id="6575010047-1">&lt;%=</span><span class="w"> </span><span class="n">form_for</span><span class="w"> </span><span class="na">@changeset</span><span class="p">,</span><span class="w"> </span><span class="na">@action</span><span class="p">,</span><span class="w"> </span><span class="k" data-group-id="6575010047-ex-1">fn</span><span class="w"> </span><span class="n">f</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="p" data-group-id="6575010047-1">%&gt;</span><span class="w">
</span><span class="w">  </span><span class="p" data-group-id="6575010047-2">&lt;%=</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="na">@changeset</span><span class="o">.</span><span class="n">errors</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="p" data-group-id="6575010047-ex-2">[</span><span class="p" data-group-id="6575010047-ex-2">]</span><span class="w"> </span><span class="k" data-group-id="6575010047-ex-3">do</span><span class="w"> </span><span class="p" data-group-id="6575010047-2">%&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nt">p</span><span class="w"> </span><span class="na">style</span><span class="p">=</span><span class="p">&quot;</span><span class="nb">color</span><span class="p">:</span><span class="w"> </span><span class="no">red</span><span class="p">&quot;</span><span class="p">&gt;</span><span class="n">Oops, something went wrong!</span><span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p" data-group-id="6575010047-3">&lt;%</span><span class="w"> </span><span class="k" data-group-id="6575010047-ex-3">end</span><span class="w"> </span><span class="p" data-group-id="6575010047-3">%&gt;</span><span class="w">
</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nt">label</span><span class="p">&gt;</span><span class="w">
</span><span class="w">      </span><span class="p">&lt;</span><span class="nt">span</span><span class="p">&gt;</span><span class="n">Title:</span><span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span><span class="w">
</span><span class="w">      </span><span class="p" data-group-id="6575010047-4">&lt;%=</span><span class="w"> </span><span class="n">text_input</span><span class="w"> </span><span class="n">f</span><span class="p">,</span><span class="w"> </span><span class="ss">:title</span><span class="w"> </span><span class="p" data-group-id="6575010047-4">%&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;/</span><span class="nt">label</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span><span class="w">
</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nt">label</span><span class="p">&gt;</span><span class="w">
</span><span class="w">      </span><span class="p">&lt;</span><span class="nt">span</span><span class="p">&gt;</span><span class="n">Rank:</span><span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span><span class="w">
</span><span class="w">      </span><span class="p" data-group-id="6575010047-5">&lt;%=</span><span class="w"> </span><span class="n">number_input</span><span class="w"> </span><span class="n">f</span><span class="p">,</span><span class="w"> </span><span class="ss">:rank</span><span class="w"> </span><span class="p" data-group-id="6575010047-5">%&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;/</span><span class="nt">label</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span><span class="w">
</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p" data-group-id="6575010047-6">&lt;%=</span><span class="w"> </span><span class="n">submit</span><span class="w"> </span><span class="s">&quot;Submit&quot;</span><span class="w"> </span><span class="p" data-group-id="6575010047-6">%&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span><span class="w">
</span><span class="p" data-group-id="6575010047-7">&lt;%</span><span class="w"> </span><span class="k" data-group-id="6575010047-ex-1">end</span><span class="w"> </span><span class="p" data-group-id="6575010047-7">%&gt;</span></code></pre>
<p>
<code class="inline">form_for</code> accepts an Ecto changeset here, but will support any data
structure that implements the <code class="inline">FormData</code> protocol. With form builders,
we inject the CSRF token for you automatically to verify requests and
you can enjoy the new form input helpers, ie <code class="inline">text_input</code>,
<code class="inline">number_input</code>.</p>
<p>
In addition to forms, we now include a <code class="inline">link</code> function for building
anchors in your templates:</p>
<pre><code class="makeup html_eex"><span class="p" data-group-id="8970747140-1">&lt;%=</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">post</span><span class="w"> </span><span class="o">&lt;-</span><span class="w"> </span><span class="na">@posts</span><span class="w"> </span><span class="k" data-group-id="8970747140-ex-1">do</span><span class="w"> </span><span class="p" data-group-id="8970747140-1">%&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;</span><span class="nt">tr</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nt">td</span><span class="p">&gt;</span><span class="p" data-group-id="8970747140-2">&lt;%=</span><span class="w"> </span><span class="n">post</span><span class="o">.</span><span class="n">title</span><span class="w"> </span><span class="p" data-group-id="8970747140-2">%&gt;</span><span class="p">&lt;/</span><span class="nt">td</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nt">td</span><span class="p">&gt;</span><span class="p" data-group-id="8970747140-3">&lt;%=</span><span class="w"> </span><span class="n">post</span><span class="o">.</span><span class="n">rank</span><span class="w"> </span><span class="p" data-group-id="8970747140-3">%&gt;</span><span class="p">&lt;/</span><span class="nt">td</span><span class="p">&gt;</span><span class="w">
</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nt">td</span><span class="p">&gt;</span><span class="p" data-group-id="8970747140-4">&lt;%=</span><span class="w"> </span><span class="n">link</span><span class="w"> </span><span class="s">&quot;Show&quot;</span><span class="p">,</span><span class="w"> </span><span class="ss">to</span><span class="p">:</span><span class="w"> </span><span class="n">post_path</span><span class="p" data-group-id="8970747140-ex-2">(</span><span class="na">@conn</span><span class="p">,</span><span class="w"> </span><span class="ss">:show</span><span class="p">,</span><span class="w"> </span><span class="n">post</span><span class="p" data-group-id="8970747140-ex-2">)</span><span class="w"> </span><span class="p" data-group-id="8970747140-4">%&gt;</span><span class="p">&lt;/</span><span class="nt">td</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nt">td</span><span class="p">&gt;</span><span class="p" data-group-id="8970747140-5">&lt;%=</span><span class="w"> </span><span class="n">link</span><span class="w"> </span><span class="s">&quot;Edit&quot;</span><span class="p">,</span><span class="w"> </span><span class="ss">to</span><span class="p">:</span><span class="w"> </span><span class="n">post_path</span><span class="p" data-group-id="8970747140-ex-3">(</span><span class="na">@conn</span><span class="p">,</span><span class="w"> </span><span class="ss">:edit</span><span class="p">,</span><span class="w"> </span><span class="n">post</span><span class="p" data-group-id="8970747140-ex-3">)</span><span class="w"> </span><span class="p" data-group-id="8970747140-5">%&gt;</span><span class="p">&lt;/</span><span class="nt">td</span><span class="p">&gt;</span><span class="w">
</span><span class="w">    </span><span class="p">&lt;</span><span class="nt">td</span><span class="p">&gt;</span><span class="p" data-group-id="8970747140-6">&lt;%=</span><span class="w"> </span><span class="n">link</span><span class="w"> </span><span class="s">&quot;Delete&quot;</span><span class="p">,</span><span class="w"> </span><span class="ss">to</span><span class="p">:</span><span class="w"> </span><span class="n">post_path</span><span class="p" data-group-id="8970747140-ex-4">(</span><span class="na">@conn</span><span class="p">,</span><span class="w"> </span><span class="ss">:delete</span><span class="p">,</span><span class="w"> </span><span class="n">post</span><span class="p" data-group-id="8970747140-ex-4">)</span><span class="p">,</span><span class="w"> </span><span class="ss">method</span><span class="p">:</span><span class="w"> </span><span class="ss">:delete</span><span class="w"> </span><span class="p" data-group-id="8970747140-6">%&gt;</span><span class="p">&lt;/</span><span class="nt">td</span><span class="p">&gt;</span><span class="w">
</span><span class="w">  </span><span class="p">&lt;/</span><span class="nt">tr</span><span class="p">&gt;</span><span class="w">
</span><span class="p" data-group-id="8970747140-7">&lt;%</span><span class="w"> </span><span class="k" data-group-id="8970747140-ex-1">end</span><span class="w"> </span><span class="p" data-group-id="8970747140-7">%&gt;</span></code></pre>
<p>
Notice how we able to write <code class="inline">post_path(@conn, :show, post)</code> instead of
<code class="inline">post_path(@conn, :show, post.id)</code>? This is thanks to the new
<code class="inline">Phoenix.Param</code> protocol that lets you define how resources should be
converted to paths and URLs. You may have also noticed the <code class="inline">method: :delete</code> option. This will convert the link tag into a form submission,
as a DELETE request, and will inject the CSRF token for you. It’s
handy for quick links to delete or update a resource without having to
build a form yourself.</p>
<h2>
Resource Generator</h2>
<p>
The Ecto integration includes a new <code class="inline">mix phoenix.gen.resource</code> task
that bootstraps a model with boilerplate code generation that lets you
get up to speed quickly with Phoenix and Ecto and start building
applications right away. From a single command like:</p>
<pre><code class="makeup console">mix phoenix.gen.resource Post posts title:string rank:integer</code></pre>
<p>
A migration file is created with the provided schema and the model,
view, template, and controller files are generated for CRUD actions.
It’s a great way to learn the basics of Phoenix and experience the
latest and greatest Ecto features.</p>
<h2>
Upgrading from 0.9.x</h2>
<p>
See these <a href="https://gist.github.com/chrismccord/cf51346c6636b5052885">0.9.x to 0.10.0 upgrade instructions</a> to bring your existing apps up to speed.</p>
<h2>
Get Involved</h2>
<p>
That’s Phoenix 0.10.0. In case you missed it in the previous release, we have done many improvements to our channel system, including support for 3rd party backends, starting with Redis. Now we have streamlined the development experience. We have some big announcements coming soon as
we head towards 1.0, so keep up to date by subscribing to the
<a href="https://groups.google.com/forum/#!forum/phoenix-core">phoenix-core</a> and <a href="https://groups.google.com/forum/#!forum/phoenix-talk">phoenix-talk</a> mailing lists, and get involved on
<a href="irc://irc.freenode.net/elixir-lang">#elixir-lang IRC</a>. Feel free to join in and ask questions, and provide
help to others.</p>
<p>
Happy coding!</p>
]]></content>
  </entry>

</feed>
