React-hot-loader: Hot loader bundled in production

Created on 3 Jul 2017  ·  34Comments  ·  Source: gaearon/react-hot-loader

Description

All the assets from react-hot-loader seems to bundle in production builds. I might have misinterpreted it but they should be omitted right?

Expected behavior

Production build without ´redbox-react´, react-proxy etc

Actual behavior

´redbox-react´, react-proxy get bundled in production build
image

Environment

React Hot Loader version: v3.0.0-beta.7

Run these commands in the project folder and fill in their results:

  1. node -v: 8.1.0
  2. npm -v: 5.0.3

Then, specify:

  1. Operating system: MacOS
  2. Browser and version: Chrome 59

Reproducible Demo

https://github.com/alexandernanberg/minimal-react-boilerplate

In webpack.config.js add webpack-bundle-analyzer again, which is currently out commented

Run yarn build and look at the outout from the bundle analyzer

Most helpful comment

Added that to my webpack config but I still seem to get the dependencies in my bundle. Anyone else seeing this as well or am I doing something wrong in my config?

All 34 comments

Add this to your configuration:
https://webpack.js.org/guides/production/#node-environment-variable

plugins:[
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('production')
    })
  ]

Added that to my webpack config but I still seem to get the dependencies in my bundle. Anyone else seeing this as well or am I doing something wrong in my config?

@alexandernanberg I am having the same issue on my production bundle. Even thou I've the NODE_ENV set to production, it seems like react-hot-loader is bundling its dependencies (like the redbox-react).

I have the same issue .
I have add
new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') })
in my webpack production config ,and use cross-env to set the NODE_ENV=production

Having the same issue here. A temporary solution would be to have two entry files, one for production and one for dev (hot reloading):

entryProd.tsx

import * as React from "react";
import * as ReactDOM from "react-dom";
import App from "./App";

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

entryDev.tsx

import * as React from "react";
import * as ReactDOM from "react-dom";
import { AppContainer } from "react-hot-loader";
import App from "./App";

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

if (module.hot) { module.hot.accept("./App", () => { render(App); }); }

In Webpack config:

entry: {
    "app": process.env.NODE_ENV !== "production"
      ? ["react-hot-loader/patch", "./src/_frontend/entryHot.tsx"]
      : ["./src/_frontend/entryProd.tsx"],
  },

Obviously not ideal if your entry files contain a lot of other logic (can try put that into a common file required in both maybe), but a way forward for now at least.

I have resolve this issue,while I update all packages and resolve it
https://github.com/bestsuperdev/react-less-boilerplate2

windows10
node v8.1,3
npm v5.0.3

I am also having this issue and would prefer not to have two entry files for each of my applications.

@alexanderchr I believe the issue is that you've got react-hot-loader/patch as part of your entry for both your dev and prod builds in your webpack.config.js. You should not include it in your prod build, so really that line should be conditional. Hopefully that resolves your issue (and others).

I'm seeing this too — I've got new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') }) set in my production webpack config file, and only specify react-hot-loader/patch as an entry in my development webpack config file.

What's the reason JSON.stringify('production') is used? It seems redundant.

Same issue for me. I would like to not have two entry points. Any suggestions?

Maybe it will be helpful. I had the same issue and it was because in my .babelc was plugin
"react-hot-loader/babel". After I removed this list it's fixed. I needed it just for building project and not developing so it's okay for me. I think it's question about production .babelrc file.

I'm not using the babel plugin and still experience this issue. My only solution currently is to create separate dev and prod entry points.

To close this issue and resume, add to your Webpack config:

plugins:[
  new webpack.DefinePlugin({
    'process.env.NODE_ENV': JSON.stringify('production')
  })
]

And then don't forget to bundle with the correct environment:

NODE_ENV=production webpack

@neoziro can you answer the question I have above? Why not just this?

plugins:[
  new webpack.DefinePlugin({
    'process.env.NODE_ENV': 'production'
  })
]

@alasdairhurst From https://webpack.js.org/plugins/define-plugin/

Note that because the plugin does a direct text replacement, the value given to it must include actual quotes inside of the string itself. Typically, this is done either with either alternate quotes, such as '"production"', or by using JSON.stringify('production').

In your case you could use '"production"', but that could confuse somebody

@alasdairhurst you can do this if you use two separated Webpack config, one for development and one for production. It depends from your configuration. Setting NODE_ENV to production even in development will disable React warnings and React Hot Loader.

Also as mentioned by @montogeek, you have to add quotes.

@alexandernanberg Do you know the root cause and fix that?

@starandtina Not sure, my guess is that some module accidentally broke tree shaking or was not reading the env variable correctly. I've not seen this problem for a while now though

@alexandernanberg Thanks! I had removed RHL in order to avoid the issue. :)

If someone having this issue, just open your bundle source code.
Try to find https://github.com/gaearon/react-hot-loader/blob/master/src/patch.js, it must look like

