Stop goofing around during build cycles with webpack HMR

Martin Snyder / @MartinSnyder / Wingspan Technology, Inc.

What is webpack HMR?

"webpack HMR is a feature to inject updated modules into the active runtime."

How does webpack HMR work?

  • Take ownership of all require/import statements
  • Build array of modules
  • Build dependency graph of module references
  • Signal changes via websocket
  • Reload all modified & dependant modules

Demo

HMR in action

webpack Mechanics

  • package.json
  • webpack.config
  • webpack-dev-server

webpack HMR minimal package.json

{
  ...
  "scripts": {
    "start": "./node_modules/.bin/webpack-dev-server --hot --inline"
  },
  "devDependencies": {
    "webpack": "^1.13.1",
    "webpack-dev-server": "^1.14.1"
  }
}

webpack HMR typical package.json

{
  ...
  "devDependencies": {
    "babel-core": "^6.10.4",
    "babel-loader": "^6.2.4",
    "babel-preset-es2015": "^6.9.0",
    "css-loader": "^0.24.0",
    "file-loader": "^0.9.0",
    "style-loader": "^0.13.1",
    "url-loader": "^0.5.7",
    "webpack": "^1.13.1",
    "webpack-dashboard": "^0.1.8",
    "webpack-dev-server": "^1.14.1"
  }
}

webpack minimal webpack.config.js

const webpack = require('webpack');

module.exports = {
    entry:  './src/index.js',
    module: {
        loaders: [{
            test: /.js$/,
            loader: 'babel',
            exclude: /node_modules/,
            query: {
                presets: 'es2015'
            }
        }]
    }
};

webpack-dev-server

webpack-dashboard

JavaScript Mechanics

  • "Activating" HMR
  • Accommodating side effects
  • Preserving state
  • Bailing out

"Activating" HMR

if (module.hot) {
    module.hot.accept();
}

Accommodating side effects

// Some operation with a side-effect (like adding a DOM element)

if (module.hot) {
    module.hot.dispose(() => {
        // Undo side effect, if possible
    });
}

Preserving state

// Initialize module state (cannot use const)
let state = {
    count: 0
};

if (module.hot) {
    // Re-assign state based on module data
    const data = module.hot.data || {};
    state = data.state || state;

    module.hot.dispose((data) => {
        // Store our retained data here so the next iteration of our module can access it
        data.state = state;
    });
}

Bailing out

import * as library from 'some-module';

library.invoke();

if (module.hot) {
    // I am not HMR safe
    module.hot.decline();

    // My dependency is not HMR safe
    module.hot.decline('some-module');
}

Live Coding

Hazards - State

import * as library from 'some-module';

let state = new library.Class()

if (module.hot) {
    // Re-assign state based on module data
    const data = module.hot.data || {};
    state = data.state || state;

    module.hot.dispose((data) => {
        // Store our retained data here so the next iteration of our module can access it
        data.state = state;
    });
}

Hazards - Async Callbacks

import * as library from 'some-module';

library
    .ajax({
        url: 'http://localhost/service'
    }).then((payload) => {
        // Process
    });

Related Topics

  • Working with symlinked modules
  • Developer vs. Production builds
  • HMR with React.js

Links

Thank You!

Martin Snyder / @MartinSnyder / Wingspan Technology, Inc.