gaearon/react-hot-loader

The end of React-Hot-Loader

theKashey opened this issue · 10 comments

By this time we have reached limits of "hot replacement", and to overpass them we need something new. (Source issue : #1001)

The problem - there are so many ways to DEFINE react component, what we could not handle all of them.

The major problems today:

  • we are unable to replay all the changes. We knew only their result, not how they were made to repeat it. As result we could not inject code updates into the existing components. It is easy to update StatelessComponents or prototype-based Class-Component methods, or bound arrow function, but not everything one could possible use.
    For example:
 class App extends Component {
    memoizedFunction: reselect(function)  // <-- there is NO way we could "re-do" changes in `function`.
 }

Unfortunately - this could become a common thing with latest React changes. Already did( #995, #978, #969, #984)
I've spiked 2 different implementation - babel and Proxy based - they could work, but this is not the things I would like to have, not the way I would like to go.

  • we are changing types presented to React. Our greatest sideeffect. We have to wrap original type with a proxy to maintain type equality in the future. We should try our best to avoid it. Cold components are good try, not not enough.

Just a two points, we are unable to overtake.
React-Hot-Loader could handle most of the React code, and would fit for almost any project. But we are stuck.


I think React-Hot-Loader was a good spike, and it's time to more forward, and look to the way to to implement it inside React, stop hacking it, but start being a part of it:

  • don't try to maintain the single proxyfied component, applying all the changes to it, but just "explain" to React that some components could be the same, even if type got changes, and transfer variables changed during lifetime (state, for example) from the old one, to the new.
    Not trying to repeat the changes, but only to preserve state.

That will solve all the issues, and super simple to implement, as long we are already having everything we need for it, in the current RHL, but in much more complex form.

Something like componentComparator we spiked for Preact integration - https://github.com/gaearon/react-hot-loader/pull/960/files#diff-b26fdb1bb246da9739b0a423fbe9a65dR6, preactjs/preact#1120 - just expose RHL internals about compareAndSwap, and that's all.
Probably it just a hundred lines for everything - changed properties tracking from proxies, class comparator from hotRender, and nothing more.

It should not be super complex to integrate this to React, as long there are only two location to hook into.

As result - we could drop instantly hot-renderer, and next drop componentProxy - two major pieces of RHL.

We have some ideas but it’s too early to share yet. I think there’ll be a way forward within a year.

By “I think” in this case I mean “I’m pretty sure”. I’m optimistic about this for the first time in a few years :-)

Could you measure your optimism in terms of should we continue our efforts to improve the RHL way to hot-reload or it would become obsolete... more sooner than later?

In my current vision - RHL is a super complex thing, and we should stop moving it that direction, should stop fighting with React, and try to teamup, as we tried with Preact.

React-Hot-Loader was started as a preserver of Component's type equality, to fool reconciler from the outside. And that is something not letting us moving forward.
Not "fooling reconciler", but "outside".
And that is something RHL could not without your help. And that is something, that will sunset it.

Yep, all of this sounds correct

There are two separate parts to this problem:

  • Wrapping things that appear to be React component
  • Extracting behavior from the fresh version into the wrapper and transplanting it

The first one isn’t really a problem. It may seem like one, but it’s really the second problem that’s making the first one difficult.

The solution I’m thinking about solves the second problem, leaving RHL to only worry about the first one, and drastically limiting its complexity.

Extracting behavior from the fresh version into the wrapper and transplanting it

This is what we are currently doing, and this is what we could not solve.

Initially we had only class.methods, and it is easy to inject a new ones into the prototype.
Next - "bound" methods. Ok - just "bind" them again
Next - arrow functions - cast them toString, and eval back (a did not found another solution)
But when someone will create a new class member using factory function(per-instance-memoization, so popular after _ componentWillReceiveProps_ deprecation, is a good example) - there is no way to repeat it, and preserve possible used this - you have to fight with JS, you have to fight with Babel.

The easiest way - extract constructor body to another function, to be able to replay ES6 constructor for custom this. I am not sure that is the right way.

The simplest way - create a new instance how it should be, and copy some known methods from old instances. Reverse operation. And then explain React that new type is the same as an old one. It will also solve ".type !== Component".

Might be it is better to store variables we want to preserve, call extracted constructor on the current component, and restore variables back, as long that's a goal - it will keep refs, but still makes .type !== Component a thing.

The current version could solve almost any code from "last year", but as long React evolves - new coding patterns emerges, and as RHL failed to support Arrow functions in V3 - it is failing to support fabricated methods in V4.
We have to found a bit more durable solution.

Yeah, the solution I'm thinking about will eventually solve those things.

I am happy to know that there is finally a hope for React + Hot Reloading. When I started to work on the project one year ago, I didn’t expect so much complexity. I quickly gave up but thanks to @theKashey, the project continued to live.

Today I think you should warn in readme that the latest supported version is React 16.5 and specify it in peerDependencies. And yes, stop the development 😢 even if it is your baby.
Thanks again for this year of hot reloading, it would not have been possible without you!

Excited to see what @gaearon and React team are cooking 🍳😁.

Its almost 16.6 compatible, I just need to make lazy a bit hotter.
And it will be 16.7 compatible, if reactjs/rfcs#74 would succeed.
With hooks we would just be free from some issues, some side effects, and so on.

The “hot replacement” stuff should be managed by something anyway.

Since version 4.6.0 we are "inside" React, and one may|shall use hooks - this would cure the problem.