/* eslint-disable global-require */

if (true) {
  module.exports = __webpack_require__("./node_modules/react-hot-loader/lib/patch.prod.js");
} else {
  module.exports = require('./patch.dev');
}

After uglify it will look like

function(e,o,s){"use strict";e.exports=s("./node_modules/react-hot-loader/lib/patch.prod.js")}

If NOT - that is the issue. Webpack does not reduce !module.hot || process.env.NODE_ENV === 'production' to the true of false and inject both version of a patch file.

PS: And the first piece of code I've got from debug __build__. As long module.hot is not defined
PPS: Tree shaking is not defined for commonjs(require) modules. You need dead code elimination.
PPPS: I've used NamedModulesPlugin to name modules.

Be sure to not include new webpack.HotModuleReplacementPlugin() in production.

To do that simply:

plugins: [
  ...(process.env.NODE_ENV !== 'production' ? [new webpack.HotModuleReplacementPlugin()] : []),
],

And to have this plugin:

plugins:[
  new webpack.DefinePlugin({
    'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
  })
]

Last step, be sure to build with the correct environment:

NODE_ENV=production webpack

Still have the issue.

cross-env NODE_ENV=production npm run babel is ok

@ganxunzou nope, doesn't work for me, still have the issue,

This code is added by react-hot-loader/babel.

In my case I solved this by conditionally adding/removing the babel plugin depends on the ENV.

IMHO this behaviour should be changed, and if babel plugin detects production environment it should simply not load.

It does it. __if__ you set env before the build.
See this PR - https://github.com/gaearon/react-hot-loader/pull/1119/files

Ah, sorry. OK, makes sense now. Didn't read the sources, just saw a bunch of code there and assumed it is similar to the dev one...

I have the same problem. Hot load mode is not used in both development mode and production mode.
In doc, when the mode is set production Sets process.env.NODE_ENV on DefinePlugin to value production.
So I think we don't need set process.env.NODE_ENV.
But how can I remove the __REACT_HOT_LOADER__ code?
image

That’s babel plugin, and by a some reason it thinks it’s dev time.

This code is added by react-hot-loader/babel.

In my case I solved this by conditionally adding/removing the babel plugin depends on the ENV.

IMHO this behaviour should be changed, and if babel plugin detects production environment it should simply not load.

did the same and it works! thanks

as an example here is my .babelrc.js

module.exports = api => {
  const plugins = ["@babel/plugin-syntax-dynamic-import"];

  // inject react-hot-loader babel plugin in development only
  if (api.env("development")) {
    plugins.unshift("react-hot-loader/babel");
  }
  return {
    presets: [
      [
        "@babel/preset-env",
        {
          modules: false,
          useBuiltIns: "usage"
        }
      ],
      "@babel/preset-react"
    ],
    plugins
  };
};

and here is my relevant part in webpack.config.js

  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        use: "babel-loader",
        exclude: /node_modules/
      }
    ]
  }

this is how I run my build process in package.json:

  "scripts": {
    "start": "webpack-dev-server --hot --env development"
  }

I have found the reason that in my project I have import third part UI component. They bundle incorrectly

