javascriptwebpack-dev-serverhot-module-replacement

Listen for hot update events on the client side with webpack-dev-derver?


This is a bit of an edge case but it would be helpful to know.

When developing an extension using webpack-dev-server to keep the extension code up to date, it would be useful to listen to "webpackHotUpdate"

Chrome extensions with content scripts often have two sides to the equation:

  1. Background
  2. Injected Content Script

When using webpack-dev-server with HMR the background page stays in sync just fine. However content scripts require a reload of the extension in order to reflect the changes. I can remedy this by listening to the "webpackHotUpdate" event from the hotEmmiter and then requesting a reload. At present I have this working in a terrible and very unreliably hacky way.

var hotEmitter = __webpack_require__(XX)

hotEmitter.on('webpackHotUpdate', function() {
    console.log('Reloading Extension')
    chrome.runtime.reload()
})

XX simply represents the number that is currently assigned to the emitter. As you can imagine this changed whenever the build changes so it's a very temporary proof of concept sort of thing.

I suppose I could set up my own socket but that seems like overkill, given the events are already being transferred and I simply want to listen.

I am just recently getting more familiar with the webpack ecosystem so any guidance is much appreciated.


Solution

  • Okay!

    I worked this out by looking around here:

    https://github.com/facebookincubator/create-react-app/blob/master/packages/react-dev-utils/webpackHotDevClient.js

    Many thanks to the create-react-app team for their judicious use of comments.

    I created a slimmed down version of this specifically for handling the reload condition for extension development.

    var SockJS = require('sockjs-client')
    var url = require('url')
    
    // Connect to WebpackDevServer via a socket.
    var connection = new SockJS(
        url.format({
            // Default values - Updated to your own
            protocol: 'http',
            hostname: 'localhost',
            port: '3000',
            // Hardcoded in WebpackDevServer
            pathname: '/sockjs-node',
        })
    )
    
    var isFirstCompilation = true
    var mostRecentCompilationHash = null
    
    connection.onmessage = function(e) {
        var message = JSON.parse(e.data)
        switch (message.type) {
            case 'hash':
                handleAvailableHash(message.data)
                break
            case 'still-ok':
            case 'ok':
            case 'content-changed':
                handleSuccess()
                break
            default:
            // Do nothing.
        }
    }
    
    // Is there a newer version of this code available?
    function isUpdateAvailable() {
        /* globals __webpack_hash__ */
        // __webpack_hash__ is the hash of the current compilation.
        // It's a global variable injected by Webpack.
        return mostRecentCompilationHash !== __webpack_hash__
    }
    
    function handleAvailableHash(data){
        mostRecentCompilationHash = data
    }
    
    function handleSuccess() {
        var isHotUpdate     = !isFirstCompilation
        isFirstCompilation  = false
    
        if (isHotUpdate) { handleUpdates() }
    }
    
    function handleUpdates() {
        if (!isUpdateAvailable()) return
        console.log('%c Reloading Extension', 'color: #FF00FF')
        chrome.runtime.reload()
    }
    

    When you are ready to use it (during development only) you can simply add it to your background.js entry point

    module.exports = {
        entry: {
            background: [
                path.resolve(__dirname, 'reloader.js'), 
                path.resolve(__dirname, 'background.js')
            ]
        }
    }
    




    For actually hooking into the event emitter as was originally asked you can just require it from webpack/hot/emitter since that file exports an instance of the EventEmitter that's used.

    if(module.hot) {
        var lastHash
    
        var upToDate = function upToDate() {
            return lastHash.indexOf(__webpack_hash__) >= 0
        }
    
        var clientEmitter = require('webpack/hot/emitter')
    
        clientEmitter.on('webpackHotUpdate', function(currentHash) {
            lastHash = currentHash
            if(upToDate()) return
    
            console.log('%c Reloading Extension', 'color: #FF00FF')
            chrome.runtime.reload()
        })
    }
    

    This is just a stripped down version straight from the source:

    https://github.com/webpack/webpack/blob/master/hot/dev-server.js