HMR not working for a React app being served by Express

204 Views Asked by At

I'm trying to serve a react app bundled with webpack using a express server. However, hot module reloading is not working when I change my client side (React) code.

Initially, I was using webpack-dev-server to serve the app in which HMR was working perfectly fine. Then I switched to an express server with webpack-dev-middleware and webpack-hot-middleware following this example but now HMR doesn't work anymore.

webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CopyPlugin = require("copy-webpack-plugin");
const json5 = require("json5");
const webpack = require('webpack');

module.exports = {
    mode: process.env.NODE_ENV || 'development',
    entry: [
        'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000',
        './src/client/index.js'
    ],
    output: {
        filename: 'main.js',
        path: path.resolve(__dirname, 'dist'),
        clean: true,
        publicPath: "/",
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: path.join(__dirname, "views", "index.html"),
            favicon: path.join(__dirname, "views", "favicon.ico"),
        }),
        new MiniCssExtractPlugin(),
        new CopyPlugin({
            patterns: [{
                from: path.join(__dirname, 'public'),
                to: path.join(__dirname, 'dist'),
            }]
        }),
        new webpack.HotModuleReplacementPlugin(),
    ],
    devServer: {
        static: {
            directory: path.join(__dirname, "dist"),
        },
        port: 3000,
        historyApiFallback: true,
    },
    module: {
        rules: [
            {
                test: /\.(js|jsx)$/,
                exclude: /node_modules/,
                use: ["babel-loader"],
            },
            {
                test: /\.css$/i,
                use: [MiniCssExtractPlugin.loader, "css-loader"],
            },
            {
                test: /\.(png|jpg|gif|ico|svg)$/i,
                type: 'asset/resource',
            },
            {
                test: /\.json5$/,
                type: "json",
                parser: {
                    parse: json5.parse,
                },
            },
        ],
    },
};

src/server/index.js

const express = require('express');
const cors = require('cors');
const path = require('path');

const app = express();

app.use(cors());
app.use(express.json());
app.use(express.static('dist'));

if (process.env.NODE_ENV === 'development') {
    (function () {
        // Step 1: Create & configure a webpack compiler
        const webpackConfig = require(path.resolve(process.cwd(), './webpack.config'));
        const compiler = require('webpack')(webpackConfig);

        // Step 2: Attach the dev middleware to the compiler & the server
        app.use(
            require('webpack-dev-middleware')(compiler, {
                publicPath: webpackConfig.output.publicPath,
            })
        );

        // Step 3: Attach the hot middleware to the compiler & the server
        app.use(
            require('webpack-hot-middleware')(compiler, {
                log: console.log,
                path: '/__webpack_hmr',
                heartbeat: 10 * 1000,
            })
        );
    })();
}

app.get('*', (req, res) => {
    console.log('Serving website!')
    res.sendFile(path.resolve('dist', 'index.html'));
});

app.listen(3000, () => {
    console.log(`Server is running on port 3000.`);
});

The console shows the following when I try to change any client side code: enter image description here

I switched to running both the webpack-dev-server and the express server (proxying everything to the webpack-dev-server using the below server code) following this example and this seems to work perfectly fine again.

src/server/index.js

const express = require('express');
const cors = require('cors');
const path = require('path');
const webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');
const webpackConfig = require(path.resolve(process.cwd(), './webpack.config'));

const app = express();

app.use(cors());
app.use(express.json());
app.use(express.static('dist'));

app.get('*', (req, res) => {
    console.log('Serving website!')
    res.sendFile(path.resolve('dist', 'index.html'));
});


new WebpackDevServer(webpack(webpackConfig), {
    devMiddleware: {
        publicPath: webpackConfig.output.publicPath,
    },
    hot: true,
    historyApiFallback: true,
    proxy: {
        "*": "http://localhost:3000"
    }
}).listen(8080, 'localhost', (err, result) => {
    if (err) {
        return console.log(err);
    }
    console.log('Server is running on port http://localhost:8080/');
});

app.listen(3000, () => {
    console.log(`Server is running on port 3000.`);
});

But I don't want to have to run two servers in dev so I wanted to ask if there's a way I can run only the express server and still be able to use HMR in my react app.

P.S. Also attaching my package.json below for reference:

{
  "name": "foo-bar",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "cors": "^2.8.5",
    "express": "^4.18.2",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.8.1",
    "styled-components": "^5.3.8",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "rs-start": "react-scripts start",
    "rs-build": "react-scripts build",
    "rs-test": "react-scripts test",
    "rs-eject": "react-scripts eject",
    "wp-start": "webpack serve --mode development",
    "build": "NODE_ENV=development webpack",
    "build-prod": "NODE_ENV=production webpack",
    "start": "node ./src/server/index.js",
    "dev": "NODE_ENV=development nodemon ./src/server/index.js",
    "clean": "rm -rf dist",
    "burnthemall": "rm -rf node_modules && rm package-lock.json && npm install"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "@babel/core": "^7.21.0",
    "@babel/preset-env": "^7.20.2",
    "@babel/preset-react": "^7.18.6",
    "babel-loader": "^9.1.2",
    "copy-webpack-plugin": "^11.0.0",
    "css-loader": "^6.7.3",
    "html-webpack-plugin": "^5.5.0",
    "json5": "^2.2.3",
    "mini-css-extract-plugin": "^2.7.2",
    "nodemon": "^2.0.22",
    "react-scripts": "^2.1.3",
    "webpack": "^5.83.1",
    "webpack-cli": "^5.0.1",
    "webpack-dev-middleware": "^6.1.1",
    "webpack-dev-server": "^4.15.0",
    "webpack-hot-middleware": "^2.25.3"
  }
}
0

There are 0 best solutions below