Giter VIP home page Giter VIP logo

react-hot-loader's Introduction

React Hot Loader

Build Status version Code Coverage MIT License

PRs Welcome Chat Backers on Open Collective Sponsors on Open Collective

Watch on GitHub Star on GitHub

Tweak React components in real time ⚛️⚡️

Watch Dan Abramov's talk on Hot Reloading with Time Travel.

Moving towards next step

React-Hot-Loader has been your friendly neighbour, living outside of React. But it has been limiting its powers and causing not the greatest experience. It's time to make a next step.

React-Hot-Loader is expected to be replaced by React Fast Refresh. Please remove React-Hot-Loader if Fast Refresh is currently supported on your environment.

Install

npm install react-hot-loader

Note: You can safely install react-hot-loader as a regular dependency instead of a dev dependency as it automatically ensures it is not executed in production and the footprint is minimal.

Getting started

  1. Add react-hot-loader/babel to your .babelrc:
// .babelrc
{
  "plugins": ["react-hot-loader/babel"]
}
  1. Mark your root component as hot-exported:
// App.js
import { hot } from 'react-hot-loader/root';
const App = () => <div>Hello World!</div>;
export default hot(App);
  1. Make sure react-hot-loader is required before react and react-dom:
  • or import 'react-hot-loader' in your main file (before React)
  • or prepend your webpack entry point with react-hot-loader/patch, for example:
    // webpack.config.js
    module.exports = {
      entry: ['react-hot-loader/patch', './src'],
      // ...
    };
  1. If you need hooks support, use @hot-loader/react-dom

Hook support

Hooks would be auto updated on HMR if they should be. There is only one condition for it - a non zero dependencies list.

❄️ useState(initialState); // will never updated (preserve state)
❄️ useEffect(effect); // no need to update, updated on every render
❄️ useEffect(effect, []); // "on mount" hook. "Not changing the past"
🔥 useEffect(effect, [anyDep]); // would be updated

🔥 useEffect(effect, ["hot"]); // the simplest way to make hook reloadable

Plus

  • any hook would be reloaded on a function body change. Enabled by default, controlled by reloadHooksOnBodyChange option.
  • you may configure RHL to reload any hook by setting reloadLifeCycleHooks option to true.

To disable hooks reloading - set configuration option:

import { setConfig } from 'react-hot-loader';

setConfig({
  reloadHooks: false,
});

With this option set all useEffects, useCallbacks and useMemo would be updated on Hot Module Replacement.

Hooks reset

Hooks would be reset if their order changes. Adding, removing or moving around would cause a local tree remount.

Babel plugin is required for this operation. Without it changing hook order would throw an error which would be propagated till the nearest class-based component.

@hot-loader/react-dom

@hot-loader/react-dom replaces the "react-dom" package of the same version, but with additional patches to support hot reloading.

There are 2 ways to install it:

  • Use yarn name resolution, so @hot-loader/react-dom would be installed instead of react-dom
yarn add react-dom@npm:@hot-loader/react-dom
yarn add @hot-loader/react-dom
// webpack.config.js
module.exports = {
  // ...
  resolve: {
    alias: {
      'react-dom': '@hot-loader/react-dom',
    },
  },
};

Old API

Note: There is also an old version of hot, used prior to version 4.5.4. Please use the new one, as it is much more resilient to js errors that you may make during development.

Meanwhile, not all the bundlers are compatible with new /root API, for example parcel is not.

React-Hot-Load will throw an error, asking you to use the old API, if such incompatibility would be detected. It is almost the same, but you have to pass module inside hot.

import { hot } from 'react-hot-loader';
const App = () => <div>Hello World!</div>;
export default hot(module)(App);
  1. Run webpack with Hot Module Replacement:
webpack-dev-server --hot

What about production?

The webpack patch, hot, Babel plugin, @hot-loader/react-dom etc. are all safe to use in production; they leave a minimal footprint, so there is no need to complicate your configuration based on the environment. Using the Babel plugin in production is even recommended because it switches to cleanup mode.

Just ensure that the production mode has been properly set, both as an environment variable and in your bundler. E.g. with webpack you would build your code by running something like:

NODE_ENV=production webpack --mode production

NODE_ENV=production is needed for the Babel plugin, while --mode production uses webpack.DefinePlugin to set process.env.NODE_ENV inside the compiled code itself, which is used by hot and @hot-loader/react-dom.

Make sure to watch your bundle size when implementing react-hot-loader to ensure that you did it correctly.

