PostHole
Compose Login
You are browsing us.zone2 in read-only mode. Log in to participate.
rss-bridge 2018-12-08T00:00:00+00:00

My Wishlist for Hot Reloading

I don't want a lot for Christmas. There is just one thing I need.


My Wishlist for Hot Reloading

December 8, 2018

Pay what you like

Do you have a project that you approach repeatedly with a mix of success and failure, step aside for a while, and then try again — year after year? For some, it might be a router or a virtual list scroller. For me, it’s hot reloading.

My first exposure to the idea of changing code on the fly was a brief mention in a book about Erlang that I read as a teenager. Much later, like many others, I fell in love with Bret Victor’s beautiful demos. I’ve read somewhere Bret was unhappy with people cherry-picking “easy” parts of his demos and screwing up the big vision. (I don’t know if this is true.) In either case, to me shipping even small incremental improvements that people take for granted later is a success. Smarter people than me will work on Next Big Ideas.

Now, I want to be clear that none of the ideas discussed in this post are mine. I’ve been inspired by many projects and people. In fact, even people whose projects I’ve never tried occasionally told me I’ve ripped off their stuff.

I’m not an inventor. If I have a “principle”, it is to take a vision that inspires me, and share it with more people — through words, code, and demos.

And hot reloading inspires me.


I’ve taken several attempts at implementing hot reloading for React.

In retrospect, the first demo I cobbled together changed my life. It got me my first Twitter followers, first thousand GitHub stars, later first HN frontpage hit, and even my first conference talk (bringing Redux into existence, oops). This first iteration worked fairly well. However, soon React moved away from createClass, making a reliable implementation much more difficult.

Since then I’ve done a few more attempts to fix it, each flawed in a different way. One of them is still being used in React Native (hot reloading functions doesn’t work there because of my mistakes — sorry!)

Frustrated with my inability to work around some issues and the lack of time, I handed React Hot Loader over to a few talented contributors. They have been pushing it forward and found clever workarounds for my design flaws. I am grateful to them for keeping the project in a good state despite the challenges.


To be clear, hot reloading in React is quite usable today. In fact, this blog uses Gatsby which uses React Hot Loader under the hood. I save this post in my editor and it updates without refreshing. Magic! In some ways, the vision that I worried wouldn’t ever see mainstream usage is already almost boring.

But there are plenty of people who feel it isn’t as good as it could be. Some dismiss it as a gimmick and that breaks my heart a little bit, but I think what they’re really saying is: the experience is not seamless. It’s not worth it if you’re never sure whether a hot reload worked, if it breaks in confusing ways, or if it’s easier to just refresh. I agree with this 100%, but to me it means we have more work to do. And I’m excited to start thinking about what official React support for hot reloading could look like in the future.

(If you use a language like Elm, Reason or ClojureScript, maybe those problems are already solved in your ecosystem. I’m happy that you’re happy. This won’t stop me from trying and failing to bring good stuff to JavaScript.)


I think I’m ready to take another attempt at implementing it. Here’s why.

Ever since createClass stopped being the primary way we define components, the biggest source of complexity and fragility in hot reloading components was dynamically replacing class methods. How do you patch existing instances of classes with new “versions” of their methods? The simple answer is “replace them on the prototype” but even with Proxies, in my experience there are too many gnarly edge cases for this to work reliably.

By comparison, hot reloading functions is easy. A Babel plugin could split any function component exported from a module into two functions:

// Reassigns the latest version
window.latest_Button = function(props) {
// Your actual code is moved here by a plugin
return <button>Hello</button>;

// Think of this as a "proxy"
// that other components would use
export default function Button(props) {
// Always points to latest version
return window.latest_Button(props);

Every time this module re-executes after an edit, window.latest_Button would point to the latest implementation. Reusing the same Button function between module evaluations would let us trick React into not unmounting our component even though we swapped out the implementation.

For a long time, it seemed to me that implementing reliable hot reloading for functions alone would encourage people to write convoluted code just to avoid using classes. But with Hooks, function components are fully featured so this is not a concern anymore. And this approach “just works” with Hooks:

// Reassigns the latest version
window.latest_Button = function(props) {
// Your actual code is moved here by a plugin
const [name, setName] = useState('Mary');
const handleChange = e => setName(e.target.value);
return (
<input value={name} onChange={handleChange} />
<h1>Hello, {name}</h1>
</>

// Think of this as a "proxy"
// that other components would use
export default function Button(props) {
// Always points to latest version
return window.latest_Button(props);

As long as the Hook call order doesn’t change, we can preserve the state even as window.latest_Button is replaced between file edits. And replacing event handlers “just works” too — because Hooks rely on closures, and we replace the whole function.


This was just a rough sketch of one possible approach. There are more (some are very different). How do we evaluate and compare them?

Before I get too attached to a specific approach that might be flawed in some way, I decided to write down a few principles that I think are important for judging any hot reloading implementation for component code.

It would be nice to express some of these principles as tests later. These rules aren’t strict and there might be reasonable compromises. But if we decide to break them, that should be an explicit design decision and not something we accidentally discover later.

Here goes my wish list for hot reloading React components:

Correctness

Hot reloading should be unobservable before the first edit. Until you save a file, the code should behave exactly as it would if hot reloading was disabled. It’s expected that things like fn.toString() don’t match, which is already the case with minification. But it shouldn’t break reasonable application and library logic.

Hot reload shouldn’t break React rules. Components shouldn’t get their lifecycles called in an unexpected way, accidentally swap state between unrelated trees, or do other non-Reacty things.

Element type should always match the expected type. Some approaches wrap component types but this can break <MyThing />.type === MyThing. This is a common source of bugs and should not happen.

It should be easy to support all React types. lazy, memo, forwardRef — they should all be supported and it shouldn’t be hard to add support for more. Nested variations like memo(memo(...)) should also work. We should always remount when the type shape changes.

[...]


Original source

Reply