React Hot Reload with React-hot-loader 3, React-router 4, and Webpack-hot-middleware

Viewed 2414

I am trying to make React-hot-loader 3 work with React-hot-loader 3, React-router 4 and Webpack-hot-middleware (last version, 2.18.2).

Here is my server.js:

const express = require('express');
const bodyParser = require('body-parser');
const cookiesMiddleware = require('universal-cookie-express');
/* eslint-disable import/no-extraneous-dependencies */
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');
const webpackHotServerMiddleware = require('webpack-hot-server-middleware');
/* eslint-enable import/no-extraneous-dependencies */
const clientConfig = require('./webpack.config.dev.client');
const serverConfig = require('./webpack.config.dev.server');

const PORT_NUMBER = process.env.PORT || 3000;
const app = express();

app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookiesMiddleware());
app.use(express.static('public'));

const multiCompiler = webpack([clientConfig, serverConfig]);
const clientCompiler = multiCompiler.compilers[0];

app.use(webpackDevMiddleware(multiCompiler, {
  publicPath: clientConfig.output.publicPath,
  noInfo: true,
  stats: { children: false },
}));
app.use(webpackHotMiddleware(clientCompiler));
app.use(webpackHotServerMiddleware(multiCompiler, {
  serverRendererOptions: { outputPath: clientConfig.output.path },
}));

app.listen(PORT_NUMBER, () => {
  // eslint-disable-next-line no-console
  console.log(`Server listening at port ${PORT_NUMBER}`);
});

My client entry point:

import React from 'react';
import { render } from 'react-dom';
import { AppContainer } from 'react-hot-loader';

import * as Bundles from './components/Bundles';
import App from './App';

const doRender = () => {
  render(
    <AppContainer>
      <App type="client" />
    </AppContainer>,
    document.getElementById('content'),
  );
};

const splitPoints = window.splitPoints || [];
Promise.all(splitPoints.map(chunk => Bundles[chunk].loadComponent()))
  .then(doRender);

if (module.hot) {
  module.hot.accept('./App', doRender);
}

.babelrc:

{
  "plugins": [
    "transform-decorators-legacy",
    "transform-object-rest-spread"
  ],
  "presets": [
    ["es2015", { "modules": false }],
    "react",
    "stage-0"
  ],
  "env": {
    "development": {
      "plugins": ["react-hot-loader/babel"]
    },
    "test": {
      "plugins": ["transform-es2015-modules-commonjs"]
    }
  }
}

It looks like I followed every step of react-hot-loader's README, yet everytime I change some code in a component, I get this message in the console:

[HMR] bundle rebuilding
client.js:207 [HMR] bundle rebuilt in 8218ms
process-update.js:27 [HMR] Checking for updates on the server...
process-update.js:81 [HMR] The following modules couldn't be hot updated: (Full reload needed)
This is usually because the modules which have changed (and their parents) do not know how to hot reload themselves. See http://webpack.github.io/docs/hot-module-replacement-with-webpack.html for more details.

Has anyone stumbled on this one? Thanks in advance!

Edit: here is my client webpack config:

const path = require('path');
const webpack = require('webpack');
const autoprefixer = require('autoprefixer');
const StyleLintPlugin = require('stylelint-webpack-plugin');
const notifier = require('node-notifier');

const configFileName = './.env.development.json';
let envConfig;

try {
  // eslint-disable-next-line import/no-dynamic-require, global-require
  envConfig = require(configFileName);
} catch (e) {
  envConfig = {};
}

const eslintSettings = {
  extends: path.join(__dirname, '.eslintrc.js'),
  configFile: path.join(__dirname, '.eslintrc.js'),
  emitWarning: true,
  cache: true,
};
const babelSettings = {
  extends: path.join(__dirname, '.babelrc'),
  cacheDirectory: true,
};

const excludes = [
  /node_modules(?![/\\]@local-package[/\\])/,
];
const roots = [
  path.join(__dirname, '../../node_modules'),
  path.join(__dirname, 'node_modules'),
  path.join(__dirname, 'client'),
];

