Elixir 1.18's new “auto_reload” option in IEx

Learn Phoenix LiveView is the comprehensive tutorial that teaches you everything you need to build a complex, realistic, fully-featured web app with Phoenix LiveView. Click here to learn more!

Christmas has come early - Elixir 1.18 has been released! Combined with the recent announcement that Phoenix LiveView has reached its long-awaited v1.0.0, it’s a good time to be an Elixir developer.

Elixir 1.18 contains some major enhancements, which you can read about in the changelog, but here I’ll focus on one minor new feature: IEx’s new :auto_reload config option.

In short: IEx (Elixir’s REPL console) can now automatically reload your code changes without you needing to call recompile(). It’s a nice improvement, but there are some subtleties to be aware of, so let’s go over them in detail.

recompile/0 (the old way)

Let’s use mix new to generate a new Mix project called “example”:

$ mix new example
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating lib
* creating lib/example.ex
* creating test
* creating test/test_helper.exs
* creating test/example_test.exs

Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:

    cd example
    mix test

Run "mix help" for more commands.
$ cd example

The generated project contains a module with one function, Example.hello/0. Fire up a console with iex -S mix and call it:

iex> Example.hello()
:world

Now edit the function so it returns Portuguese instead of English:

 # lib/example.ex
 defmodule Example do
   
   def hello do
-    :world
+    :mundo
   end
 end

This change doesn’t take effect in IEx automatically:

iex> Example.hello()
:world

To use the new code, we must manually recompile using recompile/0:

iex> recompile()
Compiling 1 file (.ex)
Generated example app
:ok
iex> Example.hello()
:mundo

Prior to Elixir 1.18, recompile/0 was the only way to make our updated code available in IEx without closing the console and opening a new one. But now there’s a new way.

:auto_reload (the new way)

Elixir 1.18 introduces a new configuration option for IEx, :auto_reload. By default it’s false, as you can see by calling IEx.configuration/0:

iex> IEx.configuration()
[
  
  auto_reload: false
]

To set it to true, use IEx.configure/1:

iex> IEx.configure(auto_reload: true)
:ok
iex> IEx.configuration()[:auto_reload]
true

Better yet, put the configuration code in a file called .iex.exs in the project root:

 # .iex.exs
+IEx.configure(auto_reload: true)

Now the code will be run automatically when you open an IEx console, so you don’t have to call it manually every time.

Let’s update our “hello, world” function again, this time changing the output from Portuguese to French:

 # lib/example.ex
 defmodule Example do
   
   def hello do
-    :mundo
+    :monde
   end
 end

With the new configuration, you might expect IEx to automatically use the new function. But it hasn’t happened yet. 🤔

iex> Example.hello()
:mundo

Here’s the thing: IEx now automatically reloads, but it doesn’t automatically recompile. We still need to recompile the Example module somehow, for example by calling mix compile in a separate terminal window to the one running IEx:

$ mix compile
Compiling 1 file (.ex)
Generated example app

Now, with :auto_reload set to true, IEx detects that the compiled Example module[1] has changed, and quietly loads the new version:

iex> Example.hello()
:monde

By itself this doesn’t make much difference - typing mix compile is hardly more convenient than typing recompile(). It becomes useful when another process is watching for changes and automatically recompiling.

Your IDE might do this. If not, one option is to use entr.

We’ll be using git ls-files to make this work, so first we need to initialize a git repo:

$ git init
$ git add .
$ git commit -m "initial commit"

You can install entr on a Mac using brew install entr. Then to automatically recompile files when they change, run:

$ git ls-files | entr mix compile

Let’s change the function one last time, to German:

 # lib/example.ex
 defmodule Example do
   
   def hello do
-    :monde
+    :Welt
   end
 end

Back in the terminal that’s running entr, you’ll see the recompilation happened automatically:

Compiling 1 file (.ex)
Generated example app

And IEx picked up the changes:

iex> Example.hello()
:Welt

The new :auto_reload option is part of a set of changes to support Elixir’s new Official Language Server project. The changelog explains it well, so I won’t repeat it: read about it here.

It’s still early days but I’m looking forward to using this new feature and everything else in Elixir 1.18.

Merry Christmas 🎄


Edit: José Valim points out another improvement in 1.18 that I wasn’t aware of:

[P]reviously, if you did this:

  • Started iex -S mix
  • Changed a file
  • Called mix compile
  • Called recompile() in IEx

recompile() would not pick up the new module at all, because it had already been compiled. Elixir v1.18 makes it so recompile() works consistently AND adds the auto_reload option. Overall, it is all about playing better with multiple OS processes.

Want more posts like this in your inbox?

No spam. Unsubscribe any time.

Thanks to members of Elixir Forum, including José Valim himself, and also to Benjamin Milde (@lostkobrakai) and Thomas Depierre (@di4na) on the Elixir Slack, who replied to my questions about :auto_reload and helped clarify up my understanding of this new feature.

Image credit: Jay Heike on Unsplash.