浏览器插件开发
浏览器扩展(Browser Extension)开发相关工具和框架。
WXT
基本信息
- 简介: 下一代 Web 扩展开发框架
- 链接: https://wxt.dev/
- GitHub: https://github.com/wxt-dev/wxt
特点
- ✅ 支持 Chrome、Firefox、Safari、Edge
- ✅ 基于 Vite 构建,开发体验极佳
- ✅ 热模块替换(HMR)
- ✅ TypeScript 支持
- ✅ 自动生成 manifest.json
- ✅ 支持 Vue、React、Svelte 等框架
- ✅ 内置打包和发布工具
安装
bash
pnpm create wxtbash
bun create wxtbash
npm create wxt@latestbash
yarn create wxt项目结构
my-extension/
├── entrypoints/
│ ├── background.ts # 后台脚本
│ ├── content.ts # 内容脚本
│ ├── popup/ # 弹出窗口
│ │ ├── index.html
│ │ └── main.ts
│ └── options/ # 选项页面
│ ├── index.html
│ └── main.ts
├── components/ # 共享组件
├── utils/ # 工具函数
├── public/ # 静态资源
├── wxt.config.ts # WXT 配置
└── package.json基础示例
Popup 页面(Vue 3)
vue
<script setup lang="ts">
import { ref } from 'vue'
const count = ref(0)
const currentTab = ref<string>('')
// 获取当前标签页信息
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
if (tabs[0]) {
currentTab.value = tabs[0].title || ''
}
})
const increment = () => {
count.value++
// 发送消息到 content script
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
if (tabs[0]?.id) {
chrome.tabs.sendMessage(tabs[0].id, {
type: 'COUNT_UPDATE',
count: count.value
})
}
})
}
</script>
<template>
<div class="w-80 p-4">
<h1 class="text-xl font-bold mb-4">我的扩展</h1>
<div class="mb-4">
<p class="text-sm text-gray-600">当前页面:</p>
<p class="font-medium truncate">{{ currentTab }}</p>
</div>
<div class="flex items-center gap-4">
<button
@click="increment"
class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
点击次数: {{ count }}
</button>
</div>
</div>
</template>ts
import { createApp } from 'vue'
import App from './App.vue'
import './style.css'
createApp(App).mount('#app')html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Popup</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./main.ts"></script>
</body>
</html>Background Script
typescript
// entrypoints/background.ts
export default defineBackground(() => {
console.log('Background script started')
// 监听扩展安装
chrome.runtime.onInstalled.addListener((details) => {
if (details.reason === 'install') {
console.log('Extension installed')
// 打开欢迎页面
chrome.tabs.create({ url: 'https://example.com/welcome' })
}
})
// 监听消息
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
console.log('Received message:', message)
if (message.type === 'GET_DATA') {
// 处理数据请求
sendResponse({ data: 'Hello from background' })
}
return true // 保持消息通道开启
})
// 监听标签页更新
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.status === 'complete') {
console.log('Tab loaded:', tab.url)
}
})
})Content Script
typescript
// entrypoints/content.ts
export default defineContentScript({
matches: ['*://*/*'],
main() {
console.log('Content script loaded')
// 监听来自 popup 的消息
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'COUNT_UPDATE') {
console.log('Count updated:', message.count)
// 在页面上显示通知
showNotification(`点击次数: ${message.count}`)
}
})
// 向页面注入元素
function showNotification(text: string) {
const notification = document.createElement('div')
notification.textContent = text
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: #4CAF50;
color: white;
padding: 16px;
border-radius: 4px;
z-index: 10000;
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
`
document.body.appendChild(notification)
setTimeout(() => {
notification.remove()
}, 3000)
}
// 监听页面事件
document.addEventListener('click', (e) => {
console.log('Page clicked:', e.target)
})
}
})配置文件
typescript
// wxt.config.ts
import { defineConfig } from 'wxt'
export default defineConfig({
manifest: {
name: '我的扩展',
description: '一个强大的浏览器扩展',
version: '1.0.0',
permissions: [
'tabs',
'storage',
'activeTab'
],
host_permissions: [
'*://*/*'
]
},
// 开发服务器配置
dev: {
server: {
port: 3000
}
}
})存储数据
typescript
// 使用 Chrome Storage API
import { storage } from 'wxt/storage'
// 保存数据
await storage.setItem('local:count', 42)
// 读取数据
const count = await storage.getItem<number>('local:count')
// 监听变化
storage.watch('local:count', (newValue, oldValue) => {
console.log('Count changed:', oldValue, '->', newValue)
})
// 删除数据
await storage.removeItem('local:count')常用 API
标签页操作
typescript
// 获取当前标签页
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true })
// 创建新标签页
await chrome.tabs.create({ url: 'https://example.com' })
// 更新标签页
await chrome.tabs.update(tabId, { url: 'https://example.com' })
// 关闭标签页
await chrome.tabs.remove(tabId)
// 发送消息到标签页
await chrome.tabs.sendMessage(tabId, { type: 'HELLO' })通知
typescript
// 创建通知
chrome.notifications.create({
type: 'basic',
iconUrl: '/icon.png',
title: '通知标题',
message: '通知内容'
})右键菜单
typescript
// background.ts
chrome.contextMenus.create({
id: 'my-menu',
title: '使用我的扩展',
contexts: ['selection']
})
chrome.contextMenus.onClicked.addListener((info, tab) => {
if (info.menuItemId === 'my-menu') {
console.log('Selected text:', info.selectionText)
}
})开发调试
启动开发模式
1. 启动 WXT 开发服务器
WXT 提供了内置的开发服务器,支持热模块替换(HMR):
bash
# 启动 Chrome 开发模式
pnpm dev
# 或指定浏览器
pnpm dev:chrome
pnpm dev:firefox
pnpm dev:edge运行后,WXT 会:
- 🔨 自动构建扩展到
.output/chrome-mv3目录 - 👀 监听文件变化
- 🔄 自动重新构建
- 📡 建立 WebSocket 连接用于热更新
2. 加载扩展到浏览器
首次需要手动加载扩展:
打开浏览器扩展管理页面
- Chrome: 访问
chrome://extensions/ - Edge: 访问
edge://extensions/ - Firefox: 访问
about:debugging#/runtime/this-firefox
- Chrome: 访问
开启"开发者模式"(右上角开关)
点击"加载已解压的扩展程序"
选择项目中的
.output/chrome-mv3目录扩展加载成功!
提示
只需要加载一次!之后 WXT 会自动通过 WebSocket 通知浏览器重载扩展,不需要手动刷新。
3. 开始开发
加载扩展后,就可以开始开发了:
vue
<!-- 修改 popup/App.vue -->
<script setup lang="ts">
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<div>
<!-- 保存文件后,popup 会自动热更新 -->
<button @click="count++">{{ count }}</button>
</div>
</template>保存文件后:
- ✅ WXT 检测到文件变化
- ✅ 自动重新构建
- ✅ 通过 WebSocket 通知浏览器
- ✅ 扩展自动更新(Popup/Options 支持 HMR,Background 会重载)
开发模式特性:
- ✅ 自动重载:代码修改后自动重新构建
- ✅ HMR:Popup 和 Options 页面支持热更新,无需刷新
- ✅ 实时预览:Content Script 和 Background 修改后自动重载扩展
- ✅ 错误提示:构建错误会在终端显示
热更新工作原理
1. Popup/Options 页面
这些页面支持完整的 HMR,修改代码后:
- Vue 组件会热替换
- 样式会即时更新
- 状态会保持(如果使用 Vue HMR)
vue
<script setup lang="ts">
import { ref } from 'vue'
// 修改这里的代码,页面会自动热更新
const count = ref(0)
</script>
<template>
<div>
<!-- 修改模板,也会自动更新 -->
<button @click="count++">{{ count }}</button>
</div>
</template>2. Background Script
Background 修改后会自动重载整个扩展:
- WXT 检测到文件变化
- 自动重新构建
- 通知浏览器重载扩展
- Background 重新初始化
3. Content Script
Content Script 修改后:
- 扩展会自动重载
- 需要刷新页面才能看到效果
- 或使用
chrome.tabs.reload()自动刷新
调试技巧
Chrome DevTools
加载扩展
- 访问
chrome://extensions/ - 开启"开发者模式"
- 点击"加载已解压的扩展程序"
- 选择
.output/chrome-mv3目录
- 访问
调试 Popup/Options
- 右键点击扩展图标 → "检查弹出内容"
- 或在扩展管理页面点击"检查视图"
调试 Background
- 在扩展管理页面点击"Service Worker"
- 打开 DevTools 控制台
调试 Content Script
- 在目标网页打开 DevTools
- Content Script 的日志会显示在控制台
- 可以在 Sources 面板找到注入的脚本
查看日志
typescript
// 在 content script 中
console.log('[Content]', 'Message')
// 在 background script 中
console.log('[Background]', 'Message')
// 在 popup 中
console.log('[Popup]', 'Message')使用 WXT 开发工具
WXT 提供了一些调试辅助工具:
typescript
// entrypoints/background.ts
export default defineBackground(() => {
// 开发模式下的日志
if (import.meta.env.DEV) {
console.log('Development mode')
}
// 监听重载事件
if (import.meta.hot) {
import.meta.hot.on('wxt:reload-extension', () => {
console.log('Extension reloaded')
})
}
})常见问题
1. Content Script 不更新
原因:Content Script 已注入到页面,需要刷新页面
解决:
typescript
// background.ts - 自动刷新所有标签页
chrome.runtime.onInstalled.addListener(() => {
chrome.tabs.query({}, (tabs) => {
tabs.forEach((tab) => {
if (tab.id) {
chrome.tabs.reload(tab.id)
}
})
})
})2. Background 修改后状态丢失
原因:Background 重载会重置所有状态
解决:使用 Storage API 持久化重要数据
typescript
import { storage } from 'wxt/storage'
// 保存状态
await storage.setItem('local:state', { count: 42 })
// 恢复状态
const state = await storage.getItem('local:state')3. 扩展图标不显示
原因:图标路径配置错误或文件缺失
解决:检查 wxt.config.ts 和 public/ 目录
typescript
// wxt.config.ts
export default defineConfig({
manifest: {
icons: {
16: '/icon-16.png',
48: '/icon-48.png',
128: '/icon-128.png'
}
}
})性能监控
typescript
// 监控扩展性能
console.time('Extension Init')
export default defineBackground(() => {
// 初始化逻辑
console.timeEnd('Extension Init')
})
// 监控 API 调用
const start = performance.now()
await chrome.storage.local.get('key')
console.log(`Storage read: ${performance.now() - start}ms`)开发工具
Extension Reloader
自动重载扩展的开发工具。
bash
npm install --save-dev webpack-extension-reloaderwebextension-polyfill
跨浏览器 API 兼容层。
bash
pnpm add webextension-polyfillbash
bun add webextension-polyfillbash
npm install webextension-polyfillbash
yarn add webextension-polyfill使用示例:
typescript
import browser from 'webextension-polyfill'
// 统一的 API,兼容 Chrome 和 Firefox
const tabs = await browser.tabs.query({ active: true })发布流程
Chrome Web Store
- 构建生产版本
bash
pnpm build
# 或
pnpm build:chrome- 打包扩展
bash
cd .output/chrome-mv3
zip -r extension.zip .- 上传到 Chrome Web Store
Microsoft Edge Add-ons
Edge 使用 Chromium 内核,可以使用相同的 Chrome 构建产物:
- 使用 Chrome 构建版本
bash
pnpm build:chrome- 打包扩展
bash
cd .output/chrome-mv3
zip -r extension.zip .提示
Edge 和 Chrome 使用相同的扩展格式(Manifest V3),所以可以共用同一个构建产物。
Firefox Add-ons
- 构建 Firefox 版本
bash
pnpm build:firefox- 打包扩展
bash
cd .output/firefox-mv2
zip -r extension.zip .- 提交到 Firefox Add-ons
注意
Firefox 目前主要使用 Manifest V2,与 Chrome/Edge 的 V3 有所不同。WXT 会自动处理这些差异。
最佳实践
- 使用 TypeScript: 提供类型安全和更好的开发体验
- 模块化开发: 将功能拆分成独立模块
- 权限最小化: 只请求必需的权限
- 性能优化: 避免在 content script 中执行耗时操作
- 错误处理: 完善的错误处理和日志记录
- 测试: 在多个浏览器中测试扩展