2
votes

I am currently trying to set up HOT MODULE REPLACEMENT with a my webpack project. At the moment when i make changes to a react component in the source code, I am seeing the module reload in the browser without a refresh, followed by a full refresh of the page.

Here is what I am seeing in the console.

WDS seems to be running TWICE?

then these two lines in the console:

  • List item

dev-server.js:28 [HMR] Cannot find update. Need to do a full reload!(anonymous function) @ dev-server.js:28(anonymous function) @

  • List item

index.js:238request.onreadystatechange @ index.js:37 dev-server.js:29 [HMR] (Probably because of restarting the webpack-dev-server)

As you can see the 'client:37 [WDS] App updated. Recompiling...' seems to be run TWICE... which is maybe why it is both hot swapping and then doing a full reload.

Any ideas?

Here is my webpack.config

module.exports = {
  devtool: 'eval',
  cache: true,

  entry: {
    index: [
        'react-hot-loader/patch',
        'webpack-dev-server/client?http://localhost:8080',
        'webpack/hot/only-dev-server',
        './js/src/index.js'
    ],
    login: './login/index.js',
  },

  output: {
    path: path.join(__dirname, 'dist'),
    publicPath: '/dist/',
    filename: '[name].js',
    chunkFilename: '[name].js'
  },

  module: {

    loaders: [

      // Transpile ES6 JSX to ES5 JS
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        loader: 'babel',
      },

      // SCSS
      {
        test: /\.scss$/,
        loaders: [
          'style',
          'css?importLoaders=1&localIdentName=[local]-[hash:base64:5]',
          'postcss-loader',
          'resolve-url',
          'sass'
        ]
      },

        {
            test: /\.json$/,
            loaders: ['json-loader'],
        }
    ]

  },

  postcss: function () {
    return {
      defaults: [autoprefixer],
      cleaner:  [autoprefixer({ browsers: ['last 2 versions'], cascade: true })]
    };
  },

  plugins: [
    new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /en|de|fr|zh|ja|it/),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.IgnorePlugin(/ReactContext/),
    new webpack.DefinePlugin({ 'process.env.apiEndPointUrl': '"'+apiEndPointUrl+'"' }),
    new JsonBundlerPlugin({
        fileInput: '**/locales/*.json',
        omit: /\/locales|\/?components|\/?services|\/?scenes|\/?features/g,
        rootDirectory: 'src'
    }),
  ],

  resolve: {
    extensions: ['', '.js', '.json', '.jsx']
  }
};

Here is the devServer.js file i'm running in my npm task.

const webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');
const opener = require('opener');
const config = require('./webpack.config.js');
const host = 'localhost';
const port = 8080;

new WebpackDevServer(webpack(config), {
        publicPath: config.output.publicPath,
        hot: true,
        historyApiFallback: true,
        stats: {
            colors: true // color is life
        },
    })
    .listen(port, host, (err) => {
        if (err) {
            console.log(err);
        }
        console.log(`Listening at ${host}:${port}`);
        opener(`http://${host}:${port}`);
    });
1
I've been trying to figure out the same error for months. It's super annoying. I'll let you know if I figure it out.AjaxLeung

1 Answers

1
votes

What you and I were both trying to do was use code snippets meant for webpack 2 with a webpack 1 install and config file.

My solution was to closely follow the webpack 2 guide for React HMR found here: https://webpack.js.org/guides/hmr-react/

npm i --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react babel-preset-stage-2 [email protected] [email protected] [email protected]

npm i --save react react-dom

.babelrc

{
  "presets": [["es2015", {"modules": false}], "stage-2", "react"],
  "plugins": ["react-hot-loader/babel"]
}

webpack.config.js

const { resolve } = require('path');
const webpack = require('webpack');

const publicPath = '/';
const contentBase = resolve(__dirname, 'dist');

module.exports = {
  entry: [
    'react-hot-loader/patch',
    'webpack-dev-server/client?http://localhost:8080',
    'webpack/hot/only-dev-server',
    './index.js'
  ],
  output: {
    filename: 'bundle.js',
    path: contentBase,
    publicPath: publicPath
  },
  devtool: 'inline-source-map',
  devServer: {
    hot: true,
    contentBase: contentBase,
    publicPath: publicPath
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        use: ['babel-loader'],
        exclude: /node_modules/
      }
    ],
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NamedModulesPlugin()
  ],
  resolve: {
    extensions: ['.json', '.js', '.jsx']
  }
};

Now here's the new and important part

index.js

import React from 'react';
import ReactDOM from 'react-dom';

import { AppContainer } from 'react-hot-loader';
// AppContainer is a necessary wrapper component for HMR

import App from './components/App';

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

render(App);

// Hot Module Replacement API
if (module.hot) {
  module.hot.accept('./components/App', () => {
    const NewApp = require('./components/App').default
    render(NewApp)
  });
}

Now your App.jsx may be an actual component such as

import * as React from 'react';

class App extends React.Component {

  ...

  render() {
    return <div>hello world!</div>
  }
}

export default App;

in which case leave that index.js file as is. However, your App.jsx may have a bit more to is such as using react-router or redux, etc. In that case your App.jsx may look similar to the following

import * as React from 'react';
import { Router, Route, IndexRoute, browserHistory } from 'react-router';

export default <Router history={browserHistory}>
  <Route component={Main}>
    <Route path="page-one" component={PageOne} />
  </Route>
</Router>;

in this case you're going to want to modify the render method in index.js to look like the following

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

I'm just using webpack-dev-server from the command-line, so I haven't tried this with middleware from an express server yet. Good luck on that and happy coding!