Create-react-app: Question: differences between Webpack HMR & React-Hot-Loader ?

Created on 20 Nov 2016  路  11Comments  路  Source: facebook/create-react-app

I'm looking forward integration of webpack2 _"and"_ React-Hot-Loader 3 (RHL) in CRA...
... but then i realized i'm not sure to fully understand the differences between webpack's HMR and RHL:

Are they doing same thing ? Are they mutually exclusive, or on contrary they must be used together to prevent full page refresh ?

At the moment, i'm using current CRA which seems to only use HMR: during dev, a change of a simple text or inner component, auto-refreshes the whole page.
Fine but when i saw this RHL video, i was like WoW it's awesome... but then why my CRA (which includes built-in "HMR") doesnot behave like that o.O ?!

I'm kinda confused, could someone explain the differences to get similar dev experience with CRA like in RHL video please ?

P.S.: not sure if that is pertinent, i'm using Webstorm 2016.3

Most helpful comment

If you want to fully understand the topic in depth I wrote something about it.

Webpack HMR is the underlying mechanism for replacing JS modules on the fly. It offers an API for doing so (module.hot.accept) but it requires that you explicitly "mark" the modules that can be updated on the fly, and specify how those updates should be handled.

HMR API is available in Create React App. It means you can write code like this:

a.js

const b = require('./b');
alert('At first b is ' + b);

if (module.hot) {
  module.hot.accept('./b', function() {
    const updatedB = require('./b');
    alert('But now it is ' + updatedB);
  });
}

b.js

module.exports = 42; // edit to make it 43

This is all HMR API gives you: an ability for the app to handle "new versions" of the same module and do something with it.

React Hot Loader is a project that attempts to build on top of Webpack HMR and preserve DOM and React component state when components are saved. Normally it's hard to achieve with vanilla Webpack HMR because if you just replace a module, React will think it's a different component type and destroy DOM and local state. So React Hot Loader does many tricks attempting to prevent this, but it's also way more complex than underlying HMR API.

At the moment, i'm using current CRA which seems to only use HMR: during dev, a change of a simple text or inner component, auto-refreshes the whole page.

This is not "HMR". This is just a refresh. "HMR" is what you see in CSS: when you change CSS file, it updates without refreshing. This is because style-loader (which is what we use for styles in development) calls HMR API to replace stylesheets on change.

The reason you don't get HMR behavior even though it's enabled in Create React App is because you haven't written any module.hot.accept calls. If Webpack doesn't find them traversing the "import chain" up, it decides to refresh the page. But if all changed modules are handled with module.hot.accept somewhere, it doesn't refresh.

Of course manually adding module.hot.accept calls in all modules for React components would be incredibly tedious. This is why React Hot Loader was created, so that it can generate that code for you. But it's hard to do right, and React Hot Loader 1.x had many issues, and is now unsupported. 3.x is a ground-up rewrite that embraces HMR API and actually asks you to write a single module.hot.accept call at the very top of the app. Unfortunately it's also not stable yet. You can contribute to it. :-)

I'm kinda confused, could someone explain the differences to get similar dev experience with CRA like in RHL video please ?

You can get reasonably close with this approach. However, unlike React Hot Loader, it wouldn't preserve the component local state or DOM, so it's just a "faster reload".

There is no way to fully have RHL-like experience in CRA. I hope to gradually get there but it's a longer road. See https://github.com/gaearon/react-hot-boilerplate/issues/97#issuecomment-249862775 for details.

I hope this helps!

All 11 comments

If you want to fully understand the topic in depth I wrote something about it.

Webpack HMR is the underlying mechanism for replacing JS modules on the fly. It offers an API for doing so (module.hot.accept) but it requires that you explicitly "mark" the modules that can be updated on the fly, and specify how those updates should be handled.

HMR API is available in Create React App. It means you can write code like this:

a.js

const b = require('./b');
alert('At first b is ' + b);

if (module.hot) {
  module.hot.accept('./b', function() {
    const updatedB = require('./b');
    alert('But now it is ' + updatedB);
  });
}

b.js

module.exports = 42; // edit to make it 43

This is all HMR API gives you: an ability for the app to handle "new versions" of the same module and do something with it.

React Hot Loader is a project that attempts to build on top of Webpack HMR and preserve DOM and React component state when components are saved. Normally it's hard to achieve with vanilla Webpack HMR because if you just replace a module, React will think it's a different component type and destroy DOM and local state. So React Hot Loader does many tricks attempting to prevent this, but it's also way more complex than underlying HMR API.

