Webpack — Hot Module Replacement
One of the most useful features of webpack is its ability to watch changes in the application and then reflect those changes in the browser without reloading the application.
On the surface, it might not sound like solving a very critical problem. But trust me, it is something very useful. Let’s try and understand HMR and its purpose in web development.
Why ?
When we are in the middle of developing a web application, we make hundreds of changes, and most of the time we need those changes to be visible in the browser instantly. Webpack and most text editors today provide auto reload by default, where the editor keeps watching your files, and when you make any change, it reloads the application in the browser.
This way has a few issues,
- On every change, the whole application is rebuilt/recompiled, the browser is refreshed and the application is reloaded in the browser. All of this can take a long time when working on big applications.
- Sometimes while working on something like a modal or a dialog box or the third page of a navigation wizard, when we make a change, we want to see the change taking place to the page we’re on, but the browser reloads and takes us back to the initial screen.
- Live reload also loses your state, so if you are developing a feature deep within a large single page application, that can be annoying.
Hot module replacement (HMR) takes care of issues like these.
What does it do exactly ?
What HMR basically does is, instead of reloading the whole application when it detects a change, it only replaces the modules that have been changed without reloading the application or refreshing the browser.
This can significantly speed up development as:
- It saves up development time by only updating what has changed.
- It retains the state of the application which is normally lost during a full reload.
The ‘modules’ here basically mean your JavaScript files.
How does it work ?
We’ve already established that HMR is a Webpack feature that updates your Javascript without a browser reload.
Webpack provides two components to make HMR possible.
- HMR Runtime.
- HMR Server (included in webpack-dev-server as middleware).
HMR Runtime is installed into the output bundle (your application) that webpack generates. The HMR Runtime is basically an extra piece of code that webpack adds to the generated output bundle .This piece of code (HMR Runtime) is responsible for receiving the module updates in your code.
The webpack compiler informs the HMR server when an update is made in any of the modules. The HMR server then notifies the HMR Runtime that an update has occurred and it provides those updates to the Runtime in .json format.
Let’s go through what happens when you make a change in your code, and how does HMR replaces only the changed module in the code without touching any other part of the application.
- You make a change in one of the files.
- The file system picks up the change and informs webpack.
- The Webpack compiler rebuilds one or more modules and notifies the HMR server that an update has been made.
- The HMR server uses websockets to inform the HMR Runtime that an update has been made in the code, and that the Runtime needs to pull the update and replace the respective module.
- The HMR Runtime then replaces the module(s) in the update.
This is how Hot Module replacement works conceptually.
Now how do we use it in our Application ?
A module can only be updated if you ‘accept’ it.
What does it mean for a module to be ‘accepted’ ?
So to do it’s magic, webpack exposes some APIs that the user needs to use for HMR.
One such API is module.hot.accept().
When you make a change in any module, webpack checks to see if the module is being accepted anywhere. What that means is webpack looks for the function module.hot.accept() in the parent of the component.
module.hot.accept() takes in two parameters:
- The path of the module which is being changed.
- The callback function which handles the change.
If (module.hot) {module.hot.accept(‘<path of module changed>’, cb( ) )}
Here module.hot property determines whether HMR is enabled or not.
Now whenever webpack detects a change in any of the modules, it looks for the module.hot.accept() function in the parent of that module. If it is not present in the parent component, webpack looks for it one level higher. The update keeps on ‘bubbling’ till it finds the module.hot.accept() function.
Once the update has been ‘accepted’, the callback function is executed. Now even though most of the time, in our callback function, we write logic to replace the respective module, technically, we can do anything in the callback function. Technically, we ‘handle’ the update in the callback (cb).
And that’s why we can say module.hot.accept() is nothing but an update handler.
Now let’s see a real world example/walk-through of setting up and using HMR.
To use HMR, we need to set up a couple of things first.
First we need to enable HMR in our application. There are a couple of ways to do that. Note that to use HMR, we need to have the webpack dev server configured.
The easiest way to enable HMR is doing it inline.
1 .Enabling HRM : Inline method
All you need to do here is add — hot and — inline
//Case 1. from package.json's script{..."scripts": {"start": "webpack-dev-server --inline --hot"}...}//Case 2. When WDS is installed globallywebpack-dev-server --inline --hot//Case 3. WDS is installed as a dev-dependencynode_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot
And we’re done. That’s all you need to do to enable HMR , the inline way.
Another way to enable HMR is through the webpack config file.
2. Enabling HMR through webpack.config.js
plugins: [ new webpack.HotModuleReplacementPlugin() ],devServer: {hot: true,}
Here all you need to do is add the HotModuleReplacementPlugin() in the webpack config file, and set the hot property to true in the devServer object.
There are more ways you can configure HMR in , but these two are the simplest and most straightforward.
At this point, our webpack project is configured for HMR. But if you fire up a browser, and make some code changes, it won’t work. Either you won’t see the update, or you’ll see a full browser refresh.
It isn’t working right now because Webpack doesn’t know yet when it is acceptable to reload a particular JS file.
To let webpack know that we’ll use the module.hot API and module.hot.accept API, provided by webpack.
Note: For the sake of this example we’re going to assume that we are working on a react application. However HMR is not related to react in any way. You can use it with any application, as long as you are using a webpack dev server.
So we will have an App component (App.js) in this example, which is going to be the top level component in our app.
Now we need to go to our entry file (index.js or main.js), this is what it typically looks like
<All the Imports>const render = () => {ReactDOM.render(</App>, document.getElementById(‘root’));}render();
Now we will be adding the code we need to use HMR here:
const render = () => {ReactDOM.render(</App>, document.getElementById(‘root’));}render();if(module.hot) {module.hot.accept(‘./App.js , () => { render() }}
Here first we’re checking if hot module replacement is enabled in the project by the module.hot API.
If it is, we are using the module.hot.accept handler to handle the updates in our module.
As we mentioned above, the module.hot.accept() handler will have two parameters:
- The path to the module that needs to be watched. In this case we are going to watch our App module. So now whenever any updates are made in the App module, or any of the children of the App module, webpack will start looking for the module.hot.accept() method . As we discussed above, the update will keep ‘bubbling’ up till it finds module.hot.accept() in any parent module of the module being changed. In this case, it will find it in the App module.
- The callback function. Now here what we’re basically doing is executing this function whenever any change is detected in App.js or any of its children. Here, we are calling the render() function in the callback. So whenever any change is detected, webpack will update the App module by calling the render function.
So to recap, here we’re looking for any changes in the App module or its children, and whenever a change is detected, all we’re doing is calling the render() function in our callback, and replacing the App module with whatever the render function returns ( in this case, our updated App module).
This is how the HMR is used most commonly.
We can use it to do pretty much anything we want since we write our own logic in the update handler module.hot.accept(). HMR provides a lot of other APIs and handlers too.
If you’re interested to learn more about them, head over to https://webpack.js.org/api/hot-module-replacement/.
This post is also published on https://blog.imaginea.com/webpack%E2%80%8A-%E2%80%8Ahot-module-replacement/