# CF Workers Bundle 体积优化 · 综合总结

> 日期：2026-06-10　仓库 payment-kit，分支 `feat/cross-blocklet-session-issue`（HEAD `3c8a1c46`）
> 产物：`blocklets/core/cloudflare/dist/worker.js`
> 本文是这次 bundle 优化的**唯一权威总结**，已合并原 investigation-log / dead-code-removal-reference / optimization-summary / package-audit 四份。所有数字均经实测（git 历史真实 build + esbuild metafile + wrangler dry-run/deploy），并用「落盘文件 + 交叉验证」方法取得。

---

## 1. 结论速览（TL;DR）

- worker.js 从迁移基线 **2.68 MiB** 涨到 **4.07 MiB**，增长 **100% 来自 IAP**（PR #1381），其中 **85% 是单个提交 `3700b061`**（Apple JWS 验签）。
- 可砍的纯死代码有两处，均已移除：**① node-fetch polyfill 链 ~781K**（encoding/tr46/whatwg-url）；**② ethers 非英语 BIP39 助记词词表 ~67K**（worker 0 引用 Mnemonic）。两者在 Workers 上永不执行。
- 配合 wrangler 部署优化（`minify` 或 `no-bundle`），实际部署 gzip 从 **1185 KiB → 957.73 KiB（< 1 MiB，含 node-fetch + wordlists 两处优化）**。
- 其余大头全是**业务代码 / 加密货币核心能力 / DID 协议 / 必需 SDK / 高风险验签库**，无更多低风险高收益项。

### 体积演变（两口径，均实测）

| 阶段 | wrangler Total Upload | gzip | 进 1 MiB |
|---|---|---|---|
| 基线（未优化） | 4862 KiB | 1185 KiB | ✗ |
| + node-fetch alias | 4438 KiB | 1123 KiB | ✗ |
| + wrangler `minify` | 3462 KiB | 1033 KiB | ✗（差 9K，最安全） |
| + wrangler `no-bundle`（仅 node-fetch） | 3385 KiB | 1006 KiB | ✅（worker 可跑，依赖 nodejs_compat） |
| **+ wordlists（最终态，已部署 staging）** | **3318 KiB** | **958 KiB** | ✅ **实测部署 gzip 957.73 KiB** |

