Embedding external resources in Elixir

31 Mar 2025

The Elixir ErrorTracker implements a web dashboard from which users can see and manage reported errors. Although this web dashboard is built using Phoenix LiveView we still need additional CSS and JS files.

If the ErrorTracker were an application itself this would be easier, but the ErrorTracker lives inside your own Phoenix web application. This means that serving those static assets becomes a little bit more complicated. Your Phoenix application serves the different paths and files, but it doesn’t know about the ErrorTracker assets.

To avoid this problem the ErrorTracker embeds its own assets. To do this we read the CSS and JS files during compilation and store the result in a couple of functions that return their contents.

While this works well. In some cases we would make changes to the CSS or JS files, restarted the dev server and noticed how those changes did not exist on the browser…

It took some time to understand what was going on. The problem is that we are reading the files content during compilation, and compilation only happened when we changed the Elixir module containing this code. This means that when we changed our static assets but not the Elixir module code, it was not recompiled and still contained the old files.

Fortunately Elixir provides the @external_resource attribute, which according to the docs:

Specifies an external resource for the current module.

Sometimes a module embeds information from an external file. This attribute allows the module to annotate which external resources have been used.

Tools may use this information to ensure the module is recompiled in case any of the external resources change

In this commit you can see how we used this attribute in the ErrorTracker. After this change modifying the CSS or JS file results in a recompilation of the Elixir module, which fixed our problem.

@css_path Application.app_dir(:error_tracker, ["priv", "static", "app.css"])
@js_path Application.app_dir(:error_tracker, ["priv", "static", "app.js"])

# This tells the Elixir compiler to track those external resources and recompile
# this module when they change.
@external_resource @css_path
@external_resource @js_path

@css File.read!(@css_path)
@js File.read!(@js_path)

If you want to know more about this I suggest you take a look at the relevant docs and the articles that Jordan Elver and Eric Oestrich wrote on this same topic.