Skip to content
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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

@nfcampos
Copy link

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

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. “Loader” originally meant a Webpack transform but since it’s a project for “hot reloading” I think it’s 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’d like to try it.

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/es2015/latest/?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@benjamindulau
Copy link

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

@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({

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@valmassoi
Copy link

@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));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link

@parris parris Apr 8, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 in fact this line should be:

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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

Copy link

@Jessidhia Jessidhia Apr 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@elyobo
Copy link

elyobo commented Aug 2, 2017

@dantesolis dev dependency

@aymericbouzy
Copy link

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?

@monokrome
Copy link

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 join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.