Phoenix.LiveView.ColocatedHook (Phoenix LiveView v1.1.0-rc.2)

View Source

A special HEEx :type that extracts hooks from a co-located <script> tag at compile time.

Introduction

Colocated hooks are defined as with :type={Phoenix.LiveView.ColocatedHook}:

defmodule MyAppWeb.DemoLive do
  use MyAppWeb, :live_view

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

  def render(assigns) do
    ~H"""
    <input type="text" name="user[phone_number]" id="user-phone-number" phx-hook=".PhoneNumber" />
    <script :type={Phoenix.LiveView.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
end

You can read more about the internals of colocated hooks in the documentation for colocated JS. A brief summary: at compile time, the hook's code is extracted into a special folder, typically in your _build directory. Each hook is also imported into a special manifest file. The manifest file provides a named export which allows it to be imported by any JavaScript bundler that supports ES modules:

import {hooks} from "phoenix-colocated/my_app"

console.log(hooks);
/*
{
  "MyAppWeb.DemoLive.PhoneNumber": {...},
  ...
}
*/

Options

Colocated hooks are configured through the attributes of the <script> tag. The supported attributes are:

  • name - The name of the hook. This is required and must start with a dot, for example: name=".myhook". The same name must be used when referring to this hook in the phx-hook attribute of another HTML element.

  • runtime - If present, the hook is not extracted, but instead registered at runtime. You should only use this option if you know that you need it. It comes with some limitations:

    1. The content is not processed by any bundler, therefore it must only use features supported by the targeted browsers.
    2. You need to take special care about any Content Security Policies that may be in place. See the section on runtime hooks below for more details.

Runtime hooks

Runtime hooks are a special kind of colocated hook that are not removed from the DOM when rendering the component. Instead, the hook's code is executed directly in the browser with no bundler involved.

One example where this can be useful is when you are creating a custom page for a library like Phoenix.LiveDashboard. The live dashboard already bundles its hooks, therefore there is no way to add new hooks to the bundle when the live dashboard is used inside your application.

Because of this, runtime hooks must also use a slightly different syntax. While in normal colocated hooks you'd write an export default statement, runtime hooks must evaluate to the hook itself:

<script :type={Phoenix.LiveView.ColocatedHook} name=".MyHook" runtime>
  {
    mounted() {
      ...
    }
  }
</script>

This is because the hook's code is wrapped by LiveView into something like this:

window["phx_hook_HASH"] = function() {
  return {
    mounted() {
      ...
    }
  }
}

Still, even for runtime hooks, the hook's name needs to start with a dot and is automatically prefixed with the module name to avoid conflicts with other hooks.

When using runtime hooks, it is important to think about any limitations that content security policies may impose. If CSP is involved, the only way to use runtime hooks is by using CSP nonces:

<script :type={Phoenix.LiveView.ColocatedHook} name=".MyHook" runtime nonce={@script_csp_nonce}>
  function() {
    return ...;
  }
</script>

This is assuming that the @script_csp_nonce assign contains the nonce value that is also sent in the Content-Security-Policy header.