上文我们了解了如何实现一个基础版的插件开发。当然,在现代开发模式下,我们肯定不能接受如此简陋的开发模式,那么我们如何进行下一步的优化呢,现在我们继续就如何使用组件库和热更新融合方面进行探索
一、优化
如今前端工具百花齐放。我们放着更好的工具不用岂不是浪费。能不能把我们的前端工具集成到chrome插件开发过程中,来实现更快捷的开发体验和更合适的ui展示呢?那我们就试着把vite+vue接入进来吧。
目录调整
- 首先我们先快速生成vite与vue,TS的项目,
- 同时调整目录结构,将我们需要的content、popup等功能分开在不同的结构中。
- 安装组件库、pinia等工具;安装chrome-types 来对我们的api进行提示
目录结构如下所示
vite配置
- 通过
rollup-plugin-copy
将静态资源(manifest.json
、图标)等文件复制到打包后的目录中
- 通过设置vite的
build
选项,将content
、popup
等设置为打包的入口,并输出到manifest设置的目录中。
vite配置如下所示:
打包之后,我们把打包后的文件夹扔到浏览器中,就可以达到我们上述第二步的目标。
但是,如何能进一步方便我们的开发过程呢?在我们日常开发过程中的热加载可不可以也用上呢?
HMR
我们知道。webpack或者vite的dev环境下,工具为我们自动集成了HMR功能,可以使我们改动的代码自动更新到页面上。但是chrome插件与普通的浏览器页面不太一样。插件更新的话是需要在管理界面手动刷新插件,同时需要重新打开popup,整个插件的代码才会更新。所以有没有办法可以在插件内监听文件夹的变化呢?
1、Chrome.runtime.getPackageDirectoryEntry
根据官方文档介绍。getPackageDirectoryEntry
可以获取文件夹内容并监听其变化。可是在V3版本中被限制了,只能在popup
页面中使用,如果popup
没有被点击弹出来的话,无法做到其他模块的热更新的。
2、live-server + vite watch + 轮询
- 通过
vite --watch build
来自动编译我们的改动到dist文件。
- 通过
live-server
在本地启动一个静态资源服务器,保证所有的dist文件都可以热更新。
- 在
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: = {}
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功能,来替代上述两个简单粗暴的方案。
- 首先我们需要借用vite的插件能力,在vite启动和打包的时候,注入我们的ws代码,并在文件更新后通过ws通道发出需要更新的区域。
return { name: 'vite-plugin-crx-hmr', enforce: 'pre',
configResolved(config) {
if (isDev) { if (isBackground) { startWebSocketServer() } else{ setTimeout(connectWebSocketServer, 2000) } } }, closeBundle() { console.log('closeBundle');
if (isDev) { if (isBackground) { handleServerChanged() } else { handleClientChanged({ isPage }) } } }, }
|
- 在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() } }
|
- 整体流程如下所示
四、成品展示
无依赖纯手撸版本
工具集成版本(仅使用了部分组件库组件+pinia,主要是在开发中可以及时hmr)
从wiki中解析出来后,可以直接复制到代码中进行业务开发。一想到不用挨个复制就美滋滋~😜🥂
五、尾声
本文从实践的角度学习了chrome插件的基础知识与使用,同时深入介绍了和前端工具的集成方案,欢迎感兴趣的同事一起学习探讨。
done~~ 🏃♂️