Skip to content
This repository has been archived by the owner. It is now read-only.

React Hot Loader 3.0 beta demo #61

Closed
wants to merge 33 commits into from
Closed

React Hot Loader 3.0 beta demo #61

wants to merge 33 commits into from

Conversation

@gaearon
Copy link
Owner

gaearon commented Apr 17, 2016

A Big Update Is Coming

React Hot Loader 3 is on the horizon, and you can try it today. It fixes some long-standing issues with both React Hot Loader and React Transform, and is intended as a replacement for both.

Some nice things about it:

  • Editing functional components preserves state
  • Works great with higher order components
  • Little configuration
  • Disabled in production
  • Works with or without Babel (you can remove react-hot-loader/babel from .babelrc and instead add react-hot-loader/webpack to loaders)

The docs are not there yet, but they will be added before the final release.
For now, this commit is a good reference to upgrading your project from React Hot Loader 1 to React Hot Loader 3 alpha. Then see another commit as a reference for upgrading from React Hot Loader 3 alpha to React Hot Loader 3 beta.


With lessons learned both from RHL and RT, here is a demo of a unified approach.

This is really undocumented for now, and we might change API later, so feel free to play with it at your own risk. 馃槈

react-hot-loader/webpack is intended to be optional. We will provide a complementary react-hot-loader/babel that detects unexported components as well. You will be able to use either, depending on whether you already use Babel or not.


Known Issues

  • Server rendering AppContainer crashes (gaearon/react-hot-loader#283). Until fixed, a workaround is to render the app directly on the server instead.
  • Error reporting only works for initial mount, but this should be fixed when facebook/react#6020 lands.
  • RedboxReact sometimes fails to display an error message due to stacktracejs/stackframe#11.
  • Hot reloading doesn鈥檛 currently work with React.createFactory or React.DOM.* factories. It works either with JSX or with React.createElement. (gaearon/react-hot-loader#277)
@nfcampos
Copy link

nfcampos commented Apr 17, 2016

This looks great! Without the babel part of the loader, this won't yet work for HOC-wrapped components (because the original isn't exported), right?

@gaearon
Copy link
Owner Author

gaearon commented Apr 17, 2016

Without the babel part of the loader, this won't yet work for HOC-wrapped components (because the original isn't exported), right?

Correct.

@nfcampos
Copy link

nfcampos commented Apr 17, 2016

React.createElement will be patched before any user code is ran, preventing issues with code like this https://github.com/reactjs/react-router/blob/master/modules/RouterContext.js#L29 ?

@CrocoDillon
Copy link

CrocoDillon commented Apr 17, 2016

Just to get this straight, react-hot-loader was superseded by react-transform-hmr right? Do you want to go back to react-hot-loader as favorite once v3.0.0 is done?

@gaearon
Copy link
Owner Author

gaearon commented Apr 17, 2016

React.createElement will be patched before any user code is ran, preventing issues with code like this https://github.com/reactjs/react-router/blob/master/modules/RouterContext.js#L29?

Yes (presuming the configuration is correct). In this example I just put react-hot-loader/patch in entry as the first item.

Just to get this straight, react-hot-loader was superseded by react-transform-hmr right? Do you want to go back to react-hot-loader as favorite once v3.0.0 is done?

The existing implementations of both React Transform / React Hot Loader are going away. The new implementation will be branded as React Hot Loader 3, but will internally use a combination of their approaches. It will also not be webpack-specific. 鈥淟oader鈥 originally meant a Webpack transform but since it鈥檚 a project for 鈥渉ot reloading鈥 I think it鈥檚 fine that we expand the scope, and react-hot-loader will contain opt-in react-hot-loader/webpack, react-hot-loader/babel, and potentially other integrations.

@tyscorp
Copy link

tyscorp commented Apr 18, 2016

Here is an example repo with jspm/SystemJS used in place of webpack: https://github.com/tyscorp/react-hot-boilerplate/tree/jspm

The proxying stuff does not currently work as no exports have __source attached.

It seems like react-hot-loader/babel would work fine with jspm/SystemJS for those using plugin-babel.

