Skip to content

Commit

Permalink
Reload a webpage when a file changes
Browse files Browse the repository at this point in the history
  • Loading branch information
jakelazaroff committed Sep 19, 2024
1 parent 5c8db9c commit 6727d68
Showing 1 changed file with 76 additions and 0 deletions.
76 changes: 76 additions & 0 deletions entr/reload-a-webpage-when-a-file-changes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Reload a webpage when a file changes

I store my blog in a git repo of plain Markdown files. It's a pretty spartan setup, which I like — it forces me to keep all my writing extremely portable between website generators.

To preview the files in my browser, I [render the Markdown with Caddy](/caddy/serve-markdown-files-as-html/). I wanted to add live reloading when I made a change, which was tricky because Caddy is just acting as a plain file server here. With the help of ChatGPT, I was able to come up with a solution involving a bunch of shell scripts.

The general workflow:

1. Create a temporary file using [`mktemp`](https://www.gnu.org/software/autogen/mktemp.html).
2. Use the network utility [`socat`](http://www.dest-unreach.org/socat/) to listen to network requests, setting up a server-sent events connection and sending any lines appended to the file.
3. Use [`entr`](http://eradman.com/entrproject/) to append events to the temporary file whenever a watched file changes.
4. On the webpage, listen for server-sent events and reload the page when one comes through.
5. Use [`trap`](https://tldp.org/LDP/Bash-Beginners-Guide/html/sect_12_02.html) to clean up the temporary file when the process exits.

Let's go through the steps one by one.

First, we run `mktemp` to make a temporary file, saving the path in a variable called `$TMPFILE`:

```sh
TMPFILE=$(mktemp)
```

Next, we set up step 5 using `trap` to remove `$TMPFILE` when the process exits:

```sh
trap 'rm -f $TMPFILE; exit' INT TERM EXIT
```

Now we're getting to the meat of things. We use `socat` to listen for requests on port 8081. When a client connects, we initially responds with `HTTP/1.1 200 OK` and a bunch of headers, then keep the connection open and tail any lines appended to `$TMPFILE`.

```sh
socat TCP-LISTEN:8081,fork,reuseaddr SYSTEM:' \
echo HTTP/1.1 200 OK; \
echo Content-Type\: text/event-stream; \
echo Cache-Control\: no-cache; \
echo Connection\: keep-alive; \
echo; \
tail -n0 -F $TMPFILE'
```

The `-n0` flag to `tail` makes it start at the _end_ of `$TMPFILE`, so the client only receives events sent after opening the connection.

While that's running, we run `entr`, appending `reload` events to `$TMPFILE` whenever a watched file changes. In this case, it's watching files that are tracked by git.

```sh
git ls-files | entr -s 'echo "event: reload\ndata:\n" >> $TMPFILE'
```

One last important thing: these all need to run concurrently. You can do that by chaining them together using `&`.

ChatGPT tells me that I need to run [`wait`](https://linux.die.net/man/2/wait) at the end of the `&` chain to ensure that `socat` and `entr` don't quit prematurely, but in my testing that doesn't seem to matter.

Here's the full shell script:

```sh
# Create a temporary file
TMPFILE=$(mktemp)

# Set the trap to remove the temporary file on INT, TERM and EXIT
trap 'rm -f $TMPFILE; exit' INT TERM EXIT

# Start the server-side events server and tail lines from the temporary file
socat -v -v TCP-LISTEN:8081,fork,reuseaddr SYSTEM:'echo HTTP/1.1 200 OK; echo Content-Type\: text/event-stream; echo Cache-Control\: no-cache; echo Connection\: keep-alive; echo; tail -n0 -F '"$TMPFILE"'' &

# Append lines to the temporary file whenever a file tracked by git changes
git ls-files | entr -s 'echo "event: reload\ndata:\n" >> '"$TMPFILE"'' &

# Wait for background processes to finish
wait
```

On the webpage side, we can connect to the server, listen for `reload` events and reload the page when we receive one:

```js
new EventSource("http://localhost:8081").addEventListener("reload", () => location.reload());
```

0 comments on commit 6727d68

Please sign in to comment.