谷歌V3插件热更新
项目配置
vue-cli构建,vue3+webpage+谷歌插件V3
实现方法
SSE链接+本地的HMR服务
使用到的依赖:
- express(搭建本地 HTTP 服务,处理 SSE 连接)
- cors(允许前端页面跨域访问本地 HMR 服务)
- eventemitter3(实现服务端事件推送和监听)
代码实现
实现SSE
// sse.js 文件路径:最外层const express = require('express') // 引入 express,用于创建 HTTP 服务
const cors = require('cors') // 引入 cors,允许跨域请求
const EventEmitter = require('eventemitter3') // 引入 eventemitter3,用于事件管理和消息推送
const pkg = require('./package.json') // 引入 package.json,用于日志输出项目名// 工厂函数:创建 SSE 服务
exports.createSSE = (option) => {const { port, isWatch } = optionlet emitter = new EventEmitter(); // 创建事件管理器if (!isWatch) return { send: () => {}} // 非 watch 模式返回空 send 方法const app = express()app.use(cors()); // 启用跨域// 监听 /hmr 路径,建立 SSE 连接app.get('/hmr', (_, res) => {console.log(`\x1B[32m[${pkg.name}]\x1B[0m client connected.`);res.set({'Content-Type': 'text/event-stream', // SSE 必须的响应头'Cache-Control': 'no-cache','X-Accel-Buffering': 'no',});// 当有 hmr 事件时,向客户端推送消息emitter.on('hmr', (data) => {res.write(`data: ${JSON.stringify({ type: 'hmr', data })}\n\n`);})})// 启动服务,监听指定端口app.listen(Number(port), () => {console.log('启动成功')})// 返回 send 方法,触发 hmr 事件,推送消息给所有客户端return {send: (data) => emitter.emit('hmr', data)}
}
在vue.config.js中引入sse.js文件,创建SSE协议并自定义插件,在webpack的afterEmit钩子(即所有文件输出到磁盘后)用SSE向前端发送消息
// vue.config.jsconst { createSSE } = require('./sse')
const isWatch = process.argv.includes('--watch')
const sse = createSSE({ port: process.env.PORT, isWatch })chainWebpack: (config) => {config.plugin('custom-plugin').use({apply: (compiler)=>{compiler.hooks.afterEmit.tap('CustomPlugin',(state)=>see.send())}})
})
构建热更新处理函数
// hmr.js// 热更新处理函数,env 表示当前环境(background 或 contentScript)
async function onHMR (env) {// 仅在开发环境下启用if (process.env.NODE_ENV !== "development") return// 获取扩展的 manifest 信息const pgk = await fetch(chrome.runtime.getURL('manifest.json')).then(res => res.json())// 控制台输出连接建立提示console.log(`%c【${pgk.name}】已建立连接`,"color: white; background-color: green; padding: 2px 6px; border-radius: 2px; font-weight: bold;");// 连接本地 HMR 服务const source = new EventSource('http://localhost:3000/hmr');source.addEventListener('message', () => {// 如果是 background 环境,重载扩展if (env === 'background') {chrome.runtime.reload()}// 如果是 contentScript 环境,刷新页面if (env === 'contentScript') {location.reload();}});
}// 启动 background 环境的 HMR
onHMR('background')// 监听页面标签更新事件
chrome.tabs.onUpdated.addListener(async function (tabId, changeInfo, tab) {// 当页面加载完成且有 URL 时if (changeInfo.status === 'complete' && tab.url) {// 获取扩展的 manifest 信息const pgk = await fetch(chrome.runtime.getURL('manifest.json')).then(res => res.json())// 获取 content_scripts 的匹配规则const matches = pgk.content_scripts[0].matches// 判断当前标签页的 URL 是否匹配 content_scripts 规则const ismatch = matches.some(url => tab.url.includes(url.replace('*', '')))if (ismatch) {// 在匹配的标签页注入 HMR 脚本chrome.scripting.executeScript({target: { tabId },func: onHMR,args: ['contentScript']});}}
});
将热更新处理还是引入到background.js
// background.jsimport './hmr.js'
结语
在本地构建SSE协议,通过在webpage中注入自定义插件,在修改页面触发本地热更新重新打包后,向客户端发送SSE,客户端通过在页面嵌入js代码,启用监听SSE的message,如果是backgournd环境就chrome.runtime.reload()重载插件,如果是页面就location.reload()
其他
- 关于SSE
严格地说,HTTP 协议无法做到服务器主动推送信息。但是,有一种变通方法,就是服务器向客户端声明,接下来要发送的是流信息(streaming)。
也就是说,发送的不是一次性的数据包,而是一个数据流,会连续不断地发送过来。这时,客户端不会关闭连接,会一直等着服务器发过来的新的数据流,视频播放就是这样的例子。本质上,这种通信就是以流信息的方式,完成一次用时很长的下载。
SSE 就是利用这种机制,使用流信息向浏览器推送信息。它基于 HTTP 协议,目前除了 IE/Edge,其他浏览器都支持。 - new EventSource(url)
是浏览器提供的 Server-Sent Events (SSE) API,用于创建一个到服务器的单向实时连接。