Last updated: 2026-05-05
Status: draft
- 结论:在你的目标组合(长期可维护 + 复杂 UI 持续迭代 + 团队协作 + 可接受构建链 + 必须 React)下,迁移到 TS + React 是“值得做且可行”的,但必须 分阶段,先把构建/产物/测试护栏做稳,再逐页迁移 UI,最后完成全仓 TS 化与旧架构下线。
- 当前仓库已落地的是
allowJs + checkJs的npm.cmd run typecheck、types/*契约文件、Node 单元测试和 Playwright 主链路;还没有把dist/作为运行事实来源,也没有引入 React 或构建产物加载。 - 关键硬约束:
- MV3 background 是 extension service worker:若想使用 ES module
import,需要在 manifest 里声明type: "module";同时 不支持动态import()(只支持静态 import)。(developer.chrome.com) - 你偏好的开发方式(watch + reload)可以直接用
vite build --watch。(vite.dev)
- MV3 background 是 extension service worker:若想使用 ES module
- 替代方案(不选,仅作为对照):只做 TS typecheck + 继续模块化(不引入 React/构建)能解决一部分债,但无法从根上解决 UI 复杂度、组件复用与多人协作成本。
- 收益
- 类型层:把“消息协议、settings、history、diagnostics、adapter contracts”从隐式 JS 约定变成可演进的显式接口,降低重构回归与跨模块误用。
- UI 层:React 统一状态与渲染模型,减少手写 DOM 状态机与大文件拆分成本,利于后续做“资料库化”(标签/备注编辑、复杂筛选、批量操作、更多视图)。
- 工程层:把“入口契约(manifest/resources/脚本顺序)”从人工维护变成构建产物约束,减少脚本顺序/全局变量依赖的脆弱性。
- 成本
- 引入构建链与
dist/成为事实来源:本地开发、E2E、发布都要适配。 - 调试方式变化:source map/构建产物定位问题成为常态。
- 引入构建链与
- 主要风险与规避
- Service worker 的动态 import 限制:禁止在 background 使用
import(),并让构建保证 background 不产生动态加载路径。(chromium.googlesource.com) - Rollup “单文件内联动态导入”与“多入口”冲突:
output.inlineDynamicImports只能单入口,因此 background/content 需要独立构建或独立入口策略。(rollup.nodejs.cn) - content script 的模块化限制:不要依赖“content script 里直接写静态 import”;统一用 bundler 输出单文件 content 脚本(或采用明确的动态 import + WAR 声明,但优先不走这条)。(stackoverflow.com)
- 依赖库的 CSP/worker 环境不兼容:在选型上避免需要
eval、DOM、XMLHttpRequest 的库进入 background。
- Service worker 的动态 import 限制:禁止在 background 使用
阶段目标是“每一阶段都可交付、可回滚、可验收”,不把产品功能升级和框架迁移混在同一批。
- 锁定当前行为的验收清单:
- Node:现有
node tests/run-tests.js - TS:现有
npm.cmd run typecheck - E2E:现有 Playwright 主链路(popup 保存/自动保存/连接测试、注入侧栏、生成、历史复用、导出、reader)。
- Node:现有
- 产物与入口基线:
- 明确未来以
dist/为加载目录;根目录脚本进入“过渡期保留,但不再新增特性”。
- 明确未来以
验收:当前 Node + E2E 全绿;基线手工烟测清单可执行。
- 技术选型(决策已锁定):
- UI:Vite + React(多页)用于
popup/reader/sidebar。Vite 多页通过build.rollupOptions.input声明多 HTML 入口。(v4.vite.dev) - Core(background/content):单独构建,保证:
- background:不产生动态
import();如使用 ESM 静态 import,manifest 加type:"module";否则输出单文件 classic/IIFE。(developer.chrome.com) - content:输出单文件,不依赖静态 import。(stackoverflow.com)
- background:不产生动态
- UI:Vite + React(多页)用于
- 输出结构(最终稳定形态):
dist/manifest.json、dist/popup.html、dist/reader.html、dist/sidebar.html、dist/background.js、dist/content.js等稳定入口文件。- icons/libs 等静态资源进入 Vite
public/,由 Vite 原样拷贝到dist/(保证文件名稳定、无需 hash)。(vite.dev)
- Dev flow:
npm.cmd run dev(或等价)= 并行执行多个vite build --watch(UI + background + content),然后手动或脚本触发扩展 reload。(vite.dev)
- 测试适配:
- Playwright harness 从加载 repo 根目录改为加载
dist/。 - 静态契约测试改为校验
dist/manifest.json及dist/下资源存在性。
- Playwright harness 从加载 repo 根目录改为加载
验收:不改任何产品行为的前提下,dist/ 可被浏览器 Load unpacked;Node + E2E 全绿。
- 先迁“纯逻辑高复用层”:shared 工具、adapters、transport、domain、errors、run-utils、trust-policy、url utils。
- 再迁“存储与后台编排”:db、background(注意 SW 环境限制,避免引入 DOM/动态 import)。(developer.chrome.com)
- TS 策略:
- 新 TS 文件默认开启更严格规则;旧 JS/旧 TS 文件允许逐步提升(以“每次改动不扩大红线”为原则)。
- 引入
@types/chrome与最小自维护类型层,保持消息协议在types/*(或src/types/*)为单一事实来源。
验收:TS 编译产物完整替代旧 JS 入口;类型门禁稳定;不新增运行时行为差异。
迁移顺序与策略(强约束):
- 先
popup(最小面、状态相对集中) - 再
reader - 最后
sidebar(最大、状态最多)
每一页的迁移要求:
- 先把现有 UI 逻辑拆成 “纯 view model + UI 渲染层”,再把 UI 渲染层换成 React。
- 关键选择器/DOM contract:
- Playwright 依赖的
#apiKey/#baseURL/#modelName/#testBtn等必须保持,或同步升级测试选择器与静态契约。
- Playwright 依赖的
- 样式策略:
- 先“像素级等价”,不在迁移批次里做视觉重设计(降低回归面)。
验收:每迁移一个页面,Node + E2E 全绿;手工烟测通过;性能与包体无明显倒退(给出基准:加载时间、内存占用、bundle size)。
- 删除根目录旧页面/脚本的“运行时入口”角色,只保留源码在
src/。 - 文档与贡献指南统一以
dist/为准;发布脚本固定从dist/打包。
验收:仓库结构清晰、无双入口;任何变更都必须通过构建与测试才能被加载。
- 必跑:
- Node:现有
node tests/run-tests.js - TS:
npm.cmd run typecheck(或迁移后的等价命令) - E2E:Playwright 主链路(确保
dist/形态也跑通)
- Node:现有
- 必测场景(针对迁移风险):
- background 不含动态 import;SW 启动/事件监听正常。(chromium.googlesource.com)
- popup 自动保存/连接测试/错误展示仍一致。
- content 注入 + sidebar iframe 注入 + SPA 路由刷新仍一致。
- reader session 恢复路径与失效回退不变。
- 类型契约继续覆盖
types/messages.ts、types/history.ts、types/settings.ts、types/diagnostics.ts。
- 目标浏览器:仅 Chromium(Chrome/Edge)。
- UI 框架:必须 React。
- 开发方式:
vite build --watch+ 扩展 reload(不依赖 dev server/HMR)。 - 迁移范围:最终全仓 TS 编译产物接管(background/content/shared/adapters/db/ui 全部进入构建链)。
- 迁移原则:行为等价优先;不混合产品功能大改与框架迁移。