前言
写了个vite插件,用于构建Chrome Extension MV3项目。项目已放在github,详情请查看: vite-plugin-crx-mv3
如果你觉得这个插件有用,欢迎点个 Star 支持,谢谢!
特性
- 支持Manifest V3
- 支持Typescript
- 支持在manifest.json中配置sass/less
- 支持多种框架或库,如vue,react等等
- Live Reload
- 上手简单
使用
安装
# pnpm
pnpm add vite-plugin-crx-mv3 -D
# or npm
npm install vite-plugin-crx-mv3 -D
# or yarn
yarn add vite-plugin-crx-mv3 -D
vite配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue' //如果是使用vue开发的,引入相关的插件
import crx from 'vite-plugin-crx-mv3'
export default defineConfig({
plugins: [
vue(),
crx({
manifest: './src/manifest.json'
}),
],
})
实现的整体思路
先回顾一下,一个基本的Chrome插件项目一般有哪些内容:
目录结构
├─assets //静态资源目录,存放icon,css等等
├─content.js //content_scripts,这是运行在页面中的内容脚本
├─sw.js //service_worker, 这是个运行在后台的脚本。也可以根据自己习惯命名为background.js
├─popup.html //popup弹框
├─menifest.json //清单文件
menifest.json
{
"name": "chrome-extension-demo",
"description": "A Chrome extension demo.",
"version": "1.0.0",
"icons": {
"16": "assets/icons/icon16.png",
"48": "assets/icons/icon48.png",
"128": "assets/icons/icon128.png"
},
"action": {
"default_title": "Hello World",
"default_popup": "popup.html"
},
"content_scripts": [{
"matches": [ "<all_urls>" ],
"js": ["content.js"]
}],
"background": {
"service_worker": "sw.js"
},
"manifest_version": 3
}
这其中version, manifest_version,name为必填项。
好了,现在我们都清楚项目需要有什么了。这个vite插件就是以manifest.json作为一个入口文件,解析出里面的内容,比如service_worker、content_scripts、popup.html等等的路径。再通过这些得到的内容,利用构建工具打包出我们想要的最终的文件。因此,配合上这个vite插件,原本只能配置原生js,css后缀的manifest.json支持了配置.ts(x)、.scss、.less这样格式的文件了,因为这些都会在插件内部作解析的。这样,content_scripts也实现了使用vue、react等等这样的技术栈开发了。
例如,配置了tsx文件:
...
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content.tsx"],
}]
...
打包后是这样。产生的css文件会自动写入到配置:
...
"content_scripts": [{
"matches": [ "<all_urls>" ],
"js": ["content.js"],
"css": ["content.hashxxxx.css"],
}]
...
自动刷新功能详解
画了一个简单的流程图

先从开始的vite build --watch说起,为什么开发环境也要build呢?还是得解释一下,避免刚入门的小伙伴不理解。
开发步骤是这样的,首先打开chrome://extensions/, 右上角勾选“开发者模式”,然后点击“加载已解压的扩展程序”,加载项目。
但是,使用构建工具搭建的项目,都是初始文件,并没打包,这样是不能加载进去的。平常我们开发其它项目的时候,npm run dev运行后,并不会产生实体文件的,而是在内存里。所以在开发Chrome插件项目时就需要build,生成dist目录,再点击“加载已解压的扩展程序”加载这个dist目录。vite还提供了watch这个参数,修改文件后,会自动重新打包。如果你是用webpack搭建,想达到相同的效果,需要把devServer.writeToDisk设置为true,writeToDisk意为写入到磁盘。
到了这步,尽管watch到文件修改,会重新打包。但是Chrome插件并不会感知到文件已发生变化。content_scripts和service_worker是不会自动更新的。通常都需要手动到后台刷新一下。这很不方便,于是实现了修改文件,自动重载的功能。Chrome提供了chrome.runtime.reload方法,可以利用这个做文章。
首先我们需要知道,content_scripts、service_worker、popup这三者是可以互相通信的。插件利用了content_scripts和service_worker之间的消息传递,从而达到刷新的目的。
//content_scripts发送消息
chrome.runtime.sendMessage({ msg: 'RELOAD' }, () => {
window.location.reload() //service_worker回传消息给content_scripts的回调
})
//service_worker接收消息
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.msg == 'RELOAD') {
chrome.runtime.reload() //重载Chrome插件
sendResponse() //回传消息给content_scripts
}
})
vite-plugin-crx-mv3 在开发环境会将上述代码分别注入到 content-dev.js 和 sw-dev.js 中(如果用户未配置 service_worker,则生成 sw-dev.js;如果已配置,则将 chrome.runtime.onMessage.addListener那段代码插入到原有的 sw.js 中)。同时,这些 *-dev.js 文件路径也会写入 manifest.json:
...
"content_scripts": [{
"matches": ["<all_urls>"], //设置为<all_urls>,这样所有页面都会加载到
"js": ["content-dev.js"],
}]
"background": {
"service_worker": "sw-dev.js"
},
...
为实现完整的自动刷新功能,还需要 WebSocket 支持。文件修改并重新打包后,插件会通过 WebSocket 发送消息通知客户端(content-dev.js),客户端再向 service_worker 发送消息,执行 chrome.runtime.reload()。
在 Vite 插件的 writeBundle阶段(实际上是 Rollup 的钩子)发送 WebSocket 消息。此阶段表示构建已完成,文件已写入磁盘:
writeBundle(){
//发送消息给客户端(content-dev.js)
//content-dev.js里面接收到消息后,就走上面和service_worker通信的流程了。
if(socket){
socket.send('something')
}
}
附上一个GIF预览图:

写在最后
这个 Vite 插件基本满足了我目前的开发需求。如果你在使用过程中遇到任何问题,欢迎反馈,谢谢!