Skip to content
返回项目教程

浏览器扩展

构建 Manifest V3 扩展,支持 Chrome/Firefox 并发布到 Web Store。

设计与 Manifest

定义扩展类型、权限,以及决定一切的 manifest.json。

为什么

Manifest V3 在 Chrome 是强制要求——manifest 决定权限、CSP 和能力,Web Store 严格审查。权限过多触发审核拒绝和用户不信任;过少则功能失效。先把这个契约做对。

何时使用

在写任何 content/popup/background 代码之前。2-5 天定稿 manifest + 权限范围。

如何做

1. 选择扩展界面 - Popup:点图标 -> 小 HTML 面板(最常见) - Content script:注入页面(DOM 修改、抓取) - Background service worker(MV3):事件、闹钟、跨 tab 状态 - Side panel / DevTools panel:专用 UI 2. 写 manifest.json(MV3) - manifest_version: 3 - permissions:请求最小集(activeTab、storage、alarms) - host_permissions:只放你实际触碰的域名 - action.default_popup 指向 popup HTML - background.service_worker 指向 SW 3. 强制内容安全策略 - MV3 默认 CSP 禁止远程代码(无 eval、无远程脚本) - 所有代码必须打包进扩展包 - 用打包器(Vite/webpack)产出单一 dist/ 4. 规划跨浏览器(Chrome + Firefox) - Firefox 支持 MV3,用 browser.* API(基于 Promise) - 用 webextension-polyfill 统一 chrome.* 和 browser.* - 从一开始就双测;仅在浏览器检测后分歧 5. 为审核限定权限 - activeTab(点击时授予)> tabs(常开)— 优先 activeTab - 在商店描述中为每个权限给出理由

示例

manifest.json(MV3 popup + content script): { "manifest_version": 3, "name": "Page Tidy", "version": "1.0.0", "action": { "default_popup": "popup.html" }, "background": { "service_worker": "sw.js" }, "permissions": ["activeTab", "storage", "alarms"], "host_permissions": ["https://*.example.com/*"], "content_scripts": [{ "matches": ["https://*.example.com/*"], "js": ["content.js"] }] } 打包器(Vite):src/ -> dist/,manifest 拷入 跨浏览器:webextension-polyfill 统一 chrome/browser API

常见问题

  • ⚠请求宽泛主机权限(<all_urls>)——审核慢且吓退用户
  • ⚠用 eval/远程脚本——MV3 CSP 运行时拒绝
  • ⚠只为 Chrome 构建,发布时才发现 Firefox MV3 差异

最佳实践

  • ✓请求 activeTab 而非 tabs——仅在用户操作时授予,便于审核
  • ✓用 Vite 打包一切;MV3 禁远程代码,运行时无 CDN 脚本
  • ✓第一天就用 webextension-polyfill,让 Chrome 和 Firefox 共享一套代码

开发与测试

构建 popup/content/SW,配备热重载和跨浏览器测试。

为什么

扩展 bug 与环境强相关:service worker 空闲 30 秒后挂起,content script 与页面 JS 竞态,popup 状态每次关闭后重置。不测这些生命周期,就会上线只在真实浏览中出现的 bug。

何时使用

manifest 定稿后。先做 popup UI(反馈最快),再 content script,再 SW。

如何做

1. 搭建热重载开发 - vite + @crxjs/vite-plugin:popup、content script 的 HMR - 加载未打包:chrome://extensions -> 开发者模式 -> 加载 dist/ 2. 构建 popup - React/Vue/Svelte popup,保持 < 300KB(每次点击都加载) - 状态存 chrome.storage.local,不放内存(popup 会重置) 3. 构建 content script - 只匹配特定 URL;在 document_idle 注入避免竞态 - 用 chrome.runtime.sendMessage 与 SW 通信 - 用 Shadow DOM 隔离样式,避免污染页面 CSS 4. 处理 service worker 生命周期 - SW 空闲约 30 秒后挂起——绝不在 SW 内存中存状态 - 用 chrome.storage 持久化,chrome.alarms 做定时 - 每次唤醒重建监听器(events API 幂等) 5. 跨浏览器测试 - 在 Chrome(chromium)和 Firefox(about:debugging)加载 - Vitest 做单元逻辑;Playwright + 扩展做 E2E - 测试 SW 挂起:空闲 60 秒,再触发事件

