上文我们了解了如何实现一个基础版的插件开发。当然,在现代开发模式下,我们肯定不能接受如此简陋的开发模式,那么我们如何进行下一步的优化呢,现在我们继续就如何使用组件库和热更新融合方面进行探索

一、优化

如今前端工具百花齐放。我们放着更好的工具不用岂不是浪费。能不能把我们的前端工具集成到chrome插件开发过程中,来实现更快捷的开发体验和更合适的ui展示呢?那我们就试着把vite+vue接入进来吧。

目录调整

  1. 首先我们先快速生成vite与vue,TS的项目,
  2. 同时调整目录结构,将我们需要的content、popup等功能分开在不同的结构中。
  3. 安装组件库、pinia等工具;安装chrome-types 来对我们的api进行提示

目录结构如下所示
image.png

vite配置

  1. 通过rollup-plugin-copy 将静态资源(manifest.json、图标)等文件复制到打包后的目录中
  2. 通过设置vite的build选项,将contentpopup等设置为打包的入口,并输出到manifest设置的目录中。
    vite配置如下所示:
    image.png

打包之后,我们把打包后的文件夹扔到浏览器中,就可以达到我们上述第二步的目标。

但是,如何能进一步方便我们的开发过程呢?在我们日常开发过程中的热加载可不可以也用上呢?

HMR

我们知道。webpack或者vite的dev环境下,工具为我们自动集成了HMR功能,可以使我们改动的代码自动更新到页面上。但是chrome插件与普通的浏览器页面不太一样。插件更新的话是需要在管理界面手动刷新插件,同时需要重新打开popup,整个插件的代码才会更新。所以有没有办法可以在插件内监听文件夹的变化呢?

1、Chrome.runtime.getPackageDirectoryEntry

根据官方文档介绍。getPackageDirectoryEntry 可以获取文件夹内容并监听其变化。可是在V3版本中被限制了,只能在popup页面中使用,如果popup没有被点击弹出来的话,无法做到其他模块的热更新的。

2、live-server + vite watch + 轮询

  1. 通过vite --watch build 来自动编译我们的改动到dist文件。
  2. 通过 live-server 在本地启动一个静态资源服务器,保证所有的dist文件都可以热更新。
  3. background 脚本中对所有的静态资源进行轮询,保存上一次的内容与这一次进行对比,发现内容有变化的话调用api进行更新
    描述代码如下所示:
const fileList = [
'http://127.0.0.1:5501/dist/manifest.json',
'http://127.0.0.1:5501/dist/popup/popup.js',
'http://127.0.0.1:5501/dist/background/service-worker.js',
'http://127.0.0.1:5501/dist/content/content.js',
'http://127.0.0.1:5501/dist/contentPage/contentPage.js'
]
const fileObj: = {}
/**
* reload 重新加载
*/
const reload = () => {
chrome.tabs.query(
{
active: true,
currentWindow: true
},
(tabs: chrome.tabs.Tab[]) => {
if (tabs[0]) {
chrome.tabs.reload(tabs[0].id);
}
chrome.runtime.reload();
}
);
};

/**
* 遍历监听的文件,通过请求获取文件内容,判断是否需要刷新
*/
const checkReloadPage = () => {
fileList.forEach((item) => {
fetch(item).then((res) => res.text())
.then(files => {
if (fileObj[item] && fileObj[item] !== files) {
reload()
} else {
fileObj[item] = files
}
})
.catch(error => {
});
})
}

setInterval(() => {
checkReloadPage()
}, 1000)

3、websocket

当然,我们可以参考webpack 和vite的 hmr模式,来自己实现一套基于websocket的hmr功能,来替代上述两个简单粗暴的方案。

  1. 首先我们需要借用vite的插件能力,在vite启动和打包的时候,注入我们的ws代码,并在文件更新后通过ws通道发出需要更新的区域。
return {
name: 'vite-plugin-crx-hmr',
enforce: 'pre',

configResolved(config) {

if (isDev) {
if (isBackground) {
// 启动 ws 服务端
startWebSocketServer()
} else{
// 链接 ws 服务端
setTimeout(connectWebSocketServer, 2000)
}
}
},
// 打包完成后通知服务端(background)或客户端(page)代码更新
closeBundle() {
console.log('closeBundle');

if (isDev) {
if (isBackground) {
handleServerChanged()
} else {
handleClientChanged({ isPage })
}
}
},
}

  1. 在background中对ws服务进行链接,建立信号通道。并根据收到的信号量进行不同的reload操作
hmrWebSocketClient = new WebSocket(
`ws://127.0.0.1:${crxHmrPort}?mode=background`
)
hmrWebSocketClient.onopen = (event) => {
console.log('background initCrxHmr::', 'onopen', event)
}
hmrWebSocketClient.onclose = (event) => {
console.log('background initCrxHmr::', 'onclose', event)
hmrWebSocketClient = null
}

hmrWebSocketClient.onmessage = (e) => {
const { data } = e
if (data === 'BACKGROUND_CHANGED') {
console.log(
'background initCrxHmr::',
'收到更新 background.js 消息,关闭 ws 并重新加载'
)
hmrWebSocketClient?.close()
chrome.runtime.reload()
} else if (data === 'IIFE_CHANGED') {
console.log(
'background initCrxHmr::',
'收到更新 iife 消息'
)

reloadIife(hmrWebSocketClient)
} else if (data === 'PAGE_CHANGED') {
console.log('background initCrxHmr::', '收到更新 page 消息')
reloadPage()
}
}
  1. 整体流程如下所示

分销.png

4、@crxjs/vite-plugin

5、rollup-plugin

四、成品展示

无依赖纯手撸版本

image.png

工具集成版本(仅使用了部分组件库组件+pinia,主要是在开发中可以及时hmr)

动画.gif

从wiki中解析出来后,可以直接复制到代码中进行业务开发。一想到不用挨个复制就美滋滋~😜🥂

五、尾声

本文从实践的角度学习了chrome插件的基础知识与使用,同时深入介绍了和前端工具的集成方案,欢迎感兴趣的同事一起学习探讨。


done~~ 🏃‍♂️