@bboydflo Your solution worked for me too, with a minor adjustment. The api.env call in the babel config wasn't picking up on the correct environment, just "development". In case anyone runs into a similar scenario, I had to do the following:

  1. Export a function from my webpack.config.js: module.exports = env => ({ // config
  2. Set the mode based on the env value (since webpack only supports none, development and production:
// webpack.config.js
mode: env === "production" ? env : "development"
  1. Update the babel loader config to set the babel envName based on the webpack env, letting this pass through as you may have other environment names in your babel config e.g. test:
// webpack.config.js
{
    test: /\.js$/,
    exclude: /node_modules/,
    use: [
        {
            loader: 'babel-loader',
            options: { envName: env },
        },
    ],
},
  1. Update webpack related package.json scripts to set the env flag:
// package.json
"start": "webpack-dev-server --env development",
"build": "webpack --env production",

Also do not use the webpack.HotModuleReplacementPlugin for your production build, this appeared to be root cause for all that code added in the first place.

I haven't written against React for some time, thus revisiting webpack setup for all-things-react.

As this closed post comes high in search for current state for excluding react-hot-loader in nondevelopment, I'll give my 2 cents how to exclude any react-hot-loader stuff when not on development tier without having to maintain separate configuration files.

The baseline

Count to four according to docs.

_.babelrc.js_

Add react-hot-loader/babel plugin.

_webpack.front.config.js_

react-hot-loader/patch in entry and @hot-loader/react-dom in aliases.

Parts of webpack config

// ...

const development = process.env.NODE_ENV === 'development';
const testing = process.env.NODE_ENV === 'testing';
const staging = process.env.NODE_ENV === 'staging';
const production = process.env.NODE_ENV === 'production';
const devServerRunning = !!process.env.WEBPACK_DEV_SERVER;

// ...

  entry: {
    index: [
      'react-hot-loader/patch',
      // ... any other stuff
      path.resolve(__dirname, 'src/index.js')
    ]
  },

// ...

  resolve: {
     // ...
    alias: {
      // ...
      'react-dom': '@hot-loader/react-dom'
    }
    // ...
  }

// ...

config.devServer = {
  // ...

  hot: true,

  // ...
};

// ...

if (development && devServerRunning) {
  config.plugins.push(new webpack.HotModuleReplacementPlugin());
}

// ...

config.plugins.push(new webpack.DefinePlugin({
  'process.env': {
    NODE_ENV: (development) ? JSON.stringify('development') : JSON.stringify('production'),
    BROWSER: true
  },
  __CLIENT__: true,
  __SERVER__: false,
  __DEVTOOLS__: development,
  __DEV__: development,
  __PROD__: !development,
  __DEVELOPMENT__: development,
  __TESTING__: testing,
  __STAGING__: staging,
  __PRODUCTION__: production
}));

// ...

_App.js_

Export component as hot

'use strict';

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

class App extends Component {
  constructor (props) {
    console.log('App constructor');
    super(props);
    this.state = {myValue: 1};
    this.onClickHandler = this.onClickHandler.bind(this);
  }

  onClickHandler () {
    this.setState({myValue: this.state.myValue + 1});
  }

  render () {
    console.log('App render');
    const {myValue} = this.state;
    return (
      <div
        onClick={this.onClickHandler}
        className="template-component"
      >
        {`I am App and this is my value: ${myValue}`}
      </div>
    );
  }
}

export default hot(App);

_index.js_

/* global __DEVELOPMENT__ */

'use strict';

import React from 'react';
import ReactDOM from 'react-dom';
import App from './Containers/App/App.js';

ReactDOM.render(
  <App />,
  document.querySelector('.app')
);

if (__DEVELOPMENT__) {
  console.log('I\'m in development!');
  window.React = React;
}

When building setup above for nondevelopment tier bundle will hold

react-hot-loader/dist
react-hot-loader/root.js
react-hot-loader/patch.js

Getting rid of it in non-development

Simple edit in webpack config that conditionally excludes entry and switches back to nonpatched react-dom

_webpack.front.config.js_

// ...

  entry: {
    index: [
      (development) ? 'react-hot-loader/patch' : null,
      // ... any other stuff
      path.resolve(__dirname, 'src/index.js')
    ].filter((e) => e !== null)
  },

// ...

  resolve: {
     // ...
    alias: {
      // ...
      'react-dom': (development) ? '@hot-loader/react-dom' : 'react-dom'
    }
    // ...
  }

// ...

yields that nondevelopment tier bundle will hold now only

react-hot-loader/root.js

In order to get rid of that last one bit one has to switch from hot export to hot import, as conditional imports (requires) are possible which in conjunction with TerserPlugin / dead code stripping makes this work.
Thus avoiding react-hot-loader/root HOC in root component and conditionally wrapping it in react-hot-loader/AppContainer.

_App.js_

'use strict';

import React, {Component} from 'react';

class App extends Component {
  constructor (props) {
    console.log('App constructor');
    super(props);
    this.state = {myValue: 1};
    this.onClickHandler = this.onClickHandler.bind(this);
  }

  onClickHandler () {
    this.setState({myValue: this.state.myValue + 1});
  }

  render () {
    console.log('App render');
    const {myValue} = this.state;
    return (
      <div
        onClick={this.onClickHandler}
        className="template-component"
      >
        {`I am App and this is my value: ${myValue}`}
      </div>
    );
  }
}

export default App;

_index.js_

/* global __DEVELOPMENT__ */

'use strict';

import React from 'react';
import ReactDOM from 'react-dom';
import App from './Containers/App/App.js';

if (__DEVELOPMENT__ && module.hot) {
  const AppContainer = require('react-hot-loader').AppContainer;
  const renderHot = (Component) => {
    ReactDOM.render(
      <AppContainer>
        <Component />
      </AppContainer>,
      document.querySelector('.app')
    );
  };
  renderHot(App);
  module.hot.accept('./Containers/App/App.js', () => {
    renderHot(App);
  });
}
else {
  const renderCold = (Component) => {
    ReactDOM.render(
      <Component />,
      document.querySelector('.app')
    );
  };
  renderCold(App);
}

if (__DEVELOPMENT__) {
  console.log('I\'m in development!');
  window.React = React;
}

Tadā.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

esturcke picture esturcke  ·  3Comments

jljorgenson18 picture jljorgenson18  ·  3Comments

niba picture niba  ·  4Comments

zlk89 picture zlk89  ·  3Comments

mtscout6 picture mtscout6  ·  3Comments