|
| 1 | +--- |
| 2 | +title: How to Configure Hot Module Replacement with React? |
| 3 | +--- |
| 4 | +Hot Module Replacement (HMR) exchanges, adds, or removes modules while an |
| 5 | +application is running without a page reload. |
| 6 | +HMR is particularly useful in applications using a single state tree, |
| 7 | +since components are "dumb" and will reflect the latest application state, even |
| 8 | +after their source is changed and they are replaced. |
| 9 | + |
| 10 | +##Project Config |
| 11 | +This guide will be demonstrating the use of HMR with Babel, |
| 12 | +React, and PostCSS (using CSS Modules). |
| 13 | +To follow along, please add the following deps to your `package.json`: |
| 14 | + |
| 15 | +To use HMR, you'll need the following dependencies: |
| 16 | + |
| 17 | +```shell |
| 18 | + |
| 19 | +``` |
| 20 | + |
| 21 | +In addition, for the purposes of this walkthrough, you'll need: |
| 22 | + |
| 23 | +```shell |
| 24 | + |
| 25 | +``` |
| 26 | + |
| 27 | + |
| 28 | +###Babel Config |
| 29 | +Your `.babelrc` file should look like the following: |
| 30 | + |
| 31 | +```js |
| 32 | +{ |
| 33 | + "presets": [ |
| 34 | + ["es2015", {"modules": false}], |
| 35 | + //Webpack understands the native import syntax, and uses it for tree shaking |
| 36 | + |
| 37 | + "stage-2", |
| 38 | + //Specifies what level of language features to activate. |
| 39 | + //State 2 is "draft", 4 is finished, 0 is strawman. |
| 40 | + //See https://tc39.github.io/process-document/ |
| 41 | + |
| 42 | + "react" |
| 43 | + //Transpile React components to JS |
| 44 | + ], |
| 45 | + "plugins": [ |
| 46 | + "react-hot-loader/babel" |
| 47 | + //Enables React code to work with HMR. |
| 48 | + ] |
| 49 | +} |
| 50 | +``` |
| 51 | + |
| 52 | +###Webpack config |
| 53 | +While there's many ways of setting up your Webpack config - via API, |
| 54 | +via multiple or single config files, etc - here is the basic information |
| 55 | +you should have available. |
| 56 | + |
| 57 | +```js |
| 58 | +const { resolve } = require('path'); |
| 59 | +const webpack = require('webpack'); |
| 60 | + |
| 61 | +module.exports = env => { |
| 62 | + return { |
| 63 | + entry: [ |
| 64 | + 'react-hot-loader/patch', |
| 65 | + //activate HMR for React |
| 66 | + |
| 67 | + 'webpack-dev-server/client?http://localhost:8080', |
| 68 | + //bundle the client for webpack dev server |
| 69 | + //and connect to the provided endpoint |
| 70 | + |
| 71 | + 'webpack/hot/only-dev-server', |
| 72 | + //bundle the client for hot reloading |
| 73 | + //only- means to only hot reload for successful updates |
| 74 | + |
| 75 | + |
| 76 | + './index.js' |
| 77 | + //the entry point of our app |
| 78 | + ], |
| 79 | + output: { |
| 80 | + filename: 'bundle.js', |
| 81 | + //the output bundle |
| 82 | + |
| 83 | + path: resolve(__dirname, 'dist'), |
| 84 | + |
| 85 | + publicPath: '/' |
| 86 | + //necessary for HMR to know where to load the hot update chunks |
| 87 | + }, |
| 88 | + |
| 89 | + context: resolve(__dirname, 'src'), |
| 90 | + |
| 91 | + devtool: 'inline-source-map', |
| 92 | + |
| 93 | + devServer: { |
| 94 | + hot: true, |
| 95 | + //activate hot reloading |
| 96 | + |
| 97 | + contentBase: '/dist' |
| 98 | + //match the output path |
| 99 | + |
| 100 | + publicPath: '/' |
| 101 | + //match the output publicPath |
| 102 | + }, |
| 103 | + |
| 104 | + module: { |
| 105 | + loaders: [ |
| 106 | + { test: /\.js$/, |
| 107 | + loaders: [ |
| 108 | + 'babel', |
| 109 | + ], |
| 110 | + exclude: /node_modules/ |
| 111 | + }, |
| 112 | + { |
| 113 | + test: /\.css$/, |
| 114 | + loaders: [ |
| 115 | + 'style', |
| 116 | + 'css-loader?modules', |
| 117 | + 'postcss-loader', |
| 118 | + ], |
| 119 | + }, |
| 120 | + ], |
| 121 | + }, |
| 122 | + |
| 123 | + plugins: [ |
| 124 | + new webpack.HotModuleReplacementPlugin(), |
| 125 | + //activates HMR |
| 126 | + |
| 127 | + new webpack.NamedModulesPlugin(), |
| 128 | + //prints more readable module names in the browser console on HMR updates |
| 129 | + ], |
| 130 | + } |
| 131 | +}; |
| 132 | +``` |
| 133 | + |
| 134 | +There's a lot going on above, and not all of it is related to HMR. |
| 135 | +You may benefit from reading the |
| 136 | +[full documentation](https://webpack.github.io/docs/webpack-dev-server.html) |
| 137 | +on webpack dev server, and the [other articles](https://webpack.github.io/webpack.io/concepts/) |
| 138 | +here on webpack.io. |
| 139 | + |
| 140 | +The basic assumption here is that your JS entry is located at `./src/index.js`, |
| 141 | +and that you're using CSS Modules for your styling. |
| 142 | + |
| 143 | +Please see the comments inline that explain each portion of the config. The main |
| 144 | +areas to look are the `devServer` key and the `entry` key. The `HotModuleReplacementPlugin` is |
| 145 | +also necessary to include in the `plugins` array. |
| 146 | + |
| 147 | +There are two modules included here for the purposes of this guide. |
| 148 | +The react-hot-loader addition to the entry, as noted above, is necessary to enable |
| 149 | +HMR with React components. The NamedModulesPlugin is a useful addition |
| 150 | +to better understand what modules are being updated when using HMR. |
| 151 | + |
| 152 | +###Code |
| 153 | +In this guide, we're using the following files: |
| 154 | + |
| 155 | +```js |
| 156 | +// ./src/index.js |
| 157 | +import React from 'react'; |
| 158 | +import ReactDOM from 'react-dom'; |
| 159 | + |
| 160 | +import { AppContainer } from 'react-hot-loader' |
| 161 | +// AppContainer is a necessary wrapper component for HMR |
| 162 | + |
| 163 | +import App from './components/App'; |
| 164 | + |
| 165 | +const render = () => { |
| 166 | + ReactDOM.render( |
| 167 | + <AppContainer> |
| 168 | + <App/> |
| 169 | + </AppContainer>, |
| 170 | + document.getElementById('root') |
| 171 | + ); |
| 172 | +}; |
| 173 | + |
| 174 | +render(); |
| 175 | + |
| 176 | +// Hot Module Replacement API |
| 177 | +if (module.hot) { |
| 178 | + module.hot.accept('./components/App', render); |
| 179 | +} |
| 180 | + |
| 181 | + |
| 182 | +// ./src/components/App.js |
| 183 | +import React from 'react'; |
| 184 | +import styles from './App.css'; |
| 185 | + |
| 186 | +const App = () => ( |
| 187 | + <div className={styles.app}> |
| 188 | + <h2>Hello, </h2> |
| 189 | + </div> |
| 190 | +); |
| 191 | + |
| 192 | +export default App; |
| 193 | +``` |
| 194 | + |
| 195 | +```css |
| 196 | +// ./src/components/App.css |
| 197 | +.app { |
| 198 | + text-size-adjust: none; |
| 199 | + font-family: helvetica, arial, sans-serif; |
| 200 | + line-height: 200%; |
| 201 | + padding: 6px 20px 30px; |
| 202 | +} |
| 203 | +``` |
| 204 | + |
| 205 | +The important thing to note in the code above is the `module` reference. |
| 206 | +First, we wrap the HMR code inside of `module.hot` check; |
| 207 | +webpack exposes `module` to the code, and if we are running with `hot: true` configured, |
| 208 | +we'll enter the inside of the conditional. |
| 209 | + |
| 210 | +While the module API offers more options than what's above, the most |
| 211 | +important element is the `module.hot.accept` call. |
| 212 | +It specific how to handle changes to specific dependencies. |
| 213 | + |
| 214 | +So in this case, `module.hot` will fire the `render` method ONLY |
| 215 | +when `src/components/App.js` changes! Note that would also include when the |
| 216 | +dependencies of `App.js` change - |
| 217 | +so the `render` method will file not just for changes made directly to the |
| 218 | +source of `App.js`, but also changes made to `App.css`, since `App.css` |
| 219 | +is included in `App.js`. |
| 220 | + |
| 221 | +###Package.json |
| 222 | +Finally, we need to start up webpack dev server to bundle our code and see HMR in action. |
| 223 | +We can use the following package.json entry: |
| 224 | + |
| 225 | +```js |
| 226 | + "start" : "webpack-dev-server --env.dev", |
| 227 | +``` |
| 228 | + |
| 229 | +Run `npm start`, open up your browser to `localhost:8080`, |
| 230 | +and you should see the folling entries printed in your console.log: |
| 231 | + |
| 232 | +``` |
| 233 | +dev-server.js:49[HMR] Waiting for update signal from WDS... |
| 234 | +only-dev-server.js:74[HMR] Waiting for update signal from WDS... |
| 235 | +client?c7c8:24 [WDS] Hot Module Replacement enabled. |
| 236 | +``` |
| 237 | + |
| 238 | +Go ahead and edit and save your App.js file. |
| 239 | +You should see something like the following in your console.log: |
| 240 | + |
| 241 | +``` |
| 242 | +[WDS] App updated. Recompiling... |
| 243 | +client?c7c8:91 [WDS] App hot update... |
| 244 | +dev-server.js:45 [HMR] Checking for updates on the server... |
| 245 | +log-apply-result.js:20 [HMR] Updated modules: |
| 246 | +log-apply-result.js:22 [HMR] - ./components/App.js |
| 247 | +dev-server.js:27 [HMR] App is up to date. |
| 248 | +``` |
| 249 | +Note that HMR specifies the paths of the updated modules. |
| 250 | +That's because we're using the NamedModules plugin! |
| 251 | + |
| 252 | + |
0 commit comments