Limitations

  • (that's the goal) React-Hot-Loader would not change the past, only update the present - no lifecycle event would be called on component update. As a result, any code changes made to componentWillUnmount or componentDidMount would be ignored for already created components.
  • (that's the goal) React-Hot-Loader would not update any object, including component state.
  • (1%) React-Hot-Loader may not apply some changes made to a component's constructor. Unless an existing component is recreated, RHL would typically inject new data into that component, but there is no way to detect the actual change or the way it was applied, especially if the change was made to a function. This is because of the way React-Hot-Loader works - it knows what class functions are, not how they were created. See #1001 for details.

Recipes

Migrating from create-react-app

  1. Run npm run eject
  2. Install React Hot Loader (npm install --save-dev react-hot-loader)
  3. In config/webpack.config.dev.js, add 'react-hot-loader/babel' to Babel loader configuration. The loader should now look like:
  {
    test: /\.(js|jsx)$/,
    include: paths.appSrc,
    loader: require.resolve('babel-loader'),
    options: {
      // This is a feature of `babel-loader` for webpack (not Babel itself).
      // It enables caching results in ./node_modules/.cache/babel-loader/
      // directory for faster rebuilds.
      cacheDirectory: true,
      plugins: ['react-hot-loader/babel'],
    },
  }
  1. Mark your App (src/App.js) as hot-exported:
// ./containers/App.js
import React from 'react';
import { hot } from 'react-hot-loader';

const App = () => <div>Hello World!</div>;

export default hot(module)(App);

Migrating from create-react-app without ejecting

Users report, that it is possible to use react-app-rewire-hot-loader to setup React-hot-loader without ejecting.

TypeScript

As of version 4, React Hot Loader requires you to pass your code through Babel to transform it so that it can be hot-reloaded. This can be a pain point for TypeScript users, who usually do not need to integrate Babel as part of their build process.

Fortunately, it's simpler than it may seem! Babel will happily parse TypeScript syntax and can act as an alternative to the TypeScript compiler, so you can safely replace ts-loader or awesome-typescript-loader in your Webpack configuration with babel-loader. Babel won't typecheck your code, but you can use fork-ts-checker-webpack-plugin (and/or invoke tsc --noEmit) as part of your build process instead.

A sample configuration:

{
  // ...you'll probably need to configure the usual Webpack fields like "mode" and "entry", too.
  resolve: { extensions: [".ts", ".tsx", ".js", ".jsx"] },
  module: {
    rules: [
      {
        test: /\.(j|t)sx?$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            cacheDirectory: true,
            babelrc: false,
            presets: [
              [
                "@babel/preset-env",
                { targets: { browsers: "last 2 versions" } } // or whatever your project requires
              ],
              "@babel/preset-typescript",
              "@babel/preset-react"
            ],
            plugins: [
              // plugin-proposal-decorators is only needed if you're using experimental decorators in TypeScript
              ["@babel/plugin-proposal-decorators", { legacy: true }],
              ["@babel/plugin-proposal-class-properties", { loose: true }],
              "react-hot-loader/babel"
            ]
          }
        }
      }
    ]
  },
  plugins: [
    new ForkTsCheckerWebpackPlugin()
  ]
};

For a full example configuration of TypeScript with React Hot Loader and newest beta version of Babel, check here.

As an alternative to this approach, it's possible to chain Webpack loaders so that your code passes through Babel and then TypeScript (or TypeScript and then Babel), but this approach is not recommended as it is more complex and may be significantly less performant. Read more discussion here.

Parcel

Parcel supports Hot Module Reloading out of the box, just follow step 1 and 2 of Getting Started.

We also have a full example running Parcel + React Hot Loader.

Electron

You need something to mark your modules as hot in order to use React Hot Loader.

One way of doing this with Electron is to simply use webpack like any web-based project might do and the general guide above describes. See also this example Electron app.

A webpack-less way of doing it to use electron-compile (which is also used by electron-forge) - see this example. While it requires less configuration, something to keep in mind is that electron-compile's HMR will always reload all modules, regardless of what was actually edited.

Source Maps

If you use devtool: 'source-map' (or its equivalent), source maps will be emitted to hide hot reloading code.

Source maps slow down your project. Use devtool: 'eval' for best build performance.

Hot reloading code is just one line in the beginning and one line at the end of each module so you might not need source maps at all.

Linking

If you are using npm link or yarn link for development purposes, there is a chance you will get error Module not found: Error: Cannot resolve module 'react-hot-loader' or the linked package is not hot reloaded.

There are 2 ways to fix Module not found:

  • Use include in loader configuration to only opt-in your app's files to processing.
  • Alternatively if you are using webpack, override the module resolution in your config:
{
  resolve: {
    alias: {
      'react-hot-loader': path.resolve(path.join(__dirname, './node_modules/react-hot-loader')),
    }
  }
}

And to make your linked package to be hot reloaded, it will need to use the patched version of react and react-dom, if you're using webpack, add this options to the alias config

{
  resolve: {
    alias: {
      'react-hot-loader': path.resolve(path.join(__dirname, './node_modules/react-hot-loader')),
      // add these 2 lines below so linked package will reference the patched version of `react` and `react-dom`
      'react': path.resolve(path.join(__dirname, './node_modules/react')),
      'react-dom': path.resolve(path.join(__dirname, './node_modules/react-dom')),
      // or point react-dom above to './node_modules/@hot-loader/react-dom' if you are using it
    }
  }
}

Preact

React-hot-loader should work out of the box with preact-compat, but, in case of pure preact, you will need to configure it:

  • create configuration file (setupHotLoader.js)
import reactHotLoader from 'react-hot-loader';
import preact from 'preact';

reactHotLoader.preact(preact);
  • dont forget to import it

Preact limitations

  • HOCs and Decorators as not supported yet. For Preact React-Hot-Loader v4 behave as v3.

React Native

React Native supports hot reloading natively as of version 0.22.

