Skip to content

浏览器插件开发

浏览器扩展(Browser Extension)开发相关工具和框架。

WXT

基本信息

特点

  • ✅ 支持 Chrome、Firefox、Safari、Edge
  • ✅ 基于 Vite 构建,开发体验极佳
  • ✅ 热模块替换(HMR)
  • ✅ TypeScript 支持
  • ✅ 自动生成 manifest.json
  • ✅ 支持 Vue、React、Svelte 等框架
  • ✅ 内置打包和发布工具

安装

bash
pnpm create wxt
bash
bun create wxt
bash
npm create wxt@latest
bash
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

基础示例

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. 加载扩展到浏览器

首次需要手动加载扩展

  1. 打开浏览器扩展管理页面

    • Chrome: 访问 chrome://extensions/
    • Edge: 访问 edge://extensions/
    • Firefox: 访问 about:debugging#/runtime/this-firefox
  2. 开启"开发者模式"(右上角开关)

  3. 点击"加载已解压的扩展程序"

  4. 选择项目中的 .output/chrome-mv3 目录

  5. 扩展加载成功!

提示

只需要加载一次!之后 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

  1. 加载扩展

    • 访问 chrome://extensions/
    • 开启"开发者模式"
    • 点击"加载已解压的扩展程序"
    • 选择 .output/chrome-mv3 目录
  2. 调试 Popup/Options

    • 右键点击扩展图标 → "检查弹出内容"
    • 或在扩展管理页面点击"检查视图"
  3. 调试 Background

    • 在扩展管理页面点击"Service Worker"
    • 打开 DevTools 控制台
  4. 调试 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.tspublic/ 目录

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-reloader

webextension-polyfill

跨浏览器 API 兼容层。

bash
pnpm add webextension-polyfill
bash
bun add webextension-polyfill
bash
npm install webextension-polyfill
bash
yarn add webextension-polyfill

使用示例:

typescript
import browser from 'webextension-polyfill'

// 统一的 API,兼容 Chrome 和 Firefox
const tabs = await browser.tabs.query({ active: true })

发布流程

Chrome Web Store

  1. 构建生产版本
bash
pnpm build
# 或
pnpm build:chrome
  1. 打包扩展
bash
cd .output/chrome-mv3
zip -r extension.zip .
  1. 上传到 Chrome Web Store

Microsoft Edge Add-ons

Edge 使用 Chromium 内核,可以使用相同的 Chrome 构建产物:

  1. 使用 Chrome 构建版本
bash
pnpm build:chrome
  1. 打包扩展
bash
cd .output/chrome-mv3
zip -r extension.zip .
  1. 上传到 Microsoft Edge Add-ons

提示

Edge 和 Chrome 使用相同的扩展格式(Manifest V3),所以可以共用同一个构建产物。

Firefox Add-ons

  1. 构建 Firefox 版本
bash
pnpm build:firefox
  1. 打包扩展
bash
cd .output/firefox-mv2
zip -r extension.zip .
  1. 提交到 Firefox Add-ons

注意

Firefox 目前主要使用 Manifest V2,与 Chrome/Edge 的 V3 有所不同。WXT 会自动处理这些差异。


最佳实践

  1. 使用 TypeScript: 提供类型安全和更好的开发体验
  2. 模块化开发: 将功能拆分成独立模块
  3. 权限最小化: 只请求必需的权限
  4. 性能优化: 避免在 content script 中执行耗时操作
  5. 错误处理: 完善的错误处理和日志记录
  6. 测试: 在多个浏览器中测试扩展

相关资源