const getCommonCSSLoaders = enableCSSModules => [
  {
    loader: 'style-loader',
  },
  {
    loader: 'css-loader',
    options: {
      modules: enableCSSModules,
      importLoaders: 1,
      localIdentName: '[name]_[local]_[hash:base64:3]',
    },
  },
  {
    loader: 'postcss-loader',
    options: {
      sourceMap: true,
      ident: 'postcss',
      plugins: () => [
        // eslint-disable-next-line global-require, import/no-extraneous-dependencies
        require('postcss-flexbugs-fixes'),
        autoprefixer({
          env: 'development',
          flexbox: 'no-2009',
        }),
      ],
    },
  },
];

const rules = [
  {
    enforce: 'pre',
    test: /\.js$/,
    exclude: /node_modules/,
    loader: 'eslint-loader',
    options: eslintSettings,
  },
  {
    test: /\.js$/,
    exclude: excludes,
    loader: 'babel-loader',
    options: babelSettings,
  },
  {
    test: /\.css$/,
    exclude: excludes,
    use: [
      ...getCommonCSSLoaders(true),
    ],
  },
  {
    test: /\.css$/,
    include: excludes,
    use: [
      ...getCommonCSSLoaders(false),
    ],
  },
  {
    test: /\.scss$/,
    exclude: excludes,
    use: [
      ...getCommonCSSLoaders(true),
      {
        loader: 'resolve-url-loader',
      },
      {
        loader: 'sass-loader',
        options: {
          sourceMap: true,
        },
      },
    ],
  },
  {
    test: /.*\.(eot|woff|woff2|ttf|svg|png|jpe?g|gif)$/i,
    use: [
      {
        loader: 'url-loader',
        options: {
          name: 'images/[name].[hash].[ext]',
          limit: 20000,
        },
      },
      {
        loader: 'image-webpack-loader',
        options: {
          mozjpeg: {
            quality: 80,
          },
          pngquant: {
            quality: '80-90',
          },
          bypassOnDebug: true,
        },
      },
    ],
  },
];

const plugins = [
  new webpack.LoaderOptionsPlugin({
    debug: true,
  }),
  new webpack.HotModuleReplacementPlugin(),
  new webpack.NoEmitOnErrorsPlugin(),
  new StyleLintPlugin({
    configFile: path.join(__dirname, '.stylelintrc.js'),
    files: [
      '**/*.s?(a|c)ss',
      '../shared/**/*.s?(a|c)ss',
    ],
    emitErrors: false,
  }),
  new webpack.NormalModuleReplacementPlugin(/\/components\/Bundles/, './components/AsyncBundles'),
  new webpack.NormalModuleReplacementPlugin(/\/Bundles/, './AsyncBundles'),
  new webpack.optimize.CommonsChunkPlugin({
    name: 'client',
    async: 'common',
    children: true,
    minChunks: (module, count) => {
      if (module.resource && (/^.*\.(css|scss)$/).test(module.resource)) {
        return false;
      }
      return count >= 3 && module.context && !module.context.includes('node_modules');
    },
  }),
  new webpack.optimize.CommonsChunkPlugin({
    name: 'client',
    children: true,
    minChunks: module => module.context && module.context.includes('node_modules'),
  }),
  new webpack.optimize.CommonsChunkPlugin({
    name: 'vendors',
    minChunks: module => module.context && module.context.includes('node_modules'),
  }),
  new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
  // eslint-disable-next-line func-names
  function () {
    this.plugin('done', (stats) => {
      notifier.notify({
        title: 'Webpack : Build Succeeded',
        message: `${stats.compilation.errors.length} Error(s) - ${stats.compilation.warnings.length} Warning(s)`,
      });
    });
    this.plugin('failed', () => {
      notifier.notify({
        title: 'Webpack',
        message: 'Build Failed HARD',
      });
    });
  },
];

const config = {
  name: 'client',
  target: 'web',
  devtool: 'inline-source-map',
  entry: ['webpack-hot-middleware/client', 'react-hot-loader/patch', './client/src/entry/js/polyfills', './client/src/entry/js/client'],
  output: {
    filename: 'client/[name].js',
    chunkFilename: 'client/chunks/[name].chunk.js',
    path: path.join(__dirname, 'public/dist'),
    publicPath: '/dist/',
    pathinfo: true,
  },
  module: {
    rules,
  },
  plugins,
  resolve: {
    modules: roots,
  },
  resolveLoader: {
    modules: roots,
  },
  node: {
    fs: 'empty',
    net: 'empty',
    tls: 'empty',
  },
};

module.exports = config;
1 Answers
Related