从零开始开发一个 Chrome 扩展,与构建常规 Web 应用有着本质区别。本文将以「网页内容总结助手」这个基于 React + Vite 的真实项目为例,深入剖析扩展开发的核心差异、常见陷阱以及最佳实践,帮助你快速完成从 Web 开发到扩展开发的思维转换。

本文结合开源项目 网页内容总结助手(React + Vite + Manifest V3)总结插件开发中的注意点,并对比插件开发与普通 Web 开发的差异,方便从前端转型或入门扩展开发的同学少踩坑。

一、扩展开发 vs 传统 Web 开发:核心差异概览

在深入细节之前,先通过一张表格快速了解两者在关键维度上的区别:

维度普通 Web 开发Chrome 扩展开发
运行环境单一页面或 SPA,同源策略限制多个隔离环境:Popup、Background(如 Service Worker)、Content Script、可选 Offscreen
脚本加载方式可自由使用 Popup 可用 ESM;Content Script 按普通脚本注入,不能直接写顶层
存储常用 localStorage、Cookie、后端 DB推荐 / ,跨页面且可同步(sync)
网络与权限受 CORS 限制,需后端或代理在 manifest 中声明 后可直连指定域名,无 CORS 问题
与页面交互直接操作当前页 DOM/JSContent Script 与页面共享 DOM,与 Popup/Background 通过 消息 通信,不能直接共享变量
构建与部署通常单入口打包,部署到服务器多入口:Popup 页面 + Content Script(+ Background);加载的是本地目录(如 ),不是 URL
安全与审核主要防 XSS、CSRF、敏感信息泄露还需注意权限最小化、Manifest V3 规则、商店审核策略

这些差异直接影响你的技术选型、构建配置和调试方式。下面我们结合「网页内容总结助手」项目,逐一展开说明。

二、Manifest 配置:权限与入口的精准把控

扩展的“身份证”是 manifest.json 文件(本项目位于 public/manifest.json,构建时自动拷贝到 dist/)。配置不当会导致功能失效或被商店拒绝。

权限声明:只申请真正需要的权限。本插件使用了 activeTabstoragescriptingalarms 等。对于外部 API 调用,必须在 host_permissions 中明确声明域名,例如调用 ModelScope 时需要 https://api.modelscope.cn/*,否则请求会被 CORS 拦截。

Content Script 入口:manifest 中指定的 content_scripts.js 必须是构建后的单文件名(如 content.js),且该文件不能包含顶层 ESM 导入(见下一条)。

⚠️ 建议每添加一项能力就对照 Manifest 文档 检查一遍,避免权限过少导致功能不可用,或权限过多引发用户不信任。

三、Content Script 与 ES Module 的冲突

这是从 Web 开发转向扩展开发时最容易踩的坑之一。Content Script 由 Chrome 按“传统脚本”注入到页面,不支持 import / export。如果代码中出现顶层 import,控制台会报错:

Cannot use import statement outside a module

解决方案:本项目采用双构建策略——Popup 页面使用 Vite 默认的 ESM 输出(因为 Popup 运行在扩展独立环境中),而 Content Script 则单独配置一份 Vite 构建(vite.content.config.ts),将入口文件 content.ts 打包为 IIFE 格式的单文件,所有依赖(如 readability)都内联进去,最终产出 dist/content.js。构建命令类似:

vite build && vite build --config vite.content.config.js

如果你使用其他打包器(如 Webpack、Rollup),思路相同:Content 入口单独打包,输出格式为 IIFE 或 UMD,且避免拆分多个 chunk。

四、Popup 与 Content Script:两个世界的消息通信

Popup 和 Content Script 运行在完全不同的环境中:

  • Popup:点击图标打开的页面,可以自由使用 React、Vue、ESM 等现代前端框架。
  • Content Script:注入到用户当前浏览的网页中,能操作 DOM,但与 Popup 不共享 JS 变量。

两者只能通过 chrome.runtime.sendMessagechrome.tabs.sendMessage 进行通信。以本插件为例:

  • Popup 发送 enterHighlightMode 消息 → Content Script 进入高亮选择模式。
  • 用户选中文本后,Content Script 将选中内容通过消息回传 → Popup 再调用 AI 或本地 mock 进行总结。