> `dist/worker.js` 本身（run-build.js 产物）：4.07 MiB / gzip 1.23 → node-fetch 后 3.30 / gzip 0.97 → **+wordlists 后 3.24 MiB / gzip 0.927**。
> **最终态实测部署（no-bundle，两处死代码移除）：Total Upload 3317.93 KiB / gzip 957.73 KiB（< 1 MiB，已部署个人 staging 并验证支付路由正常运行）。**
> 公司 staging 线上（旧代码）实测 module body 3.46 MiB / gzip 0.83，与本地 HEAD 不同次构建，不可直接比。
> CF 脚本限额按 gzip 算（免费版历史 1MB→后提 3MB，以 [CF 官方 limits](https://developers.cloudflare.com/workers/platform/limits/) 为准），当前 ~1 MiB 无容量压力。

---

## 2. 膨胀根因：IAP（PR #1381，squash 成 `0039b36a`）一次 +1.38 MiB

迁移基线 `bad72364`/`1486b54f` = 2.68 MiB（就是"最早 2.7"）。squash 的 PR 子提交需 `git fetch origin pull/1381/head` 才能取到，对 48 个相关子提交逐个 build 定位跳变点：

| 子提交 | 内容 | Δ |
|---|---|---|
| `a140541a` | A2-real Google Play API | +0.65 MiB（引入 googleapis / google-play-billing-validator / google-auth-library / node-fetch） |
| `3700b061` | **A1-real-3 Apple SDK JWS 验签** | **+1.18 MiB** 🔴（引入 @apple/app-store-server-library + jsrsasign + jsonwebtoken + node-jose） |
| `2594bafe` | A4-cf Google Play 改 Workers 原生 fetch | −0.62 MiB（把 Google 重 SDK 换原生，几乎抵消 a140541a） |

其余 ~32 个子提交对 bundle ≈ 0。净增长 85% 来自 `3700b061` 的 Apple 验签链。

---

## 3. 已做的优化

### 3.1 node-fetch polyfill 死代码移除（代码改动）

`@apple/app-store-server-library`（dist/jws_verification.js、dist/index.js）内部 `import node-fetch`，node-fetch 又拖进一整套 Node polyfill：

| 包 | 占用 | 为何是死代码 |
|---|---|---|
| encoding（= iconv-lite，CJK 编码表 cp936/cp950/eucjp/cp949/shiftjis） | 481 KiB | Workers 有原生 TextDecoder，编码表永不查（API 都是 UTF-8） |
| tr46（IDNA 域名映射表） | 259 KiB | Workers 有原生 URL，域名都 ASCII |
| whatwg-url / node-fetch | 41 KiB | Workers 有原生 URL/fetch |

> 「死代码」≠ 源码没人 import；恰恰是被 import 了才打进 bundle，"死"在**运行时永不执行**。

**改动（主仓库工作区，UNCOMMITTED）**：
1. 新增 `shims/node-fetch.ts`：转发 `globalThis.fetch`，补全 node-fetch v2 导出（default/Headers/Request/Response/FormData/Blob/FetchError/AbortError/isRedirect）。
2. `run-build.js` 的 alias 对象（含 axios/stripe/@blocklet/sdk 一大套 shim 那个，**不是 lodash 那个**）加 `"node-fetch": s("shims/node-fetch.ts")`。
3. 新增 `scripts/verify-bundle-optimization.sh`（构建层自动验 + IAP 端到端清单）。

效果（mtime+size 双证）：dist/worker.js **4.07 → 3.30 MiB**（gzip 1.23→0.97），死代码 encoding/iconv-lite/tr46/whatwg-url/node-fetch 全归零。
**升级 SDK 无效**：@apple SDK 最新 3.1.0 仍依赖 node-fetch@2.7.0、jsrsasign@11。
替代落地方式：pnpm.overrides / pnpm patch（首选仍是 esbuild alias，与现有 shim 同手法）。

### 3.2 ethers 非英语助记词词表移除（代码改动）

ethers `lib.esm` 完整版把 9 种语言的 BIP39 助记词词表全打进 bundle（供 Mnemonic/HD 钱包用）。worker **0 引用** Mnemonic/HDNode/fromPhrase/wordlist —— ethereum 链上签名走 `RemoteSigner extends ethers.AbstractSigner` + `@ocap/wallet` 远程签名服务，**不碰助记词**。所以 8 种非英语词表永不执行（ethers 官方 /dist 也默认 strip 掉，省 ~80kb）。

**改动**：`run-build.js` 加 esbuild plugin `dropEthersWordlistsPlugin`，onLoad 把 8 个 `wordlists/lang-{cz,es,fr,ja,ko,it,pt,zh}.js` stub 成 `export class LangXx { static wordlist() { return null; } }`（保留 static 方法，避免 wordlists.js 模块初始化时顶层 `LangXx.wordlist()` 调用崩溃；保留 LangEn）。

效果（实测）：dist/worker.js **3.30 → 3.24 MiB**，再省 raw 67K / gzip 48K。build 成功，ethers 核心（provider-jsonrpc/contract/transaction）完好。
**影响**：仅"用**非英语**助记词派生 HD 钱包"会失效 —— worker 从不使用（远程签名，不碰助记词），**零业务影响**。合并前 staging 顺带验一次 ethereum 链上支付即可。

### 3.3 wrangler 二次打包优化（配置层）

**陷阱**：所有 wrangler config 都是 `main = "dist/worker.js"`，但 `wrangler deploy` 仍会用它自己的 esbuild 对这个文件**再 bundle 一次**且**默认不 minify**，把 3.30 MiB 膨胀回 ~4.33 MiB（Total Upload 4438 KiB），稀释了 run-build 层的优化（dist 省 785K，部署 gzip 只降 63K）。

- `--minify`：让 wrangler 二次打包也压缩 → 3462 KiB / gzip 1033（**最安全**，行为同现状 + 压缩）。
- `--no-bundle`：跳过二次打包，直接传 dist/worker.js → 3385 KiB / gzip **1006**（**最小**，进 1 MiB；但依赖 nodejs_compat，见 §4 验证）。

**落地**：在各 wrangler config（local/dev/staging/jsonc）加 `minify = true`（或采用 no-bundle）。

---

## 4. 部署验证（个人 staging）

部署目标：`payment-kit-staging.zhuzhuyule-779.workers.dev`（Pengfei 个人账号，`wrangler.local.toml`，OAuth）。compat：`compatibility_date = "2024-12-01"` + `nodejs_compat`。

no-bundle 版实测 curl（证明 worker 真能跑，node 内置 import 全解析）：

| 路径 | HTTP | 含义 |
|---|---|---|
| `/`（SPA via ASSETS） | 200 | 正常 |
| `/__blocklet__.js` | 200 | 身份正常 |
| `/api/payment-methods` | 403 `{"error":"Not authorized"}` | **后端代码正常跑**（业务授权响应，非 500 崩溃） |

> `/api/payment-methods` 返回业务 403 而非 500，证明 worker 成功启动、node 内置 import（crypto/path/events/url…）在 no-bundle + nodejs_compat 下全部解析。

---

## 5. Package 审计：可优化 / 不可改

当前优化后 3.30 MiB 的 package 分类（metafile 实测）：

| 类别 | packages | 体积 | 处置 |
|---|---|---|---|
| ✅ 已移除（死代码1） | encoding/tr46/whatwg-url/node-fetch | 0（原 781K） | 已 alias |
| ✅ 已移除（死代码2） | ethers 非英语 BIP39 词表（cz/es/fr/ja/ko/it/pt/zh） | 0（原 ~70K） | 已 plugin stub |
| 🔒 业务代码 | app-src（routes/libs/queues/models/integrations） | 1353K | 不可改 |
| 🔒 加密货币核心能力 | ethers 361 + @noble/curves 73 + bn.js 44 + aes-js 42 + ens-normalize 35 + bignumber.js 19 + @noble/hashes·ciphers·ed25519 | ~600K | 保留（用户拍板） |
| 🔒 DID/ArcBlock 协议 | @ocap/client 130 + message 67 + proto 48 + mcrypto/util/asset/wallet + @arcblock/* | ~360K | 业务核心 |
| 🔒 业务必需 SDK | stripe 96（已 stripe-cf shim）、joi 134、hono 20、dayjs 21 | ~270K | 不可改 |
| 🟡 IAP 验签（高风险不动） | jsrsasign 299、@apple SDK 85、jsonwebtoken 12、node-apple-receipt-verify 13 | ~410K | 换 WebCrypto 需重写验签，安全关键 |
| 🟡 已最优/不值得动 | lodash-es 121（子路径 import 已优）、semver/async/qs 等小项 | — | gzip 后收益微、改动面大 |

**`token-addresses.json`（加密货币 357 个代币范围）和 `locales` zh/en（in-use 订阅通知本地化）是业务功能数据，非死代码，保留。**

### 不可修改点汇总（一句话版）

| 不可改 | 原因 |
|---|---|
| app-src | Payment Kit 后端业务逻辑 |
| ethers 及加密 crypto 栈 | 加密货币（ethereum）支付核心能力（用户拍板）。位置 `api/src/integrations/ethereum/`，8 文件用 ethers（payment-methods.ts `new ethers.JsonRpcProvider`、refunds.ts 链上退款等） |
| @ocap/* @arcblock/* | DID 登录/协议核心 |
| stripe / joi / hono | 业务必需 SDK/框架 |
| jsrsasign / @apple SDK / jsonwebtoken / node-apple-receipt-verify | IAP 验签，安全关键高风险 |
| token-addresses.json / locales | 加密货币代币范围 / in-use 通知本地化（非死代码） |

**结论**：分析指出的可优化死代码已全部移除；剩余无更多低风险高收益项。不建议为最后 <100K gzip 去动加密货币栈/验签库/joi/lodash（风险 = 资金/安全/大面积回归，远大于收益，且无容量压力）。

---

## 6. 业务代码组成 · 健康度评判 · 全量可优化点

> 死代码移除到顶后，进一步审视"剩下的体积到底合不合理"。**结论：bundle 没有虚胖——业务代码大是真复杂度；真正的债在结构（超大文件）和测试（路由层），与体积无关。**

### 6.1 worker bundle 组成（raw 3313 KiB / gzip 948 KiB）

| 分类 | 占用 | 占比 |
|---|---:|---:|
| 业务代码（本仓 api/src） | 1348 KiB | 41% |
| 第三方库（node_modules） | 1958 KiB | 59% |

第三方库按功能域（metafile 实测）：

| 功能域 | 占用 | 主导库 |
|---|---:|---|
| 加密货币/区块链 | 514 KiB | ethers 294 · noble-curves 73 · bn.js 44 · aes-js 42 · ens-normalize 36 |
| IAP（App Store + Play） | 409 KiB | jsrsasign 299 · @apple SDK 85 · apple-receipt 13 · jsonwebtoken 12 |
| ArcBlock/DID | 356 KiB | @ocap/client 130 · message 66 · proto 48 · did-connect 38 · mcrypto 31 |
| 参数校验 | 166 KiB | joi 133 · @sideway/address 21 |
| 通用工具 | 156 KiB | lodash-es 121 · dayjs 21 |
| Stripe | 96 KiB | stripe 96 |
| 框架/适配/其它 | 261 KiB | semver 26 · async 22 · hono 20 · bignumber 19 · qs 16 · ~80 个小库 |

### 6.2 业务代码真实规模（cloc 权威）

> ⚠️ 早期口头报的 "99K 行" 是 `xargs | tail` 分批统计只取最后一批的假象，已作废。cloc 全量扫描为准：

- **780 个 `.ts` 文件**（排除 test）：**147,631 行纯代码** + 10,393 注释（6.6%）+ 13,970 空行 ≈ **172K 物理行**
- 唯一大数据文件：`token-addresses.json` 2146 行（链上代币地址表，合理）

| 目录 | 进 bundle | 内容 |
|---|---:|---|
| `routes` | 456 KiB | API 路由（最大头）|
| `libs` | 360 KiB | 业务逻辑（subscription/invoice/session/quote/coupon/exchange-rate）|
| `queues` | 173 KiB | 异步队列 |
| `integrations` | 113 KiB | stripe / ethereum / IAP 三通道对接 |
| `store` | 107 KiB | 数据模型 |
| `locales` | 41 KiB | i18n（zh 23 + en 17）|
| worker.ts + shims | 76 KiB | CF 入口 + 适配垫片 |
| `crons` | 20 KiB | 定时任务 |

### 6.3 评判：147K 行 / 1.35M 业务代码合理吗？

**合理（四条实证）**：

| 维度 | 数据 | 判断 |
|---|---|---|
| API 广度 | **318 个端点**（171 GET / 61 POST / 57 PUT / 29 DELETE），57 个资源文件 | Stripe 级别的支付 API 面 |
| 功能域 | checkout/subscription/invoice/credit/refund/coupon/exchange-rate/payment-intents/payouts/webhooks/meter-events/vendor/donate + 3 通道 + 队列 + i18n | 完整多通道计费平台，数十万行量级属常态 |
| 重复率 | **4.14%**（jscpd，153 克隆/71587 行）| 优秀（<5%），没靠复制堆量 |
| 杂质 | 无多版本依赖、无功能重复库、无生成代码虚高 | 干净 |

**有债（两条，与体积无关）**：

1. **超大文件（结构债）**：21 个文件 >800 行，5 个 >1500 行——`checkout-sessions.ts` **5182 行**、`subscriptions.ts` 3324、`queues/subscription.ts` 2160、`libs/session.ts` 1937。违反单一职责，改动风险高。**代码"显得大"的不适感来自单文件密度，不是总量。**
2. **路由层测试缺口（工程债）**：测试在 `api/tests/`（48 个文件，libs 占 24，覆盖好）；但 **57 个路由资源 / 318 个端点只有 3 个路由测试文件**，API 契约几乎无回归保护。

### 6.4 全量可优化点清单

**A. 体积维度（针对 948K gzip）——已基本到顶**

| 优化点 | 量级 | 评级 |
|---|---|---|
| node-fetch polyfill 链 | −754K raw | ✅ 已做（本 PR）|
| ethers BIP39 wordlists | −67K raw | ✅ 已做（本 PR）|
| joi（133K）→ zod/valibot | ~−100K | ⚠️ 改全部入参校验，大面积回归，不建议 |
| lodash-es（121K）→ 原生/按需 | 余量小 | ⚠️ esbuild 已 tree-shake |
| token-addresses.json（37K）运行时外置 | −37K | ⚠️ 微小，引入加载复杂度，不值 |
| jsrsasign（299K）→ @noble 重写 IAP 验签 | −200K+ | ❌ 资金/安全红线，不碰 |
| 业务代码 147K 行 | 0 | ❌ 真实逻辑，动不了 |

> 体积已到 gzip <1MiB，无更多低风险高收益项。**别再为体积动业务代码或核心库。**

**B. 可维护性维度（不减体积，但这才是真正该投入的）**

| 优化点 | 范围 | 收益 | 优先级 |
|---|---|---|---|
| 拆分超大文件 | checkout-sessions 5182 等 21 个 | 可读性/复杂度/降风险 | 🔴 高 |
| 补路由层测试 | 57 资源 / 318 端点 | 回归保护 | 🔴 高 |
| 抽列表查询 `where` helper | 8~11 个 route 的 status/metadata 过滤 | 一致性（改一处生效全部）| 🟡 中 |
| `settings.ts` `amountSchema` 去重 | 同文件复制 2 次 | 低垂果实 | 🟢 低 |

> B 类应**各自独立开 PR**，不与本体积优化 PR 混合。拆超大文件需先有路由测试兜底，建议顺序：补关键路由测试 → 拆 `checkout-sessions.ts` → 抽公共 helper。

### 6.5 重复 / 公共抽取审计

- **依赖之间**：无同名多版本（pnpm+esbuild 去重彻底）；所谓"重叠"的库各有归属——valibot/jsonwebtoken 是 `@arcblock`/`@apple` 内部依赖（动不了），bignumber.js（汇率小数）与 bn.js（链上整数）用途正交。**无可合并项。**
- **业务之间**：重复率 4.14%（健康）。可抽的公共逻辑见 6.4-B 的列表查询 helper（扩散到 8~11 个 route）与 settings amountSchema。

---

## 7. 合并前必做：IAP 端到端验证（staging）

node-fetch→原生 fetch 和 no-bundle 都改变了底层行为，必须在 staging 验：

- **不受影响**（纯本地密码学）：App Store StoreKit2 JWS 验签 —— SignedDataVerifier 用本地内置 Apple root 证书（`apple-root-certs.ts` 3 个）+ jsrsasign 验 x5c 证书链，不走网络。
- **需重点验**（走网络）：
  - [ ] App Store legacy receipt（node-apple-receipt-verify → Apple verifyReceipt）
  - [ ] App Store Server API 调用 / Notifications V2 webhook
  - [ ] Google Play verify / RTDN webhook
- 关注原生 fetch 与 node-fetch 差异（redirect / timeout / AbortSignal）。
- no-bundle 还需确认 nodejs_compat 覆盖所有 node 内置 import（dist 依赖 crypto/path/events/url/querystring/async_hooks/assert + node:stream/buffer/util 等）。
- 验证脚本：`scripts/verify-bundle-optimization.sh`。

---

## 8. 方法论：如何判断"某依赖是否被用到 / 能否移除"

1. **是否打包 + 占多少**：esbuild metafile（`dist/meta.json`）聚合 `bytesInOutput`，按 `node_modules/<pkg>` 分组。
2. **谁引入的**：metafile importer 反查 + `pnpm why <pkg>`（区分直接依赖 vs 传递依赖）。
3. **源码是否真引用**：`grep -rlE "from [\"']<pkg>" api/src cloudflare`（兼容单双引号，别用 sed 转换路径，易误导）。
4. **运行时是否真执行**（打包≠执行）：环境是否有原生替代？触发条件是否可能满足？alias 成空/原生 shim 重 build，build 过 + 功能验证过 → 证明对运行无贡献。
5. **移除省多少**：worktree 隔离（symlink node_modules）+ alias/external + 重 build，两套量法（python / build 自报 / wrangler）交叉验证。

### 复现命令速查
```bash
git fetch origin pull/1381/head:pr1381                 # 取 squash PR 子提交
git worktree add --detach /path/wt <commit>            # 隔离 worktree
ln -sfn $MAIN/node_modules /path/wt/node_modules        # symlink 依赖免重装
ln -sfn $MAIN/blocklets/core/node_modules /path/wt/blocklets/core/node_modules
cd /path/wt/blocklets/core/cloudflare && node run-build.js
python3 -c "import gzip;d=open('dist/worker.js','rb').read();print(len(d),len(gzip.compress(d,9)))"
pnpm why <pkg>                                          # 依赖引入链
npm view @apple/app-store-server-library version dependencies   # 上游是否仍依赖
npx wrangler deploy --dry-run --minify -c "$PWD/wrangler.local.toml" 2>&1 | grep "Total Upload"
```

---

## 9. 数据可信度（教训归档）

本次调查环境后期不稳（Bash 内联输出重复污染/吞空、worktree 偶发空 checkout、Write/mv/rm 偶发假成功、Read 源文件偶发返回幻觉内容），曾产生多个错误数字，**均已被硬证据纠正**，勿引用：

| 错误（幻觉/推断） | 正确（实测） |
|---|---|
| wrangler「4747.91 / 1059.40 KiB」、「gzip 1.03 MiB」 | 基线 4862.07 / 1185.40；dist gzip 1.23 |
| 「迁移涨 2M 来自这些依赖」（推断） | 实测基线 2.68，IAP +1.38 |
| 单测 1486b54f「2.29 MiB」 | 2.68（残留改动污染，全新 worktree 复测） |
| 「crypto 渠道 cloudflare/src/integrations/crypto 3683 行」 | 真实 api/src/integrations/ethereum ~290 行 |
| wt-opt「优化后 3.33 MiB」（首次）/「verify 脚本 EXISTS」 | 当时 worktree 空 / 文件未落盘，均幻觉 |

**可信判据（务必遵守）**：关键结果重定向到文件再 Read；build 用 mtime+size 双证；多源交叉验证（python / build 自报 / wrangler）；逻辑自洽护栏（gzip 不可能比单文件 gzip 更小）；写操作后用 bash `wc -l`/`grep` 落盘复验，不盲信工具成功返回；拿不到干净输出就明说，绝不填空。本文数字均按此取得。

---

## 10. 后续待办

- [ ] node-fetch + wordlists 两处优化与 payment-ios 改动**分开单独 commit**（都在 `run-build.js` + `shims/`）。
- [ ] 合并前在 staging 跑 §7 的 IAP 端到端验证，并**顺带验一次 ethereum 链上支付**（确认 RemoteSigner 签名/上链正常，wordlists 改动不影响签名路径）。
- [ ] 决定 `minify`（最稳，1033）还是 `no-bundle`（进 1MiB，叠加 wordlists 后预计 ~958，需补全验证），并把 `minify = true`/no-bundle 正式写进各 wrangler config（local/dev/staging/jsonc）。
- [ ] **质量债（与体积无关，各自独立 PR 排期，见 §6.4-B）**：① 补关键路由测试（57 资源/318 端点仅 3 测试）→ ② 拆 `checkout-sessions.ts`（5182 行）等超大文件 → ③ 抽列表查询 `where` helper（8~11 route 重复）。