At the moment, i'm using current CRA which seems to only use HMR: during dev, a change of a simple text or inner component, auto-refreshes the whole page.

This is not "HMR". This is just a refresh. "HMR" is what you see in CSS: when you change CSS file, it updates without refreshing. This is because style-loader (which is what we use for styles in development) calls HMR API to replace stylesheets on change.

The reason you don't get HMR behavior even though it's enabled in Create React App is because you haven't written any module.hot.accept calls. If Webpack doesn't find them traversing the "import chain" up, it decides to refresh the page. But if all changed modules are handled with module.hot.accept somewhere, it doesn't refresh.

Of course manually adding module.hot.accept calls in all modules for React components would be incredibly tedious. This is why React Hot Loader was created, so that it can generate that code for you. But it's hard to do right, and React Hot Loader 1.x had many issues, and is now unsupported. 3.x is a ground-up rewrite that embraces HMR API and actually asks you to write a single module.hot.accept call at the very top of the app. Unfortunately it's also not stable yet. You can contribute to it. :-)

I'm kinda confused, could someone explain the differences to get similar dev experience with CRA like in RHL video please ?

You can get reasonably close with this approach. However, unlike React Hot Loader, it wouldn't preserve the component local state or DOM, so it's just a "faster reload".

There is no way to fully have RHL-like experience in CRA. I hope to gradually get there but it's a longer road. See https://github.com/gaearon/react-hot-boilerplate/issues/97#issuecomment-249862775 for details.

I hope this helps!

Thanks for the deep explanations, they were a long read but were what i was searching for, thanks !

FOr now, i'll just follow your advice to activate Webpack HMR only till you or someone else gets this to work, quoting the 4th point of your plan:

_Add the necessary hooks to React to avoid hacks like this, a top-level , or module.hot.accept code. The user shouldn鈥檛 change their code in any way for hot reloading to work. Incidentally we need the same hooks for React DevTools so it aligns with React team plans._

Speaking about folowing the adviced approach linked in your answer, it works for a barebone React Project but... real projects uses a router, an internationalization, and CSS framework, most of the time i think.

So for instance, my project's index.js looks like that:

import React from 'react'
import { render } from 'react-dom'
import Provider from 'react-redux/lib/components/Provider'
import Router from 'react-router/lib/Router'
import browserHistory from 'react-router/lib/browserHistory'
import './index.css'

import routes from './routes'
import store from './store'

const rootEl = document.getElementById('root')

render(
  <Provider store={store}>
    <Router history={browserHistory} routes={routes} />
  </Provider>,
  rootEl
)

// activates Webpack's HotModuleReload
if (module.hot) {
  module.hot.accept('./components/App', () => {
    const NextApp = require('./components/App').default
    render(
      <NextApp />,
      rootEl
    )
  })
}

... it doesnot change anything, still full page reload. Btw it takes about 2secs to recompile, then 2 other for the browser to refresh. That's why i was really astonished when seeing your video with instant change refresh.
I also tried the module.hot condition in routes.js but still doesnot improve anything.

What am i doing wrong ?

PS: my App component is actually the base one in routes.js

What am i doing wrong ?

NextApp is not a "magic" thing, it's your App component. But you don't have an App component in the example.

You likely want something like this?

import React from 'react'
import { render } from 'react-dom'
import Provider from 'react-redux/lib/components/Provider'
import Router from 'react-router/lib/Router'
import browserHistory from 'react-router/lib/browserHistory'

// Note: I can't see where you defined them so I added this:
import routes from './routes';

const rootEl = document.getElementById('root')

render(
  <Provider store={store}>
    <Router history={browserHistory} routes={routes} />
  </Provider>,
  rootEl
)

if (module.hot) {
  // This is not a magic thing!
  // You need to put whatever changes in *your* project here.
  module.hot.accept('./routes', () => {
    const nextRoutes = require('./routes').default // Again, depends on your project
    render(
      <Provider store={store}>
        <Router history={browserHistory} routes={nextRoutes} />
      </Provider>,
      rootEl
    )
  })
}

As I explained above module.hot.accept is not a magic block, you need to tell it what to do.

Still 4secs full page reload :s

I'm wondering if i forgot to install something else besides CRA itself ?

Here 's an example of my routes.js

import React from 'react'
import { Route, IndexRoute } from 'react-router'
import App from './components/App'
import HelloPage from './components/HelloPage'

