Skip to content

HMR (hot module reloading replacement)

Created: 2018-02-12 13:40:15 -0800 Modified: 2018-05-08 10:22:07 -0700

On 2/1/2017, I tried to get HMR working. The main issues that I ran into had to do with how HMR takes place entirely in memory, so anything that I used Gulp for wasn’t going to work. There were two things that I used Gulp for: one was to replace “static-assets-placeholder” with the CloudFront link, and the other was the convert Pug to HTML.

A year later, in February 2018, I tried to get HMR working again and ran into a bunch of difficulties. The original issues around static-assets-placeholder and Pug—>HTML still needed to be tackled. However, I had already done work at the end of November 2017 to get rid of static-assets-placeholder in all but one location (this work was for the purpose of being able to version files individually). Also, converting Pug—>HTML is something that Webpack allows via html-webpack-plugin, so my original issues were much more straightforward this time around.

In general, my steps went something like this:

  1. Follow the instructions in Webpack’s documentation to set up things like the plugins (NamedModulesPlugin and HotModuleReplacementPlugin), devServer config for webpack-dev-server, etc.
  2. Support changes in React by wrapping my React-Router <Router> component in an even higher-level component, then listening via module.hot.accept for changes to that wrapper component and reinserting it into the DOM.
    1. I got stuck here for a while because of how I transpile modules into ES5 code, so I actually needed to explicitly re-require the updated wrapper class. This wouldn’t have been an issue if I weren’t transpiling modules
    2. I got stuck for even longer because I needed to extract my React-Router routes into their own file (see this issue and this example repo which has a solution). This wouldn’t have been an issue if I were on React-Router v4.
  3. Support changes to styles by updating style-loader. This was probably not technically needed, but all the update did in relation to HMR for me was changing an argument that defaulted to true so that it was false (or vice versa; doesn’t matter). The point is that HMR was on by default in the new version and I didn’t realize that I needed to enable it in the old version.

Just add “—host 0.0.0.0” to the webpack-dev-server command.

HiDeoo gave me just a quick checklist to go through in case there was something obviously wrong. In looking into all but the last one, I didn’t notice a huge difference.

  • Just for sanity, you have 2 Webpack config right? One for dev & one for prod? Or at least the plugins array is different between the two env right and you don’t minify or things like that in dev? Correct?
  • babel-loader use the cacheDirectory option?
  • css-loader sourceMap option is enabled or disabled in dev?
  • css-loader minimize option is enabled or disabled in dev?
  • For sanity, in the webpack root config, try adding cache: true, it should be auto in your case but for sanity.
  • Could you try measuring reloading time by replacing new webpack.HotModuleReplacementPlugin() by new webpack.HotModuleReplacementPlugin({ multiStep: true }) (this may not work with your current config)
  • Last idea would be to use the webpack.DllPlugin to avoid rebuilding some parts (like some vendors) but not sure you want to go that route “yet”.

Just saving this in my notes since I don’t plan on doing this right now (2/12/2018):

2:05 HiDeoo: Adam13531 For notes or even later if you want that, I’ll link that now: Redux HMR, it’s basically the same, you just need 1 special HMR handler that when it detects a change in a reducer file, it call store.replaceReducer after requiring it https://gist.github.com/markerikson/dc6cee36b5b6f8d718f2e24a249e0491#file-configurestore-js-L50 (ignore the saga part)

I’m not entirely sure why you’d want this, but I wanted to figure out how to get this to work, so here it is:

  1. Make sure you have an NPM script for running “cold” or running “hot”

“start:dev:hot”: “webpack-dev-server —hot”,

“start:dev”: “webpack-dev-server —inline=false”

The —inline=false is so that live reloading isn’t done for you

  1. Remove all of the hot-by-default things from webpack.config:
    1. Stop adding HotModuleReplacementPlugin since —hot will do that for you
    2. Stop specifying hot:true in devServer config since —hot will do that for you

A file doesn’t seem to exist or doesn’t have the contents that I expect

Section titled A file doesn’t seem to exist or doesn’t have the contents that I expect

Because webpack-dev-server serves from memory, you can’t just go inspect what’s on the disk to figure out why something isn’t what you expect. Instead, you can go to “webpack-dev-server” (e.g. http://localhost:3000/webpack-dev-server) to click files and figure out what their contents are in memory.

”[HMR] Cannot apply update. Need to do a full reload!” / the page is fully refreshing instead of just “hotly” reloading

Section titled ”[HMR] Cannot apply update. Need to do a full reload!” / the page is fully refreshing instead of just “hotly” reloading

When there’s an error or a non-accepted module that gets modified, the page will do a full reload. First, you should figure out if that error message is even printing by doing this:

  1. In Chrome, go to the console
  2. Click the gear
  3. Check the “Preserve log” checkbox

This will maintain the log across refreshes, which will let you see the error right before HMR caused a refresh. In the case of an error, you’ll see a typical error message like “can’t call a function on undefined” or whatever. In the case that it just doesn’t match an accepted module, you’ll have to add something like this:

module.hot.accept('./react_and_redux/containers/testcontainer.js', () => {
const NextContainer = require('./react_and_redux/containers/testcontainer.js').default;
setupReact(NextContainer, main.reactRouterHistory, main.store);
});

I was running into warnings that weren’t really in my code, e.g.

./node_modules/@botland/shared/node_modules/escodegen/node_modules/source-map/lib/source-map/source-map-generator.js

10:43-50 Critical dependency: require function is used in a way in which dependencies cannot be statically extracted

@ ./node_modules/@botland/shared/node_modules/escodegen/node_modules/source-map/lib/source-map/source-map-generator.js

@ ./node_modules/@botland/shared/node_modules/escodegen/node_modules/source-map/lib/source-map.js

@ ./node_modules/@botland/shared/node_modules/escodegen/escodegen.js

@ ./node_modules/@botland/shared/lib/softwarevalidator.js

@ ./node_modules/@botland/shared/lib/index.js

@ ./public/javascripts/main.js

@ multi (webpack)-dev-server/client?http://localhost:3001 webpack/hot/dev-server babel-polyfill main.js

I think the problem was a conditional “require” statement, and I wasn’t going to fork a library just to fix the warning. Thankfully, Webpack has an option for this (reference), but you need to add it to the devServer configuration:

devServer: {
stats: {
// There are a bunch of warnings about escodegen using conditional
// "require" statements that I'd like to hide.
warningsFilter: /escodegen/,
},
},

Fixing it in Webpack is apparently only one part of this; the warnings will still show up in Chrome. The best workaround for this is apparently to just patch console.log (reference)

Despite filtering warnings above, I still get a pop-up about the warning thanks to webpack-notifier. I configured it to have excludeWarnings: true (which means I won’t get any warnings, not just the filtered ones from above).

An update to the code causes a React ref to be undefined

Section titled An update to the code causes a React ref to be undefined

This is actually quite simple (at least in the cases I ran into): chances are, the code that you updated caused the ref to no longer be defined. For example, I was testing this out in Bot Land and I’d removed a “renderAvatar” function just for testing. Then, “avatarRef” was undefined and I’d counted on it being defined, so obviously I just needed to put renderAvatar back in or fake having the ref again.

I made a plug-in and I pass in data like this:

htmlWebpackPlugin.options.data.includeGoogleAnalytics

So if I just try using “if includeGoogleAnalytics”, it will always be false.