I did experiment with creating react-hot-loader/systemjs by patching SystemJS' _export function with mixed results. I did manage to get ES6 class-based components to successfully hot reload while maintaining state. It looks like the "instantiate" hook (as opposed to "translate") might be more suited for this task but I was unable to write a plugin that even worked using it. Perhaps @guybedford could weigh in on the most appropriate way to write such a plugin?

@gaearon
Copy link
Owner Author

gaearon commented Apr 18, 2016

I added Babel plugin in case you鈥檇 like to try it.

@@ -1,3 +1,10 @@
{
"presets": ["es2015", "stage-0", "react"]
"presets": [
["es2015", {"modules": false}],

This comment has been minimized.

@tracker1

tracker1 Feb 13, 2017

s/es2015/latest/?

This comment has been minimized.

@Kikobeats

Kikobeats Feb 14, 2017

why is necessary disable modules? looks like works without this option

This comment has been minimized.

@dlebedynskyi

dlebedynskyi Feb 14, 2017

@Kikobeats disable modules has no effect on hot loader. Webpack now can handle es 6 modules and for better tree shaking this has to be off in babel.

This comment has been minimized.

@dlebedynskyi

dlebedynskyi Feb 14, 2017

@tracker1 might as well use babel-present-env instead

This comment has been minimized.

@tracker1

tracker1 Mar 10, 2017

@dlebedynskyi agreed, apparently there's a deprecation message on -latest that says as much now.

@benjamindulau
Copy link

benjamindulau commented Feb 17, 2017

What would be the correct setup if instead of having a SPA we have an hybrid server rendered page with a bunch of standalone React components rendered independently at different places on the same page. For instance when you just want to add a JS widget (in React) on an existing page.

Note that I have a single entry point "main.js" in webpack which works with some kind of a router calling different functions (which render react components) depending on the current URL path.

For now, I have to wrap each component in a different along with module.hot.accept stuff. While this makes sense and works fine, it's kind of repetitive when I need to render more than 1 component in the same page.

I know that since each component is rendered separately, they are each different mini-application with their own lifecycle. Just wondered if I can do better than this :)

@peter-mouland
Copy link

peter-mouland commented Feb 17, 2017

@benjamindulau i don't know if this is the right place for this discussion, but i'll answer anyway, then if any more info is needed i'd recommend stack overflow rather than adding to the discussion here.

sounds like a custom webpack-loader might work here. You can make the loader wrap the files for you. I've only ever had a hand in building one so i don't know best practises, but the reason for mine and yours were very similar. I wrapped each component in code that would render each component within a ruby app, so we could drop in components independently.

here is the source to get you started.

const loaderUtils = require('loader-utils');
const getComponentName = (componentPath) => {
  const componentPathSegments = componentPath.split('/');

  //return the second-to-last segment
  return componentPathSegments.slice(-2, -1)[0];
};

module.exports = function (source) {
  let componentName;
  let loaderSource = source;

  this.value = loaderSource;
  this.cacheable && this.cacheable();
  try {
    componentName = getComponentName(loaderUtils.getRemainingRequest(this));
  }
  catch (e) {
    return source;
  }
  try {
    loaderSource = `${source};
    window.document.addEventListener("DOMContentLoaded", function(event) {
      window.setTimeout(function(){
        const elems = document.querySelectorAll('[toga=${componentName}]');
        [].forEach.call(elems, function(elem) {
          let props;
          try {
            props = JSON.parse(elem.getAttribute('props'));
          } catch (e) {
            props = {};
          }
          const Component = (typeof exports.default === 'undefined')
              ? module.exports
              : exports.default;
          ReactDOM.render(<Component {...props}/>, elem);
        });
      }, 1);
    });`;
  }
  catch (e) {
    return source;
  }
  this.value = loaderSource;

  return loaderSource;
};
@satazor
Copy link

satazor commented Feb 21, 2017

For those who are having trouble with react-router 3 + async routes + hot-module-reload, please see: gaearon/react-hot-loader#288 (comment)

You may look at https://github.com/moxystudio/react-with-moxy for a working example.

},

