来发信谷歌地图扩展 — 版本更新规则¶
本文件是本项目版本更新的总规范。每次更新前请先完整阅读本文件。 最后更新:2026-05-28(v0.10.108 从 repo 根迁移到 docs/rules/)
⚠️ 特别提示:第七章「改进前自查清单」是历次踩坑后总结的,改设置页 / 调度逻辑 / 文案前必读。
一、项目概况¶
| 项目 | 说明 |
|---|---|
| 名称 | 来发信 谷歌地图数据采集专业版(Chrome 扩展) |
| 技术栈 | WXT + React + MUI + TypeScript |
| 包管理器 | pnpm |
| 当前版本 | 0.8.37(见 package.json 的 version 字段) |
| 构建输出 | dist/chrome-mv3/ |
二、目录结构¶
05_google-map/
├── 更新规则.md ← 本文件,更新规范
├── development-log.md ← 问题 / 解决方案 / 注意点记录
├── CLAUDE.md ← 给 AI 的项目说明(自动遵守本规则)
├── backup/ ← 【规则1】更新前的源码备份
├── src/ ← 源代码
├── test/ ← 已构建版本的归档
├── dist/chrome-mv3/ ← 【规则3】构建输出,加载到浏览器的就是它
├── package.json ← 版本号在这里改
└── wxt.config.ts ← 扩展配置
三、三条核心规则¶
规则 1 — 更新前必须备份¶
在动任何代码之前,先把当前版本完整备份到 backup/。
- 备份目录命名:
backup/v<当前版本号>_<日期>/,例如backup/v0.8.37_20260521/ - 备份内容:
src/、package.json、wxt.config.ts、tsconfig.json - 一键备份命令(在项目根目录执行,先把
0.8.37换成当前实际版本号):
V=0.8.37; D=$(date +%Y%m%d); mkdir -p "backup/v${V}_${D}" && cp -R src package.json wxt.config.ts tsconfig.json "backup/v${V}_${D}/" && echo "已备份到 backup/v${V}_${D}"
- 备份目录只增不删,出问题随时可以回滚。
规则 2 — 全程记录开发日志¶
每次更新都要在 development-log.md 写一条记录,内容包括:
- 本次改动:这次要做 / 做了什么
- 遇到的问题:现象 + 原因 + 解决方案
- 注意点:下次要避开的坑、约定、易错点
最新记录放最上面。日志模板见 development-log.md 顶部。
规则 3 — 新版本输出到专门文件夹,浏览器刷新即测¶
- 构建输出统一在
dist/chrome-mv3/(由wxt.config.ts的outDir配置,是可见文件夹)。 - 首次加载:Chrome 打开
chrome://extensions→ 右上角开启「开发者模式」→ 点「加载已解压的扩展程序」→ 选择dist/chrome-mv3/文件夹。 - 改完代码后测试:
- 方式 A(推荐):
pnpm dev—— 改代码后自动重新构建并热重载,浏览器无需手动刷新。 - 方式 B:
pnpm build重新构建 → 到chrome://extensions点该扩展的「刷新」图标 → 刷新网页测试。 - 版本发布:
pnpm zip打包,把产物归档到test/。
四、标准更新流程¶
把上面三条规则串成一套固定动作,每次更新照着走。
- 读本文件 +
development-log.md,了解历史坑点。 - 【规则1】 执行备份命令,备份到
backup/。 - 【规则2】 在
development-log.md顶部新建一条记录,写下本次改动目标。 - 修改
package.json的version(如 0.8.37 → 0.8.38)。 - 修改源代码。
pnpm compile—— TypeScript 类型检查,无报错再继续。pnpm build—— 构建到dist/chrome-mv3/。- 【规则3】 浏览器加载 / 刷新,实测功能。
- 【规则2】 把过程中遇到的问题、解决方案、注意点补写进
development-log.md。 - (需要分发时)
pnpm zip打包,归档到test/。
五、常用命令¶
| 命令 | 作用 |
|---|---|
pnpm install |
安装依赖 |
pnpm dev |
开发模式,自动热重载 |
pnpm build |
构建生产版本 → dist/chrome-mv3/ |
pnpm zip |
打包 zip → dist/laifaxin-chajian-ditu-<版本>-chrome.zip |
pnpm compile |
TypeScript 类型检查(不输出文件) |
六、注意事项¶
- 版本号只在
package.json改,WXT 会自动同步到扩展 manifest。 - 备份目录
backup/和归档目录test/只进不出,不要删。 - 每次只做一类改动,方便出问题时定位和回滚。
- 本项目有历史 TS 报错,
pnpm compile不作为通过标准;以pnpm build成功出包为准。 - 依赖用 pnpm 安装;pnpm 11+ 需根目录
pnpm-workspace.yaml的allowBuilds放行 esbuild 等构建脚本,否则pnpm install/build会报错退出。 - 改了
wxt.config.ts(权限、host 等)后必须重新build并在扩展页重载。
七、改进前自查清单(必读 — 历次踩坑总结)¶
这些坑都是真实踩过的(v0.10.12 / v0.10.13 / v0.10.14 反复犯过),每次改进前花 2 分钟扫一遍能省 30 分钟返工。
🚨 A. 改「设置页 / UI 文案」前 — 反复踩坑最多¶
症状:望文生义命名设置项,导致与实际调度逻辑矛盾,用户读了反而误导。
铁律 1:读源码确认语义,不靠字段名猜
| 字段 | 字段名暗示 | 实际语义(已确认) | 出处 |
|---|---|---|---|
maxConcurrentTasks |
"同时几个任务" | activeTabs.size 全局上限,与任务数无关。1 个任务也能用满 N 个 Tab | batch-controller.ts 顶部注释第 18-30 行 |
deepScrapeConcurrency |
"同时几个网站" | worker 线程数全局上限。所有任务的 MapTaskData 共用一张表 |
engine-manager.ts getConcurrency + manageQueue |
requestConcurrency |
"请求并发" | v0.10.0 后废弃字段,调度器不读。仅保留兼容老 storage | task-manager.ts 第 41 行注释 |
pagerConcurrency |
"翻页并发" | 全局翻页 Fetch 并发上限,不是 per-task | batch-controller.ts pumpPager |
deepScrapeDomainConcurrency |
"单网站并发" | 同一 hostname 最多同时几个 worker(防 DDoS 对方站) | engine-manager.ts domainActive |
铁律 2:v0.10.0 起一切都是「共享队列」架构
- 任务(status='running' 的)→ 共享队列 ← worker 池(N 个槽位)
- N 个槽位 =
maxConcurrentTasks(地图)/deepScrapeConcurrency(网站) - 任务数与槽位数完全解耦,调度器 round-robin 从所有 running 任务取 URL
改文案的 checklist:
- [ ] label 是否暗示 per-task?(错)应改为「全局上限」「上限」
- [ ] helper 是否解释了「与任务数无关」?
- [ ] 是否给了具体例子?(1 任务 vs 5 任务下的行为)
- [ ] 改完后回看 batch-controller.ts / engine-manager.ts 顶部注释,文案与代码是否自洽?
📦 B. 加新功能 / 设置项前¶
铁律 3:写死的常量都是设置项的候选
任何 const FOO_BLACKLIST = [...] / const PATTERNS = [...] / 行内的 if/else 判断,问自己:
- 用户可能想关掉吗?→ 加 Switch
- 用户可能想加一些自定义项吗?→ 加 textarea,自动 merge 到内置
- 内置内容用户能看见吗?→ export const 出来,给「查看内置 N 项」按钮用
铁律 4:用户在某个粒度上设置 X,往往也想在更粗/更细的粒度上设置 X
- 邮箱黑名单 → 也要支持域名级(v0.10.14 补的)
- 单根域跟随 → 也要支持跨根域白名单(v0.10.14 补的)
- 关键词白名单 → 顺手加黑名单(同价但不同语义)
铁律 5:默认值必须保持老版本行为(向后兼容)
新加的 boolean 字段默认 true 或 false 要保持上一版的实际行为;text 字段默认 '' 走内置;不要让老用户升级后突然丢功能。
新功能 checklist: - [ ] 是否有"打包成一个函数 / 一段逻辑"导致用户没法部分关闭? - [ ] 是否有"硬编码列表"用户想改改不了? - [ ] 默认值是否 = 上一版行为? - [ ] 是否需要在更粗 / 更细粒度也提供?
🔧 C. Edit 工具操作¶
铁律 6:含中文的旧 JSX,不要手写 old_string 猜全角半角
源码里这些字符常常是全角:
- () U+FF08/FF09(不是 ASCII ())
- : U+FF1A(不是 ASCII :)
- , U+FF0C(不是 ASCII ,)
- 。 U+3002(不是 ASCII .)
- ; U+FF1B(不是 ASCII ;)
症状:Edit 报 String to replace not found。
正确做法(按性价比排序):
1. 小改动:先 awk 'NR==<行号>' 把目标行打印出来直接复制粘贴
2. 大块替换:用 Python 切片,根据唯一锚点定位区间,整体替换:
cat > /tmp/new.txt << 'EOF'
... 新内容 ...
EOF
python3 << 'PYEOF'
from pathlib import Path
p = Path("src/path/to/file.tsx")
src = p.read_text(encoding='utf-8')
start = src.find("唯一锚点1")
end = src.find("唯一锚点2", start)
new_block = Path("/tmp/new.txt").read_text(encoding='utf-8')
p.write_text(src[:start] + new_block + src[end:], encoding='utf-8')
PYEOF
- 从不:连续猜 3 次 ASCII vs 全角 —— 浪费 token。
🗄️ D. storage / 数据兼容¶
铁律 7:storage 字段加只能加,不要删
- 废弃字段 → 加
// v0.x.y deprecated, no longer read注释,保留在 interface - 改语义 → 加新字段,老字段保留(写迁移代码可选)
- 删了字段 → 老用户升级时 storage 里仍有,新代码读出来是 undefined,可能引发崩溃
🧪 E. 验证标准¶
pnpm build必须过(生产构建出包 = 真验证)pnpm compile仅查"新增的 TS 错误",已有历史错误不算- 改了 settings UI 必须在浏览器实际打开看一眼(文字溢出 / 换行 / 折叠是否对)
- 改了调度逻辑必须用 2+ 个任务跑一遍,验证共享队列行为(不是只跑 1 个任务)
🗒️ F. 历次大事件(避免重蹈)¶
| 版本 | 事件 | 教训 |
|---|---|---|
| v0.10.0 | 多任务调度重写,从 per-task 改共享队列 | 这是基线架构,所有"并发"字段都是全局 |
| v0.10.12 | 把 maxConcurrentTasks label 写成"= 任务数" |
改文案前没读 batch-controller.ts 顶部注释 |
| v0.10.13 | scraper.ts 把 7 类规则打包写死 | 用户能想关的逻辑,全部独立开关 + 可编辑 |
| v0.10.13 | Edit 中文标点踩坑(3 次) | Python 切片代替 old_string |
| v0.10.14 | 再次把 deepScrapeConcurrency 写成"网站数" |
与 v0.10.12 同坑 → 必须查源码 |
| v0.10.15 | 长跑后浏览器累积孤儿 Tab 卡死 | 引入 watchdog(5min 巡检 + 3 次累积自动重启) |
| v0.10.16 | popup loading 状态死循环(无超时) | 铁律 8:任何"等异步结果"的 loading 必须有超时 + 兜底入口,否则 = 用户卡死无法操作 |
| v0.10.17 | 主面板登录按钮 force=false 跳过开窗 | 铁律 9:「主动点击」≠「静默尝试」,用户点击的入口必须 force=true 绕过缓存 |
| v0.10.18 | filter chip 视觉变了但 jsstore 查询是空 | 铁律 10:UI 视觉与业务查询必须同步。"TODO: 暂留空"是定时炸弹 |
| v0.10.19 | 列宽 stale closure + columns prop 引用不稳定 | 铁律 11:setState 用 functional 避免 stale;prop 传数组/对象用 useMemo 保稳定 |
| v0.10.20 | useMemo deps 含 object/array 但调用方传不稳定引用 → memo 形同虚设 | 铁律 12:useMemo deps 含 array/object 时,用字符串签名 arr.map(...).join(',') 做稳定 key;不能假设调用方一定 useMemo |
| v0.10.20 | hasEmailOnly 在 taskId 分支「先分页再过滤」分页错乱 | 铁律 13:分页 + 过滤必须「先过滤再分页」,反过来数学上不等价 |
| v0.10.20 | 修 ISSUE-0010 写了 useMemo 但没回到浏览器实测 → 没修到根 | 铁律 14:写 fix 后必须实测复现。"修了"≠"修好了",必跑 happy path 验证 |
| v0.10.21 | 设置页双滚动条 v0.10.18 修不彻底,二次撞 → 父级 main-layout 已 overflow:auto,settings 又加 overflow → 双条 | 铁律 15:修嵌套滚动 bug 必须查父级链。每一层父级的 overflow / height 都要看清楚 |
| v0.10.21 | 同上 — 子组件不应假设自己是页面根 | 铁律 16:「让父级负责滚动」是更稳的模式。子组件高度自适应内容,比硬撑高度自滚动更不易与父级冲突 |
📋 改进流程的扩展版¶
把第四章「标准更新流程」第 1 步细化为:
- 读源码语义(A 节)—— 任何要改的设置/调度,先打开对应源码看顶部注释和实际逻辑
- 扫本清单(A→E)—— 2 分钟
- 读
development-log.md最近 3 条记录,避免和最近的改动冲突 - 然后才开始改代码