【问题标题】:Listen for hot update events on the client side with webpack-dev-derver?使用 webpack-dev-server 在客户端监听热更新事件?
【发布时间】:2017-08-09 16:16:35
【问题描述】:

这有点极端,但了解一下会很有帮助。

在使用 webpack-dev-server 开发扩展以保持扩展代码最新时,收听“webpackHotUpdate”会很有用

带有内容脚本的 Chrome 扩展程序通常有两个方面:

  1. 背景
  2. 注入的内容脚本

当使用带有 HMR 的 webpack-dev-server 时,后台页面保持同步就好了。然而,内容脚本需要重新加载扩展以反映更改。我可以通过监听来自 hotEmmiter 的“webpackHotUpdate”事件然后请求重新加载来解决这个问题。目前,我的工作方式很糟糕,而且非常不可靠。

var hotEmitter = __webpack_require__(XX)

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

XX 仅表示当前分配给发射器的编号。正如您可以想象的那样,每当构建发生变化时,这都会发生变化,因此这是一种非常临时的概念证明。

我想我可以设置自己的套接字,但这似乎有点过头了,因为事件已经在传输,我只是想听。

我最近对 ​​webpack 生态系统越来越熟悉,因此非常感谢任何指导。

【问题讨论】:

    标签: javascript webpack-dev-server hot-module-replacement


    【解决方案1】:

    好的!

    我通过查看这里解决了这个问题:

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

    非常感谢 create-react-app 团队明智地使用 cmets。

    我为此创建了一个精简版本,专门用于处理扩展开发的重新加载条件。

    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()
    }
    

    当您准备好使用它时(仅在开发期间),您可以简单地将其添加到您的 background.js 入口点

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




    要真正按照最初的要求连接到事件发射器,您可以只从 webpack/hot/emitter 中要求它,因为该文件导出了所使用的 EventEmitter 的实例。
    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()
        })
    }
    

    这只是源代码的精简版:

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

    【讨论】:

    • 听起来很酷,我会试试看。如果我理解正确,您答案中源代码的最后一部分(以 if(module.hot) { 开头)只是一种替代解决方案。您能否首先分享有关如何让扩展从 webpack 开发服务器运行的任何提示,因为似乎 localhost 上的捆绑 js 文件会导致 manifest.json 中的内容脚本无效?
    • 任何人都可以确认上述代码对您有效吗?对我来说它不起作用:'(
    【解决方案2】:

    我已经微调了 crx-hotreload 包的核心逻辑,并提出了一个与构建工具无关的解决方案(这意味着它可以与 Webpack 一起使用,也可以与其他任何东西一起使用)。

    它向扩展询问其目录(通过chrome.runtime.getPackageDirectoryEntry),然后监视该目录的文件更改。在该目录中添加/删除/更改文件后,它会调用chrome.runtime.reload()

    如果您还需要重新加载活动选项卡(在开发内容脚本时),那么您应该运行 tabs.query,从结果中获取第一个(活动)选项卡并对其调用 reload。

    整个逻辑大约是 35 行代码:

    /* 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))
      }
    })
    

    您应该将此脚本添加到您的 manifest.json 文件的后台脚本条目中:

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

    自述文件中有一个简单解释的要点:https://gist.github.com/andreasvirkus/c9f91ddb201fc78042bf7d814af47121

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-10-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-29
      • 2018-02-08
      • 2017-08-07
      相关资源
      最近更新 更多