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

只愿长相守 提交于 2021-02-18 18:08:58

问题


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.


回答1:


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




回答2:


I've fine-tuned the core logic of the crx-hotreload package and come up with a build-tool agnostic solution (meaning it will work with Webpack but also with anything else).

It asks the extension for its directory (via chrome.runtime.getPackageDirectoryEntry) and then watches that directory for file changes. Once a file is added/removed/changed inside that directory, it calls chrome.runtime.reload().

If you'd need to also reload the active tab (when developing a content script), then you should run a tabs.query, get the first (active) tab from the results and call reload on it as well.

The whole logic is ~35 lines of code:

/* global chrome */

const filesInDirectory = dir => new Promise(resolve =>
  dir.createReader().readEntries(entries =>
    Promise.all(entries.filter(e => e.name[0] !== '.').map(e =>
      e.isDirectory
        ? filesInDirectory(e)
        : new Promise(resolve => e.file(resolve))
    ))
      .then(files => [].concat(...files))
      .then(resolve)
  )
)

const timestampForFilesInDirectory = dir => filesInDirectory(dir)
  .then(files => files.map(f => f.name + f.lastModifiedDate).join())

const watchChanges = (dir, lastTimestamp) => {
  timestampForFilesInDirectory(dir).then(timestamp => {
    if (!lastTimestamp || (lastTimestamp === timestamp)) {
      setTimeout(() => watchChanges(dir, timestamp), 1000)
    } else {
      console.log('%c 🚀 Reloading Extension', 'color: #FF00FF')
      chrome.runtime.reload()
    }
  })
}

// Init if in dev environment
chrome.management.getSelf(self => {
  if (self.installType === 'development' &&
    'getPackageDirectoryEntry' in chrome.runtime
  ) {
    console.log('%c 📦 Watching for file changes', 'color: #FF00FF')
    chrome.runtime.getPackageDirectoryEntry(dir => watchChanges(dir))
  }
})

You should add this script to your manifest.json file's background scripts entry:

"background": ["reloader.js", "background.js"]

And a Gist with a light explanation in the Readme: https://gist.github.com/andreasvirkus/c9f91ddb201fc78042bf7d814af47121



来源:https://stackoverflow.com/questions/42868821/listen-for-hot-update-events-on-the-client-side-with-webpack-dev-derver

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!