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

在插件的首页我们与本地建立一个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
  })
}

当前环境为开发模式的话插入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()
  }
}

设置好了之后,我们在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
    },

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

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容