New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

React Hot Loader 3.0 beta demo #61

Open
wants to merge 31 commits into
base: master
from

Conversation

Projects
None yet
@gaearon
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

This comment has been minimized.

Show comment
Hide comment
@nfcampos

nfcampos 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?

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

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Apr 17, 2016

Owner

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

Correct.

Owner

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

This comment has been minimized.

Show comment
Hide comment
@nfcampos

nfcampos 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 ?

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

This comment has been minimized.

Show comment
Hide comment
@CrocoDillon

CrocoDillon 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?

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

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Apr 17, 2016

Owner

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.

Owner

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.

@gaearon gaearon referenced this pull request Apr 17, 2016

Closed

Status #2

@tyscorp

This comment has been minimized.

Show comment
Hide comment
@tyscorp

tyscorp 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?

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

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Apr 18, 2016

Owner

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

Owner

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/?

@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

@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.

@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

@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.

@tracker1

tracker1 Mar 10, 2017

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

@benjamindulau

This comment has been minimized.

Show comment
Hide comment
@benjamindulau

benjamindulau 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 :)

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

This comment has been minimized.

Show comment
Hide comment
@peter-mouland

peter-mouland 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;
};

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

This comment has been minimized.

Show comment
Hide comment
@satazor

satazor 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.

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.

@tracker1

tracker1 Feb 28, 2017

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

@valmassoi

This comment has been minimized.

Show comment
Hide comment
@valmassoi

valmassoi 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

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.

@thekemkid

thekemkid Apr 4, 2017

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

@thekemkid

thekemkid 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));
}
@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.

@Kovensky

Kovensky 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).

@Kovensky

Kovensky 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.

@thekemkid

thekemkid 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

@thekemkid

thekemkid 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.

@Kovensky

Kovensky 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

@Kovensky

Kovensky 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

This comment has been minimized.

Show comment
Hide comment
@parris

parris 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.

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.

@dantesolis

This comment has been minimized.

Show comment
Hide comment
@dantesolis

dantesolis 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

dantesolis 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

This comment has been minimized.

Show comment
Hide comment
@elyobo

elyobo Aug 2, 2017

@dantesolis dev dependency

elyobo commented Aug 2, 2017

@dantesolis dev dependency

@aymericbouzy

This comment has been minimized.

Show comment
Hide comment
@aymericbouzy

aymericbouzy 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)?

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

This comment has been minimized.

Show comment
Hide comment
@peter-mouland

peter-mouland 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>
);

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

This comment has been minimized.

Show comment
Hide comment
@MuLoo

MuLoo 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

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

This comment has been minimized.

Show comment
Hide comment
@sibelius

sibelius Jul 3, 2018

can we merge green?

or upgrade to react-hot-loader 4?

sibelius commented Jul 3, 2018

can we merge green?

or upgrade to react-hot-loader 4?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment