Phoenix’s live reload functionality instantly refreshes your browser when you update a file in your code editor. The experience is beautiful in its simplicity and the Phoenix code that does it is remarkably lightweight as well.

Let’s walk through the elegant magic of LiveReload.

How is the required code injected into the HTML?

When you run mix phoenix.new to generate a Phoenix app, you’ll see the following in web/endpoint.ex:

if code_reloading? do

socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket

plug Phoenix.LiveReloader

...

end

Phoenix.LiveReloader is responsible for injecting the required HTML bits that trigger live reloading. This is a module plug: it transforms the connection by implementing call/2 .

Phoenix.LiveReloader actually declares two call/2 functions:

There’s a generic function definition (source):

def call(conn, _) do

This injects an iframe into the HTML that will load “/phoenix/live_reload_iframe”.

2. The other call/2 function definition loads the iframe content by pattern matching against the request path (source):

def call(%Plug.Conn{path_info: ["phoenix", "live_reload", "frame"]} = conn , _) do

This returns HTML that injects the client-side Javascript to connect to the Phoenix.LiveReloader.Socket .

How are changes to files observed?

The fs Erlang package is used to listen to changes to files. When the javascript in the iframe joins the channel, LiveReloader begins subscribing to file system changes (source).

Which files are observed?

By default, your static assets (javascript, css, etc) and web views and templates.

Have you deployed a Phoenix app? Try Scout, a new Elixir app monitoring solution I helped develop. Signup is free.

How does LiveReload listen for changes?

After :fs.subscribe/0 is called, the channel process listens for fs message events by defining handle_info/ (source):

def handle_info({_pid, {:fs, :file_event}, {path, _event}}, socket) do

This receives all file events on the filesystem. Since we only care about a subset, matches_any_pattern?/2 checks to see if the file is within our Phoenix app.

Then, we send a message via the channel notifying of the change:

push socket, "assets_change", %{asset_type: asset_type}

How does the Javascript code refresh the page?

The JS listens for the assets_change event and updates the window in one of two ways:

When a css file is changed, a full reload isn’t required. Instead, a new link element is inserted in the page that loads the updated stylesheet. When a file of any other type is changed, a full window reload is triggered.

Summary

LiveReload weaves an interesting path through your app: inserting a plug to inject content into the HTML, rendering an iframe, inserting Javascript into the iframe and subscribing to a channel, listening for filesystem events, sending messages via the channel on relevant events, and finally, reloading your browser window.

View the source on GitHub.