extension-toolchain
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseExtension Toolchain
浏览器扩展工具链
Modern browser extension development with Manifest V3, focusing on framework-agnostic solutions.
基于Manifest V3的现代浏览器扩展开发,聚焦于与框架无关的解决方案。
Recommended Stack: WXT (Default)
推荐技术栈:WXT(默认选项)
Why WXT (2025):
- Framework-agnostic (React, Vue, Svelte, SolidJS, Vanilla)
- Vite-powered (fast HMR, optimized builds)
- Auto-reload on code changes (content scripts too!)
- TypeScript-first with excellent type generation
- Automated publishing to stores
- Manifest V3 by default
bash
undefined为什么选择WXT(2025年):
- 与框架无关(支持React、Vue、Svelte、SolidJS、原生JS)
- 基于Vite构建(快速HMR、优化的构建流程)
- 代码变更时自动重载(包括内容脚本!)
- 优先支持TypeScript,具备出色的类型生成能力
- 支持自动发布至应用商店
- 默认采用Manifest V3
bash
undefinedCreate new extension
创建新扩展
npm create wxt@latest
npm create wxt@latest
Choose your framework
选择你的框架
? Select a template:
vanilla react vue svelte solid
? Select a template:
vanilla react vue svelte solid
Start development
启动开发服务
cd my-extension
npm run dev # Chrome (default)
npm run dev:firefox # Firefox
npm run dev:edge # Edge
npm run dev:safari # Safari (experimental)
undefinedcd my-extension
npm run dev # Chrome(默认)
npm run dev:firefox # Firefox
npm run dev:edge # Edge
npm run dev:safari # Safari(实验性支持)
undefinedWhen to Use WXT
何时使用WXT
✅ Multi-framework teams (framework-agnostic)
✅ Need cross-browser compatibility
✅ Want modern DX (HMR, TypeScript, auto-reload)
✅ Publishing to multiple stores
✅ Complex extensions with multiple entry points
✅ 多框架团队(与框架无关)
✅ 需要跨浏览器兼容性
✅ 追求现代化开发体验(HMR、TypeScript、自动重载)
✅ 发布至多个应用商店
✅ 包含多个入口点的复杂扩展
Alternative: Plasmo
替代方案:Plasmo
Best for React developers:
- Next.js-like file-based routing
- Automatic code splitting
- Built-in remote code bundling
- Very opinionated (React-centric)
bash
undefined最适合React开发者:
- 类Next.js的基于文件的路由机制
- 自动代码分割
- 内置远程代码打包功能
- 高度约定式(以React为中心)
bash
undefinedCreate Plasmo extension
创建Plasmo扩展
npm create plasmo
npm create plasmo
Start development
启动开发服务
npm run dev
undefinednpm run dev
undefinedWhen to Use Plasmo
何时使用Plasmo
✅ React-only team
✅ Want Next.js-like DX
✅ Need remote code bundling
✅ Prefer opinionated frameworks
✅ 纯React技术栈团队
✅ 追求类Next.js的开发体验
✅ 需要远程代码打包功能
✅ 偏好约定式框架
Alternative: CRXJS (Vite Plugin)
替代方案:CRXJS(Vite插件)
Minimal, unopinionated:
- Just a Vite plugin (you control everything)
- Best-in-class HMR (especially for content scripts)
- Lightweight, minimal overhead
- Requires more manual setup
bash
undefined轻量、无约定:
- 仅作为Vite插件(完全由你掌控所有配置)
- 业界领先的HMR(尤其针对内容脚本)
- 轻量级,开销极小
- 需要更多手动配置
bash
undefinedAdd to existing Vite project
添加至现有Vite项目
npm install @crxjs/vite-plugin -D
undefinednpm install @crxjs/vite-plugin -D
undefinedWhen to Use CRXJS
何时使用CRXJS
✅ Want maximum control
✅ Already using Vite
✅ Minimal tooling preference
✅ Expert developer team
✅ 希望获得最大控制权
✅ 已在使用Vite
✅ 偏好极简工具链
✅ 资深开发团队
Toolchain Comparison
工具链对比
| WXT | Plasmo | CRXJS | |
|---|---|---|---|
| Frameworks | All | React-focused | All |
| Setup | Batteries-included | Opinionated | Manual |
| DX | Excellent | Excellent | Great |
| HMR | Yes | Yes | Best |
| Auto-publish | Yes | Yes | No |
| Learning Curve | Low | Low | Medium |
| Flexibility | High | Medium | Highest |
| WXT | Plasmo | CRXJS | |
|---|---|---|---|
| 支持框架 | 全部 | 聚焦React | 全部 |
| 配置难度 | 开箱即用 | 约定式 | 手动配置 |
| 开发体验 | 优秀 | 优秀 | 良好 |
| HMR支持 | 是 | 是 | 最佳 |
| 自动发布 | 是 | 是 | 否 |
| 学习曲线 | 低 | 低 | 中等 |
| 灵活性 | 高 | 中等 | 最高 |
Project Structure (WXT)
项目结构(WXT)
my-extension/
├── entrypoints/
│ ├── background.ts # Service worker
│ ├── content.ts # Content script
│ ├── popup/ # Extension popup
│ │ ├── index.html
│ │ └── main.tsx
│ └── options/ # Options page
│ ├── index.html
│ └── main.tsx
├── components/ # Shared UI components
├── utils/ # Shared utilities
├── public/ # Static assets
│ └── icon.png # Extension icon
├── wxt.config.ts # WXT configuration
└── package.jsonmy-extension/
├── entrypoints/
│ ├── background.ts # Service Worker
│ ├── content.ts # 内容脚本
│ ├── popup/ # 扩展弹窗
│ │ ├── index.html
│ │ └── main.tsx
│ └── options/ # 选项页面
│ ├── index.html
│ └── main.tsx
├── components/ # 共享UI组件
├── utils/ # 共享工具函数
├── public/ # 静态资源
│ └── icon.png # 扩展图标
├── wxt.config.ts # WXT配置文件
└── package.jsonManifest V3 Essentials
Manifest V3核心配置
typescript
// wxt.config.ts
import { defineConfig } from 'wxt'
export default defineConfig({
manifest: {
name: 'My Extension',
version: '1.0.0',
permissions: ['storage', 'tabs'],
host_permissions: ['https://*.example.com/*'],
action: {
default_title: 'My Extension',
},
},
})typescript
// wxt.config.ts
import { defineConfig } from 'wxt'
export default defineConfig({
manifest: {
name: 'My Extension',
version: '1.0.0',
permissions: ['storage', 'tabs'],
host_permissions: ['https://*.example.com/*'],
action: {
default_title: 'My Extension',
},
},
})Key Manifest V3 Changes
Manifest V3关键变更
- Service Workers replace background pages (no DOM access)
- separate from
host_permissionspermissions - API for dynamic content script injection
scripting - No remotely hosted code (bundle everything)
- Limited capabilities
executeScript
- Service Worker 替代后台页面(无DOM访问权限)
- 与
host_permissions分离permissions - API 用于动态注入内容脚本
scripting - 禁止远程托管代码(需打包所有代码)
- 功能受限
executeScript
Communication Patterns
通信模式
Popup ↔ Background
弹窗 ↔ 后台
typescript
// popup/main.tsx
import browser from 'webextension-polyfill'
const response = await browser.runtime.sendMessage({
type: 'GET_DATA',
payload: { key: 'value' },
})
// background.ts
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'GET_DATA') {
// Process and respond
sendResponse({ data: 'result' })
}
return true // Keep channel open for async response
})typescript
// popup/main.tsx
import browser from 'webextension-polyfill'
const response = await browser.runtime.sendMessage({
type: 'GET_DATA',
payload: { key: 'value' },
})
// background.ts
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'GET_DATA') {
// 处理并返回响应
sendResponse({ data: 'result' })
}
return true // 保持通道开放以支持异步响应
})Content Script ↔ Background
内容脚本 ↔ 后台
typescript
// content.ts
import browser from 'webextension-polyfill'
// Send message to background
const result = await browser.runtime.sendMessage({
type: 'ANALYZE_PAGE',
url: window.location.href,
})
// background.ts
browser.runtime.onMessage.addListener(async (message) => {
if (message.type === 'ANALYZE_PAGE') {
const analysis = await analyzePage(message.url)
return { analysis }
}
})typescript
// content.ts
import browser from 'webextension-polyfill'
// 向后台发送消息
const result = await browser.runtime.sendMessage({
type: 'ANALYZE_PAGE',
url: window.location.href,
})
// background.ts
browser.runtime.onMessage.addListener(async (message) => {
if (message.type === 'ANALYZE_PAGE') {
const analysis = await analyzePage(message.url)
return { analysis }
}
})Content Script ↔ Page (Web Page)
内容脚本 ↔ 网页
typescript
// content.ts - inject into page context
const script = document.createElement('script')
script.src = browser.runtime.getURL('injected.js')
document.head.appendChild(script)
// Listen for messages from page
window.addEventListener('message', (event) => {
if (event.source !== window) return
if (event.data.type === 'FROM_PAGE') {
// Handle message from page
}
})
// injected.js (runs in page context, has access to page's window/DOM)
window.postMessage({ type: 'FROM_PAGE', data: 'value' }, '*')typescript
// content.ts - 注入到页面上下文
const script = document.createElement('script')
script.src = browser.runtime.getURL('injected.js')
document.head.appendChild(script)
// 监听来自网页的消息
window.addEventListener('message', (event) => {
if (event.source !== window) return
if (event.data.type === 'FROM_PAGE') {
// 处理来自网页的消息
}
})
// injected.js(运行在页面上下文,可访问页面的window/DOM)
window.postMessage({ type: 'FROM_PAGE', data: 'value' }, '*')Storage Patterns
存储模式
typescript
// Using chrome.storage.sync (syncs across devices)
import browser from 'webextension-polyfill'
// Save
await browser.storage.sync.set({ key: 'value' })
// Load
const { key } = await browser.storage.sync.get('key')
// Listen for changes
browser.storage.onChanged.addListener((changes, areaName) => {
if (areaName === 'sync' && changes.key) {
console.log('Value changed:', changes.key.newValue)
}
})typescript
// 使用chrome.storage.sync(跨设备同步)
import browser from 'webextension-polyfill'
// 保存数据
await browser.storage.sync.set({ key: 'value' })
// 加载数据
const { key } = await browser.storage.sync.get('key')
// 监听数据变更
browser.storage.onChanged.addListener((changes, areaName) => {
if (areaName === 'sync' && changes.key) {
console.log('值已变更:', changes.key.newValue)
}
})Essential Libraries
核心依赖库
bash
undefinedbash
undefinedCross-browser compatibility
跨浏览器兼容性
npm install webextension-polyfill
npm install webextension-polyfill
State Management
状态管理
npm install zustand
npm install zustand
Forms
表单处理
npm install react-hook-form zod
npm install react-hook-form zod
UI Components (if using React)
UI组件(若使用React)
npm install @radix-ui/react-* # Headless components
undefinednpm install @radix-ui/react-* # 无头组件
undefinedTesting Strategy
测试策略
bash
undefinedbash
undefinedInstall testing libraries
安装测试依赖
npm install --save-dev vitest @testing-library/react @testing-library/user-event
npm install --save-dev @wxt-dev/testing
**Example test:**
```typescript
// popup/main.test.tsx
import { render, screen } from '@testing-library/react'
import { describe, it, expect } from 'vitest'
import Popup from './main'
describe('Popup', () => {
it('renders heading', () => {
render(<Popup />)
expect(screen.getByRole('heading')).toBeInTheDocument()
})
})npm install --save-dev vitest @testing-library/react @testing-library/user-event
npm install --save-dev @wxt-dev/testing
**测试示例:**
```typescript
// popup/main.test.tsx
import { render, screen } from '@testing-library/react'
import { describe, it, expect } from 'vitest'
import Popup from './main'
describe('Popup', () => {
it('渲染标题', () => {
render(<Popup />)
expect(screen.getByRole('heading')).toBeInTheDocument()
})
})Quality Gates Integration
质量门禁集成
yaml
undefinedyaml
undefined.github/workflows/extension-ci.yml
.github/workflows/extension-ci.yml
name: Extension CI
on: [pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm test
- run: npm run build
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: extension-build
path: .output/undefinedname: Extension CI
on: [pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm test
- run: npm run build
- name: 上传构建产物
uses: actions/upload-artifact@v4
with:
name: extension-build
path: .output/undefinedPublishing Automation
发布自动化
bash
undefinedbash
undefinedBuild for all browsers
为所有浏览器构建
npm run build # Chrome
npm run build:firefox # Firefox
npm run build:safari # Safari
npm run build # Chrome
npm run build:firefox # Firefox
npm run build:safari # Safari
Zip for submission
打包用于提交
npm run zip # All stores
npm run zip # 所有商店通用
Or use WXT's publish command (requires API keys)
或使用WXT的发布命令(需要API密钥)
wxt publish --chrome --firefox
**Store submission setup:**
```typescript
// wxt.config.ts
export default defineConfig({
zip: {
artifactTemplate: '{{name}}-{{version}}-{{browser}}.zip',
},
manifest: {
name: '__MSG_extName__',
description: '__MSG_extDescription__',
default_locale: 'en',
},
})wxt publish --chrome --firefox
**商店提交配置:**
```typescript
// wxt.config.ts
export default defineConfig({
zip: {
artifactTemplate: '{{name}}-{{version}}-{{browser}}.zip',
},
manifest: {
name: '__MSG_extName__',
description: '__MSG_extDescription__',
default_locale: 'en',
},
})Performance Best Practices
性能最佳实践
- Lazy load content scripts: Only inject when needed
- Use storage efficiently: Minimize sync storage writes
- Debounce frequent operations: Especially in content scripts
- Minimize background script work: Use alarms/events, not intervals
- Optimize bundle size: Code splitting, tree shaking
- 懒加载内容脚本:仅在需要时注入
- 高效使用存储:减少同步存储的写入操作
- 防抖频繁操作:尤其在内容脚本中
- 最小化后台脚本工作量:使用定时器/事件,而非间隔轮询
- 优化包体积:代码分割、摇树优化
Security Considerations
安全注意事项
typescript
// Content Security Policy
manifest: {
content_security_policy: {
extension_pages: "script-src 'self'; object-src 'self'"
}
}
// Validate messages
browser.runtime.onMessage.addListener((message) => {
// Always validate message structure
if (typeof message !== 'object' || !message.type) {
return
}
// Type guard
if (message.type === 'EXPECTED_TYPE') {
// Process
}
})
// Never inject user content directly into DOM
// Use textContent, not innerHTML
element.textContent = userInput // Safe
element.innerHTML = userInput // XSS vulnerability!typescript
// 内容安全策略
manifest: {
content_security_policy: {
extension_pages: "script-src 'self'; object-src 'self'"
}
}
// 验证消息合法性
browser.runtime.onMessage.addListener((message) => {
// 始终验证消息结构
if (typeof message !== 'object' || !message.type) {
return
}
// 类型守卫
if (message.type === 'EXPECTED_TYPE') {
// 处理逻辑
}
})
// 切勿直接将用户内容注入DOM
// 使用textContent,而非innerHTML
element.textContent = userInput // 安全
element.innerHTML = userInput // 存在XSS漏洞!Common Gotchas
常见陷阱
Service Worker Lifecycle:
- Service workers can be terminated anytime
- Use for persistence, not in-memory state
chrome.storage - Set up event listeners at top level (not inside async functions)
Content Script Isolation:
- Content scripts run in isolated world
- No direct access to page's JavaScript
- Must use to communicate with page
postMessage
Manifest V3 Restrictions:
- No or
eval()new Function() - No inline scripts in HTML
- All code must be bundled
- Limited service worker APIs
Service Worker生命周期:
- Service Worker可能随时被终止
- 使用进行持久化存储,而非内存状态
chrome.storage - 在顶层注册事件监听器(不要放在异步函数内)
内容脚本隔离:
- 内容脚本运行在隔离环境
- 无法直接访问网页的JavaScript
- 必须使用与网页通信
postMessage
Manifest V3限制:
- 禁止使用或
eval()new Function() - HTML中禁止内联脚本
- 所有代码必须打包
- Service Worker API受限
Recommendation Flow
选型推荐流程
New browser extension:
├─ Multi-framework team → WXT ✅
├─ React-only team → Plasmo
└─ Want maximum control → CRXJS
Existing extension (Manifest V2):
└─ Migrate to WXT (handles V2→V3 migration)When agents design browser extensions, they should:
- Default to WXT for new projects (framework-agnostic, best DX)
- Use Manifest V3 (V2 deprecated in 2024)
- Apply quality-gates skill for testing/CI setup
- Use webextension-polyfill for cross-browser compatibility
- Follow Content Security Policy strictly
- Plan for service worker lifecycle (no persistent background page)
- Use chrome.storage for state persistence
- Validate all messages between components
新建浏览器扩展:
├─ 多框架团队 → WXT ✅
├─ 纯React团队 → Plasmo
└─ 希望获得最大控制权 → CRXJS
现有扩展(Manifest V2):
└─ 迁移至WXT(支持V2→V3迁移)当开发人员设计浏览器扩展时,应遵循以下原则:
- 新项目默认选择WXT(与框架无关,开发体验最佳)
- 使用Manifest V3(V2已于2024年废弃)
- 应用质量门禁能力搭建测试/CI流程
- 使用webextension-polyfill保证跨浏览器兼容性
- 严格遵循内容安全策略
- 考虑Service Worker生命周期(无持久后台页面)
- 使用chrome.storage进行状态持久化
- 验证所有组件间的通信消息