Search code examples
webpackreact-hot-loaderwebpack-dev-middlewarewebpack-hot-middleware

Can't get a multi-app webpack configuration to work with react-hot-loader


I have a basic setup with two apps each in a separate directory, I'm using a custom server to compile them using webpack-dev-middleware/webpack-hot-middleware. Things are working fine except that I can't get HMR to work for the second app (I'm using react-hot-loader).

Here's a minimal repo illustrating the problem: https://github.com/AmrN/multi-react-app-hmr

My main code files:

webpack.config.js

var path = require('path');
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = function (appName) {
  return {
    devtool: 'cheap-module-eval-source-map',
    entry: [
      'react-hot-loader/patch',
      'webpack-hot-middleware/client',
      path.join(__dirname, appName, 'index'),
    ],
    output: {
      path: path.join(__dirname, 'dist', appName),
      filename: 'bundle.js',
      publicPath: '/'+appName+'/'
    },
    plugins: [
      new webpack.HotModuleReplacementPlugin(),
      new webpack.NamedModulesPlugin(),
      new webpack.NoEmitOnErrorsPlugin(),
      new HtmlWebpackPlugin({
        template: path.join(__dirname, appName, 'index.html'),
      }),
    ],
    module: {
      loaders: [{
        test: /\.jsx?$/,
        loaders: ['babel-loader'],
        exclude: /node_modules/,
      }]
    },
  };
};

server.js

var path = require('path');
var webpack = require('webpack');
var express = require('express');
var config1 = require('./webpack.config')('app1');
var config2 = require('./webpack.config')('app2');

var app = express();

[config1, config2].forEach((config) => {
  var compiler = webpack(config);
  app.use(require('webpack-dev-middleware')(compiler, {
    publicPath: config.output.publicPath
  }));

  app.use(require('webpack-hot-middleware')(compiler));
});

app.listen(3000, function (err) {
  if (err) {
    return console.error(err);
  }

  console.log('Listening at http://localhost:3000/');
});

(app1|app2)/index.js

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

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

render(App);
if (module.hot) module.hot.accept('./App', () => render(App));

Now if I run the server, my files are compiled correctly, and I can visit http://localhost:3000/app1/index.html successfully, and HMR is working properly here. However, if I visit the second app http://localhost:3000/app2/index.html it opens but HMR is not working and looking at the console it gives me the following error:

GET http://localhost:3000/app2/640a44b6b47b67436af2.hot-update.json 404 (Not Found)

[HMR] Cannot find update (Full reload needed)

[HMR] (Probably because of restarting the server)

Another thing I've noticed is that changing the order in which I apply my apps webpack configs in server.js from:

[config1, config2].forEach((config) => {...})

to:

[config2, config1].forEach((config) => {...})

switches the problem to app1, now HMR works for app2 but not app1.

Any help is appreciated, thanks.


Solution

  • The problem was that both apps used the same path for hot reloading (I think it's /__webpack_hmr by default). So I had to use a different one for each:

    in webpack.config.js I did:

    entry: [
      // ...
      'webpack-hot-middleware/client?path=/__webpack_hmr_'+appName,
      // ...
    ]
    

    and in server.js:

    app.use(require('webpack-hot-middleware')(compiler, {
      path: '/__webpack_hmr_'+appName
    }));
    

    Now it's working properly.