• chrome浏览器插件热更新vite实战


    热更新,又名模块热替换:Hot Module Replacement,简称HMR,无需完全刷新整个页面的同时,更新模块。HMR主要用于提升开发体验。

    页面的刷新有多种情况:页面级别的刷新,简单粗暴,但是不保留页面状态。即:window.location.reload(),
    刀耕火种时代的页面更新都是采用这种方法,写完代码之后页面并不能及时看到效果。需要手动刷新页面,或者触发reload。

    后来webpack提供了更为友好的开发体验,只更新局部模块,而不是刷新页面。在代码变更之后自动触发对应的更新。同时保留页面的状态(输入框的值、选择器的选中等)。
    我们首先知道webpack热更新原理:

    1. dev-server和页面建立了websocket连接
    2. 监听到文件变化
    3. 通过websocket告诉页面模块发送了变化
      4.页面局部更新

    想要了解更详细的可以搜webpack热更新,此处不多做赘述。
    我们了解了大概原理之后,就可以在没有webpack的时候自己实现。
    浏览器插件的开发在代码编写完之后需要扔到浏览器扩展应用里面才可以看到效果,并且在代码变更之后是需要手动刷新,这时候我们就没有webpack工具来给我们提供热更新的支持。

    我在尝试用vite开发浏览器插件的时候,就在思考,我们可以通过热更新一样的原理去帮插件自动更新。

    同样的原理:

      const ws = new WebSocket('ws://localhost:2333')
        
      ws.onmessage = (event) => {
        console.log('reload trigger', event)
          let msg = JSON.parse(event.data)
          if (msg === 'reload-app') {
            chrome.runtime.reload()
          }
    
          if(msg === 'reload-window') {
            window.location.reload()
          }
      }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在插件的首页我们与本地建立一个ws的连接,来接受本地代码变更之后发送的消息。
    当接收到reload-window时,执行页面的重刷新。
    当接收到reload-app时,执行插件的重刷新,这个方法相当于在插件扩展页面点击刷新按钮。

    在vite的vite.config.ts配置文件我们加入自定义的hrm plugin

    import { defineConfig, loadEnv } from 'vite'
    import react from '@vitejs/plugin-react'
    import { HRMMiddleware } from './rollup-plugins/hrm'
    
    // https://vitejs.dev/config/
    export default ({ mode }) => {
      const env = loadEnv(mode, __dirname)
      const isDev = env.VITE_NODE_ENV = 'development'
    
      const plugins: any[] = [react()]
      if(isDev) plugins.push(HRMMiddleware())
    
      return defineConfig({
        build: {
          emptyOutDir: false
        },
        plugins
      })
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    当前环境为开发模式的话插入hrm的pulgin。

    import { ConfigEnv, UserConfig } from "vite"
    import WebSocket, { WebSocketServer } from "ws"
    
    export const HRMMiddleware = () => {
      let wsClient: WebSocket | null
      let socketServer : WebSocketServer | null
      
      console.log('HRM middleware in', '-----------')
      // 发送通知
      const send = (msg) => {
        console.log('before send meg', msg)
        if (!wsClient) return
        msg = JSON.stringify(msg)
        wsClient.send(msg)
        console.log('sended meg', msg)
      }
    
      const close = () => {
        wsClient && wsClient.close()
        wsClient = null
        socketServer = null
      }
      
      return {
        name: 'hrm-plugin',
        apply(config: UserConfig, { command }: ConfigEnv) {
          // build 且 watch 的情况下插件生效
          const canUse = command === 'build' && Boolean(config.build?.watch)
          if (canUse) {
            // 创建 websocket server
            socketServer = new WebSocketServer({ port: 2333 })
            socketServer.on('connection', (client) => { wsClient = client })
          }
          return canUse
        },
        // popup页面发生变动,重新加载window即可。
        closeBundle: () => send('reload-window'),
        closeWatcher: () => close()
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    设置好了之后,我们在vite开发的时候使用watch模式,当代码变化之后重新打包会触发页面的更新。

    不过这里还有个问题就是:
    当插件的配置文件变更了之后,不会自动刷新插件。也就是配置不生效。

        apply(config: UserConfig, { command }: ConfigEnv) {
          // build 且 watch 的情况下插件生效
          const canUse = command === 'build' && Boolean(config.build?.watch)
          if (canUse) {
            // 创建 websocket server
            socketServer = new WebSocketServer({ port: 2333 })
            socketServer.on('connection', (client) => { wsClient = client })
            // public 文件发生变动,需要reload插件。
            chokidar
              .watch([PUBLIC_DIR, CONTENT_FILE], { ignoreInitial: true })
              .on('all', debounce((event, path) => {
                console.log(event, path)
                if(path.includes('/public/')) {
                  const dest = resolve(__dirname, `../dist/${path.split('/').pop()}`)
                  console.log(`copy file ${path} to ${dest}`)
                  fs.copyFileSync(path, dest)
                }
                send('reload-app')
              }))
          }
          return canUse
        },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    这里和webpack一样使用chokidar模块去监听配置文件的变化,然后触发reload-app。

  • 相关阅读:
    FPGA project : ROM_VGA
    Vscode禁止插件自动更新
    CentOS7环境下定时任务执行不成功的排查
    Python文件处理相关操作
    【Java】监听器
    USB母座引脚定义
    【BI看板】Superset2.0+图表二次开发初探
    Solidity 的 ABI 和 bytecode 是什么
    zookeeper 查询注册的 dubbo 服务
    Python Flask框架(二)Flask与HTTP
  • 原文地址:https://blog.csdn.net/q275757160/article/details/127919231