Beginner - am I doing "reloadable" correctly?

After reading lots of blog posts and Github source code, I’ve started trying out Clojure. I didn’t get much further than a single HTTP server saying “Hello” before running into lots of little questions/issues.

Current level of experience:

  • My day job is Java/C#, have used F#
  • I’ve played around with Scala, ScalaJS and Elm, but haven’t made any complete applications in them
  • I have used and do like React. I can use but really don’t like Webpack and the other JS-based dev tools.

Project
I’ve been using Boot, since I can reload the project file with (load-file "build.boot") without leaving the REPL.
This works until I use (boot (watch) ...)- even though it says “Starting file watcher (Ctrl+C to quit)”, neither Ctrl+C or Ctrl+D do anything. Outside a task, the REPL does respond to both. (Ctrl+C: boot.user=>, Ctrl+D: “Bye”/quit)
I’m on Windows 10 - Powershell, Cmd, Git Bash and VSCode’s terminal all do the same thing.

  • How do I get back to the REPL from here properly?
  • I noticed multiple cached copies of the “fileset” in .boot/cache/tmp that aren’t being cleaned up - it’s already at 400MB for my first two test projects. Can I put these somewhere else or make sure it gets cleaned up? I don’t really want to be spending SSD space on that.
  • Should I be using Lein instead? Does it have a “reload the project file” command?

Frontend
I got boot-reload working. The incremental compile cycle is slightly over 1 second, even for a single line of reagent rendering “Hello” - it’s usable, but not significantly better than the JS hot-loading tools.
When I added a second entry point .cljs.edn for Devcards, the cycle time doubled - there’s an entire second copy of reagent, goog, etc in target/public/devcards.out.

  • Is there a way to share that output for both entry points or use CDN <script>s instead?
  • Why does CLJS need all the “document.write” script tags in the browser? ScalaJS (also using Closure compiler) seems to do well enough without it.

Backend
I want to have some of the same reload-on-save workflow for the backend. Most sources point to tools.namespace and component, with dire warnings about old versions of code and loading order otherwise. I found system as a Boot task for this.

  • Since everything is used through HTTP routes, the handler is always a “dependency” that gets unloaded. Does this mean I have to (reset) every time? I eventually want to keep some state per user, I don’t want to forget them and kill every websocket connection every time I save.
  • The system-websockets example has an undocumented :mode lisp in the system task that appears to disable unloading anything. Will this have awful side effects later?
  • I’m not using tools.namespace on the frontend. Will this have awful side effects later?
  • Am I overcomplicating this? Is there anything which works like Figwheel/boot-reload for the JVM i.e. (defonce state (atom {...}))?

First of all, congrats trying to learn some Clojure !

Without your code it’s hard to answer all questions, I’ll do my best.

Not sure I understand, but in our project we use 2 separate boot tasks, 1 for the frontend and 1 for the backend. Our backend task doesn’t include watch, it only starts a repl with the repl task.

Same for me. I think it’s safe to delete the cache folder though. Mine is 3GB !

Don’t know.

Looks like you’re doing 2 CLJS builds in the same task. Hard to say without seeing your code and build.boot file. However, know in our project we ~6k lines of CLJS+CLJC and our incremental is around 1 second too. So I guess something is wrong in your config.

No idea, good question.

