defmodule Phoenix.LiveView.ColocatedHook do @moduledoc ~S''' A special HEEx `:type` that extracts [hooks](js-interop.md#client-hooks-via-phx-hook) from a co-located ` """ end end You can read more about the internals of colocated hooks in the [documentation for colocated JS](`Phoenix.LiveView.ColocatedJS#internals`). 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 `import`ed into a special *manifest* file. The manifest file provides [a named export](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export) which allows it to be imported by any JavaScript bundler that supports [ES modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules): ```javascript import {hooks} from "phoenix-colocated/my_app" console.log(hooks); /* { "MyAppWeb.DemoLive.PhoneNumber": {...}, ... } */ ``` > #### Compilation order {: .info} > > Colocated hooks are only written when the corresponding component is compiled. > Therefore, whenever you need to access a colocated hook, you need to ensure > `mix compile` runs first. This automatically happens in development. > > If you have a custom mix alias, instead of > release: ["assets.deploy", "release"] > do > release: ["compile", "assets.deploy", "release"] > to ensure that all colocated hooks are extracted before esbuild or any other bundler runs. ## Options Colocated hooks are configured through the attributes of the ` ``` This is because the hook's code is wrapped by LiveView into something like this: ```javascript 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: ```heex ``` This is assuming that the `@script_csp_nonce` assign contains the nonce value that is also sent in the `Content-Security-Policy` header. ''' @behaviour Phoenix.Component.MacroComponent @impl true def transform({"script", attributes, [text_content], _tag_meta} = _ast, meta) do validate_phx_version!() opts = Map.new(attributes) name = case opts do %{"name" => "." <> name} -> "#{inspect(meta.env.module)}.#{name}" %{"name" => name} when is_binary(name) -> raise ArgumentError, """ colocated hook names must start with a dot, invalid hook name: #{name} Hint: name your hook