Javascript HMR considered harmful, change my mind

Jan Friedensreich
HackerNoon.com
Published in
5 min readJun 9, 2020

Whenever i start a new web-app from a template or use some new development tool, one of the first things I do is disable or remove everything connected to “Hot Module Replacement”. I never wrote down every reason in one place before, but now that I am preparing to release a tool of my own, I need to document the decision appropriately. At the same time this article is a request to challenge my points in case i did not think of all use cases or reasons for HMR, so please do comment!

It’s weird that I hate js HMR so much because I was an Erlang developer before and its “hot code reloading” is something every Erlang developer likes to mention in discussions. However it is a feature for high reliability production applications that have internal state. It is not a developer experience tool or something you would use unless you really have to, it is a tool in your tool belt that makes something possible that is very hard or impossible otherwise.

This brings me to my first point: because js HMR is for developer experience, not for production use, it means having quite different runtime and loading behaviour of production and development environments. After days of debugging-sessions that had to go to live environments eventually, everything that makes the environments different makes me uncomfortable. It does not even necessarily mean that bugs slip from dev to prod environments because test/staging are usually setup as close to production as possible, but it is a level of disconnect to the production software that just makes me feel uncertain how exactly the loading of the complete application feels, especially uncached reload and initial application launch.

The second point is loading performance. One reason people use HMR is the removal of reload waiting times, however i question you should force end users to use an application that loads too slow on localhost to refresh the page on every code change. It sounds like an absurd preference of developer experience over end-user experience, because it will not force developers to fix bloat and load timing. The counter argument of minification, bundling and other optimisations only done in production do not really hold true. The effect of network outweighs other effects like parsing and even if you have a bundle size of 800KB the request will be possible in 20ms on localhost and probably take 50ms+ on the fully minified and properly bundled/tree-shaken production version. And if it is just the physical activity of hitting refresh in the browser, it’s quite simple to auto-reload the application without any of the complexities of HMR.

My third point is local state handling. I am daily annoyed by web applications losing state when my browser updates or some other event forces tabs to refresh. The two main reasons for this are redirect behaviour on login/logout scenarios and not exposing hierarchical navigation state as url path or as an anchor hash (eg. using “/settings” instead of “/settings/security/two-way-auth” or “/settings/#two-way-auth”)

In addition to broken reloads, not exposing these types of state in the URL prevent sharing URLs and navigating with browser buttons, there is no excuse for this in 2020. Not having HMR makes these kinds of issues immediately obvious and forces developers to fix them before even going to QA. What remains is more complex client side state that does not belong in the URL and there are two answers here: 1. Having to manually recreate this kind of state is a great motivator to write integration tests for cypress or similar that take care of this, it is much more fun when it saves you doing the same task oll over again while you implement a feature vs. just for quality assurance after the feature is finished. 2. For situations where much faster iterations are needed, a simple state dump to localstorage solves the same problem as HMR without any of the added downsides. Most state handling libraries have a tutorial how to do this in 10 lines of code and if it takes much more work you should probably reconsider your choice of state handling.

The last point is the HMR complexity itself and lack of easy reasoning about the local dev environment. HMR implementations usually rely on a mixture of special bundler in memory build setups, websocket communication with a module hash + chunking protocol, application side HMR checks and replace/destroy handling and depending on the plugins and state libraries some state preserving magic. Especially when fixing actual bugs vs. working on styles or layout often the system breaks at an exception or syntax error and you need to refresh the page anyways. Most people dislike the idea of having to touch a working webpack setup, and I am sure HMR does not reduce this feeling. More than once did i see suboptimal webpack settings that were avoided changing because of the fragile nature and non linear complexity when dipping into the details of webpack to fix things. I would assume most bigger projects even have complete codepaths or file types that do not work at all in HMR and no one wanted to fix this.

Unfortunately i see a trend among young javascript projects to feel pressured(?) to do HMR out of the box and as a default, i assume because it can have a magic WOW demo effect. Even more worrisome is the incompleteness of opting out that most of these projects have, i saw at least two projects that would even inject code to open the web-socket connection when HMR was “disabled”.

I rest my case and this is where your opinion is appreciated: Do you disagree? Did i forget something? Most importantly: would you not consider using a framework/tool if it decidedly would not support HMR, but tried to optimise the reload experience? Really helpful would also be if you could share the acceptable/experienced times that you have in your real world projects from saving a file in the editor to seeing the change in the browser tab or if you don’t care about this that much. Lastly I am interested if any of you add little hints to your web app that allow you to see more easily if a subtle change is loaded yet (eg. build-counters in a corner, flashing of the page on update, etc.)

Thank you!

--

--