返回博客

微信小程序分包悄悄越线:亚稳态累积炸掉全员提交

某天早上,团队成员突然反馈「提交失败了」。报错信息清晰无误:

Error: 系统错误,错误码:80058, preloadRule [2] source size 2126KB exceed max limit 2MB

但怪的是——前几天还能提交,昨天也没有任何大改动,只是一次普通的 UI 细节优化。排查了半小时,真正的根因令人后背发凉:这个分包已经在 2040KB 附近「稳定」了两个多星期。

现象

  • 突然全员提交报 source size NNNKB exceed max limit 2MB
  • 报错出现之前没有任何大功能上线,只有若干 UI polish 和小 refactor
  • 回溯 git log,没有哪一个单独的 commit 显得「理所当然地超限」
  • 关键词 preloadRule 指向某个预加载分包,不是主包

根因

这是一个亚稳态累积问题,不是某次单点失误。

分包体积长期停在距 2MB 上限不足 10KB 的区间时,系统进入了一种危险的平衡态:

  • 每次 PR 新增几 KB(图标、组件、样式),单独看都「合理」
  • Code Review 时没有人盯整体体积,Reviewer 不会去计算「再加 8KB 就炸了」
  • 这种状态可以持续数周,直到某次中等体量的 commit(比如一次 UI polish +24KB)把它推过线
  • 推过线的那一刻,全部正在提交的人同时被阻断
坑:用 du -sk 做 CI 哨兵,精度差 30-40%,会误报也会漏报

一开始用 du -sk <分包目录> 统计源码大小并设置阈值告警。这个方案有根本性缺陷:

du -sk 统计的是源码原始大小,不是微信实际打包体积。

微信打包会对 JS 做 minify + tree-shake(压缩率接近 50%),但对图片、JSON、字体几乎不压缩(保留率接近 80%)。项目实测:源码 2.90MB,打包产物 2126KB,保留率约 73%——但这个比例随包内容不同可以在 50%–80% 大幅波动。

用固定的源码阈值倒推打包体积,既可能「源码才 1.8MB,以为没问题,实际打包已 2.0MB」漏报,也可能「源码 2.1MB 就告警,实际打包才 1.5MB」误报。

正解

立即解堵

分包超限时的快速减重手段:

  1. 注销已下线页面(从 app.json 的 pages 中删除注册,物理文件可暂时保留)
  2. 删除无引用的 wxss 样式和未用图标资源
  3. 把分包内的大静态 JSON 改为接口动态拉取

长期防护:三层分工

真正可靠的方案是用 miniprogram-ci 做真实编译检测,数字与 DevTools「代码依赖分析」完全一致,可作为硬性阈值的权威来源:

// scripts/check-subpackage-size.js
const ci = require('miniprogram-ci')
const path = require('path')
 
async function main() {
  const project = new ci.Project({
    appid: process.env.WX_APPID,
    type: 'miniProgram',
    projectPath: path.resolve(__dirname, '..'),
    privateKeyPath: path.resolve(__dirname, '../ci-private.key'),
    ignores: ['node_modules/**', '.git/**']
  })
 
  const result = await ci.preview({
    project,
    desc: 'size-check',
    robot: 1,
    setting: { es6: true, minify: true, autoPrefixWXSS: true },
    qrcodeFormat: 'terminal'
  })
 
  const MAX_KB = 2048
  const WARN_KB = 1900
 
  let hasError = false
  result.subPackageInfo.forEach(pkg => {
    const kb = (pkg.size / 1024).toFixed(1)
    if (pkg.size > MAX_KB * 1024) {
      console.error(`FAIL ${pkg.name}: ${kb}KB > ${MAX_KB}KB`)
      hasError = true
    } else if (pkg.size > WARN_KB * 1024) {
      console.warn(`WARN ${pkg.name}: ${kb}KB (接近上限)`)
    } else {
      console.log(`OK   ${pkg.name}: ${kb}KB`)
    }
  })
 
  if (hasError) process.exit(1)
}
 
main().catch(err => { console.error(err); process.exit(1) })

但真编译耗时 60–120s,不能塞进每次 push。合理的分层策略:

| 时机 | 方法 | 阻断力 | |------|------|------| | 每次 push(pre-push hook) | du -sk 粗估,源码 > 1.8MB 阻断 | 轻量防护 | | PR check / 每日 cron | miniprogram-ci 真编译,打包 > 1.9MB warn / > 2.0MB fail | 精确强阻断 | | 发版前 | DevTools「代码依赖分析」手动核查 | 权威确认 |

正解:CI 跑 miniprogram-ci 真编译,直接读 subPackageInfo[].size
  • npm i -D miniprogram-ci,在 CI 的 PR check job 跑真编译
  • 直接读 result.subPackageInfo[].size,单位 bytes,与 DevTools 数字一致
  • 打包体积 > 1.9MB 告警,> 2.0MB fail check,阻止 merge
  • pre-push hook 用 du -sk 做轻量粗估兜底(阈值按实测压缩率倒推,留充足缓冲)
  • 发版前开发者手动用 DevTools「代码依赖分析」确认一次

亚稳态危险区分类

| 分包打包体积 | 状态 | 建议动作 | |-----------|------|------| | < 1.5 MB | 健康 | 无 | | 1.5–1.8 MB | 关注 | Review 时提醒体积增量 | | 1.8–1.95 MB | 亚稳态 | CI 告警 + 限制新增 | | > 1.95 MB | 临界 | 阻止 merge,要求先瘦身 | | > 2.0 MB | 爆炸 | 提交全员阻塞 |

一句话外卖

分包体积接近上限时,「每次只加一点点」是最危险的模式——系统进入亚稳态,任何一次普通 commit 都可能成为压垮全员的最后一根稻草;用 miniprogram-ci 真编译做 CI 硬卡,是唯一可靠的防线。