plugins: [
new webpack.optimize.UglifyJsPlugin({

This comment has been minimized.

@tracker1

tracker1 Feb 28, 2017

This is redundant since webpack -p will process output via uglify without the plugin configured.

@valmassoi
Copy link

valmassoi commented Mar 6, 2017

@JeremyBernier your solution seems to be working for me, but I get Warning: [react-router] You cannot change <Router routes>; it will be ignored in the console. Not sure if this matters or if it slowing down the update. Also routes = require('./routes').default line is missing a const BTW

);

render(App);
if (module.hot) module.hot.accept('./App', () => render(App));

This comment has been minimized.

@GlenTiki

GlenTiki Apr 4, 2017

@gaearon Do you need to re-require the App before calling render?

This comment has been minimized.

@parris

parris Apr 8, 2017

+1 in fact this line should be:

if (module.hot) {
    // eslint-disable-next-line global-require
    module.hot.accept('./App', () => render(require('./App').default));
}

This comment has been minimized.

@Jessidhia

Jessidhia Apr 8, 2017

It should not, unless you are incorrectly transforming modules before webpack sees them.

You only need to require or import() again if you required or import()ed the first time. Static imports are live and are always up-to-date (internally, webpack will re-require it before calling your callback).

This comment has been minimized.

@GlenTiki

GlenTiki Apr 9, 2017

@kovensky I'm inclined to believe you're wrong due to the official 3.0 docs saying otherwise: https://github.com/gaearon/react-hot-loader/edit/master/docs/README.md#L96

This comment has been minimized.

@Jessidhia

Jessidhia Apr 10, 2017

Yeah, that is incorrect for webpack 2, as long as you disable the module transform in babel/typescript/etc. webpack has to see the raw import / export commands. The require is necessary if they are getting converted to require calls by your preprocessor.

See the webpack 2 section in https://github.com/gaearon/react-hot-loader/blob/master/docs/README.md#webpack-2

@parris
Copy link

parris commented Apr 8, 2017

Not sure if this is the right forum for the following comments, but I see lots of references to universal rendering projects. I just got universal rendering + react-hot-loader@3.0.0-beta.6 working together and have some tips.

  1. You should split webpack-dev-server out into its own process (especially if you are using node-foreman or some other backend reloader).
  2. Your webpack config's output entry should look like this:
output: {
        path: __dirname + '/build/',
        filename: 'app.js',
        publicPath: `http://localhost:${DEV_SERVER_PORT}/build/`,
    },

That is, your publicPath should contain the full URL of your webpack-dev-server otherwise it will try to make the hot module reload request to your backend server instead of the webpack-dev-server.

@code4cake

This comment has been minimized.

Copy link

code4cake commented on package.json in b52c727 Apr 17, 2017

Ok this is a newbie question, but should react-hot-loader be included as a devDependency or a dependecy? A Google search has given me both approaches. Thanks

@elyobo
Copy link

elyobo commented Aug 2, 2017

@dantesolis dev dependency

@aymericbouzy
Copy link

aymericbouzy commented Aug 18, 2017

Shouldn't be a dependency since you import {AppContainer} from 'react-hot-loader' even when your app is in production (and AppContainer does nothing in this case)?

@peter-mouland
Copy link

peter-mouland commented Aug 19, 2017

@aymericbouzy you can work around that problem with a require rather than import based on NODE_ENV. i.e.

// HmrContainer.js
export default (process.env.NODE_ENV === 'development')
  ? require('react-hot-loader').AppContainer // eslint-disable-line
  : ({ children }) => (children);

to be used like:

// client-entry.js
const App = (
  <HmrContainer>
    <Root />
  </HmrContainer>
);
@MuLoo
Copy link

MuLoo commented Dec 25, 2017

if (module.hot) {
module.hot.accept('./App', () => {
const NewApp = require('./App').default;
render();
});
}
we must require App first, and then render newApp component, otherwise it will not work

@sibelius
Copy link

sibelius commented Jul 3, 2018

can we merge green?

or upgrade to react-hot-loader 4?

vikr01 and others added 2 commits Oct 5, 2018
Update README
@monokrome
Copy link

monokrome commented Jul 19, 2019

What is going on here?...

@theKashey
Copy link
Collaborator

theKashey commented Jul 19, 2019

Nothing. And already for a while.

Let's close this as long as React-Hot-Loader v3 is outdated, and React-Hot-Loader v4 is also might sunset soon.

@theKashey theKashey closed this Jul 19, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

None yet

You can鈥檛 perform that action at this time.