If you change some backend then yes, you’ll have to (reset) every time (I suppose you’re talking about the reset function from the system library). Although for small changes you can directly reload from the Terminal REPL itself like this : (require 'my.ns :reload). It will reload def and defn but won’t handle all cases like macros and multimethods. This is why (reset) is preferable.

Reading briefly the Readme of system it seems there’s a way to automatically reload the app when source code changes.

Not sure I understand but there should be some way to tell system to keep some state when using reset. I’ve never used it, we use integrant and we were able to.

Don’t know about it.

And I think you’re right. The boot-reload should be more than enough.

As I said, the system library and regular use of (reset) should do the job. An experienced user of system could probably give you better answers.

Hope this helps !

And keep trying Clojure, it really is a nice language and opens up tons of possibilites.

Not understood the details, but discussing about the title, how to “reload” correctly?

For pure functions and states, reloading it as easy as replacing the functions since they contain no side-effects. States can be retained by using defonce since they are just data. In Clojure, function are more likely to be pure than others.

If the functions contains side-effects, then you have to consider whether or not reset the states during the last time functions ran. If reloading is prefect, it should appear like the program is started from scratch.

ClojureScript hot code swapping is simple. In JavaScript, there are a lot more side-effects, which leads to very complicated APIs for disposing side-effects. Check out https://webpack.js.org/api/hot-module-replacement/ if you are interested.

There are two common patterns here:

  1. You connect to your nREPL process from elsewhere (an editor or simply boot repl --client)
  2. You wrap the boot invocation in it’s own “background process”: (def p (future (boot (watch) ,,,))) — this allows you to continue using the REPL session. If you want to kill the background boot process you can just run (future-cancel p).

Boot uses this cache for it’s “blob” storage. I assume you have some large files in your project, which will be mirrored there so Boot can guarantee that it will not mess with your actual project files. You can change the location of this directory using the $BOOT_HOME env var as described in the Boot wiki.

That’s unusual for a small app as you already noted. Do you have a large number of files in any of the directories listed in :source-paths or :resource-paths?

When you create two builds using .cljs.edn they will not know anything about another and thus there will be some duplication. You could consider making only one build and eliding the devcards code for production. There are different ways to do this I described one way on my blog.

I’m afraid you will need to implement some kind of persistence for this scenario. It could be as simple as an atom that stores connections on stop and “injects” them back in on after start. I’m not fully familiar what the lisp mode means in that context but maybe @Daniel_Szmulewicz can chime in here.

Hope this helps & good luck :slight_smile:

Thanks, everyone - that points me in the right direction.

Here’s where I’ve got to so far: jc776/lets-do-clojure Client reloads on save, server does but occasionally needs manual (reset) to pick up changes. (system can reset automatically, still working out when I want it to)

  • You wrap the boot invocation in it’s own “background process”

I think that’s what I was looking for. repl --client works most of the time, but I wanted to redefine the task that was using watch without exiting the JVM. ((cljs)'s warm-up time)

That’s unusual for a small app as you already noted.
Do you have a large number of files
You can change the location of this directory using the $BOOT_HOME

Only a couple short source files and HTML files, but every fileset copy has 8MB+ of JS from CLJS, Reagent, Devcards and boot-reload.

Looks like you’re doing 2 CLJS builds

Makes sense. There’s a couple alternative compiler options to that, will try.

an atom that stores connections on stop and “injects” them back in on after start.

Pretty much, just figuring out how much ceremony component needs to hold on to it.

Hi,

It sounds like you’ve made progress and that you got yourself a reloadable setup. Congrats!

I’ll just answer the question about system's lisp mode for future reference.

When you modify source code, tools.namespace unloads the affected namespaces in order, then reloads them in order. In traditional Lisp environments, there is no concept of unloading definitions. You override them, and if you really want to “unload” a definition, you give it a nil value.
The system library leverages tools.namespace and uses it as is by default. In Lisp mode, however, the system library leverages tools.namespace so as to only reload namespaces when source code changes. It skips the “unloading” part. The result is an environment familiar to Lisp users (like in Emacs, or Geiser, or Slime).
The reason for Lisp mode is not only to restore familiarity, but also because it is more reliable, predictable and correct. The problem with unloading namespaces is that it is not supported by the runtime, creating leaks for Vars that are trapped in threads. They never get de-referenced.
This is described in the blog post Ghosts in the machine.
Lisp mode is my personal preference in reloading setups.

I hope this helps.

Daniel

2 Likes