3

I'm using the express middlewares instead of webpack-dev-server:

const config = require("../webpack.config.js");

if(process.env.NODE_ENV === 'development') {
    const webpack = require('webpack');
    const webpackDevMiddleware = require('webpack-dev-middleware');
    const webpackHotMiddleware = require('webpack-hot-middleware');
    const compiler = webpack(config);

    app.use(webpackDevMiddleware(compiler, {
        stats: {colors: true},
    }));
    app.use(webpackHotMiddleware(compiler));
}

And I've tried react-hot-loader/patch, react-hot-loader/babel and react-hot-loader/webpack from react-hot-loader@3:

module.exports = {
    context: path.join(__dirname, 'client'),
    entry: [
        'webpack-hot-middleware/client',
        'react-hot-loader/patch',
        './entry.less',
        './entry',
    ],
    output: {
        path: path.join(__dirname, 'public'),
        filename: 'bundle.js',
        publicPath: '/',
    },
    module: {
        rules: [
            {
                test: /\.jsx/,
                use: [
                    {
                        loader: 'babel-loader',
                        options: {
                            plugins: ['transform-react-jsx', 'transform-class-properties', 'react-hot-loader/babel'],
                        },
                    },
                    'react-hot-loader/webpack'
                ],
            },

But none of them seem to work. I just get this error message:

[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. logUpdates @ bundle.js:29964 applyCallback @ bundle.js:29932 (anonymous) @ bundle.js:29940 bundle.js:29972
[HMR] - ./client/components/CrawlForm.jsx

What's the trick to making it work?

N.B. CSS hot loading works just fine, so I got that part working.

4 Answers 4

2

I spent several days before I finally cracked the case. Here's my code that works:

Webpack Config Object

const clientConfig = {
  entry: {
    client: [
      'react-hot-loader/patch',
      'webpack-hot-middleware/client',
      'babel-polyfill',
      './src/client/client.js',
    ],
  },
  output: {
    path: path.resolve(__dirname, './build/public'),
    filename: '[name].js',
    publicPath: '/',
  },
  devtool: 'inline-source-map',
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoEmitOnErrorsPlugin(),
    new webpack.LoaderOptionsPlugin({
      debug: true,
    }),
    new CopyWebpackPlugin([
      { from: './src/assets/fonts', to: 'fonts' },
      { from: './src/assets/images', to: 'images' },
    ]),
    new webpack.EnvironmentPlugin(['GOOGLE_MAP_API_KEY']),
  ],
  module: {
    rules: [
      {
        test: /(\.js|\.jsx)$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        options: {
          presets: [['es2015', { loose: true }], 'react', 'stage-2'],
        },
      },
      {
        test: /\.scss$/,
        use: [
          'style-loader',
          'css-loader',
          'sass-loader',
        ],
      },
    ],
  },
};

Server index.js

I am using both dev middleware and hot middleware same as you. I am also importing AppContainer from react-hot-loader and wrapping my component.

import express from 'express';
import React from 'react';
import routes from 'components/Routes';
import html from './html';
import { renderToString } from 'react-dom/server';
import { match, RouterContext } from 'react-router';
import { Provider } from 'react-redux';
import makeStore from 'store';
import Immutable from 'immutable';
import setupNameless from './setupNameless';
import db from './database';
import { actions } from '../client/constants';
import webpack from 'webpack';
import webpackHotMiddleware from 'webpack-hot-middleware';
import webpackDevMiddleware from 'webpack-dev-middleware';
import { clientConfig as wpConfig } from '../../webpack.config.js';
import { AppContainer } from 'react-hot-loader';
import dotenv from 'dotenv';

dotenv.config();

const compiler = webpack(wpConfig);

db();

const app = express();
app.use(webpackDevMiddleware(compiler, {
  publicPath: wpConfig.output.publicPath,
  // noInfo: true,
  stats: {
    colors: true,
  },
}));
app.use(webpackHotMiddleware(compiler));
app.use(express.static('build/public'));

const { commander: nameless, apiPrefix } = setupNameless(app);

app.use((req, res, next) => {
  // make DB call here to fetch jobs.
  nameless.exec('jobs', actions.GET_JOBS).then((jobs) => {

    const store = makeStore(Immutable.fromJS({
      // filters: {},
      app: {
        apiPrefix,
        search: {
          query: '',
          options: {},
        },
      },
      jobs,
    }));

    match({
      routes,
      location: req.originalUrl,
    }, (error, redirectLocation, renderProps) => {
      if (error) {
        res.status(500).send(error.message);
      } else if (redirectLocation) {
        res.redirect(302, redirectLocation.pathname + redirectLocation.search);
      } else if (renderProps) {
        // You can also check renderProps.components or renderProps.routes for
        // your "not found" component or route respectively, and send a 404 as
        // below, if you're using a catch-all route.
        try {
          res.status(200).send(html(renderToString(
            <AppContainer>
              <Provider store={store}>
                <RouterContext {...renderProps} />
              </Provider>
            </AppContainer>
          ), store.getState()));
        } catch (err) {
          next(err);
        }
      } else {
        res.status(404).send('Not found');
      }
    });
  }, (e) => {
    next(e);
  }).catch(e => {
    next(e);
  });
});

app.use(logErrors);

function logErrors(err, req, res, next) {
  console.error(err.stack);
  next(err);
}

app.listen(process.env.PORT || 3000, () => {
  console.log(`App listening on port ${process.env.PORT || 3000}`);
});

Client.js

This was the magic that made it work. I had to add the if (module.hot) code and also import AppContainer from react-hot-loader. Another important aspect was adding key={Math.random()} to my <Router /> component.

import { match, Router, browserHistory as history } from 'react-router';
import routes from './components/Routes';
import ReactDOM from 'react-dom';
import React from 'react';
import { Provider } from 'react-redux';
import makeStore from './store';
import Immutable from 'immutable';
import createLogger from 'redux-logger';
import createSagaMiddleware from 'redux-saga';
import sagas from './sagas';
import { AppContainer } from 'react-hot-loader';

const logger = createLogger();
const sagaMiddleware = createSagaMiddleware();

const store = makeStore(
    Immutable.fromJS(window.__INITIAL_STATE__),
    logger,
    sagaMiddleware
);

sagaMiddleware.run(sagas);

ReactDOM.render(
  <AppContainer>
    <Provider store={store}>
      <Router history={history} routes={routes} />
    </Provider>
  </AppContainer>,
  document.getElementById('app'));

if (module.hot) {
  module.hot.accept('./components/Routes', () => {
    const nextRoutes = require('./components/Routes').default;
    ReactDOM.render(
      <AppContainer>
        <Provider store={store}>
          <Router key={Math.random()} history={history} routes={nextRoutes} />
        </Provider>
      </AppContainer>,
      document.getElementById('app'));
  });
}

Good luck 👍

Sign up to request clarification or add additional context in comments.

1 Comment

Wow...that is way more complicated than it used to be. The <AppContainer> along with the if(module.hot) bit worked for me. Thank you!!
2

Paraphrasing from Dan Abramov and borrowing some code from realseanp, the full instructions are:

  1. yarn add react-hot-loader@3
  2. Update webpack.config.js:
    1. Add react-hot-loader/patch and webpack-hot-middleware/client to top of your entry
    2. Add react-hot-loader/babel to your babel-loader plugins
    3. Add new HotModuleReplacementPlugin() to your webpack plugins
  3. Add webpack-dev-middleware and webpack-hot-middlware to express:

    // server/entry.jsx
    const express = require('express');
    const path = require('path');
    const cons = require('consolidate');
    const fs = require('fs');
    
    const port = 5469;
    const app = express();
    
    app.disable('x-powered-by');
    app.engine('hbs', cons.handlebars);
    app.set('view engine', 'hbs');
    app.set('views', path.join(__dirname, '../views'));
    
    const wpConfig = require("../webpack.config.js");
    
    if(process.env.NODE_ENV === 'development') {
        const webpack = require('webpack');
        const webpackDevMiddleware = require('webpack-dev-middleware');
        const webpackHotMiddleware = require('webpack-hot-middleware');
        const compiler = webpack(wpConfig);
    
        app.use(webpackDevMiddleware(compiler, {
            stats: {colors: true},
        }));
        app.use(webpackHotMiddleware(compiler));
    }
    
    app.use(require('./routes'));
    
    app.use(express.static(wpConfig.output.path));
    
    app.listen(port, function () {
        console.log(`Listening on http://localhost:${port}`);
    });
    
  4. Add <AppContainer> and react.hot to your client entry point:

    // client/entry.jsx
    import ReactDOM from 'react-dom';
    import App from './components/App';
    import { AppContainer } from 'react-hot-loader';
    
    function render(Root) {
        ReactDOM.render(<AppContainer><Root/></AppContainer>, document.getElementById('react-root'));
    }
    
    render(App);
    
    if(module.hot) {
        module.hot.accept('./components/App', () => {
            render(require('./components/App').default);
        });
    }
    

Comments

2

I had some trouble getting the error overlay to render for runtime errors as well as recovering from it. I noticed that webpack-dev-server does a full reload when an error happens.

This can be simulated with the following snippet:

if (module.hot) module.hot.accept('./App', () => {
  try {
    render(App)
  } catch (e) {
    location.reload();
  }
});

My working fork of react-hot-boilerplate is available on Github.

Comments

0

I also added every points to my app but it didn't work The problem was at publicPath in my webpack.config.js I had

publicPath: '/client/dist'

then I changed to

publicPath: '/'

And now it works

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.