Hot reloading a Halogen app with Parcel requires almost no configuration. Demo
I’m doing an archtitecural spike with Halogen and Firebase (if you want to know why, see Background near the end, I won’t bore you with the details otherwise).
I want hot-reloading, as it is sometimes useful when working on a UI, but I was dreading to use webpack again. Webpack is fine when you are using someone else’s template, but a year or two in, upgrading a webpack configuration can easily take me a couple of days. Justin Woo explained how he uses parcel to build a web audio player.
It turns out hot-reloading with Parcel works out of the box with the halogen template application, only it will display the UI again and again after each reload.
In case you want to follow along, check out this fork of purescript-halogen-template.
It has an .nvmrc
, because Parcel requires node 8.
1
2
3
4
nvm use
npm install
psc-package install
npm run build
We need to build the app once, and after that tell parcel to watch the output
directory and reload on changes.
1
2
3
4
5
6
7
npm run parcel:watch
> @ parcel:watch /home/willem/dev/spikes/purescript-halogen-parcel
> parcel index-watch.html
Server running at http://localhost:1234
✨ Built in 2.09s.
As the prompt suggests, this will run a server on http://localhost:1234 .
Any rebuilt file ends up in output
and parcel will reload the file for you. I let psc-ide
rebuild the source when I edit with spacemacs. You could run psc-ide-server
from the command line instead, or a bit easier, put pulp -w watch
in package.json.
The script for npm run parcel:watch
in package.json
is brief:
1
parcel index.html
Just like webpack hot-reloading (included in the template as well), it requires an entry.js
file to figure out where the main is. On top of that, it needs an html file of some kind. I gave it a separate name to let it stand out from the one in /dist
.
There is one gotcha though. Every time you rebuild, you’ll get a copy of the UI below the previouw one. Unless you remove the old one.
It appears I had got it working by accident, and when cleaning it up for this blog post, by starting from scratch, things fell apart. Justin Woo helped me out, it appears that besides hooking in to module.hot
to detect hot-loading, we also need two different entrypoints in Main
. One for the first time the app loads, and one for each time it reloads
So Main.purs
now has a main
and a rerunUI
, with some console logging so we can see which part is executing. Here is the final entry.js:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if(module.hot) {
module.hot.accept(function () {
console.log('hot module accepted');
Array.from(document.querySelectorAll('body > div')).map(x => x.remove());
console.log('deleted old entries');
require("./output/Main").rerunUI(document.body)();
});
require("./output/Main").main();
console.log('started in hot reloading mode');
} else {
require("./output/Main").main();
console.log('started in normal mode');
}
The way this works is that you do get a fresh state on each reload. After some discussion and reflection on #purescript on Slack, I realized that is not necessarily a bad thing. @monoidmusician
suggested the state implementation might change, and how would you know. The other thing one can do with a setup like this, make a main
per component, so there is not that much state to load anyway.
I’ve got an experimental setup where authentication happens before loading Halogen (maybe more on that later), so I stay logged in, even when Halogen reloads.
There is a way to keep the state, but I’m nog sure it’s worth it, given the above. This setup allows for some very rapid prototyping (parcel rebuilds in < 200 milliseconds on my machine).
Conclusion
Recreating the setup for this blog post was a bit more work than I hoped, given that I got lucky the first time around. I do think I understand hot reloading and Halogen’s main a bit better now. I hope you do too.