Skip to content

Commit 595e0ac

Browse files
sbaidonTheDutchCoder
authored andcommitted
docs(guides): rewrite of the HMR guide (#1439)
* Rewrite of the hot-module-replacement.md guide
1 parent cfc4e60 commit 595e0ac

File tree

1 file changed

+215
-79
lines changed

1 file changed

+215
-79
lines changed

content/guides/hot-module-replacement.md

+215-79
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ contributors:
1010
- joshsantos
1111
- drpicox
1212
- skipjack
13+
- sbaidon
1314
- gdi2290
1415
related:
1516
- title: Concepts - Hot Module Replacement
@@ -18,128 +19,220 @@ related:
1819
url: /api/hot-module-replacement
1920
---
2021

22+
T> This guide extends on code examples found in the [Development](/guides/development) guide.
23+
2124
Hot Module Replacement (or HMR) is one of the most useful features offered by webpack. It allows all kinds of modules to be updated at runtime without the need for a full refresh. This page focuses on __implementation__ while the [concepts page](/concepts/hot-module-replacement) gives more details on how it works and why it's useful.
2225

2326
W> __HMR__ is not intended for use in production, meaning it should only be used in development. See the [building for production guide](/guides/production) for more information.
2427

2528

2629
## Enabling HMR
2730

28-
This feature is great for productivity. Let's take a look at how to set it up with [webpack-dev-server](https://github.com/webpack/webpack-dev-server)...
29-
30-
``` js
31-
const path = require('path');
32-
const webpack = require('webpack');
31+
This feature is great for productivity. All we need to do is update our [webpack-dev-server](https://github.com/webpack/webpack-dev-server) configuration, and use webpack's built in HMR plugin.
3332

34-
module.exports = {
35-
entry: './index.js',
33+
__webpack.config.js__
3634

37-
plugins: [
38-
new webpack.HotModuleReplacementPlugin() // Enable HMR
39-
],
35+
``` diff
36+
const path = require('path');
37+
const HtmlWebpackPlugin = require('html-webpack-plugin');
38+
+ const webpack = require('webpack');
39+
40+
module.exports = {
41+
entry: {
42+
app: './src/index.js',
43+
print: './src/print.js'
44+
},
45+
devtool: 'inline-source-map',
46+
devServer: {
47+
contentBase: './dist',
48+
+ hot: true
49+
},
50+
plugins: [
51+
new HtmlWebpackPlugin({
52+
title: 'Hot Module Replacement'
53+
}),
54+
+ new webpack.HotModuleReplacementPlugin()
55+
],
56+
output: {
57+
filename: 'bundle.js',
58+
path: path.resolve(__dirname, 'dist')
59+
}
60+
};
61+
```
4062

41-
output: {
42-
filename: 'main.js',
43-
path: path.resolve(__dirname, 'dist'),
44-
publicPath: '/'
45-
},
63+
You can also use the CLI to modify the [webpack-dev-server](https://github.com/webpack/webpack-dev-server) configuration with the following command: `webpack-dev-server --hotOnly`.
4664

47-
devServer: {
48-
hot: true, // Tell the dev-server we're using HMR
49-
contentBase: path.resolve(__dirname, 'dist'),
50-
publicPath: '/'
51-
}
52-
};
53-
```
65+
To get it up and running let's run `npm start` from the command line.
5466

55-
Not too bad, huh? Let's test it out using `module.hot.accept`...
67+
Now let's update the `index.js` file so that when a change inside `print.js` is detected we tell webpack to accept the updated module.
5668

5769
__index.js__
5870

59-
``` js
60-
import Library from './library';
71+
``` diff
72+
import _ from 'lodash';
73+
import printMe from './print.js';
6174

62-
if (module.hot) {
63-
module.hot.accept('./library', function() {
64-
console.log('Accepting the updated library module!');
65-
Library.log();
66-
})
67-
}
75+
+ if (module.hot) {
76+
+ module.hot.accept('./print.js', function() {
77+
+ console.log('Accepting the updated printMe module!');
78+
+ printMe();
79+
+ })
80+
+ }
81+
82+
function component() {
83+
var element = document.createElement('div');
84+
var btn = document.createElement('button');
85+
86+
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
87+
88+
btn.innerHTML = 'Click me and check the console!';
89+
btn.onclick = printMe;
90+
91+
element.appendChild(btn);
92+
93+
return element;
94+
}
95+
96+
document.body.appendChild(component());
6897
```
6998

70-
__library.js__
99+
Start changing the `console.log` statement in `print.js`, and you should see the following output in the browser console.
71100

72-
``` js
73-
export default {
74-
log() {
75-
// Change this after the server is started to test
76-
console.log('Initial log...')
101+
__print.js__
102+
103+
``` diff
104+
export default function printMe() {
105+
- console.log('I get called from print.js!');
106+
+ console.log('Updating print.js...')
77107
}
78-
}
79108
```
80109

81-
Start changing the `console.log` statement in `library.js`, to `'Second log...'` for example, and you should see the following output in the browser console...
110+
__console__
82111

83112
``` diff
84113
[HMR] Waiting for update signal from WDS...
85-
main.js:9998 Initial log...
86-
main.js:9468 [WDS] Hot Module Replacement enabled.
87-
+ 2main.js:9468 [WDS] App updated. Recompiling...
88-
+ main.js:9468 [WDS] App hot update...
89-
+ main.js:9912 [HMR] Checking for updates on the server...
90-
+ main.js:9982 Accepting the updated library module!
91-
+ 0.1bafc70….hot-update.js:11 Second log...
92-
+ main.js:9955 [HMR] Updated modules:
93-
+ main.js:9957 [HMR] - ./src/library.js
94-
+ main.js:9894 [HMR] App is up to date.
114+
main.js:4395 [WDS] Hot Module Replacement enabled.
115+
+ 2main.js:4395 [WDS] App updated. Recompiling...
116+
+ main.js:4395 [WDS] App hot update...
117+
+ main.js:4330 [HMR] Checking for updates on the server...
118+
+ main.js:10024 Accepting the updated printMe module!
119+
+ 0.4b8ee77….hot-update.js:10 Updating print.js...
120+
+ main.js:4330 [HMR] Updated modules:
121+
+ main.js:4330 [HMR] - 20
122+
+ main.js:4330 [HMR] Consider using the NamedModulesPlugin for module names.
95123
```
96124

97125

98126
## Gotchas
99127

100-
Hot Module Replacement can be tricky. For example, let's say I have the following class:
128+
Hot Module Replacement can be tricky. To show this, let's go back to our working example. If you go ahead and click the button on the example page, you will realize the console is printing the old `printMe` function.
129+
130+
This is happening because the button's `onclick` event handler is still bound to the original `printMe` function.
101131

102-
``` js
103-
class Logger {
104-
log(text) {
105-
console.log('Logging some text: ', text)
132+
To make this work with HMR we need to update that binding to the new `printMe` function using `module.hot.accept`:
133+
134+
__print.js__
135+
136+
``` diff
137+
import _ from 'lodash';
138+
import printMe from './print.js';
139+
140+
if (module.hot) {
141+
module.hot.accept('./print.js', function() {
142+
console.log('Accepting the updated printMe module!');
143+
- printMe();
144+
+ document.body.removeChild(element);
145+
+ element = component(); // Re-render the "component" to update the click handler
146+
+ document.body.appendChild(element);
147+
})
106148
}
107-
}
108-
```
109149

110-
Even if the underlying module containing this class is patched with new code, any existing instances of the class still have the old `log` method. Meaning if we changed what that method does, it wouldn't be reflected in those old instances unless we re-instantiate them somehow using `module.hot.accept`.
150+
let element = component();
151+
152+
function component() {
153+
var element = document.createElement('div');
154+
var btn = document.createElement('button');
155+
156+
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
157+
158+
btn.innerHTML = 'Click me and check the console!';
159+
btn.onclick = printMe; // onclick event is bind to the original printMe function
160+
161+
element.appendChild(btn);
162+
163+
return element;
164+
}
165+
166+
document.body.appendChild(element);
167+
```
111168

112-
This is just one example, but there are many others that can easily trip people up. Luckily, there are a lot of loaders out there, some mentioned below, that will make using this process much easier.
169+
This is just one example, but there are many others that can easily trip people up. Luckily, there are a lot of loaders out there (some of which are mentioned below) that will make hot module replacement much easier.
113170

114171

115172
## HMR with Stylesheets
116173

117-
We can use the `style-loader` to achieve Hot Module Replacement with CSS. This loader uses `module.hot.accept` behind the scenes to patch `<style>` tags when CSS dependencies are updated. So, with the following webpack configuration...
118-
119-
``` js
120-
module.exports = {
121-
// ...
122-
module: {
123-
rules: [
124-
{
125-
test: /\.css$/,
126-
use: [ 'style-loader', 'css-loader' ]
127-
}
128-
]
129-
},
130-
// ...
131-
}
174+
Hot Module Replacement with CSS is actually fairly straightforward with the help of the `style-loader`. This loader uses `module.hot.accept` behind the scenes to patch `<style>` tags when CSS dependencies are updated.
175+
176+
First let's install both loaders with the following command:
177+
178+
```bash
179+
npm install --save-dev style-loader css-loader
132180
```
133181

134-
hot loading stylesheets is a breeze...
182+
Now let's update the configuration file to make use of the loader.
183+
184+
__webpack.config.js__
185+
186+
```diff
187+
const path = require('path');
188+
const HtmlWebpackPlugin = require('html-webpack-plugin');
189+
const webpack = require('webpack');
190+
191+
module.exports = {
192+
entry: {
193+
app: './src/index.js',
194+
print: './src/print.js'
195+
},
196+
devtool: 'inline-source-map',
197+
devServer: {
198+
contentBase: './dist',
199+
hot: true
200+
},
201+
+ module: {
202+
+ rules: [
203+
+ {
204+
+ test: /\.css$/,
205+
+ use: ['style-loader', 'css-loader']
206+
+ }
207+
+ ]
208+
+ },
209+
plugins: [
210+
new HtmlWebpackPlugin({
211+
title: 'Hot Module Replacement'
212+
}),
213+
new webpack.HotModuleReplacementPlugin()
214+
],
215+
output: {
216+
filename: 'bundle.js',
217+
path: path.resolve(__dirname, 'dist')
218+
}
219+
};
220+
```
135221

136-
__index.js__
222+
Hot loading stylesheets is as easy as importing them into a module:
137223

138-
``` js
139-
import Lib from './library';
140-
import './styles.css';
224+
__project__
141225

142-
// ...
226+
``` diff
227+
webpack-demo
228+
| - package.json
229+
| - webpack.config.js
230+
| - /dist
231+
| - bundle.js
232+
| - /src
233+
| - index.js
234+
| - print.js
235+
+ | - styles.css
143236
```
144237

145238
__styles.css__
@@ -150,8 +243,51 @@ body {
150243
}
151244
```
152245

246+
__index.js__
247+
248+
``` diff
249+
import printMe from './print.js';
250+
+ import './styles.css';
251+
252+
if (module.hot) {
253+
module.hot.accept('./print.js', function() {
254+
console.log('Accepting the updated printMe module!');
255+
document.body.removeChild(element);
256+
element = component(); // Re-render the "component" to update the click handler
257+
document.body.appendChild(element);
258+
})
259+
}
260+
261+
let element = component();
262+
263+
function component() {
264+
var element = document.createElement('div');
265+
var btn = document.createElement('button');
266+
267+
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
268+
269+
btn.innerHTML = 'Click me and check the console!';
270+
btn.onclick = printMe; // onclick event is bind to the original printMe function
271+
272+
element.appendChild(btn);
273+
274+
return element;
275+
}
276+
277+
document.body.appendChild(element);
278+
```
279+
153280
Change the style on `body` to `background: red;` and you should immediately see the page's background color change without a full refresh.
154281

282+
__styles.css__
283+
284+
``` diff
285+
body {
286+
- background: blue;
287+
+ background: red;
288+
}
289+
```
290+
155291

156292
## Other Code and Frameworks
157293

0 commit comments

Comments
 (0)