roadtolarissa
Adam Pearce github twitter email rss

Hackable Hot Reloading

Ever since seeing Bret Victor rewire a platformer live on stage, I’ve wanted to write code more interactively.


I dabbled with programming languages that facilated this, like clojure’s REPL and R notebooks. But most of my work is with javascript and I was stuck pressing ⌘+S ⌘+Tab ⌘+R over and over again to save my changes, tab over to the browser and reload the page.

This gets pretty tedious.

Live reload offers an improvement – instead of manually clicking the reload button after you’ve made a change, the computer does it for you. With the right libraries, this isn’t too difficult to get up and running.

First, set up a server that watches for file changes and pings a websocket when they happen:

var wss = new SocketServer({server})
chokidar
  .watch('*.js')
  .on('change', path => {
    wss.clients.forEach(d => d.send('reload'))
  })

Then, add a bit of javascript to your page that connects to the websocket and reloads the page when it receives a ping:

<script>
new WebSocket(location.origin.replace(/^http/, 'ws'))
  .onmessage = () => location.reload()
</script>

Now you can make tweaks without risking a RSI flare up.

Still, clearing the whole page to reload isn’t ideal.

The flash of white is particularly harmful when the output of your code is visual. This isn’t just an aesthetic consideration. Our eyes have evolved to notice small changes—just what you want when trying to decide between 10 or 15px of padding—but after a hard reload everything looks like it changed.

Even with automatic reloading you can’t instantly sense how your tweak affected the output; you have to pay close attention to what you’re changing and intentionally remember what it looked like before.

Toggling between images makes it easy to spot the difference. Inserting a blank frame prevents changes from being picked up preattentively.

Instead of reloading the whole page, we can use the server to pass the changed file through websocket:

  .on('change', path => {
    var str = fs.readFileSync(path, 'utf8')
    wss.clients.forEach(d => d.send(str))
  })

Now the browser only needs to eval your program; it doesn’t need to rebuild the entire page or reparse all of the libraries you’re using:

new WebSocket(location.origin.replace(/^http/, 'ws'))
  .onmessage = str => eval(str)

Much better! If you want to try this out on your project, I’ve packaged the code here in hot-server. It serves a directory statically, like python -m http.server. Each html files gets a small <script> tag appended which listens for file changes over a websocket, injecting updated javascript and css onto the page.

</script>

Bret’s talk suggested a ton of other ideas like sliders to control numbers and directly manipulating elements. I’ve played around with some of them, but they’re trickier to generalize.

Other people are figuring out how to do that. Eve prototyped a dozen different approaches. Observable bolts a reactive runtime onto javascript. Figwheel hot loads ClojureScript. And webpack does more than bundle:

Hot Module Reloading in webpack parses your dependency tree and figures out how to run your new code.

This is great if you’re building a whole application. Most of my work is simpler than that though, so I’ve been sticking with my config-free, no build step setup.

hot-sever feels magical, but there’s just a couple of dozen lines of code powering it. Short enough for me to do things like jimmy it into a slow requirejs build step at work or repurpose in a few hours for a multiplayer musical game with live updating rules at a music hackathon.

It took a few years to find and figure out how to put them together, but with file watching, websockets and eval, you don’t need a complex edifice of code to start experimenting with radically altering your workflow.