示例

Popup 状态持久化: // popup.tsx — 打开时加载,变更时保存 const [prefs, setPrefs] = useState<Prefs>(); useEffect(() => { chrome.storage.local.get('prefs').then(r => setPrefs(r.prefs)); }, []); const update = (p) => chrome.storage.local.set({ prefs: p }); SW 闹钟(抗挂起): chrome.alarms.create('sync', { periodInMinutes: 30 }); chrome.alarms.onAlarm.addListener(a => { if (a.name === 'sync') doSync(); }); E2E(Playwright + 扩展): const ctx = await chromium.launchPersistentContext('', { args: [`--load-extension=${distPath}`], });

常见问题

  • ⚠在 SW 内存存状态——挂起后丢失,导致间歇性'首次点击无效'bug
  • ⚠Content script CSS 泄漏到页面——破坏宿主站点样式,导致卸载
  • ⚠Popup 包 > 1MB——打开慢,用户觉得扩展卡顿

最佳实践

  • ✓把 service worker 当无状态——持久化到 chrome.storage,每次事件重建
  • ✓把 content script UI 包在 Shadow DOM 根中,样式永不与页面冲突
  • ✓Popup 包保持小(< 300KB);重视图代码分割,按需懒加载

商店发布

打包、签名并发布到 Chrome Web Store 和 Firefox Add-ons(AMO)。

为什么

Web Store 是你唯一的分发渠道——一次拒绝就阻断所有用户。商店审核强制权限说明、隐私实践和单一用途政策。首次干净提交建立信任;反复拒绝可能让开发者账号被标记。

何时使用

扩展跨浏览器测试通过且 manifest 权限已说明时。Chrome 审核预留 1-7 天,Firefox < 2 天。

如何做

1. 打包扩展 - vite build -> dist/ 含 manifest.json - Chrome 打包 zip dist/;Firefox 用 web-ext 签名 - 含 128x128 图标和商店素材(截图、推广图) 2. 创建商店上架信息 - Chrome Web Store:$5 一次性开发者费,再上传 .zip - Firefox AMO:免费,用 web-ext sign --api-key/--api-secret 签名 - 写清晰描述,为每个权限给出理由 3. 准备隐私实践 - 收集任何数据都需隐私政策 URL - 说明收集什么数据、存哪里、如何使用 - "单一用途"声明:一句话描述扩展用途 4. 提交并处理审核 - Chrome:上传 zip,提交审核,关注开发者面板 - 精确回应拒绝反馈——只重提修复项 - Firefox:签名 xpi 审核后自动在 AMO 上线 5. 上线后更新 - 更新上传新 zip(Chrome 重新审核,通常更快) - 用 chrome.storage.sync 跨设备备份用户设置 - 通过 Web Store 面板监控卸载原因

示例

打包 + 发布(Chrome): vite build # -> dist/ cd dist && zip -r ../ext.zip . # 打包 # 在 chrome.google.com/webstore/devconsole 上传 ext.zip 签名 + 发布(Firefox): web-ext build # -> web-ext-artifacts/*.xpi web-ext sign --api-key $KEY --api-secret $SECRET # 签名 xpi 自动在 AMO 上线 上架描述(权限说明): "权限: - activeTab:点击图标时读取当前标签页 - storage:本地记住你的偏好 无数据离开你的设备。"

常见问题

  • ⚠收集数据却无隐私政策——立即拒绝,可能标记账号
  • ⚠单一用途声明模糊——审核员拒绝'做太多事'的扩展
  • ⚠未针对具体审核反馈就重提——数周循环

最佳实践

  • ✓打包前就写好隐私政策和单一用途声明——它们是审核要求
  • ✓在上架描述中为每个权限给出理由;审核员首先读这个
  • ✓逐条回应拒绝反馈,只重提修复版而非重写

完整开发者生命周期

从想法到维护的 14 阶段开发者路径图 — 可视化依赖关系、追踪进度。

下一步