714 words, 4 min read

To upgrade an application using Phoenix LiveView 1.0 to version 1.1 (RC 3 at the moment), you need to take a couple of different steps.

Upgrading the dependencies

  1. In your mix.exs, update phoenix_live_view to latest and add lazy_html as a dependency (you also need Phoenix 1.8, if you want to try colocated hooks):

    {:phoenix, "~> 1.8.0-rc.4", override: true},
    {:phoenix_live_view, "~> 1.1.0-rc.3", override: true},
    {:lazy_html, ">= 0.0.0", only: :test},
    

    Note you may remove floki as a dependency if you don't use it anywhere.

  2. Still in your mix.exs, prepend :phoenix_live_view to your list of compilers inside def project, such as:

    compilers: [:phoenix_live_view] ++ Mix.compilers(),
    
  3. (optional) In your config/dev.exs, find debug_heex_annotations, and also add debug_tags_location for improved annotations:

    config :phoenix_live_view,
      debug_heex_annotations: true,
      debug_tags_location: true,
      enable_expensive_runtime_checks: true
    
  4. Run mix deps.get to install the dependencies

  5. Run the following command to update the code for the new changes:

    mix phoenix_live_view.upgrade 1.0.0 1.1.0
    

Once the final version of Phoenix LiveView 1.1 will be released, it will be as simple as executing the following command to do all the hard work (thanks to igniter from Zach Daniel):

mix igniter.upgrade phoenix_live_view

Moving from Floki to LazyHTML

LiveView v1.1 moves to LazyHTML as the HTML engine used by LiveViewTest. LazyHTML is based on lexbor and allows the use of modern CSS selector features, like :is(), :has(), etc. to target elements. Lexbor's stated goal is to create output that "should match that of modern browsers, meeting industry specifications".

This is a mostly backwards compatible change. The only way in which this affects LiveView projects is when using Floki specific selectors (fl-contains, fl-icontains), which will not work any more in selectors passed to LiveViewTest's element/3 function. In most cases, the text_filter option of element/3 should be a sufficient replacement, which has been available since LiveView v0.12.

Note that in Phoenix versions prior to v1.8, the phx.gen.auth generator used the Floki specific fl-contains selector in its generated tests in two instances, so if you used the phx.gen.auth generator to scaffold your authentication solution, those tests will need to be adjusted when updating to LiveView v1.1. In both cases, changing to use the text_filter option is enough to get you going again:

 {:ok, _login_live, login_html} =
   lv
-  |> element(~s|main a:fl-contains("Sign up")|)
+  |> element("main a", "Sign up")
   |> render_click()
   |> follow_redirect(conn, ~p"<%= schema.route_prefix %>/register")

If you're using Floki itself in your tests through its API (Floki.parse_document, Floki.find, etc.), you are not required to rewrite them when you update to LiveView v1.1.

Colocated hooks

LiveView v1.1 introduces colocated hooks to allow writing the hook's JavaScript code in the same file as your regular component code.

A colocated hook is defined by placing the special :type attribute on a <script> tag:

alias Phoenix.LiveView.ColocatedHook

def input(%{type: "phone-number"} = assigns) do
  ~H"""
  <input type="text" name={@name} id={@id} value={@value} phx-hook=".PhoneNumber" />
  <script :type={ColocatedHook} name=".PhoneNumber">
    export default {
      mounted() {
        this.el.addEventListener("input", e => {
          let match = this.el.value.replace(/\D/g, "").match(/^(\d{3})(\d{3})(\d{4})$/)
          if(match) {
            this.el.value = `${match[1]}-${match[2]}-${match[3]}`
          }
        })
      }
    }
  </script>
  """
end

Important: LiveView now supports the phx-hook attribute to start with a dot (.PhoneNumber above) for namespacing. Any hook name starting with a dot is prefixed at compile time with the module name of the component. If you named your hooks with a leading dot in the past, you'll need to adjust this for your hooks to work properly on LiveView v1.1.

Colocated hooks are extracted to a phoenix-colocated folder inside your _build/$MIX_ENV directory (Mix.Project.build_path()). See the quick update section at the top of the changelog on how to adjust your esbuild configuration to handle this. With everything configured, you can import your colocated hooks inside of your app.js like this:

...
  import {LiveSocket} from "phoenix_live_view"
+ import {hooks as colocatedHooks} from "phoenix-colocated/my_app"
  import topbar from "../vendor/topbar"
...
  const liveSocket = new LiveSocket("/live", Socket, {
    longPollFallbackMs: 2500,
    params: {_csrf_token: csrfToken},
+   hooks: {...colocatedHooks}
  })

The phoenix-colocated folder has subfolders for each application that uses colocated hooks, therefore you'll need to adjust the my_app part of the import depending on the name of your project (defined in your mix.exs). You can read more about colocated hooks in the module documentation of Phoenix.LiveView.ColocatedHook. There's also a more generalized version for colocated JavaScript, see the documentation for Phoenix.LiveView.ColocatedJS.

Other notable changes

source