On various crystal projects when I’m developing, I want something akin to live-reload. I recently found a solution for this which has really changed my workflows for the better. Maybe it’ll save you some time too.
I came across a tool called watchexec which is a general purpose file watcher and command runner. It’s invocation is trivial, and it installs with brew easily.
Then I can run script/worker to boot the worker and it’ll automatically recompile and run the process every time I save a file. When combined with save-on-focus-lost in my editor, this is a great feature.
I did the same thing with script/web which runs a kemal project, and then run both at the same time with Overmind and a Procfile.dev:
web: script/web
worker: script/worker
overmind s -f Procfile.dev runs both, and both will independently re-complie and re-run automatically.
I been using this for a while and it really is pretty great. I used to use nodemon - npm, but really like not having a dependency on node. One additional tip I’ll share is you can create a shell alias with it:
$ alias wcr
alias wcr='watchexec --signal=TERM --watch=src/ --emit-events-to=none --clear -- crystal $1'
Then this’ll allow you to do stuff like:
$ wcr run test.cr
$ wcr spec
To quickly use it without having to create a script file for it; which is especially nice for one-off test.cr files, or when working with specs. The --clear option is also really nice so you only ever see the output from the latest execution.
It just watches the files it gets listed on stdin, so whatever find (or ‘fd’ if you’re so inclined) can find.
But then I realized that 99% of the time I want to run something when I save a file in my editor, so I cut out the middleman and wrote a small package for Emacs. Has the added benefit of keeping the command output hidden until the command fails.
I looked at entr, but the caveat it comes with is that it only runs the find command once, so when I add a file to the tree it doesn’t know to watch it. watchexec doesn’t suffer from this, and I assume that’s because it’s using FSevent or something similar.
I use my fork of watcher (crystal) to do this (e.g. recompile server / restart it). I also play a sound after the compilation succeeded (or an error sound when it failed):
Watcher.watch(PATTERN) { |files, _|
WatcherHelpers.print_changes("APP/SRC", ROOT / "src", files)
r = build_cli
if r.success?
Sounds.up
else
Sounds.error
end
# ...
}
# ...
module Sounds
def self.up
Process.new("aplay", ["./sounds/up.wav"])
end
def self.error
Process.new("aplay", ["./sounds/error.wav"])
end
end
Nice to see more people using watchexec and Overmind! This is precisely the approach I took on my projects (I don’t have Crystal installed locally, only inside a container):
(note that the example predates the shards run command as that was added in 2022
Crystal has sentry serve as this, although it not live update any more. i create my own fork, fix bugs, and add sounds support(play different wav file when build sucess/failed), i create PR since Aug 8, 2022, but never get response from maintainer, so i am planning move my own fork to crystal-china org and do a big refactor then living maintain it.
it work well on linux, but never test it on macOS, maybe you can try and give some feedback? e.g. can i know wheather cat success.wav | afplay correct play sound on macOS ?
I did use Sentry in the past for this. Ultimately the problem I ran into was that I didn’t actually want to use a tool that needed to be built or compiled or have a runtime (eg node’s nodemon). Some node projects are now shipping packaged binaries (eg tailwind) which would have been fine.
I would love if Crystal had better support for this, though I confess I haven’t actually looked into it in some time. If sentry was both still maintained and published binaries which I could easily install, it would be worth considering again… until then, I just need the simplest thing that works.
I didn’t know why original README of sentry why so complex, in fact, just git pull my fork, and crystal build --release, then copy sentry into PATH, e.g. /usr/local/bin, then entry the project root, run sentry is just work.
I will add git action for build pre-build binary for each tag.