Easily Reloading your Clojure Project in Spacemacs

I’ve long been a fan of Stuart Sierra’s reloaded workflow. When I’m working on a ClojureScript project that doesn’t use Figwheel or another tool to assist with live code reloading, this is the pattern I reach for to help manage iterative and interactive development.

The Clojure layer for Spacemacs (via CIDER) has good support managing REPL sessions, but it doesn’t know anything about how to properly restart my project out of the box.

Here’s how I fixed that.

Intro to the Workflow

After opening my project in Spacemacs and launching an nREPL session (via the shortcut for cider-jack-in[, ']), I can open the REPL buffer using [, s s]. Then, due to the way I’ve configured my project, I’m automatically placed into a specific namespace with functions for starting my server, shutting it down, and reloading it.

It’s easy enough to open the REPL buffer and call these functions manually. My first approach was to create some keyboard shortcuts that would programatically open the REPL buffer, enter the text to call these functions, and then hide the REPL buffer again.

I ended up using another technique altogether. While I was researching the option of adding a custom Spacemacs menu option, I found that Spacemacs provides a function called spacemacs/set-leader-keys-for-major-mode. If you use this function, you can add your own option that appears in the major mode menu, [SPC m]. E.g.,:

(spacemacs/set-leader-keys-for-major-mode 'clojure-mode "x" 'my-emacs-lisp-fn)

This would invoke your my-emacs-lisp-fn whenever you press SPC m x.

A Better Solution

After some researching, I found that CIDER’s already got some integration with the clojure.tools.namespace library. Specifically, it uses the library when you use the command [SPC m s x], or cider-refresh to reload all the relevant namespaces.

That’s perfect, except it can’t automatically assist me with tearing down the state of my running app and then restarting it afterwards.

Fortunately, after poring through the docs for CIDER, I found that you can set some (global) variables to control this behavior.

  • cider-refresh-before-fn: a string naming the Clojure function to be called before tearing down your app
  • cider-refresh-after-fn: a string naming the Clojure function to be called after your code has been freshly loaded

As mentioned, these are variables global to the Emacs process. This does present a slight problem if you intend to work on multiple Clojure projects within Spacemacs.

Fortunately, with Spacemacs, you already have the Projectile plugin loaded. Projectile comes with some built-in support for defining project-specific configuration that you can check in to your Git repository. There’s even a built-in editing mode for it. To try it out, use [SPC p e] while you are editing a file in your project.

For this project, my .dirs.locals.el looks like this:

((nil . ((cider-refresh-before-fn . "my.ns.repl/stop")
(cider-refresh-after-fn . "my.ns.repl/start")
)))

Conclusion

With this in place, I can tear down and reload my entire app while it’s running in a REPL embedded in Spacemacs, with just this keystoke: [, s x].

All the relevant configuration is stored checked into my Git repository, so it’ll work out of the box for anyone else editing this project in Spacemacs, as well.

Conversation
  • Alex says:

    this command is called projectile-edit-dir-locals and can be also bound to [C-c p E] if you don’t use evil mode

    • dpsutton says:

      and if you ever want to prevent the before and after refresh functions from firing, call `cider-refresh` with a negative prefix. From the docs for this function:

      > With a negative prefix argument, or if MODE is `inhibit-fns’, prevent any
      > refresh functions (defined in `cider-refresh-before-fn’ and
      > `cider-refresh-after-fn’) from being invoked.

  • dpsutton says:

    also, a hidden gem of cider, pressing comma `,` in the repl brings up a really nice menu, including refresh, reload, restart, and load repl utility functions like doc, source, etc.

  • Comments are closed.