3
votes

I am learning Webpack and come across this article. I have general idea of what is hot module replacement(HMR). I can configure webpack HMR plugin by following example code:

var plugins = [ new webpack.HotModuleReplacementPlugin(), // using HMR plugin
            new HtmlWebpackPlugin({template: './index.html'})
        ]; 

module.exports = {
    // webpack config object
    context: entryBasePath,
    entry:{
        app: ['webpack/hot/dev-server', './bootstrap.js']
    },
    output: {
        path: outputBasePath,
        filename: './bundle.js',
        sourceMapFilename: '[file].map' // set source map output name rule
    },
    devtool: 'source-map', // enable source map
    plugins: plugins, 
    module: {
        loaders: [
            { test: /\.scss$/, loader: 'style!css!sass'}, 
            { test: /\.tpl$/,  loader: 'raw' }, 
            {
        test: /\.woff2?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        loader: 'url?limit=10000'
      },
      {
        test: /\.(ttf|eot|svg)(\?[\s\S]+)?$/,
        loader: 'file'
      },
      { test: /bootstrap-sass\/assets\/javascripts\//, loader: 'imports?jQuery=jquery' }

        ]
    }

}

My question is What aspect of this article is trying to explain about Webpack Hot Module Replacement? Where can I use the example code provided on that page?

In short, my question is what(the hell) is this about?

1

1 Answers

7
votes

Hot Module Replacement (HMR) is a feature to swap out code while the application is running. It allows you to edit code while preserving the state of the application. This is especially useful for styling, where you usually just want to update styles without reloading the browser.

This, however, is only possible if the code provides special hooks to remove the previous code, undo all side-effects and inject the new code. Typical side-effects: are registering event listeners, storing data in objects, modifying global state.

Replacing CSS while the application is running, for instance, is an easy task since CSS is side-effect free per definition. In order to understand the internals of HMR, let's take a look at the style-loader:

The style-loader appends this special code to handle HMR (I've removed some code that is not important for this example):

if (module.hot) {
    // When the styles change, update the <style> tags
    module.hot.accept(loaderUtils.stringifyRequest(this, !!remainingRequest), function () {
        var newContent = require(loaderUtils.stringifyRequest(this, !!remainingRequest));
        update(newContent);
    });
    // When the module is disposed, remove the <style> tags
    module.hot.dispose(function () {
        update();
    });
}
  • if (module.hot) { checks, if HMR is enabled
  • module.hot.accept(<module identifier>, handler) registers a handler to inject new code
  • module.hot.dispose(handler) registers a handler to dispose old code

The update function is hard to read, but it basically just adds new stylesheets to document.head and removes unused stylesheets.

HMR in React is a bit more complex and has gone through some serious refactoring in the last couple of month. But the basic principle is that every exported component is wrapped inside a proxy. A proxy is an object that acts like another object. This can either be achieved by forwarding all functions to the "real" object or by using ES2015 proxies (which are obviously much more powerful). This way, the original object can easily be swapped out without updating any other components. I've described this a bit more in detail in another SO answer.


In order to make HMR work, there are some requirements to be met:

  • Your code requires hooks for module.hot.accept and module.hot.dispose to handle code updates. This is usually achieved through a loader (for instance, the style-loader) or a babel transformation (for instance, the babel-preset-react-hmre preset). Technically, you could also write these hooks in every module for yourself... maybe that's a good way to start learning the internals. If an updated module does not contain this code or is unable to handle the update, the update will be rejected and webpack will reload the browser window. This might look like HMR but is actually just a browser refresh.

  • You need some infrastructure on the client and the server to establish a WebSockets connection, to push new code to the client and to inform all outdated modules to handle the update. The most easy way to achieve this is by using the webpack-dev-server with webpack-dev-server --hot --inline. There is no other code required. Especially don't add the HMR plugin or any webpack-dev-server stuff to the webpack.config.js – it will all be enabled with --hot --inline. There are more ways to configure this – which might actually be required depending on your setup. That's usually why this part is often so confusing for newcomers.

  • Webpack needs to run in watch mode since we only want to handle changed files. You don't need to add anything to the config when you're using the webpack-dev-server or the webpack-dev-middleware, it already puts the webpack compiler into watch-mode (only in case of lazy: false, of course).