LiveView force reload/remount a custom-compiled module

I’m trying to figure out how to plug in a custom compiler for Phoenix LiveView.

tl;dr:

  • The mix task runs successfully
  • The custom-compiled modules are picked by LiveView
  • A custom file watcher picks up the files and compiles them on change
  • The new changes are not picked up by LiveView

What works:

The mix task works:

  1. Create a compiler. Could be as easy as:
defmodule Custom.Compiler do
  def compile_and_reload(files) do
    Code.compiler_options(ignore_module_conflict: true)
    for template_with_path <- files do
      module_name = # create the module name any way you want

      {:ok, content} = File.read(template_with_path)

      module = """
      defmodule CustomWeb.#{module_name} do
          use Phoenix.LiveView

          def render(assigns) do
          ~H\"#{content}\"
          end

          def mount(_params, _p, socket) do
            temperature = 10
            {:ok, assign(socket, :temperature, temperature)}
          end
      end
      """
      Code.compile_string(module, template_with_path)
    end
  end
end
  1. Define a Mix.Task.Compile.Custom in your project:
defmodule Mix.Tasks.Compile.Custom do
  use Mix.Task
  @recursive true

  def run(args) do
    Code.ensure_compiled(Custom.Compiler)

    templates = Path.join("lib/**", "/*.*custom")
                |> Path.wildcard()
    Custom.Compiler.compile_and_reload(templates)
    :ok
  end
end
  1. Add it to mix.exs:
compilers: Mix.compilers() ++ [:custom],
  1. Add to your router.ex (given that your custom module name is CustomWeb.Live.Test):
scope "/", CustomWeb do
  live "/thermostat", CustomWeb.Live.Test
end
  1. Run iex -S mix phx.server and open http://localhost:4000/thermostat

What doesn’t work

Expanding the above to a file watcher that recompiles custom code with the custom compiler… doesn’t trigger Phoenix LiveView to reload the module

  1. Add a watcher to config/dev.exs:
  watchers: [
    ...
    svx: {Custom.Compiler, :watch, []}
  ]
  1. Add watch/0 to your compiler, and set up a file watch however you want (I’m using file_system
defmodule Custom.Compiler do
  require Logger
  use GenServer

  def watch() do
    # setup file watching in any way you need
  end

  ....
end
  1. ??? Now what ???
  • Simply calling Code.compile on a string with changed contents compiles… but doesn’t reload the module.
  • Calling :code.purge returns false and also does nothing
  • Phoenix.CodeReloader.Server.reload!? I can’t figure out what to pass to it
  • something else?

In any case so far when the file changes, and I recompile it, LiveView remains oblivious to the changes, and even reloading the pages doesn’t reload the module.

Hjälp! :slight_smile:

A solution is to add the mix task to the list of reloadable_compilers in config/config.exs:

config :svx, CustomWeb.Endpoint,
  ...
  reloadable_compilers: [:custom]

This, however, triggers too many recompilations: one from watcher, one from the task, so still looking for a better solution

In the end I left only the reloadable_compilers: [:custom] option and removed the watcher. This works.

The downside is that you don’t get a list of changed files in your compiler, and you have to figure it out yourself