注意:如果 Popup 打开时当前页面尚未注入 Content Script,chrome.tabs.sendMessage 会抛出“Receiving end does not exist”错误。本插件在 Popup 中对此类调用做了 try/catchcatch 处理,必要时先通过 chrome.scripting.executeScript 注入再发送消息,避免未捕获异常导致 UI 卡死。

五、无后端时代的持久化:chrome.storage 实战

扩展可以是纯前端的,不需要自己的服务器。当需要持久化用户设置(如 API Key、总结字数)时,chrome.storage 是最佳选择:

  • chrome.storage.sync:跨设备同步(需用户登录 Chrome),适合偏好设置。
  • chrome.storage.local:仅本机存储,适合较大或不需同步的数据。

本插件将 API Key、总结字数、内容类型等统一存入 chrome.storage.sync。Popup 打开时从 storage 读取并初始化 React state;用户在设置页修改后写回 storage,下次打开或其他设备上都会自动生效。

⚠️ 安全建议:绝不要在前端代码中硬编码 API Key,一律从 storage 或用户输入获取。在 UI 上对“未配置 Key”或“密钥错误”给出明确提示(如本插件的设置校验与错误文案),提升用户体验。

六、构建与调试:从 Web 到扩展的实操要点

加载方式:扩展以目录形式加载(开发者模式下的“加载已解压的扩展程序”)。构建产物必须包含完整的扩展资源:manifest.json、Popup 的 HTML/JS、Content Script 的 JS 等,且路径必须与 manifest 中声明的一致。

本项目使用 Vite 分别构建 Popup 和 Content,最终都输出到 dist/ 目录,并依赖 copy-webpack-pluginpublic/ 下的 manifest.json 拷贝到 dist/。加载时选择 dist 目录即可。

调试技巧

  • Popup:右键扩展图标 →“检查弹出内容”,打开 DevTools,可打断点、查看 Network。
  • Content Script:在被注入的网页上按 F12,在 Sources 面板中找到扩展的 content.js,或在 Console 中查看其日志。
  • Background(若使用):在 chrome://extensions 中点击扩展的“Service Worker”链接。

开发时若修改代码,需重新构建(npm run buildyarn build),并在 chrome://extensions 中点击扩展的“重新加载”按钮。

七、安全与体验优化建议

  • 权限最小化:只声明真正用到的权限和 host;API Key 等敏感信息只存 storage,不写进源码、不提交仓库。
  • 错误降级:如本插件在“未配置 Key”或“密钥错误”时提示用户并打开设置页;其他 API 失败时可降级到本地 mock,避免白屏或静默失败。
  • 用户反馈:总结前对字数、内容类型做校验;保存设置后给出“设置已保存”等反馈,提升可感知的稳定性。
[AFFILIATE_SLOT_1]

八、从 Web 到扩展的心智转换总结

回顾整个开发过程,几个关键点值得铭记:

  • 多环境思维:Popup / Content / Background 各是一块独立运行环境,用消息和 storage 串联,而不是一个单页应用里的组件通信。
  • 构建多入口:至少区分 Popup(可 ESM)和 Content(要 IIFE)两套构建,产物放到同一目录供 manifest 引用。
  • 权限与存储:manifest 里声明权限和 host;无后端时用 chrome.storage 做配置持久化,从设计上避免硬编码密钥。
  • 调试与发布:以“构建 → 加载 dist → 在真实扩展环境里点一点”为主;发布到商店前再对照审核策略做一遍检查。

如果你正在或打算开发一个与网页内容强相关的小工具(总结、翻译、高亮、剪藏等),欢迎参考或直接基于“网页内容总结助手”的架构进行改造:React + Vite、Manifest V3、Content 与 Popup 分离构建、storage 持久化——这些模式都可以复用。

[AFFILIATE_SLOT_2]

项目仓库:https://gitee.com/qiaoyuning/ai-page-summarizer.git

本地安装:执行 git clone,在 Chrome 中加载 dist/ 目录即可使用。

<script type="module">importchrome.storage.syncchrome.storage.localhost_permissionsdist