export default (
  <Route path='/' component={App}>
    <IndexRoute component={HelloPage} />
  </Route>
)

...and the HelloPage is a dumb Component

import React from 'react'
export default () => <p>Hello world! This is the home page route.</p>

If i modify "Hello" with "Yop" it doesnot "autoreflect" in browser, but if i save it triggers a full page reload.
Is it expected behavior ?

I've read in your long article ReactTransform and RHL had troubles detecting components not using class extends Component or React.createClass(), so i tried also with HelloPage with class: still same behavior :s

I also tried only with just "npm start" from Windows CLI and modify in SublimeText (instead of Webstorm, thinking maybe something in the IDE would prevent autorefresh) but still same behavior.

Did i understood correctly when i read adding module.hot condition in topmost component in the tree would be enugh to catch any change bubbling up ?
so "routes" injected in component should bubble and trigger the "silent change" through nextRoutes right ?

PS: forgot to mention CRA ask me if i want to change port on start, since port3000 is already used by my API, not sure if this could have an impact on the problem ?

It's hard to say anything without an example reproducing your problem.

Ok :

  1. started a new empty webstorm 2016.3 project
  2. cli => creat-react-app crahmr (wait ~20mins till all is installed)
  3. cd crahmr => npm i -S react-router
    should get these versions :
"devDependencies": {
    "react-scripts": "0.7.0"
  },
  "dependencies": {
    "react": "^15.4.0",
    "react-dom": "^15.4.0",
    "react-router": "^3.0.0"
  }
  1. modify index.js with:
import React from 'react'
import { render } from 'react-dom'
import Router from 'react-router/lib/Router'
import browserHistory from 'react-router/lib/browserHistory'
import routes from './routes'

const rootEl = document.getElementById('root')

render(
  <Router history={browserHistory} routes={routes} />,
  rootEl
)

/*
if (module.hot) {
  module.hot.accept('./App', () => {
    console.debug('HMR !')
    const nextRoutes = require('./routes').default
    render(
      <Router history={browserHistory} routes={nextRoutes} />,
      rootEl
    )
  })
}
*/
  1. modify App.js with :
    ```
    import React from 'react'
    import logo from './logo.svg'
    import './App.css'

export default ({children}) => (



logo

Welcome to React




To get started, edit src/App.js and save to reload.


{children}

)

6. create routes.js :

import React from 'react'
import { Route, IndexRoute } from 'react-router'
import App from './App'
import HelloPage from './HelloPage'

export default (


)

7. create HelloPage.js :

import React from 'react'
export default () =>


```

  1. npm start

expected results :

It displays the expected black Header with spinning React logo
It displays the expected 2 sentences from App and HelloPage
Change a word in HelloPage triggers a full refresh (including the unmodified Header and logo)

HMR attempt :

Now i uncomment in index.js the hmr condition and save (it recompiles)
I modify some word in HelloPage and save => full refresh including unmodified components
Same with changing inline color in HelloPage style => full refresh

It should NOT refresh the unmodified components, ie Header, logo, etc... => fail
Console should display "HMR !" => fail

@Sharlaan Typo in your code:

if (module.hot) {
  module.hot.accept('./App', () => {   // <====== modify './App' to './routes' and it works fine.
    console.debug('HMR !')
    const nextRoutes = require('./routes').default
    render(
      <Router history={browserHistory} routes={nextRoutes} />,
      rootEl
    )
  })
}

But react-router can't be updated:

2016-12-04 2 53 22

@Sharlaan I tried [email protected], hot reloading works, but seems like Re-Render, every components are remounted.

@gaearon I can't correctly configure a PERFECTLY Hot Module Reload project that it can hot reload and keep component's state. When I update a component, all components reload and remount. Is there an example that it doesn't reset the state?

@cpunion Please direct this feedback to React Hot Loader repository. Yes, there is an example, and it's linked for React Hot Loader README's first paragraph. https://github.com/gaearon/react-hot-loader/pull/240

I鈥檓 locking this particular thread because it鈥檚 not related to Create React App at the moment, and comments will just get lost unaddressed here. Please file issues with the projects you are using for hot reloading.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Aranir picture Aranir  路  3Comments

DaveLindberg picture DaveLindberg  路  3Comments

ap13p picture ap13p  路  3Comments

jnachtigall picture jnachtigall  路  3Comments

adrice727 picture adrice727  路  3Comments