Using React Hot Loader with React Native can cause unexpected issues (see #824) and is not recommended.

Webpack plugin

We recommend using the babel plugin, but there are some situations where you are unable to. If so, try the webpack plugin / webpack-loader (as seen in v3).

Remember - the webpack plugin is not compatible with class-based components. The babel plugin will inject special methods to every class, to make class members (like onClick) hot-updatable, while the webpack plugin would leave classes as is, without any instrumentation.

class MyComponent extends React.Component {
  onClick = () => this.setState(); // COULD NOT UPDATE
  variable = 1; // this is ok
  render() {} // this is ok
}

But webpack-loader could help with TypeScript or spreading "cold API" to all node_modules.

It is possible to enable this loader for all the files, but if you use babel plugin, you need to enable this loader for react-dom only. Place it after babel-loader, if babel-loader is present.

// webpack.config.js
module.exports = {
  module: {
    rules: [
      // would only land a "hot-patch" to react-dom
      {
        test: /\.js$/,
        include: /node_modules\/react-dom/,
        use: ['react-hot-loader/webpack'],
      },
    ],
  },
};

Webpack plugin will also land a "hot" patch to react-dom, making React-Hot-Loader more compliant to the principles.

If you are not using babel plugin you might need to apply webpack-loader to all the files.

{
  test: /\.jsx?$/,
  include: /node_modules/,
  use: ['react-hot-loader/webpack']
},

Code Splitting

If you want to use Code Splitting + React Hot Loader, the simplest solution is to pick a library compatible with this one:

If you use a not-yet-friendly library, like react-async-component, or are having problems with hot reloading failing to reload code-split components, you can manually mark the components below the code-split boundaries.

// AsyncHello.js
import { asyncComponent } from 'react-async-component';

// asyncComponent could not `hot-reload` itself.
const AsyncHello = asyncComponent({
  resolve: () => import('./Hello'),
});

export default AsyncHello;

Note that Hello is the component at the root of this particular code-split chunk.

// Hello.js
import { hot } from 'react-hot-loader/root';

const Hello = () => 'Hello';

export default hot(Hello); // <-- module will reload itself

Wrapping this root component with hot() will ensure that it is hot reloaded correctly.

Out-of-bound warning

You may see the following warning when code-split components are updated:

React-Hot-Loader: some components were updated out-of-bound. Updating your app to reconcile the changes.

This is because the hot reloading of code-split components happens asynchronously. If you had an App.js that implemented the AsyncHello component above and you modified AsyncHello, it would be bundled and reloaded at the same time as App.js. However, the core hot reloading logic is synchronous, meaning that it's possible for the hot reload to run before the updates to the split component have landed.

In this case, RHL uses a special tail update detection logic, where it notes that an an update to a split component has happened after the core hot reloading logic has already finished, and it triggers another update cycle to ensure that all changes are applied.

The warning is informational - it is a notice that this tail update logic is triggered, and does not indicate a problem in the configuration or useage of react-hot-loader.

If the tail update detection is not something you want or need, you can disable this behavior by setting setConfig({ trackTailUpdates:false }).

Checking Element types

Because React Hot Loader creates proxied versions of your components, comparing reference types of elements won't work:

const element = <Component />;
console.log(element.type === Component); // false

React Hot Loader exposes a function areComponentsEqual to make it possible:

import { areComponentsEqual } from 'react-hot-loader';
const element = <Component />;
areComponentsEqual(element.type, Component); // true

Another way - compare "rendered" element type

const element = <Component />;
console.log(element.type === <Component />.type); // true

// better - precache rendered type
const element = <Component />;
const ComponentType = <Component />.type;
console.log(element.type === ComponentType); // true

But you might have to provide all required props. See original issue. This is most reliable way to compare components, but it will not work with required props.

Another way - compare Component name.

Not all components have a name. In production displayName could not exists.

const element = <Component />;
console.log(element.displayName === 'Component'); // true

This is something we did not solve yet. Cold API could help keep original types.

Webpack ExtractTextPlugin

webpack ExtractTextPlugin is not compatible with React Hot Loader. Please disable it in development:

new ExtractTextPlugin({
  filename: 'styles/[name].[contenthash].css',
  disable: NODE_ENV !== 'production',
});

Disabling a type change (❄️)

It is possible to disable React-Hot-Loader for a specific component, especially to enable common way to type comparison. See #991 for the idea behind ⛄️, and #304 about "type comparison" problem.

import { cold } from 'react-hot-loader';

cold(SomeComponent) // this component will ignored by React-Hot-Loader
<SomeComponent />.type === SomeComponent // true

If you will update cold component React-Hot-Loader will complain (on error level), and then React will cold-replace Component with a internal state lose.

Reach-Hot-Loader: cold element got updated

Disabling a type change for all node_modules

You may cold all components from node_modules. This will not work for HOC(like Redux) or dynamically created Components, but might help in most of situations, when type changes are not welcomed, and modules are not expected to change.

import { setConfig, cold } from 'react-hot-loader';
setConfig({
  onComponentRegister: (type, name, file) => file.indexOf('node_modules') > 0 && cold(type),

  // some components are not visible as top level variables,
  // thus its not known where they were created
  onComponentCreate: (type, name) => name.indexOf('styled') > 0 && cold(type),
});

! To be able to "cold" components from 'node_modules' you have to apply babel to node_modules, while this folder is usually excluded. You may add one more babel-loader, with only one React-Hot-Loader plugin inside to solve this. Consider using webpack-loader for this.

React-Hooks

React hooks are not really supported by React-Hot-Loader. Mostly due to our internal processes of re-rendering React Tree, which is required to reconcile an updated application before React will try to rerender it, and fail to do that, obviously.

  • hooks should work for versions 4.6.0 and above (pureSFC is enabled by default).
  • hooks will produce errors on every hot-update without patches to react-dom.
  • hooks may loss the state without patches to react-dom.
  • hooks does not support adding new hooks on the fly
  • change in hooks for a mounted components will cause a runtime exception, and a retry button (at the nearest class component) will be shown. Pressing a retry button will basically remount tree branch.

To mitigate any hook-related issues (and disable their hot-reloadability) - cold them.

  • cold components using hooks.
import { setConfig, cold } from 'react-hot-loader';
setConfig({
  onComponentCreate: (type, name) =>
    (String(type).indexOf('useState') > 0 || String(type).indexOf('useEffect') > 0) && cold(type),
});

API

hot(Component, options)

Mark a component as hot.

Babel plugin

Right now babel plugin has only one option, enabled by default.

  • safetyNet - will help you properly setup ReactHotLoader.

You may disable it to get more control on the module execution order.

//.babelrc
{
    "plugins": [
        [
            "react-hot-loader/babel",
            {
            "safetyNet": false
            }
        ]
    ]
}

Important

!! Use hot only for module exports, not for module imports. !!

import { hot } from 'react-hot-loader/root';

const App = () => 'Hello World!';

export default hot(App);

Keep in mind - by importing react-hot-loader/root you are setting up a boundary for update event propagation.

The higher(in module hierarchy) you have it - the more stuff would be updated on Hot Module Replacement.

To make RHL more reliable and safe, please place hot below (ie somewhere in imported modules):

  • react-dom
  • redux store creation
  • any data, you want to preserve between updates
  • big libraries

You may(but it's not required) place hot to the every route/page/feature/lazy chunk, thus make updates more scoped.

You don't need to wrap every component with hot, application works fine with a single one.

(old)hot(module, options)(Component, options)

Mark a component as hot. The "new" hot is just hidding the first part - hot(module), giving you only the second (App). The "new" hot is using old API.

import { hot } from 'react-hot-loader';

const App = () => 'Hello World!';

export default hot(module)(App);

AppContainer

Mark application as hot reloadable. (Prefer using hot helper, see below for migration details).

This low-level approach lets you make **hot **imports__, not exports.

import React from 'react';
import ReactDOM from 'react-dom';
import { AppContainer } from 'react-hot-loader';
import App from './containers/App';

const render = Component => {
  ReactDOM.render(
    <AppContainer>
      <Component />
    </AppContainer>,
    document.getElementById('root'),
  );
};

render(App);

// webpack Hot Module Replacement API
if (module.hot) {
  // keep in mind - here you are configuring HMR to accept CHILDREN MODULE
  // while `hot` would configure HMR for the CURRENT module
  module.hot.accept('./containers/App', () => {
    // if you are using harmony modules ({modules:false})
    render(App);
    // in all other cases - re-require App manually
    render(require('./containers/App'));
  });
}

areComponentsEqual(Component1, Component2)

Test if two components have the same type.

import { areComponentsEqual } from 'react-hot-loader';
import Component1 from './Component1';
import Component2 from './Component2';

areComponentsEqual(Component1, Component2); // true or false

setConfig(config)

Set a new configuration for React Hot Loader.

Available options are:

  • logLevel: specify log level, default to "error", available values are: ['debug', 'log', 'warn', 'error']
  • pureSFC: enable Stateless Functional Component. If disabled they will be converted to React Components. Default value: false.
  • ignoreSFC: skip "patch" for SFC. "Hot loading" could still work, with webpack-patch present
  • pureRender: do not amend render method of any component.
  • for the rest see index.d.ts.
// rhlConfig.js
import { setConfig } from 'react-hot-loader';

setConfig({ logLevel: 'debug' });

It is important to set configuration before any other action will take a place

// index.js
import './rhlConfig' // <-- extract configuration to a separate file, and import it in the beggining
import React from 'react'
....

Migrating from v3

AppContainer vs hot

Prior v4 the right way to setup React Hot Loader was to wrap your Application with AppContainer, set setup module acceptance by yourself. This approach is still valid but only for advanced use cases, prefer using hot helper.

React Hot Loader v3:

// App.js
import React from 'react';

const App = () => <div>Hello world!</div>;

export default App;
// main.js
import React from 'react';
import ReactDOM from 'react-dom';
import { AppContainer } from 'react-hot-loader';
import App from './containers/App';

const render = Component => {
  ReactDOM.render(
    <AppContainer>
      <Component />
    </AppContainer>,
    document.getElementById('root'),
  );
};

render(App);

// webpack Hot Module Replacement API
if (module.hot) {
  module.hot.accept('./containers/App', () => {
    // if you are using harmony modules ({modules:false})
    render(App);
    // in all other cases - re-require App manually
    render(require('./containers/App'));
  });
}

React Hot Loader v4:

// App.js
import React from 'react';
import { hot } from 'react-hot-loader';

const App = () => <div>Hello world!</div>;

export default hot(module)(App);
// main.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './containers/App';

ReactDOM.render(<App />, document.getElementById('root'));

Patch is optional

Since 4.0 till 4.8

Code is automatically patched, you can safely remove react-hot-loader/patch from your webpack config, if react-hot-loader is required before React in any other way.

Error Boundary is inside every component

Since 4.5.4

On Hot Module Update we will inject componentDidCatch and a special render to every Class-based component you have, making Error Boundaries more local.

After update we will remove all sugar, keeping only Boundaries you've created.

You can provide your own errorReporter, via setConfig({errorReporter}) or opt-out from root ErrorBoundaries setting errorBoundary={false} prop on AppContainer or hot. However - this option affects only SFC behavior, and any ClassComponent would boundary itself.

import { setConfig } from 'react-hot-loader';
import ErrorBoundary from './ErrorBoundary';

// ErrorBoundary will be given error and errorInfo prop.
setConfig({ errorReporter: ErrorBoundary });

If errorReporter is not set - full screen error overlay would be shown.

Setting global Error Reporter

Global Error Reporter would, created a fixed overlay on top the page, would be used to display errors, not handled by errorReporter, and any HMR error.

You may change, or disable this global error overlay

// to disable
setConfig({ ErrorOverlay: () => null });

// to change
setConfig({ ErrorOverlay: MyErrorOverlay });

The UX of existing overlay is a subject to change, and we are open to any proposals.

Known limitations and side effects

Note about hot

hot accepts only React Component (Stateful or Stateless), resulting the HotExported variant of it. The hot function will setup current module to self-accept itself on reload, and will ignore all the changes, made for non-React components. You may mark as many modules as you want. But HotExportedComponent should be the only used export of a hot-module.

Note: Please note how often we have used exported keyword. hot is for exports.

Note: Does nothing in production mode, just passes App through.

New Components keep executing the old code

There is no way to hot-update constructor code, as result even new components will be born as the first ones, and then grow into the last ones. As of today, this issue cannot be solved.

Troubleshooting

If it doesn't work, in 99% of cases it's a configuration issue. A missing option, a wrong path or port. webpack is very strict about configuration, and the best way to find out what's wrong is to compare your project to an already working setup, check out examples, bit by bit.

If something doesn't work, in 99% of cases it's an issue with your code. The Component didn't get registered, due to HOC or Decorator around it, which is making it invisible to the Babel plugin or webpack loader.

We're also gathering Troubleshooting Recipes so send a PR if you have a lesson to share!

Switch into debug mode

Debug mode adds additional warnings and can tells you why React Hot Loader is not working properly in your application.

import { setConfig } from 'react-hot-loader';
setConfig({ logLevel: 'debug' });

Contributors

This project exists thanks to all the people who contribute. Contribute. contributors

Backers

Thank you to all our backers! 🙏 Become a backer backers

Sponsors

Support this project by becoming a sponsor. Your logo will show up here with a link to your website. Become a sponsor

License

MIT

react-hot-loader's People

Contributors

akorchev avatar alexanderchr avatar alexkuz avatar bnaya avatar bradennapier avatar calesce avatar dependabot[bot] avatar eps1lon avatar gadicc avatar gaearon avatar gregberge avatar hedgerh avatar jeetiss avatar maratfakhreev avatar mkg0 avatar montogeek avatar nfcampos avatar rokt33r avatar silvenon avatar simonkberg avatar smelukov avatar syranide avatar taion avatar theajr avatar thekashey avatar tstirrat15 avatar unimonkiez avatar werme avatar wkwiatek avatar zachasme avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-hot-loader's Issues

Using within Gulp

I'm trying to get this running with Gulp. The problem I'm running into is when I set hot: true within my webpack.config.js file, hot reload doesn't work any longer. Instead I see in my browser 404s like GET http://localhost:8080/eceaeafceca5b3d53132.hot-update.json 404 (Not Found)

Webpack also started telling me that hot: true is deprecated and that instead I should use webpack.HotModuleReplacementPlugin(). Changing to that didn't seem to affect anything.

Support dynamic createClass calls if possible

Code like this will confuse the hell out of loader:

function createRedirectHandler(to) {
  return React.createClass({
    displayName: 'redirect-' + to,

    statics: {
      willTransitionTo: function (transition, params, query) {
        transition.redirect(to, params, query);
      }
    },

    render: function () {
      return null;
    }
  });
}

Currently the loader warns it can't properly handle dynamic createClass calls:

We should think if there are some ways to support these scenarios.

Stack overflow when hotifying class twice

Say I have A.js that looks like this:

module.exports = require('./B');

This will currently cause stack overflow in hot loader when changing B because it tries to hotify the same module twice, and uses patched version as source of truth.

Make it work with JSS

  • Don't module.hot.accept if we encountered no valid React classes
  • Hot-replace statics — not needed

Divert attention from hot loader in stack traces

If you have an error inside your module, stack trace will include replaceCreateClass on the top level which makes people assume the problem is due to hot loader:

Uncaught TypeError: undefined is not a function bundle.js:924
replaceCreateClass bundle.js:924
(anonymous function) bundle.js:900
__webpack_require__ bundle.js:400
fn bundle.js:58
replaceCreateClass bundle.js:696
SUCCESS_LEVELS bundle.js:534
__webpack_require__ bundle.js:400
fn bundle.js:58
(anonymous function) bundle.js:429
__webpack_require__ bundle.js:400
fn bundle.js:58
(anonymous function) bundle.js:420
(anonymous function)

Similarly, if you have an error inside your component's method, you will get not very helpful createHotClass at top level instead of your component's actual name:

TypeError: undefined is not a function
    at createHotClass.render (http://localhost:9000/scripts/main.js:5029:50)
    at null.render (http://localhost:9000/scripts/main.js:40230:40)
    at null.<anonymous> (http://localhost:9000/scripts/main.js:43386:35)
    at null._renderValidatedComponent (http://localhost:9000/scripts/main.js:46139:22)
    at null.<anonymous> (http://localhost:9000/scripts/main.js:42939:15)
    at null.mountComponent (http://localhost:9000/scripts/main.js:46139:22)
    at ReactComponent.Mixin._mountComponentIntoNode (http://localhost:9000/scripts/main.js:41932:26)
    at ReactReconcileTransaction.Mixin.perform (http://localhost:9000/scripts/main.js:35694:21)
    at ReactComponent.Mixin.mountComponentIntoNode (http://localhost:9000/scripts/main.js:41908:20)
    at Object.<anonymous> (http://localhost:9000/scripts/main.js:45259:26)

Errors in user code should look like errors in user code.
Hot loader should strive to seem transparent.

Bonus points if we can use component's displayName or filename in the stack trace (e.g. to have A.render instead of createHotClass.render).

Find a way to support 0.13 when it comes out

React 0.13 makes React.createClass optional.
We need to find a way for hot reloading to work with plain ES6 classes.

This way should not depend on replacing any specific regexes, potentially solving #33 and #24.
Instead, we may need to monkeypatch React.

It may also be worth again looking into approach originally suggested by Pete Hunt:

The problem is that references to component descriptors could be stored in any number of places. What we could do is wrap all components in "proxy" components which look up the "real" component in some mapping

Statics are not hot-replaced

I haven't had time to verify this yet, but it seems that statics are not hot-replaced with the rest of the class (or not static values at least).

replaceCreateClass added to source url for devtool=eval

When using devtool: 'eval', webpack displays the full resolved path with all loaders. /replaceCreateClass.js gets added for react-hot-loader which is irrelevant for the user and makes it unnecessarily long, perhaps it cannot be (easily) avoided, but I wanted to float it anyway. :)

MyFile.js?./~/react-hot-loader/replaceCreateClass.js!./~/jsx-loader?harmony:68

Stop errors from reloading the page?

Personally I would prefer (if it is possible) if run-time errors during hot replacement would not reload the page, allowing me to fix any silly mistake and keep my state. If I modify the code enough that it's broken, it's easy enough to just reload manually.

Function calls to previous versions need to be forwarded

Although component's prototype is overriden after an update, there may still exist references to old versions of methods. Consider this example:

  getInitialState: function () {
    return {
      message: 'Hi'
    };
  },

  componentDidMount: function () {
    window.setInterval(this.tick, 1000);
  },

  tick: function () {
    this.setState({
      message: 'I am Batman'
    });
  },

  render: function () {
    return <div>{this.state.message}</div>;
  }

If I edit the message inside tick to say I am Robin, it will not change because setInterval was called with the old version of the method. Overwriting prototype doesn't help because tick was already accessed and a reference to it is stored somewhere else.

To solve this problem, we need to reimplement createHotClass which, instead of adding a mixin, should return a proxy instance that forwards method calls to the freshest component prototype.

Getting close to 1.0

Preliminary 1.0 is released on NPM as 1.0.0-alpha.3.

It adds support for ES6 classes (against React master), fixing #39.

It also fixes #24 via opt-in Component = module.makeHot(Component, uniqueId?) that you can use even inside a function. (The default, however, has changed to only “hotify” module.exports.)

Some similar issues are solved as a side effect (#43, #33).

Known issues:

  • Autobinding of newly added methods doesn't work against React's master
  • When component is unmounted, its methods may keep firing, potentially causing exceptions
  • It's slightly awkward to type if (module.makeHot) X = module.makeHot(X), can we think of a better API (note that this is only needed if you want to hotify something other than module.exports)? We also want to make sure no one ever forgets the check so it doesn't crash in production—maybe noop would be better.
  • Update docs, examples and blog entry

You can help by doing npm install --save-dev [email protected] and reporting issues in your projects!

Add tests

It would be nice to test that makeComponentUpdater methods behave correctly for:

  • changing a method on the fly
  • adding a method on the fly
  • removing a method on the fly
  • calling an "old" method (which should call the new version when available, and noop if it has been deleted)

flux-hot-reload?

This is more of a question. Do you think we could apply parts of this technology to flux? e.g. having flux stores be hot-reloaded? I guess that would require some kind of API on the stores, e.g. a "reset" method so that the stores can be notified of the hot reload.

Any thoughts or suggestions on this?

Add support for coffee-script

I can't seem to get this working when using coffee-script. Changing the "createClass" regExp to match a CS one only seems to be part of the problem. The issue I'm having is that the code replacement seems to be made before the coffee-loader kicks in. Don't know all that much about the internals of loaders so I can't investigate more than that at the moment.

Uncaught TypeError: undefined is not a function in ReactEmptyComponent.js:25

As reported by @natew here:

So I split up an old project into some smaller pieces, and with the app I've been running I started getting this error that I was having trouble debugging. Lo and behold disabling hot components fixes it, so I'm guessing it has something to do with react-hot-loader. Unfortunately it probably won't be open sourced for another month or so.

But, I can upload info on the error and setup. I actually had it fixed at one point because I was loading multiple versions of React, but I've triple checked that now and it seems my bundle is clean.

This is with react 0.12.1, hot-loader alpha.3.

Error comes on whenever I first require('react'):

Uncaught TypeError: undefined is not a function
ReactEmptyComponent.js:25

That line is this:

var ReactEmptyComponentInjection = {
 injectEmptyComponent: function(emptyComponent) {
   component = ReactElement.createFactory(emptyComponent);
 }
};

ReactElement is an empty object for some reason, even though when looking through the requires, it seems it's loading the file that sets up all those functions like createFactory.

Anyway, if I disable it seems to work. I'll try rolling back maybe and seeing if it's the latest version that causes this.

I have an idea for a fix for this, will get back to you soon.

Uncaught TypeError: object is not a function

When running with react-hot-loader I'm getting:

Uncaught TypeError: object is not a function

At the last line of the following:

/* WEBPACK VAR INJECTION */(function(module) {/* REACT HOT LOADER */ if (true) { (function () { var ReactHotAPI = __webpack_require__(9),     RootInstanceProvider = __webpack_require__(4);  if (typeof ReactHotAPI === "function" && typeof RootInstanceProvider === "object") { module.makeHot = module.hot.data ? module.hot.data.makeHot : ReactHotAPI(RootInstanceProvider.getRootInstances); } })(); }

module.exports = function(module) {
    if(!module.webpackPolyfill) {
        module.deprecate = function() {};
        module.paths = [];
        // module.parent = undefined by default
        module.children = [];
        module.webpackPolyfill = 1;
    }
    return module;
}


/* REACT HOT LOADER */ if (true) { (function () { module.hot.accept(function (err) { if (err) { console.error("Cannot not apply hot update to " + "module.js" + ": " + err.message); } }); module.hot.dispose(function (data) {   data.makeHot = module.makeHot; }); if (module.exports && module.makeHot) { var freshModuleExports = module.exports; module.exports = module.makeHot(module.exports, "__MODULE_EXPORTS"); for (var key in module.exports) { if (freshModuleExports[key] && freshModuleExports.hasOwnProperty(key)) { module.exports[key] = module.makeHot(freshModuleExports[key], "__MODULE_EXPORTS_" + key); } } } })(); }
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(6)(module)))

Hard reloading instead of hot loading

Not sure if this is related to #55 because I did recently split out my app into separate modules and so it's symlinking pieces of the app. I've also made quite a lot of changes though lately so not sure where to start here.

Here's a screen of my setup, the dirs with the extra icon are symlinked:

screen shot 2015-01-11 at 2 07 55 pm

I'm running my server in a separate repo, which symlinks in it's dependencies into server_modules in the app folder.

What I see happening in the app now is the "Reloading..." comes up just fine. Before it would even show the "reloaded" message but nothing would change. But recently it started refreshing the browser instead, no matter what I change. So I see Reloading... then browser refreshes.

I had tried the NewWatching plugin, but reverted that. Other than that not sure whats new. Using 1.0.7 of react-hot-loader.

Breaks for some imports

If I swap React = require 'react' out for either R = require 'react' or {createClass} = require 'react' (all Coffeescript), HMR breaks.

Looks like you're matching on the require regex, so I'm not sure how to solve this...

Dynamic Public Path

Would this be possible with the hot loader? We are currently using the webpack_public_path to set it dynamically in our entry point, but can't seem to figure out how to do it with the hot loader.

Uncaught TypeError: object is not a function makeNotifier.js:2

You helped me a while back getting started and I've slowly gotten it working as it should (it reloads correctly before adding react-hot-loader), but whenever I add react-hot-loader to loaders, I get the following error when loading the bundle.

Uncaught TypeError: object is not a function makeNotifier.js:2

var notifier = require(".../hotloader-test/node_modules/react-hot-loader/makeNotifier.js")("makeNotifier.js", "none");

This is on a minimal test-project:
config: https://gist.github.com/syranide/73cb15ce83f2bc070b2d
devserver: https://gist.github.com/syranide/1713e9065e7221b3e64e

Coffeescript/cjsx support?

I'm trying to use this module with cjsx-loader/coffee-react-transform and am hitting an error.

ERROR in ../react-hot-loader/replaceCreateClass.js!./react_components/hello_world.cjsx
Module parse failed: /Users/kylemathews/programs/react-hot-loader/replaceCreateClass.js!/Users/kylemathews/programs/coffee-react-quickstart/react_components/hello_world.cjsx Line 2: Unexpected token ILLEGAL
You may need an appropriate loader to handle this file type.
| module.exports = function replaceCreateClass(createClassProxy) {
| # @cjsx React.DOM
|
| React = require 'react'
 @ ./react_components/hello_world.cjsx 4:17-182 8:6-171

I'm brand new to Webpack so am perhaps missing something obvious. In my webpack.config.js I tried switching the order of loaders for .cjsx so the cjsx-loader went first so react-hot would get javascript but that didn't work.

If code needs written, I'd be happy to write it if given a few pointers.

Error with react-router

Use react-router and got an error on update:

Cannot not apply hot update to app.js: Found duplicate displayName in createRouter.js: "Router". react-hot-loader uses displayName to distinguish between several components in one file.

var routes = (
    <Route handler={App}>
        <Route name="aaa" path="/aaa" handler={Aaa} />
        <Route name="bbb" handler={Bbb}>
            <DefaultRoute handler={Ccc} />
            <Route name="ccc" path="/bbb/ccc" handler={Ccc} />
            <Route name="ddd" path="/bbb/ddd/:id" handler={Ddd} />
        </Route>
    </Route>
);

Router.run(routes, function(Handler, state) {
    React.render(<Handler flux={flux} />, document.getElementById('app'));
});

Storing components by 'displayName' make wrapping unavailable

In Moreartyjs we use text fields wrappers to render with requestAnimationFrame. React does not support setting displayName property directly into wrapped component (it throws Invariant Violation: addComponentAsRefTo(...): Only a ReactOwner can have refs...)

So now hot loader claims it already have an undefined component:
Found duplicate displayName in DOM.js: "undefined". react-hot-loader uses displayName to distinguish between several components in one file.

Is it possible to generate component unique id and use it instead displayName?

Webpack Server Crashing

Not sure if this is an issue with the Webpack server or the hot loader. Seems after I require a certain number of files it crashes. If I remove one require call everything works fine.

node(51175,0x7fff781cb310) malloc: *** error for object 0x10641d5a9: pointer being freed was not allocated *** set a breakpoint in malloc_error_break to debug

Hot replace component when a require is updated

I imagine this may be technically prohibitive, but I don't really know so:

If a component uses say require('file?./myimage.png') and the file is updated, the component is not hot-replaced. Whereas the same action for CSS-files causes them to be hot-replaced.

Spread the word

The rough edges are fixed and the react-hot-loader is now pretty stable to use.

However most of React community have either not heard about it, think switching to Webpack is difficult or that react-hot-loader is not mature enough.

I think live-editing is the future of
front-end development and I'm full
of enthusiasm for this approach, but this projects needs more community awareness.

I wrote an explanatory blog post but not many have seen it yet. I'm also thinking about making some other impressive videos showcasing using this with some cool community React projects.

If you want to make "cmd+s" the new "cmd+r", help spread the word. I can't do this alone.

Working with ES6 exports

I am using 6to5 and I export my components with this syntax:

export default MyComponent;

which is then transpiled to

exports["default"] = MyComponent;

To make component hot loadable I have to write (as per 1.0 doc):

if (module.makeHot) module.makeHot(MyComponent);

thing that I'd love to skip :-) Is there a way to make react-hot-loader reload also es6 exports – without the if (module.makeHot)?

Update 'Running Example' in readme

Within the readme under the header 'Running Example' one of the steps reads:

'webpack-dev-server --hot'

Unless you've already globally installed webpack-dev-server this command won't run.

Not sure how you'd want to fix so haven't submitted a PR. Options are either tell the user to install the module globally within those steps or add the module as a (dev?) dependency to the project. I'm happy to do the small bit of work for whichever solution you prefer.

Uncaught TypeError: object is not a function Scheduler.js?bb06:20

I'm having hard times debugging this error happening on the browser at the first page load when I include react-hot-reload in my webpack config.

After starting the server, with no errors/warning from webpack, I open the page and appears this error:

Uncaught TypeError: object is not a function Scheduler.js?bb06:20`

Then, I save a file and the [HMR] in the console states: App is up to date. I refresh the browser and the error disappears.

So I started removing everything from my sources to find the culprit. After some hours, there's only one line in my app left:

// client.jsx
var Router = require('react-router');

uhm looking to the call stack, it stops here:

var Queue = __webpack_require__(180);
        /**
         * Async task scheduler
        function Scheduler(async) {
            this._async = async;
            this._queue = new Queue(15); // Object is not a function

where Queue is indeed an empty object. Uh? This sounds like a circular dependency. If I search for the module 180, I see:

/* 180 */
/***/ function(module, exports, __webpack_require__) {

    /* (ignored) */

/***/ },

Why when I save a file and refresh, the error disappears? What would you do?

Access Control Allow Origin?

What is might be the problem here? Do I need to set some setting on the dev server for this to work?

Failed to load resource: the server responded with a status of 404 (Not Found)
10.0.1.27/:1 XMLHttpRequest cannot load http://10.0.1.27:9090/build/2590dcf104e749604e15.hot-update.json. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://10.0.1.27:8080' is therefore not allowed access. The response had HTTP status code 404.

Autobinding breaks cleanup in componentWillUnmount

Say I have code such as this:

componentDidMount: function () {
  window.addEventListener('scroll', this.handleWindowScroll);
},

componentWillUnmount: function () {
  window.removeEventListener('scroll', this.handleWindowScroll);
}

If I live-edit this component, the handler won't be removed on unmounting and will probably cause exceptions later.

The reason is that react-hot-loader tries to be smart and auto-bind new methods, but while doing so, it also re-binds old methods, changing their references.

We need to change auto-binding behavior in 1.0. While we're at it, we also want to adjust to changes in React 0.13, which completely removes _bindAutoBindMethods we're currently using.

Update to React 0.11.x

Hi,
Is there any reason not to upgrade React dependency?

If there's not then I could make a PR.

Isomorphic Example?

Using the hot-loader with server-side react rendering is tricky. I'm not sure if this is the best approach, but here's what I'm trying to do:

https://github.com/irvinebroque/isomorphic-hot-loader

There's a bunch of configuration complexity here -- hotLoadServer.js runs on a different port than the main server, the path to client-side assets are different in development vs. production, you can't run a watcher process on the hotLoadServer (otherwise the web sockets die), timing issues between nodemon and webpackDevServer (since they're both essentially watching the same set of files), etc.

I still have some work to do to get it all working properly together, but when I finish, I'd be happy to put it up as an example starter project.

(or, if anyone's seen a better implementation of this, let me know!)

React Hot Loader doesn't work if React is external (CDN)

Hi there,

Given:

module.exports = {
  externals: {
    react: 'React',
    'react/addons': 'React',
    'react-router': 'ReactRouter'
  }, ...

and:

    <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/react/0.12.2/react-with-addons.min.js"></script>
    <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/react-router/0.11.6/react-router.min.js"></script>

in the html. Then the React Hot Loader doesn't work. I see this in the console:

[HMR] Waiting for update signal from WDS...
[WDS] Hot Module Replacement enabled.
[WDS] App updated. Recompiling...
[WDS] App updated. Recompiling...
[WDS] App hot update...
[HMR] Checking for updates on the server...
[HMR] Updated modules:
[HMR]  - 20
[HMR] App is up to date.

but I don't see the changes getting reflected in the view. It'd work however If I commented out the externals configuration and the script lines in the html (getting webpack to use NPM react packages).

The console output is almost the same as above except that [HMR] - 20 was [HMR] - 19.

Any suggestions on how to debug/fix this would be welcome. Confirmation that anyone has successfully used React Hot Loader with external React resources would be great too.

Support React 0.12

React 0.12 is a big cleanup release and hot loader will probably break for some reason.
Need to investigate & update if necessary.

Don't hot-reload files without components

It is convenient to put react-hot next to jsx in config, but we need to be more careful with the files we can module.hot.accept.

If a JSX file has no component in it (e.g. App or if the app is not fully React, some code hosting React components), we shouldn't module.hot.accept it.

Usage with an existing webserver

Before anything else, I wanted to express just how awesome I think this is.

That aside, I ran into some issues getting this to work in the kind of setup I wanted. I have an Express server that I want to use as a RESTful API and have the / route serve the React app. In prod, I just have it build out and serve the static assets, so there's no problem there.

However, I found it a little tricky to get the hot reload working with that kind of setup in development.

I saw this section on using the webpack dev server with an existing server on the docs, and that worked well enough for basic usage, but hot reloading failed since it would try to make requests against my main server rather than the webpack one for hot reload updates.

I managed to work around this by defining a set of webpack routes to proxy through all requests for static assets.

In /server/routes/webpack.js, I have:

var express = require('express')
  , request = require('request')
  , router = express.Router();

var webpackServerUrl = 'http://localhost:3001/';

// Proxy all requests through to the webpack server
router.get('/*', function(req, res) {
  request(webpackServerUrl.concat(req.baseUrl, req.url))
  .pipe(res);
});

module.exports = router;

and in /server/app.js:

// Use webpack server to serve static assets in development and express.static 
// for all other stages
if (app.get('env') === 'development') {
  var webPackServer = require('./routes/webpack');
  app.use('/scripts', webPackServer);
  app.use('/styles', webPackServer);
} else {
  app.use(express.static(path.join(__dirname, '..', 'build', 'client')));
}

And this is a rough skeleton of how things are structured:

.
├── client
│   ├── components
│   │   └── MyView.jsx
│   └── app.js
├── package.json
├── server
│   ├── app.js
│   ├── bin
│   │   ├── webpackDevServer
│   │   └── appServer
│   ├── middleware
│   │   └── errorHandler.js
│   ├── routes
│   │   ├── index.js
│   │   ├── users.js
│   │   └── webpack.js
│   ├── services
│   └── views
│       ├── error.jade
│       ├── index.jade
│       └── layout.jade
├── styles
│   └── app.scss
└── webpack.config.js

While this works, it seems like it's the wrong way to do it. I imagine this is a fairly common use-case, but I couldn't find any decent examples to support this. Is there a better approach to this than setting up a proxy like that?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.