Webpack with Hot Module Replacement

Hello everyone, In previous article (Get up and running with webpack and react)  we learnt about webpack and how to setup react app with webpack. Now we are going to dive into the next feature of webpack called Hot Module Replacement.

What is Hot Module Replacement?

Webpack has this amazing feature called Hot Module Replacement. It helps to change the old modules with the newer ones without reloading the browser.

You may be using it already but may not know exactly how it works. So in this article, I'll go over how it works.

Some things you have to note about HMR are as follows:

  • HMR is one of optional features of webpack and needs to be turned ON explicitly.
  • You need to use Webpack via webpack-dev-server to use HMR.
  • It can only work with loaders that implement and understand HMR. Like ‘style-loader’, ‘react-hot-loader’ etc.
  • It should only be used for development because webpack-dev-server is development server.

A Deeper Look with Code

First we have to install react-hot-loader

npm install react-hot-loader 

Add react-hot-loader into your .babelrc file.

Put below line into your .babelrc file

{
   ...
  "plugins": ["react-hot-loader/babel"]
}

Make your root component as hot-exported.

Create file App.js

touch  src/App.js

Now add below code into App.js file

import React from "react";
import { hot } from "react-hot-loader";

const App = () => <h1>Hello ReactJs</h1>;

export default hot(module)(App);

And  your index.js file

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

ReactDOM.render(<App />, document.getElementById("root"));

You have to add below line into your package.json file

"scripts":{
    "dev":"webpack-dev-server --hot"
}

Use hot-loader react-dom:
@hot-loader/react-dom replaces the “react-dom” package the same version, but with additional patches to support hot reloading.

npm install @hot-loader/react-dom --save-dev

Use alias into your webpack.config.js file. So add below code into your webpack.config.js file:

resolve: {
    alias: {
      'react-dom': '@hot-loader/react-dom'
    }
}

So, now your .babelrc file look like:

{
 "presets": [
   [
     "@babel/preset-env",
     {
       "useBuiltIns": "usage",
       "corejs": {
         "version": 3,
         "proposals": true
       }
     }
   ],
   "@babel/preset-react"
 ],
 "plugins": [
   "react-hot-loader/babel",
   "@babel/plugin-proposal-class-properties"
 ]
}

So, your Webpack.config.js file look like:

const webpack = require('webpack');
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebPackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const Dotenv = require("dotenv-webpack");

module.exports = {
 //Entry Point
 entry: {
   app: "./src/index.js"
 },
 //Output
 output: {
   path: path.resolve(__dirname + "/dist"),
   filename: "app.bundle.js",
   publicPath: "/"
 },
 resolve: {
   alias: {
     "react-dom": "@hot-loader/react-dom"
   }
 },

 //webpack dev server
 devServer: {
   compress: true,
   port: 3000,
   stats: "errors-only",
   open: true
 },
 //Loader
 module: {
   rules: [
     {
       test: /\.(js|jsx)$/,
       exclude: /node_modules/,
       use: "babel-loader"
     },
     {
       test: /\.css$/i,
       use: ["style-loader", "css-loader"]
     },
     {
       test: /\.scss$/,
       use: [
         {
           loader: MiniCssExtractPlugin.loader
         }
       ][("css-loader", "sass-loader")]
     },
    {
        // For images
        test: /\.(png|jpe?g|gif|svg)$/,
        use: [
          {
            //Using File-loader for these files
            loader: "file-loader",
            /*In options we can set different things like format
             and directory to save*/
            options: {
              outputPath: "images"
            }
          }
        ]
      },
      {
        //For fonts
        test: /\.(woff|woff2|ttf|otf|eot)$/,
        use: [
          {
            //using file-loader too
            loader: "file-loader",
            options: {
              outputPath: "fonts"
            }
          }
        ]
      },
     {
       test: /\.html$/,
       use: "html-loader"
     }
   ]
 },
 //plugin
 plugins: [
   new Dotenv(),
   new HtmlWebPackPlugin({
     template: "./src/index.html",
     filename: "./index.html"
   }),
   new MiniCssExtractPlugin({
     filename: "app.css",
     disable: false,
     allChunks: true
   }),
   new CleanWebpackPlugin({
     cleanOnceBeforeBuildPatterns: ["dist"]
   }),
 ]
};

So, your package.json file look like:

{
 "name": "react_webpack",
 "version": "1.0.0",
 "description": "",
 "main": "index.js",
 "scripts": {
   "dev": "webpack-dev-server --hot",
   "build": "webpack --mode production"
 },
 "author": "",
 "license": "ISC",
 "devDependencies": {
   "@babel/core": "^7.7.2",
   "@babel/preset-env": "^7.7.1",
   "@babel/preset-react": "^7.7.0",
   "@hot-loader/react-dom": "^16.11.0",
   "babel-loader": "^8.0.6",
   "clean-webpack-plugin": "^3.0.0",
   "css-loader": "^3.2.0",
   "file-loader": "^4.3.0",
   "html-loader": "^0.5.5",
   "html-webpack-plugin": "^3.2.0",
   "mini-css-extract-plugin": "^0.8.0",
   "sass-loader": "^8.0.0",
   "style-loader": "^1.0.0",
   "webpack": "^4.41.2",
   "webpack-cli": "^3.3.10",
   "webpack-dev-server": "^3.9.0"
 },
 "dependencies": {
   "dotenv-webpack": "^1.7.0",
   "react": "^16.12.0",
   "react-dom": "^16.12.0",
   "react-hot-loader": "^4.12.18"
 }
}

If You are new to Webpack then you must have to read our first article.

Get up and running with webpack and react