adrianhesketh.com

Templ - hot reload with Air

My colleague Joe [0] introduced me to air [1] this week. He’d been using it while building a web app with the templ [2] templating language to rebuild templates, and restart his Go web server.

I’d never seen it before, but it was just what I was looking for.

Once you’ve installed it, you can run air init in your project’s root directory to create a .air.toml file containing configuration.

If you have your main package and function in the root of your project, it will build and run your project straight away, but the default configuration isn’t configured to watch for changes to *.templ files or run templ generate. It’s also important to ignore .*_templ.go files or it will end up in an infinite loop of changes.

To make these changes, change the cmd field to add the templ generate command.

cmd = "templ generate && go build -o ./tmp/main ."

Then, update the exclude_regex field to ignore .*_templ.go files:

exclude_regex = [".*_templ.go"]

Finally, update the include_ext field to include the templ extension:

include_ext = ["go", "tpl", "tmpl", "templ", "html"]

Now you’re free to run air at the command line. When you make changes the build will be executed and your web server can start up again.

Putting it together

In total, the configuration is as below:

root = "."
tmp_dir = "tmp"

[build]
  bin = "./tmp/main"
  cmd = "templ generate && go build -o ./tmp/main ."
  delay = 1000
  exclude_dir = ["assets", "tmp", "vendor"]
  exclude_file = []
  exclude_regex = [".*_templ.go"]
  exclude_unchanged = false
  follow_symlink = false
  full_bin = ""
  include_dir = []
  include_ext = ["go", "tpl", "tmpl", "templ", "html"]
  kill_delay = "0s"
  log = "build-errors.log"
  send_interrupt = false
  stop_on_error = true

[color]
  app = ""
  build = "yellow"
  main = "magenta"
  runner = "green"
  watcher = "cyan"

[log]
  time = false

[misc]
  clean_on_exit = false

MacOS tip

If you’re on a Mac, when you run a Go web server, it might pop up a warning:

Do you want the application “main” to accept incoming network connections?

That makes hot reload impossibly annoying, but it can be worked around.

If you’ve got http.ListenAndServe(":8000"), update it to http.ListenAndServe("localhost:8000") when you’re running it on your local machine and MacOS won’t put up the warning.

Just be aware that you’ll need to set it back if you want the server to accept incoming requests from outside your local computer.

Nix users

There’s no package for air at the moment in the Nix packages repo, but it’s relatively easy to add Go packages. Add this to air.nix and you can import it from your home manager or nix-darwin setup with air = pkgs.callPackage ./air.nix {};

{ lib, buildGoModule, fetchFromGitHub }:

buildGoModule rec {
  pname = "air";
  version = "1.27.3";

  src = fetchFromGitHub {
    owner = "cosmtrek";
    repo = "air";
    rev = "v${version}";
    # Calculated by downloading the code and running nix-hash --type sha256 .
    sha256 = "04xdgimbkg7kkngpfkxm7v0i3fbv3xfzvc96lafs05pn58zxrva0";
  };

  vendorSha256 = "1gnlx9rzp6vzjl7vivianhkr1c615iwvng2gfpsp6nz2b1821c07";

  meta = with lib; {
    description = "Yet another live-reloading command line utility for Go applications in development";
    homepage = https://github.com/cosmtrek/air;
    license = licenses.mit;
    maintainers = with maintainers; [ cosmtrek ];
    platforms = platforms.linux ++ platforms.darwin;
  };