# Auto-recompiling a crystal project as I work

**URL:** <https://forum.crystal-lang.org/t/auto-recompiling-a-crystal-project-as-i-work/6617>\
**Category:** Learning Resources\
**Created:** [February 19, 2024, 7:07pm UTC](https://forum.crystal-lang.org/t/auto-recompiling-a-crystal-project-as-i-work/6617 "2024-02-19T19:07:05Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![rob](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/rob/32/1409_2.png) [@rob](https://forum.crystal-lang.org/u/rob)\
**Post date:** [February 19, 2024, 7:07pm UTC](https://forum.crystal-lang.org/t/auto-recompiling-a-crystal-project-as-i-work/6617/1 "2024-02-19T19:07:05Z")

</div>

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](https://watchexec.github.io/) which is a general purpose file watcher and command runner. It’s invocation is trivial, and it installs with `brew` easily.

I added this to my project/script/worker script:

```bash
#!/usr/bin/env bash

# brew install watchexec
watchexec \
  --filter '*.cr' \
  -r shards run worker

```

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](https://github.com/DarthSim/overmind) and a Procfile.dev:

```auto
web: script/web
worker: script/worker

```

`overmind s -f Procfile.dev` runs both, and both will independently re-complie and re-run automatically.

Hope this helps someone!

---

<div class="post-metadata">

**Author:** ![Blacksmoke16](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/blacksmoke16/32/1241_2.png) [@Blacksmoke16](https://forum.crystal-lang.org/u/Blacksmoke16)\
**Post date:** [February 19, 2024, 7:17pm UTC](https://forum.crystal-lang.org/t/auto-recompiling-a-crystal-project-as-i-work/6617/2 "2024-02-19T19:17:21Z")

</div>

I been using this for a while and it really is pretty great. I used to use [nodemon - npm](https://www.npmjs.com/package/nodemon), but really like not having a dependency on node. One additional tip I’ll share is you can create a shell alias with it:

```sh
$ 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:

```sh
$ 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.

---

<div class="post-metadata">

**Author:** ![Xen](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/xen/32/2288_2.png) [@Xen](https://forum.crystal-lang.org/u/Xen)\
**Post date:** [February 19, 2024, 7:26pm UTC](https://forum.crystal-lang.org/t/auto-recompiling-a-crystal-project-as-i-work/6617/3 "2024-02-19T19:26:13Z")

</div>

I’ve been using [entr](https://github.com/eradman/entr) for some time:

`find . -iname '*.cr' | entr crystal spec -v`

`find . -iname '*.cr' -o -iname '*.ecr' | entr -r crystal run src/server.cr`

It just watches the files it gets listed on stdin, so whatever `find` (or [‘fd’](https://github.com/sharkdp/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](https://github.com/xendk/custode.el) for Emacs. Has the added benefit of keeping the command output hidden until the command fails.

---

<div class="post-metadata">

**Author:** ![rob](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/rob/32/1409_2.png) [@rob](https://forum.crystal-lang.org/u/rob)\
**Post date:** [February 19, 2024, 7:30pm UTC](https://forum.crystal-lang.org/t/auto-recompiling-a-crystal-project-as-i-work/6617/4 "2024-02-19T19:30:24Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![Ragmaanir](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/ragmaanir/32/1174_2.png) [@Ragmaanir](https://forum.crystal-lang.org/u/Ragmaanir)\
**Post date:** [February 19, 2024, 11:40pm UTC](https://forum.crystal-lang.org/t/auto-recompiling-a-crystal-project-as-i-work/6617/5 "2024-02-19T23:40:57Z")

</div>

I use [my fork](https://github.com/Ragmaanir/watcher) of [watcher (crystal)](https://github.com/faustinoaq/watcher) 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):

```auto
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

```

---

<div class="post-metadata">

**Author:** ![aravindavk](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/aravindavk/32/1020_2.png) [@aravindavk](https://forum.crystal-lang.org/u/aravindavk)\
**Post date:** [February 20, 2024, 8:08am UTC](https://forum.crystal-lang.org/t/auto-recompiling-a-crystal-project-as-i-work/6617/6 "2024-02-20T08:08:37Z")

</div>

I am using [GitHub - f/guardian: Guardian watches over your files and run assigned tasks.](https://github.com/f/guardian)

---

<div class="post-metadata">

**Author:** ![luislavena](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/luislavena/32/572_2.png) [@luislavena](https://forum.crystal-lang.org/u/luislavena)\
**Post date:** [February 20, 2024, 3:28pm UTC](https://forum.crystal-lang.org/t/auto-recompiling-a-crystal-project-as-i-work/6617/7 "2024-02-20T15:28:05Z")

</div>

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):

🥁

> **[GitHub - luislavena/hydrofoil-crystal: Opinionated, Alpine-based development...](https://github.com/luislavena/hydrofoil-crystal)**
>
> Opinionated, Alpine-based development container for Crystal - luislavena/hydrofoil-crystal

> <https://github.com/luislavena/hydrofoil-crystal/blob/main/examples/advanced/Procfile.dev>

(note that the example predates the `shards run` command as that was added in 2022 😉

---

<div class="post-metadata">

**Author:** ![jgaskins](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/jgaskins/32/2449_2.png) [@jgaskins](https://forum.crystal-lang.org/u/jgaskins)\
**Post date:** [February 20, 2024, 4:48pm UTC](https://forum.crystal-lang.org/t/auto-recompiling-a-crystal-project-as-i-work/6617/8 "2024-02-20T16:48:10Z")

</div>

> [@luislavena](#):
>
> the `shards run` command as that was added in 2022

TIL `shards run` exists!

---

<div class="post-metadata">

**Author:** ![zw963](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/zw963/32/1623_2.png) [@zw963](https://forum.crystal-lang.org/u/zw963)\
**Post date:** [February 20, 2024, 5:06pm UTC](https://forum.crystal-lang.org/t/auto-recompiling-a-crystal-project-as-i-work/6617/9 "2024-02-20T17:06:04Z")

</div>

Crystal has [sentry](https://github.com/samueleaton/sentry) serve as this, although it not live update any more. i create my own [fork](https://github.com/zw963/sentry), fix bugs, and add sounds support(play different wav file when build sucess/failed), i create [PR](https://github.com/samueleaton/sentry/pull/64) 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 ?

---

<div class="post-metadata">

**Author:** ![rob](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/rob/32/1409_2.png) [@rob](https://forum.crystal-lang.org/u/rob)\
**Post date:** [February 20, 2024, 5:57pm UTC](https://forum.crystal-lang.org/t/auto-recompiling-a-crystal-project-as-i-work/6617/10 "2024-02-20T17:57:07Z")

</div>

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](https://github.com/tailwindlabs/tailwindcss/releases)) 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.

---

<div class="post-metadata">

**Author:** ![zw963](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/zw963/32/1623_2.png) [@zw963](https://forum.crystal-lang.org/u/zw963)\
**Post date:** [February 20, 2024, 6:18pm UTC](https://forum.crystal-lang.org/t/auto-recompiling-a-crystal-project-as-i-work/6617/11 "2024-02-20T18:18:38Z")

</div>

> [@rob](#):
>
> I didn’t actually want to use a tool that needed to be built or compiled or have a runtime (eg node’s nodemon)

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.
